Dynamic Select Boxes - Ruby on Rails
UPDATE: There is a dynamic select boxes for rails 3 tutorial now, so if this isn't working for you, check it out.
I have seen this asked a lot in the forums, so I thought I would write up a little tutorial.
For this tutorial I am going to have three select boxes. The first select box will be a super category of the next two select boxes and the second select box will be a super category of the third select box. I hope that makes sense. To demonstrate, I thought I would use Genre -> Artist -> Song. So let's get started:
Create your models and build your migrations:
1 2 3 4 |
ruby script/generate model genre name:string ruby script/generate model artist name:string genre_id:integer ruby script/generate model song title:string artist_id:integer |
Populate your genres, artists and songs through a migration:
1 2 |
ruby script/generate migration create_hierarchy |
Contents of migration:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
class CreateHierarchy < ActiveRecord::Migration def self.up g1 = Genre.create(:name => "Genre 1") g2 = Genre.create(:name => "Genre 2") g3 = Genre.create(:name => "Genre 3") a1 = Artist.create(:name => "Artist 1", :genre_id => g1.id) a2 = Artist.create(:name => "Artist 2", :genre_id => g1.id) a3 = Artist.create(:name => "Artist 3", :genre_id => g2.id) a4 = Artist.create(:name => "Artist 4", :genre_id => g2.id) a5 = Artist.create(:name => "Artist 5", :genre_id => g3.id) a6 = Artist.create(:name => "Artist 6", :genre_id => g3.id) Song.create(:title => "Song 1", :artist_id => a1.id) Song.create(:title => "Song 2", :artist_id => a1.id) Song.create(:title => "Song 3", :artist_id => a2.id) Song.create(:title => "Song 4", :artist_id => a2.id) Song.create(:title => "Song 5", :artist_id => a3.id) Song.create(:title => "Song 6", :artist_id => a3.id) Song.create(:title => "Song 7", :artist_id => a4.id) Song.create(:title => "Song 8", :artist_id => a4.id) Song.create(:title => "Song 9", :artist_id => a5.id) Song.create(:title => "Song 10", :artist_id => a5.id) Song.create(:title => "Song 11", :artist_id => a6.id) Song.create(:title => "Song 12", :artist_id => a6.id) end def self.down # you can fill this in if you want. end end |
Yes, I know it is generic data, sorry. So anyway, as you can see there are 3 genres, each with 2 artists, for a total of 6 artists each with 2 songs for a total of 12 songs. Each genre has 4 songs through its artists. Ok, so now we need to populate the database:
1 2 |
rake db:migrate
|
Now we need to modify our models to set up the associations.
1 2 3 4 5 6 7 8 |
class Genre < ActiveRecord::Base has_many :artists has_many :songs, :through => :artists end class Artist < ActiveRecord::Base has_many :songs end |
That should be it, now let's go to the console and see that all this works:
1 2 3 4 5 6 7 8 9 10 |
ruby script/console Loading development environment (Rails 2.0.2) >> g = Genre.find(:first) => #<Genre id: 1, name: "Genre 1", created_at: "2008-03-30 11:52:25", updated_at: "2008-03-30 11:52:25"> >> g.artists => [#<Artist id: 1, name: "Artist 1", genre_id: 1, created_at: "2008-03-30 11:52:25", updated_at: "2008-03-30 11:52:25">, #<Artist id: 2, name: "Artist 2", genre_id: 1, created_at: "2008-03-30 11:52:25", updated_at: "2008-03-30 11:52:25">] >> g.songs => [#<Song id: 1, title: "Song 1", artist_id: 1, created_at: "2008-03-30 11:52:25", updated_at: "2008-03-30 11:52:25">, #<Song id: 2, title: "Song 2", artist_id: 1, created_at: "2008-03-30 11:52:25", updated_at: "2008-03-30 11:52:25">, #<Song id: 3, title: "Song 3", artist_id: 2, created_at: "2008-03-30 11:52:25", updated_at: "2008-03-30 11:52:25">, #<Song id: 4, title: "Song 4", artist_id: 2, created_at: "2008-03-30 11:52:25", updated_at: "2008-03-30 11:52:25">] >> |
Looks like it works
Ok, now on to the controller. Our controller needs to have some action for the view, I just used index, and two other actions, for remote function calls, I called these update_artists and update_songs. update_artists() is called when a genre is changed and it updates the list of artists and the list of songs based on the genre. update_songs() only updates the songs based on the artist. So let's look at this code:
# the controller
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
class TestItController < ApplicationController def index @genres = Genre.find(:all) @artists = Artist.find(:all) @songs = Song.find(:all) end def update_artists # updates artists and songs based on genre selected genre = Genre.find(params[:genre_id]) artists = genre.artists songs = genre.songs render :update do |page| page.replace_html 'artists', :partial => 'artists', :object => artists page.replace_html 'songs', :partial => 'songs', :object => songs end end def update_songs # updates songs based on artist selected artist = Artist.find(params[:artist_id]) songs = artist.songs render :update do |page| page.replace_html 'songs', :partial => 'songs', :object => songs end end end |
Now as far as views go we have one view (index.html.erb) and two partials (_songs and _artists). Let's take a look at those:
# the _songs partial (_songs.html.erb):
1 2 3 |
<%= collection_select(nil, :song_id, songs, :id, :title, {:prompt => "Select a Song"}) %> |
# the _artists partial (_artists.html.erb):
1 2 3 4 5 6 |
<%= collection_select(nil, :artist_id, artists, :id, :name, {:prompt => "Select an Artist"}, {:onchange => "#{remote_function(:url => {:action => "update_songs"}, :with => "'artist_id='+value")}"}) %> <br/> |
# and last, but not least, the index view (index.html.erb):
1 2 3 4 5 6 7 8 9 |
<%= javascript_include_tag :defaults %> <%= collection_select(nil, :genre_id, @genres, :id, :name, {:prompt => "Select a Genre"}, {:onchange => "#{remote_function(:url => {:action => "update_artists"}, :with => "'genre_id='+value")}"}) %> <br/> <div id="artists"><%= render :partial => 'artists', :object => @artists %></div> <div id="songs"><%= render :partial => 'songs', :object => @songs %></div> |
Ok, this probably takes some explanation. I will save that for part II, where I will also improve upon what we have so far and include a demo.
For now, if you have any questions, just ask me in the comments.
106 Responses
to “Dynamic Select Boxes - Ruby on Rails”
Sorry, comments for this entry are closed at this time.

