Rails Faux Nested Forms
Sunday 27 April 2008 3 comments
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.
<script type='text/javascript'>
function addCompany() {
var selector = $('companySelect');
var newCompany = $('newCompany_name');
var companyValue = newCompany.getValue();
var url = '/companies/createForAjax?company=' + companyValue
new Ajax.Updater(selector, url); // END AJAX UPDATER
return false;
} // END FUNCTION
</script>
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
protect_from_forgery :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']
respond_to do |format|
if @company.save
format.html { redirect_to :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:
<%= select_tag 'product[company_id]', options_from_collection_for_select(@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.

Comments
3 Comments Subscribe via RSS
Sol Irvine Tuesday, February 24, 2009, 10:33 PM
A few notes:
1. Be sure you add <%= javascript_include_tag 'prototype' %>
at the top of your view, if it's not already there.
2. The afterAjax method should be:
@companies = Company.find(:all)
@company = Company.find(params[:id])
Works like a charm, and I really needed this!
Gordon Tuesday, April 07, 2009, 12:47 AM
Thanks for mentioning that Sol, I updated the post.
Herb Wednesday, March 10, 2010, 02:03 PM
Excellent. Had been looking for a solution to this since before rails 2.0. Although not railsy, it works for me.
had to add
{ render :layout => false } to format.html as to not get my layout rendering within the new select box.