Rails Faux Nested Forms
For adding an item via Ajax to related models then updating an existing select…
Here’s the situation:
For this example we have a New Product form. Products belong to Companies so we have a select menu of Companies.
You want to add a product and you have a select menu of companies, but in this case you have a product from a company you have not dealt with in the past. So directly below your select we are going to create a text field from which you can add a new company. You fill in your company name, press the faux-submit button, your company gets added via the companies controller, then the select menu will automatically be updated, with your new company selected…
*DISCLAIMER
There is probably a better, safer, simpler, more DRY, more best-practice, more concise, more Railsy, and more Ruby ways of doing this. Personally, I had a hard time finding an answer to this problem on any Rails forums or tutorials, but I had previously done it with ease in PHP. So I applied those same principles here and it seems to work fine. Hopefully it helps you, and if you know of a better way to handle this situation I would be thrilled to hear it.
First let’s start with the main form for adding a product:
First you have your original select wrapped in a div. The contents of the div (the select) are we will be updating with your new select….
<div id='companySelect'>
<%= collection_select("company", "company_id" , @companies, "id", "name", {:prompt => 'Choose company...'}) %>
</div>
Now you have a script that identifies the div to update, gets the input value, then does an AjaxUpdate passing the new company value in the url.
`
`
Here’s your ‘internal form’ for submission of the new company. First a text field: ` <%= text_field ‘newCompany’, ‘name’ %>
`
Then a faux submit button
<a href='#' onmouseover="this.style.cursor='pointer'" onclick='return addCompany();'>
Add New Company
</a>
Now our main form view is done so let’s move on….
ADD YOUR ROUTE to routes.rb:
map.connect 'companies/afterAjax', :controller=>'companies', :action=>'afterAjax'
Beef up your Companies Controller:
` class CompaniesController < ApplicationController
protectfromforgery :only => [:create, :destroy, :update] `
the protect_from_forgery line seems to solve token error problems for Ajax - makes me nervous though - is this creating security risks? If you know how better to deal with this - let me know….
`
Add The Company
def createForAjax @company = Company.new()
@company.name = params[‘compay’]
respondto do |format|
if @company.save
format.html { redirectto :action=>‘afterAjax’, :params=>{:id=>@company.id.to_s} }
end
end end
Generate A New Select With The Company You Created Selected
def afterAjax @companies = Company.select (or find :all – whatever your method to get all companies) @company = Company.find(params[:id]) (this is the one you just created) respond_to do |format|
format.html end end `
Finally, this Is The View For The New Select:
afterAjax.html.erb view in companiesController:
` <%= selecttag ‘product[companyid]’, optionsfromcollectionforselect(@companies, :id, :name, @company.id) %>
`
That’s It
Like I said, I imagine there are better and more concise ways of doing this and if you know them I would be happy to hear about it.
I am available for Ruby on Rails consulting work – get in touch to learn more.