Nice tutorial, I will be doing it, soon.
Just a nice consideration does the render_html works in ie ?
@Adriano - do you mean the replace_html rjs line? IAE, it should work in IE, let me know if you run into any problems.
I’m kind a newbie in rails, shouldnt the controller be the app controller ?
I created a folder named test in it i added files index.rhtml and the 2 partials in this folder and modified the app controller, am I doing something wrong ?
I used to just use script generate/scaffold so I dont know which error it is. Thanks in advance ….
Thanks for the tutorial! all the previous that I saw when using IE it would not work with replace html. This is the first fully working, fully explained tutorial about cascading select I ever saw (and i searched over 40). Congrats, and thanks for making me unstuck in this problem =D
@Adriano - glad it worked for you and thanks for the nice comments
@Jonathan - sure you could use the app controller, if you mean app/controllers/app_controller.rb, and not app/controllers/application.rb. I just like using test_it for my controller for some reason.
Here is a very basic guide:
(assuming you did the database and models)
1) ruby script/generate controller <whatever you want>
2) copy the code (excluding the class (top) and end(bottom)) from what I have in my controller code to your app/controllers/<whatever_you_called_it>.rb
3) copy the code from index.html.erb to app/views/<whatever you called it>/index.html.erb
4) do step 3 for both partials
5) start your server and go to http(s)://<your domain>/<whatever you called your controller>
That ought to get you close at least.
this is truly great. thanks for your simple, clean tutorial.
I can’t get this to work. Using the debugger, execution never gets to the update_artists function. Any advice?
Thanks
Got it to work. Forgot a line of code.
Works great!
Any idea on how to get this to work with new or edit forms? I get null values saved in the genre_id and artist_id fields whenever I try to create a new song or edit a song. Thanks
@cworth -
When you create a new song, you only have to associate the artist_id. When you submit a new song, what params do you have as a result?
This is a clear and straight forward example. Thanks!
select(personal) locality. So to out!
I can’t get this to work. Execution never gets to the update_artists function. ???
Thanks for the article, it was really clear and easy to follow.
Is there any way that the artist drop-down is only populated once a genre has been selected, and the song drop-down is only populated once the artist has been selected? Ideally the unpopulated drop-downs would be disabled until they’re populated.
i cant able to select artist values as well as song value in dropdown….any suggestions pls..
@Andy - Just remove this line from update_artists:
page.replace_html ’songs’, :partial => ’songs’, :object => songs
You can start with the drop-downs disabled then in the render :update, you can use rjs to enable the appropriate one
@ganez - Did you populate your database? I will probably need more information.
Cool tutorial. How would u change this so that an Artist could be in two different Genre’s. Say you wanted to be able to find the one entry for Shakira in both the "Spanish" Genre and the "Pop" Genre. Thanks!
@Geoff - you would need a join table between genres and artists, such that artist.genres is possible. Then a few other changes … I just created an example for you - http://pullmonkey.com/2008/8/5/dynamic-select-boxes-many-to-many-ruby-on-rails
Hi
its is very nice example u have explain here and i implemneted also its working fine. I modified the application little like i added one button and when i click on this button i need the value selected like the genre id, artist Name and song name and display this value in controller action for other operation but i unable to get i tried a lot
can u help ………..
Thanks in Advance
Harish
@Harish - You can just wrap the whole thing in a form and add a submit button. Then you use params inside your action to get your values.
Thanks for this great tutorial.
I implement your method to my application.
Everything works all right in Firefox and Opera.
However, I cannot get it working in IE7.
The only difference between mine and yours is the the render target tag. You used <div id="artists"></div>. And I used <span id="artists"></span>
But I do not think this is the reason why it cannot work in IE7.
Do you have some ideas?
@boblu - seems that some of your comment did not register. Use this information to post html/ruby code in your comment - http://pullmonkey.com/2008/7/23/open-flash-chart-ii-plugin-for-ruby-on-rails-ofc2#comment-46303
I am having a problem translating this box into a create / edit form_for. I’ve simplified it and adapted to my application. So I have sections, each section has many categories. When I create an article, I use one form_for that renders a partial for the form. Within that partial, I render another partial for the categories depending on which section is chosen (like the genre / artist relationship). When I submit the form, there is not a parameter for the category_id in the param list.
My form code is below
new.html.erb
<filter:code attributes=lang="ruby">
<% form_for :post, :url => {:action => ‘create’}, :html => { :multipart => true } do |f| %>
<%= render :partial => ‘form’, :locals => { :f => f } %>
<%= submit_tag ("Create", :action => ‘create’, :style => "margin: 1.5em 0 0 100px;") %>
<% end %>
< /filter:code>
_form partial
<filter:code attributes=lang="ruby">
<table>
<tr>
<th>Title</th>
<td><%= f.text_field ( :title, :size => 40, :style => "font-size: 1.0em;") %></td>
</tr>
<tr>
<th>Section</th>
<td><%= collection_select(nil, :section_id, @sections, :id, :name,
{:prompt => "Select a section"},
{:onchange => "#{remote_function(:url => {:action => "update_categories"},
:with => "’section_id=’+value")}"}) %></td>
</tr>
<tr>
<th>Category</th>
<td><div id="categories"><%= render :partial => ‘categories_list’, :object => @categories %></div>
</td>
</tr>
</table>
< /filter:code>
_categories_list partial
<filter:code attributes=lang="ruby">
<%= collection_select(nil, :category_id, @categories, :id, :name,
{:prompt => "Select a category"}) %>
< /filter:code>
@Drew - you left the space in there < /filter:code>
Anyway, post the html page source, so I can look at what this code is generating.
Thanks for an awesome tutorial!
Simple and effective. I am trying to do this for a nested resource Company which is nested under Users. Once the Company is selected, a Contacts drop down is to be populated. And my update_contacts method is in the Companies Controller. The generated path for this is update_contacts_company_path and i need to pass the session[:user_id] along with this. I dont know how to construct the URL for this. The company_id is getting sent in the :with param whereas the url can be generated only if it sent alongwith the path like this - update_contacts_company_path(session[:user_id], company_id). Can someone help me out with fixing this?
@Vinay - Thanks. I can’t really tell what you are asking for without an example. Are you talking about this line:<filter:code attributes=lang="ruby"><%= collection_select(nil, :genre_id, @genres, :id, :name,
{:prompt => "Select a Genre"},
{:onchange => "#{remote_function(:url => {:action => "update_artists"},
:with => "’genre_id=’+value")}"}) %></filter:code>And you want to use a nested named route and get the session[:user_id] passed in as user_id, try this:<filter:code attributes=lang="ruby">{:onchange => "#{remote_function(:url => update_contacts_company_path,
:with => "’genre_id=’+value&user_id=#{session[:user_id]}}")}"}) %></filter:code>Let me know.
I messed up the quotes on the :with string:<filter:code attributes=lang="ruby">"#{remote_function(:url => update_contacts_company_path, :with => "’genre_id=’+value+’&user_id=#{session[:user_id]}’")}"}) %></filter:code>
Thanks Charlie! I cant try this until tonight though. So will let you know asap :). I was trying to pass the session[:user_id] in the :with hash(is it a hash?) and was not sure of the correct syntax. Will try this and let you know soon. Thanks again!
@Vinay - :with is a string. it is used like you would construct a GET url when passing variables.
So, http://pullmonkey.com?var1=<somevalue>&var2=<…>&... is the same as :url => "pullmonkey.com", :with => "var1=<somevalue>&var2=<…>&…"
@Charlie - Thanks for that gyaan(explanation) :). I changed it the way you have showed. The Ajax call does not seem to be running though. I have the Javacript files included. Also, this line is causing an error in the contacts partial.
<%= collection_select(nil, :contact_id, contacts, :id, :name,{:prompt => "Select Contact"}) %>
Here, ‘contacts’ is a nil object and so Rails tries to do nil.map which does not exist. How did you work around this?
Hi there,
trying this I never get above the first steps with migratin the joined table. As much as I tried to find any mistake I allways get:
== 20080927160238 CreateHierarchy: migrating ==================================
rake aborted!
undefined method `artist_id=’ for #<Song:0×31e77f8>
Any idea?
@Mayo - Make sure you typed this line correctly:
ruby script/generate model song title:string artist_id:integer
Also, post your RAILS_ROOT/db/migrate/…songs.rb migration file
Thanks. Your tutorial is clear and effective. I’m trying to use this technique to have one select for states and other for cities for a person database. However, when I change the state, I get an error:
`@person[address_attributes]‘ is not allowed as an instance variable name
The partial for cities is:
<filter:code attributes=lang="ruby">
<% fields_for "person[address_attributes]", address do |f| %>
<%= f.collection_select(:city_id, cities, :id, :nombre,
{:prompt => "City"}) %>
<%end%>
</filter:code>
Each person has_one address. I hope you can shed some light here.
@Ernest - Glad you like this tutorial.
It wouldn’t surprise me if you had to do something like this:<filter:code attributes=lang="ruby"><% fields_for "person[address_attributes][]", address do |f| %></filter:code>This way the hash of attributes can be populated … as in an address has a street which would be at person[address_attributes][street] and city at person[address_attributes][city].
Give it a shot. Let me know.
Hey Charlie,
This is a great tutorial. I was able to make it work on the song/artist/genre scenario but when I applied to my image gallery scenario it did not work
basicaly I have:
images
galleries
subcats
an the relationships are:
images belong_to subcats
images belong_to galleries
subcats belong_to galleries
subcats has_many images
galleries has_many images, through :subcats
so on image edit and new views I’m trying to create a dynamic select for galleries which will populate subcats.
Image table has gallery_id and subcat_id.
and here is how I used your tutorial:
edit.html.erb
<filter:code attributes=lang="ruby">
<p>
<label for="gallery">Gallery:</label>
<%= collection_select(nil, :gallery_id, @galleries, :id, :title,
{:prompt => "Select a Gallery"},
{:onchange => "#{remote_function(:url => {:action => "update_subcats"},
:with => "’gallery_id=’+value")}"}) %>
<div id="subcats">
<label for="subgallery">Sub Gallery:</label>
<%= render :partial => ’subcats’, :object => @subcats %>
</div>
</p>
</filter:code>
images_controller.rb
<filter:code attributes=lang="ruby">
def new
@image = Image.new
@galleries = Gallery.find(:all)
@subcats = Subcat.find(:all)
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => @image }
end
end
# GET /images/1/edit
def edit
@image = Image.find(params[:id])
@galleries = Gallery.find(:all)
@subcats = Subcat.find(:all)
end
def update_subcats
# updates artists and songs based on genre selected
gallery = Gallery.find(params[:gallery_id])
subcats = genre.subcats
render :update do |page|
page.replace_html ’subcats’, :partial => ’subcats’, :object => subcats
end
end
</filter:code>
_subcats.html.erb - is the only partial I created since I’m using gallery populate subcat.
<filter:code attributes=lang="ruby">
<%= collection_select(nil, :subcat_id, subcats, :id, :title,
{:prompt => "Select an Sub Gallery"}
) %>
<br/>
< /filter:code>
both dropdowns are showing and the second one does no respond to the first one.
Prototype is there.
Am I missing anything???
@Marcus - You have one tiny typo in your update_subcats action.
subcats = genre.subcats
should be
subcats = gallery.subcats # using gallery.
Your update_subcats() action is only called in a remote_function, so you won’t see the error or even maybe know there is an error unless you look in the log (development.log or production.log depending on the env). I am sure your log must have an undefined method subcasts for nil error or whatever.
Let me know, if you still have problems.
Great stuff. Really awesome write-up!
Is there a way to reset the select the sub-selections if the user resets the main selection? For example, a user initially selects a genre (and the genre’s entries pop up), but then decides to reset the genre selection by clicking on "Select a Genre". Can we reset the song selection list to display all songs now (or even disable it until the user selects a genre)?
@Andrew -
So if the user select "Select a Genre" again, we can display ALL Songs and ALL artists like this:
1) either check the params[:genre_id] or check the resulting genre
2) if the params[:genre_id] is 0 then we can return all the artists and songs
if the genre is nil, we can return all the artists or songs
Something like this might work (probably best to check the params[:genre_id] … should be 0) -
<filter:code attributes=lang="ruby"> def update_artists
# updates artists and songs based on genre selected
# since we can have an invalid genre_id lets rescue it
genre = Genre.find(params[:genre_id]) rescue nil
# check the genre_id, if it is 0 then the user selected the prompt and we need to redisplay all artists and songs
artists = params[:genre_id] == "0" ? Artist.all : genre.artists
songs = params[:genre_id] == "0" ? Song.all : genre.songs
# this part stays the same
render :update do |page|
page.replace_html ‘artists’, :partial => ‘artists’, :object => artists
page.replace_html ’songs’, :partial => ’songs’, :object => songs
end
end</filter:code>
** Warning — not tested, so let me know how it goes
I have a problem to translate this into a create / edit form_for.
When I create an user, I use one form_for that renders a partial for the form. Within that partial, I render another partial for the aps.
When I submit the form, there is not a parameter for the ap_id in the controller.
new/edit.html.erb
<% form_for(@user) do |u| %>
<%= render :partial => ‘form’, :locals => {:u => u} %>
<p><%= u.submit "Anlegen" %></p>
<% end %>
_form.html.erb
<%= collection_select(:user, :city_id, City.find(:all, :order => ‘id’), :id, :ort, :include_blank => true) %></td>
<%= observe_field("user_city_id",
:frequency => 1,
:url => {:action => ‘make_ap_select’},
:with => "user_city_id") %>
<div id="make_ap">
<%= render :partial => "make_ap_select", :object => @aps %>
</div>
_make_ap_select.html.erb
<%= collection_select(:user, :ap_id, @aps, :id, :nodename, :include_blank => true) %>
Controller
def make_ap_select
city = City.find(params[:user_city_id])
@aps = city.aps
if request.xhr?
render :update do |page|
page.replace_html "make_ap", :partial => "make_ap_select", :object => @aps
end
end
end
def create
@user = User.new(params[:user])
puts params[:user][:ap_id]
…
end
The result in controller from "puts params[:user][:ap_id]" is ever: nil.
Sorry for my bad english.
@Frechdax75 -
try:
<%= u.collection_select(:city_id, City.find(:all, :order => ‘id’), :id, :ort, :include_blank => true) %>
that is u.collection_select(:city_id …) instead of collection_select(:user, :city_id …)
Also, I don’t see anything with the id of "make_ap" to allow this statement to work:
page.replace_html "make_ap", :partial => "make_ap_select", :object => @aps
you need something like this:
<div id="make_ap">
<%= render :partial => "make_ap_select", :object => @aps %>
</div>
"make_ap" is a <div> before and after "render partial".
I have change collection_select in u.collection_select.
_form.html.erb
<%= u.collection_select(:city_id, City.find(:all, :order => ‘id’), :id, :ort, :include_blank => true) %>
<%= observe_field("user_city_id",
:frequency => 1,
:url => {:action => ‘make_ap_select’},
:with => "user_city_id") %>
<div id="make_ap">
<%= render :partial => "make_ap_select", :object => @aps %>
</div>
Unfortunately the result is the same.
On Create "puts params[:user][:ap_id]" is ever: nil.
@Frechdax75 — also do u.collection_select for ap_id:<filter:code attributes=lang="ruby"><%= u.collection_select(:ap_id, @aps, :id, :nodename, :include_blank => true) %></filter:code>Also, examine the html ("view page source" in firefox) to see what it looks like and post that (the generated HTML) too.
Make sure to put filter tags around the HTML you post so that we can see everything - <a href="http://pullmonkey.com/2008/7/23/open-flash-chart-ii-plugin-for-ruby-on-rails-ofc2#comment-46303">see directions here</a>.
Did you ever do the part 2? Just curious.. if so, I’d like to see it.
Hey Brian, I did do a part two - <a href="http://pullmonkey.com/2008/8/5/dynamic-select-boxes-many-to-many-ruby-on-rails">part II</a>
Touches on many to many, not sure it followed exactly where I intended to go with part II, but it is at least another example for you to look at.
I’ve tried adding this to my own app, with two dropdowns.
When I select an option in the first dropdown, I never make it into the update_ method. It reloads index instead.
any idea of what to look at?
@Brian, I would start with running `rake routes` on the command line and then looking at your routes config (RAILS_ROOT/config/routes.rb).
Also, check the logs, maybe it was a redirect or there was an error?
@charlie
Here the html output
<filter:code attributes=lang="html">
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="content-type" content="text/html;charset=UTF-8" />
<title>index</title>
<link href="/stylesheets/layout.css?1231668968" media="screen" rel="stylesheet" type="text/css" />
<link href="/stylesheets/layout2.css?1231668968" media="screen" rel="stylesheet" type="text/css" />
<link href="/stylesheets/lightbox.css?1231668968" media="screen" rel="stylesheet" type="text/css" />
<script src="/javascripts/prototype.js?1231668968" type="text/javascript"></script>
<script src="/javascripts/effects.js?1231668968" type="text/javascript"></script>
<script src="/javascripts/dragdrop.js?1231668968" type="text/javascript"></script>
<script src="/javascripts/controls.js?1231668968" type="text/javascript"></script>
<script src="/javascripts/lightbox.js?1231668968" type="text/javascript"></script>
<script src="/javascripts/application.js?1231668968" type="text/javascript"></script>
</head>
<body>
<table id="liste">
<tr>
<form action="/users" class="new_user" id="new_user" method="post"><div style="margin:0;padding:0"><input name="authenticity_token" type="hidden" value="aabc8a8e95e763c9c8dd13d62e2b1e881bd73993" /></div>
<!–[form:user]–>
<th>Nickname</th>
<td><input id="user_nick" name="user[nick]" size="30" type="text" /></td>
<th>Passwort</th>
<td><input id="user_password" name="user[password]" size="30" type="password" /></td>
</tr><tr>
<th>Strasse</th>
<td><input id="user_strasse" name="user[strasse]" size="30" type="text" /></td>
<th>Ort</th>
<td><select id="user_city_id" name="user[city_id]"><option value=""></option>
<option value="1">Mil</option>
<option value="2">Opp</option>
<option value="3">Dro</option>
<option value="4">Lip</option>
<option value="5">Wes</option></select></td>
<script type="text/javascript">
//<![CDATA[
new Form.Element.Observer('user_city_id', 1, function(element, value) {new Ajax.Request('/users/make_ap_select', {asynchronous:true, evalScripts:true, parameters:'user_city_id=' + encodeURIComponent(value) + '&authenticity_token=' + encodeURIComponent('aabc8a8e95e763c9c8dd13d62e2b1e881bd73993')})})
//]]>
</script>
</tr><tr>
<th>AP</th>
<td><div id="make_ap">
<select id="user_ap_id" name="user[ap_id]"><option value=""></option>
<option value="1">BR2</option>
<option value="2">BR1</option>
<option value="3">AP05</option>
<option value="4">AP02</option>
<option value="5">AP22</option></select>
</div></td>
</tr>
</table>
<!–[eoform:user]–>
<p><input id="user_submit" name="commit" type="submit" value="Anlegen" /></p>
</form>
</body>
</html>
< /filter:code>
@Frechdax75 — you need to take out the space < /filter:code>, between the < and the /
@charlie
next attempt, here the html output
<filter:code attributes=lang="html">
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="content-type" content="text/html;charset=UTF-8" />
<title>new</title>
<link href="/stylesheets/layout.css?1231668968" media="screen" rel="stylesheet" type="text/css" />
<link href="/stylesheets/layout2.css?1231668968" media="screen" rel="stylesheet" type="text/css" />
<link href="/stylesheets/lightbox.css?1231668968" media="screen" rel="stylesheet" type="text/css" />
<script src="/javascripts/prototype.js?1231668968" type="text/javascript"></script>
<script src="/javascripts/effects.js?1231668968" type="text/javascript"></script>
<script src="/javascripts/dragdrop.js?1231668968" type="text/javascript"></script>
<script src="/javascripts/controls.js?1231668968" type="text/javascript"></script>
<script src="/javascripts/lightbox.js?1231668968" type="text/javascript"></script>
<script src="/javascripts/application.js?1231668968" type="text/javascript"></script>
</head>
<body>
<table id="liste">
<tr>
<form action="/users" class="new_user" id="new_user" method="post"><div style="margin:0;padding:0"><input name="authenticity_token" type="hidden" value="2ad37d74020c7b8b93c3e224ab9ed07b10fe0c99" /></div>
<!–[form:user]–>
<th>Nickname</th>
<td><input id="user_nick" name="user[nick]" size="30" type="text" /></td>
<th>Passwort</th>
<td><input id="user_password" name="user[password]" size="30" type="password" /></td>
</tr><tr>
</tr><tr>
<th>Strasse</th>
<td><input id="user_strasse" name="user[strasse]" size="30" type="text" /></td>
<th>Ort</th>
<td><select id="user_city_id" name="user[city_id]"><option value=""></option>
<option value="1">Milkel</option>
<option value="2">Opp</option>
<option value="3">Dro</option>
<option value="4">Lip</option>
<option value="5">Wes</option></select></td>
<script type="text/javascript">
//<![CDATA[
new Form.Element.Observer('user_city_id', 1, function(element, value) {new Ajax.Request('/users/make_ap_select', {asynchronous:true, evalScripts:true, parameters:'user_city_id=' + encodeURIComponent(value) + '&authenticity_token=' + encodeURIComponent('2ad37d74020c7b8b93c3e224ab9ed07b10fe0c99')})})
//]]>
</script>
</tr><tr>
<th>AP</th>
<td><div id="make_ap">
<select id="user_ap_id" name="user[ap_id]"><option value=""></option>
<option value="1">BR2</option>
<option value="3">BR1</option>
<option value="4">AP05</option>
<option value="26">AP22</option></select>
</table>
<!–[eoform:user]–>
<p><input id="user_submit" name="commit" type="submit" value="Anlegen" /></p>
</form>
</body>
</html>
</filter:code>
@Frechdax75 - here is the ordering that I see:
start table
start form
inputs
select
end table
end form
Your table tags need to wrap correctly, move your ending table tag outside the form.
Seeing this, my guess is that none of your params are being sent correctly, not just the ap_id. What about params[:user][:nick], does that exist? Please post the output of params[:user].inspect.
@charlie
Table_tags I converted, but again the result remains the same.
If I city (city_id) does not specify, so do not observe trigger, I get in params [: user] [: ap] an issue.
The relationship looks like this:
<filter:code attributes=lang="ruby">
class User < ActiveRecord::Base
belongs_to :city
belongs_to :ap
end
class City < ActiveRecord::Base
has_many :aps
has_many :users
end
class Ap < ActiveRecord::Base
belongs_to :city
has_many :users
end
</filter:code>
Can the problem be here?
@Frechdax75 - no, the problem is not there that looks fine. Please answer my question from before, that is where the problem is:
Seeing this, my guess is that none of your params are being sent correctly, not just the ap_id. What about params[:user][:nick], does that exist? Please post the output of params[:user].inspect.
@charlie
The output of params[:user].inspect is:
{"nick"=>"123", "password"=>"123", "city_id"=>"4"}
nick, password and city_id are correct but ap_id is missing.
@Frechdax75 - Ah - back to your HTML, you have a form inside a ‘tr’ that also contains all your other "working" params - nick, password, city_id, but then ap_id is in another ‘tr’. Everything needs to be thought of as being in "scope".
I would move the form outside to wrap the table:
1) Start Form
2) Start Table … put all your form elements here …
3) End table
4) End Form
@charlie
Thanks, the problem is solved.
1. Start Form
2. Start Table
3. End Table
4. End Form
works.
But why does
1. Start Table
2. Start Form
3. End Form
4. End Table
not works?
@Frechdax75 - that would work too, but you have to put the entire form inside your ‘td’, you can’t start a form in one ‘td’ and end it in another. That is why it is just best to wrap the table with the form.
Hey there,
I’ve implemented a related country/region select where region is populated when a country is selected.
The problem is thatt, in your tutorial, everything (:all) is loaded into the selects then drilled down when you manually select something.
I need to be able to have the region select start empty when creating a ‘new’ address and filled base on country when updating in ‘edit’.
The other problem is that regions also fills when the page refreshes after it is caught by the validator.
None of this would be a major problem with a small data set but the are roughly 3500 regions.
Any ides would be great.
Cheers,
Jason
@Jason - two things that may work for you:
1) you could just change it from @regions = Region.all to @regions = [] or
2) do (1) and disable the regions select box then in the remote_function you could enable it in a complete hook as in :complete => this.enabled (pseudo code).
For more info, check the callbacks here - http://api.rubyonrails.org/classes/ActionView/Helpers/PrototypeHelper.html#M001613
Hey there Charlie,
Thanks for the tip. I ended up using
<filter:code attributes=lang="ruby">
@regions = Region.find(:all, :conditions => {:country_id => @address.country_id}, :order => :name)
< /filter:code>
in new, edit & create
Seemed to work.
Cheers
Jason
Nice tutorial; There is just one thing to change: if you select a Genre, - Artists are reloaded, - it’s OK. If you unselect Genre and choose again ‘Select a Genre’, - the Arists list is never updated. The same is to do for Songs. Is it possible to reload artists and songs if you select ‘Select a Genre’/'Select an Artist’? Thank you!
@Javix - sure, if the genre can’t be found, meaning genre = Genre.find(params[:genre_id]) is nil or errors, then the user must have selected the prompt "Select a Genre" and at this time we can say artists = Artist.all and songs = Song.all. The partials should take care of the rest.
Hi, This is the most clear tutorial about dynamic select boxes. Finally I Could work with replace html.
Thanks for this!
I am super newby, and still stuck with a little matter, how can I pass the values from the partial to the form ?
Hi everyone,
Just so you know, there’s a plugin called dependent_select (http://github.com/splendeo/dependent_select/tree/master) that does something similar to this, only without AJAX (it stores everything on a big javascript array at the begining)
Excellent! It helped me out, when I use a dynamic checklist below the select box
hi there
I think you need to be more thorough with your work.
I followed all the instructions there and could not get it to work.
This is not helpful for people making the move over to rails.
The least you are lacking are:
- proper routes set up (ie. firing up http://localhost:3000 on the browser doens’t work ie brings me to an invalid page)
- you need to specify “rake db:create” first other wise, how the hell would “rake db:migrate” work????
Please clean up your tutorial. It’s very helpful (as in , controlling the genre, artist and songs) but please complete it .
thanks
damn andrew
there was no need to pimp slap the guy for this article. there is a basic assumption that everyone who reads this knows a little bit of something about rails. if your localhost isn’t showing up, then you have other configuration problems with your machine. the assumption is that your rails environment has already been created.
Thanks pimp daddy …
Andrew -
Proper routes? - not even necessary … just use http://localhost:3000/test_it. The route has already been defined. The basics of rails will teach you that the default routes allow us to get to any controller and action by using ‘:controller/:action’. This is documented already, I didn’t feel that it needed repeating.
rake db:create? I’m surprised you didn’t jump all over me for failing to mention that you need to use the rails command to create your project. Thanks for taking it easy on me
It’s obvious you are having a tough time adjusting to something new and felt like taking it out on this article (and me). We’ve all been there, give it some time, its called learning - hopefully you will get used to it.
Just keep in mind that I don’t work for you or with you, I don’t know you - I just wrote an article to show you something new over a year and half ago.
hello.
first of all, great tutorial.
just have some inquiries:
i have 4 select menus
first one is country select menu
second is region select menu
third is province select menu
fourth is city select menu
i also have text_field for street
what i wanted to happen were:
1. to dynamically populate second select menu based on the value selected from the first select menu, then dynamically populate the third based on the second and so on.
2. then if the value from the first select menu doesnt have sublocations for second select menu the select menu will not be displayed.
3. however if in the third or the province select menu the value selected doesn’t have sublocations(cities) then a textfield for city must be displayed instead of the select menu for city and simultaneously the text_field for street will be displayed
4. if I select a different value in the first select menu the second, third, fourth and the text field must disappear, but if I select different value in the second select menu the third, fourth and the text field must disappear but the first must stay.
what changes should I make to implement the above situations?
please help me. thanks!
Hi.
Just to let you know I did a similar thing using observer approach.
Any comments appreciated.
http://programmers-blog.com/2009/11/12/dependant-dropdowns-select-menus-using-rails
i loved this tutorial. i had been struggling to write similar code as i m a newbie in rails. you have explained it really well . moreover having working example makes it easier to understand.
Hello,
nice tutorial. Best for dynamic selectboxes.
It works fine for ‘index’ and ‘new/create’, but I’am struggeling with the ‘update/edit’ actions.
Can someone post a code snippet for the update action or/and explain how it works?
I’am still a Newbie and not yet very close with Ruby and Rails.
Also it would be helpfull to have an example for a ‘destroy’ action which only allows to delete a genre/artist if there is no song record to that artist or genre.
Thanks a lot
SaveTimE
Hi again,
actually, i think my problem is that I want to add for every song first one genre and than one artist. So I don’t want the genres_controller to handel the selectboxes, instead I want the songs_controller to handel these requests.
Any advise what and how is needed to be modified the example?
Thanks
SaveTimE
So you are creating and editing songs and want to associate an artist and genre to it?
I think it would be done in the artists’ form, add a song to the artist.
artists/1/songs/new (which would use the songs_controller)
artists/1/songs/1/edit
To get the routes above, you would use has_many in your routes:
map.resources :artists, :has_many => :songs
By adding an artist to your song, you will automatically get the genre, since the artist belongs_to :genre.
Is this what you are after?
For destroy, you can do the check in the controller action, here is an example for the artists controller:
def destroy
@artist = Artist.find(params[:id])
if @artist.songs.empty? and @artist.destroy
flash[:notice] = “Artist has been deleted”
else
flash[:error] = “Can’t delete this artist …. ”
end
redirect_to :back
end
You could also handle this in the artist model using before_destroy to check.
Thanks Charlie,
you got the point. I manage to find a solution. It is only needed to modify the ‘update_artist’ action and and to create a new one ‘update_genres’ and a new partial ‘genres’
here the example for songs_controller.rb:
# updates artists based on song selected
genre = Genre.find(params[:genre_id])
artists = genre.artists
render :update do |page|
page.replace_html ‘artists’, :partial => ‘artists’, :object => artists
end
end
def update_genres
# updates genre based on artist selected
genres = Genre.find(:all, :conditions => ["artist_id =?", params[:artist_id]])
render :update do |page|
page.replace_html ‘genres’, :partial => ‘genres’, :object => genres
end
end
All other code is similar to the example
The new partial:
{:onchange => “#{remote_function(:url => {:action => “update_artists”},
:with => “‘genre_id=’+value”)}”}) %>
The destroy action example is very helpful.
Thanks again
What ever happened to the demo?
Big thanks.. it’s really helpful while I got problems with “country-state-city” drop down list..
Excelente tutorial, me ayudo a entender collection_select y voy aplicar esta tecnica en validaciones, muchas gracias.
Thanks a lot for such a wonderful tutorial. Lean and easy to follow
You explained very well the key concepts. I wish more “tutorials” were like yours!!
;^D
Cheers!
–TN
Charlie,
You Rock! Such a simple, straight forward tutorial. Now I’m gonna finally finish what I’ve been trying to do for the past 2 days..
Now get off your chair and tear it up on your new bike!
Thanks,
Andrew
For those who are new to Ruby… Take the plunge and follow this tutorial, it works.
I just want to say thanks for this tutorial, I got it working in Rais2 within 10min!
Now however (perhaps foolishly) I have upgraded to Rails3 and I just cant get it to work. It seems that the collection_select method no longer will allow an object input.
Has anyone here been able to get it to work with Rails3?
Thanks in advance,
Wim
It’s the only tutorial that really works, wanna say thank you.
I got it working on Rails 3.0.3.
The only think I had to change was about the partial:
View:
’song’, :locals => { :songs => @songs } %>
Partial _song.html.erb:
“– select a song –”}) %>
– choose –
It’s the only tutorial that really works, wanna say thank you.
I got it working on Rails 3.0.3.
The only think I had to change was about the partial:
View:
Partial _song.html.erb:
– choose –
How to add code?
I see this is quite an old post.Still I really like the way is written.
I tried this example on rails 3 and it just didn’t work.By “didn’t work” I mean that the page loads and shows me the three select menus…but when I change the values nothing happens,nothing gets updated.What am I missing?.
I am not sure what is wrong, but since it is JS, it is probably a JS error of some sort, could you launch it in firefox and use the firebug console and report what you see?
…These are the moments that I love (hate) the most…I was lunching the app on another machine with chrome.Just installed there firefox and run it….works!.Went back to chrome…also works!!.
Again thx for the good example!
Thanks for the great tutorial. I’m fairly with RubyonRails and have been trying to figure how to create dynamic select boxes for a while now.
Following your tutorial, I ran in to a routing error (I’m pretty sure I’m missing something in my routes.rb file)
This is my error:
No route matches {:controller=>”test_it”, :escape=>false, :action=>”update_artists”}
and was raised from this line:
“Select a Genre”},
{:onchange => “#{remote_function(:url => {:action => “update_artists”},
:with => “‘genre_id=’+value”)}”}) %>
Your help would be greatly appreciated.
Thanks
Actually, just figured it out.
Great Tutorial!
@James - I am getting the same error — how did you fix it?
Thanks
@chip
it worked when I added to routes.rb
post “testit/update_artists”
post “testit/update_songs”
Note:my controller name is testit and not test_it.
Worked a treat for me. Thank you.
The sample works quite well. When i adapt it to my legacy database which uses its own foreign keys and own primary, it errs telling me that the specified column (the foreign key) does not exist, and yet it does!
kinda lost…
Sincerely
Hello
Thanks for the tutorial, I find it very useful, but I have a problem doing my final project. I have several days with and I can not fix it. I hope I can help.
My code:
migracion: create_obras.rb
class CreateObras < ActiveRecord::Migration
def self.up
create_table :admin_obras do |t|
t.string :titulo
……
t.references :admin_coleccion
t.references :admin_artistum
t.references :admin_seccion
t.references :admin_tecnica
t.timestamps
end
end
migracion: create_admin_seccions.rb
class CreateAdminSeccions < ActiveRecord::Migration
def self.up
create_table :admin_seccions do |t|
t.string :nombre
t.timestamps
end
end
…
migracion: create_admin_tecnicas.rb
class CreateAdminTecnicas < ActiveRecord::Migration
def self.up
create_table :admin_tecnicas do |t|
t.string :nombre
t.references :admin_seccion
t.timestamps
end
end
…
Is stored in: (… = controller or views …)
app/…/admin/obras
app/…/admin/tecnicas
app/…/admin/seccions
Models:
class Admin::Tecnica “Seccion” # foreign key - admin_seccion
…
class Admin::Seccion “Obra” # Están en el mismo espacio de nombres
has_many :admin_tecnicas, :class_name => “Tecnica”
…
class Admin::Obra “Seccion” # foreign key - admin_seccion
…
Controller: #### THIS IS PROBLEM ####
def update_tecnicas
admin_seccion = Admin::Seccion.find(params[:admin_seccion_id])
admin_tecnicas = admin_seccion.admin_tecnicas #### THIS IS PROBLEM ####
render :update do |page|
#page.replace_html ‘tecnicas’, :partial => ‘/admin/obras/secciones’, :object => @admin_obra
end
end
_secciones.html.erb:
“Seleccione una sección”},
{:onchange => “#{remote_function(:url => {:controller=>”seccions”, :action => “update_tecnicas”},
:with => “‘admin_seccion_id=’ + value”)}”}) %>
He tells me the following error:
Started POST “/admin/seccions/update_tecnicas” for 127.0.0.1 at Sat Oct 01 20:27:28 +0200 2011
Processing by Admin::SeccionsController#update_tecnicas as JS
Parameters: {”authenticity_token”=>”1PXjFEx0a/ngBXIVA7EMp5SVujhffa3AkxU6w130Acc=”, “_”=>”", “admin_seccion_id”=>”1″}
Admin::Seccion Load (0.1ms) SELECT `admin_seccions`.* FROM `admin_seccions` WHERE `admin_seccions`.`id` = 1 LIMIT 1
Admin::Tecnica Load (0.5ms) SELECT `admin_tecnicas`.* FROM `admin_tecnicas` WHERE (`admin_tecnicas`.seccion_id = 1)
Mysql::Error: Unknown column ‘admin_tecnicas.seccion_id’ in ‘where clause’: SELECT `admin_tecnicas`.* FROM `admin_tecnicas` WHERE (`admin_tecnicas`.seccion_id = 1)
Completed 500 Internal Server Error in 24ms
ActiveRecord::StatementInvalid (Mysql::Error: Unknown column ‘admin_tecnicas.seccion_id’ in ‘where clause’: SELECT `admin_tecnicas`.* FROM `admin_tecnicas` WHERE (`admin_tecnicas`.seccion_id = 1)):
app/controllers/admin/seccions_controller.rb:106:in `write’
app/controllers/admin/seccions_controller.rb:106:in `print’
app/controllers/admin/seccions_controller.rb:106:in `update_tecnicas’
Rendered /usr/lib/ruby/gems/1.8/gems/actionpack-3.0.9/lib/action_dispatch/middleware/templates/rescues/_trace.erb (1.2ms)
Rendered /usr/lib/ruby/gems/1.8/gems/actionpack-3.0.9/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb (81.0ms)
Rendered /usr/lib/ruby/gems/1.8/gems/actionpack-3.0.9/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb within rescues/layout (86.3ms)
I await your favorable support.
Thank you
Regards
THIS:
Mysql::Error: Unknown column ‘admin_tecnicas.seccion_id’ in ‘where clause’:
SELECT `admin_tecnicas`.* FROM `admin_tecnicas` WHERE (`admin_tecnicas`.seccion_id = 1))
SHOULD BE, but do not know how:
SELECT `admin_tecnicas`.* FROM `admin_tecnicas` WHERE (`admin_tecnicas`.admin_seccion_id = 1))
Thank you
Solution:
class Admin::Seccion “Tecnica”, :foreign_key => “admin_seccion_id”
end
My code does not ship well, so I go back to writing. Sorry.
My solution has been:
<< class Admin::Seccion >
< “Tecnica”, :foreign_key => “admin_seccion_id” >>
<>
Thank
Hi,
Please let me know if routes are to be created for update_artists and update_songs.I am getting No route matches error.
Hi,
I added the route through routes.erb. Now I am getting the error “Ajax undefined”. I am not able to the code hitting the update_releases in my controller.
I have projects which contains releases which in turn contains cycles.
Yes, you do need to specify the routes now. At the time of writing, there was a catch-all mapping for any :controller/:action that was not previously handled.
This article was also written at a time when prototype was included. You’ll need to make jQuery modifications.
Thanks a lot Charlie. I have made all the necessary jquery modifications.
I am getting error due to this line.
page.replace_html ‘artists’, :partial => ‘artists’, :object => artists
page.replace_html ’songs’, :partial => ’songs’, :object => songs
“Element doesn’t support this property or method” error is seen which is displayed as a pop up on changing the value of the drop down list box.
Thanks for this tutorial.
I am working on rails 3.2 and I got this error when I navigated to http://localhost:3000/dynamic (dynamic is the name given to the controller)
undefined method `remote_function’ for #<#:0xa033f68>
What should I do?
Foluso - “remote_function” was the old way, this tutorial is over 4 years old
Everything has moved away from prototype and to unobtrusive JS with jquery.
So now you would do something like $(’#artist_id’).change(function() { do your jquery ajax call here });
Also, you’ll need to change the render :update bits. Just create a actionname.js.erb file in your views path for the controller. Then move the code from within the render :update block to the js.erb file, making whatever necessary changes.
I’ll see about doing a 2012 version of this tutorial.
Thank you for your reply Charlie, could you kindly elaborate in clear terms on what changes to make and in which files to do I need to make these changes using your code as guide because I am a rails learner and thus cannot figure out your explanation in the previous post. I will really appreciate if you could start from the controller action or better still provide us a new tutorial as you opined that clearly explains this concept.
This has been giving me real though time for more than a week now and I will really appreciate your help in this regard and I am pretty sure it will be helpful to lots of people out there too.
Thanks.
An updated version of this tutorial would be GREATLY appreciated. With so many of the “examples” I see on the web, I can’t tell what’s “actual” code and what is someone’s pseudo-code.
The updated version is now available, go check it out.