{"id":36087,"date":"2008-03-30T18:52:00","date_gmt":"2008-03-30T18:52:00","guid":{"rendered":"\/2008\/03\/31\/dynamic-select-boxes-ruby-on-rails"},"modified":"2012-08-11T01:20:44","modified_gmt":"2012-08-11T01:20:44","slug":"dynamic-select-boxes-ruby-on-rails","status":"publish","type":"post","link":"http:\/\/pullmonkey.com\/2008\/03\/30\/dynamic-select-boxes-ruby-on-rails\/","title":{"rendered":"Dynamic Select Boxes – Ruby on Rails"},"content":{"rendered":"

UPDATE: There is a dynamic select boxes for rails 3 tutorial<\/a> now, so if this isn't working for you, check it out.
\n<\/strong>
\nI have seen this asked a lot in the forums, so I thought I would write up a little tutorial.
\nFor 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:
\nCreate your models and build your migrations:<\/p>\n\n\n
\n
1\r\n<\/tt>2\r\n<\/tt>3\r\n<\/tt>4\r\n<\/tt><\/pre>\n<\/td>\n
\n
\r\n<\/tt>ruby script\/generate model genre name:string<\/span>\r\n<\/tt>ruby script\/generate model artist name:string<\/span> genre_id:integer<\/span>\r\n<\/tt>ruby script\/generate model song title:string<\/span> artist_id:integer<\/span>\r\n<\/tt><\/pre>\n<\/td>\n<\/tr>\n<\/table>\n

Populate your genres, artists and songs through a migration:<\/p>\n\n\n
\n
1\r\n<\/tt>2\r\n<\/tt><\/pre>\n<\/td>\n
\n
\r\n<\/tt>ruby script\/generate migration create_hierarchy\r\n<\/tt><\/pre>\n<\/td>\n<\/tr>\n<\/table>\n

Contents of migration:<\/p>\n\n\n
\n
1\r\n<\/tt>2\r\n<\/tt>3\r\n<\/tt>4\r\n<\/tt>5<\/strong>\r\n<\/tt>6\r\n<\/tt>7\r\n<\/tt>8\r\n<\/tt>9\r\n<\/tt>10<\/strong>\r\n<\/tt>11\r\n<\/tt>12\r\n<\/tt>13\r\n<\/tt>14\r\n<\/tt>15<\/strong>\r\n<\/tt>16\r\n<\/tt>17\r\n<\/tt>18\r\n<\/tt>19\r\n<\/tt>20<\/strong>\r\n<\/tt>21\r\n<\/tt>22\r\n<\/tt>23\r\n<\/tt>24\r\n<\/tt>25<\/strong>\r\n<\/tt>26\r\n<\/tt>27\r\n<\/tt>28\r\n<\/tt>29\r\n<\/tt>30<\/strong>\r\n<\/tt>31\r\n<\/tt>32\r\n<\/tt><\/pre>\n<\/td>\n
\n
\r\n<\/tt>class<\/span> CreateHierarchy<\/span> < ActiveRecord<\/span>::Migration<\/span>\r\n<\/tt>  def<\/span> self<\/span>.up\r\n<\/tt>    g1 = Genre<\/span>.create(:name<\/span> => "<\/span>Genre 1<\/span>"<\/span><\/span>)\r\n<\/tt>    g2 = Genre<\/span>.create(:name<\/span> => "<\/span>Genre 2<\/span>"<\/span><\/span>)\r\n<\/tt>    g3 = Genre<\/span>.create(:name<\/span> => "<\/span>Genre 3<\/span>"<\/span><\/span>)\r\n<\/tt>\r\n<\/tt>    a1 = Artist<\/span>.create(:name<\/span> => "<\/span>Artist 1<\/span>"<\/span><\/span>, :genre_id<\/span> => g1.id)\r\n<\/tt>    a2 = Artist<\/span>.create(:name<\/span> => "<\/span>Artist 2<\/span>"<\/span><\/span>, :genre_id<\/span> => g1.id)\r\n<\/tt>    a3 = Artist<\/span>.create(:name<\/span> => "<\/span>Artist 3<\/span>"<\/span><\/span>, :genre_id<\/span> => g2.id)\r\n<\/tt>    a4 = Artist<\/span>.create(:name<\/span> => "<\/span>Artist 4<\/span>"<\/span><\/span>, :genre_id<\/span> => g2.id)\r\n<\/tt>    a5 = Artist<\/span>.create(:name<\/span> => "<\/span>Artist 5<\/span>"<\/span><\/span>, :genre_id<\/span> => g3.id)\r\n<\/tt>    a6 = Artist<\/span>.create(:name<\/span> => "<\/span>Artist 6<\/span>"<\/span><\/span>, :genre_id<\/span> => g3.id)\r\n<\/tt>\r\n<\/tt>    Song<\/span>.create(:title<\/span> => "<\/span>Song 1<\/span>"<\/span><\/span>,  :artist_id<\/span> => a1.id)\r\n<\/tt>    Song<\/span>.create(:title<\/span> => "<\/span>Song 2<\/span>"<\/span><\/span>,  :artist_id<\/span> => a1.id)\r\n<\/tt>    Song<\/span>.create(:title<\/span> => "<\/span>Song 3<\/span>"<\/span><\/span>,  :artist_id<\/span> => a2.id)\r\n<\/tt>    Song<\/span>.create(:title<\/span> => "<\/span>Song 4<\/span>"<\/span><\/span>,  :artist_id<\/span> => a2.id)\r\n<\/tt>    Song<\/span>.create(:title<\/span> => "<\/span>Song 5<\/span>"<\/span><\/span>,  :artist_id<\/span> => a3.id)\r\n<\/tt>    Song<\/span>.create(:title<\/span> => "<\/span>Song 6<\/span>"<\/span><\/span>,  :artist_id<\/span> => a3.id)\r\n<\/tt>    Song<\/span>.create(:title<\/span> => "<\/span>Song 7<\/span>"<\/span><\/span>,  :artist_id<\/span> => a4.id)\r\n<\/tt>    Song<\/span>.create(:title<\/span> => "<\/span>Song 8<\/span>"<\/span><\/span>,  :artist_id<\/span> => a4.id)\r\n<\/tt>    Song<\/span>.create(:title<\/span> => "<\/span>Song 9<\/span>"<\/span><\/span>,  :artist_id<\/span> => a5.id)\r\n<\/tt>    Song<\/span>.create(:title<\/span> => "<\/span>Song 10<\/span>"<\/span><\/span>, :artist_id<\/span> => a5.id)\r\n<\/tt>    Song<\/span>.create(:title<\/span> => "<\/span>Song 11<\/span>"<\/span><\/span>, :artist_id<\/span> => a6.id)\r\n<\/tt>    Song<\/span>.create(:title<\/span> => "<\/span>Song 12<\/span>"<\/span><\/span>, :artist_id<\/span> => a6.id)\r\n<\/tt>  end<\/span>\r\n<\/tt>\r\n<\/tt>  def<\/span> self<\/span>.down\r\n<\/tt># you can fill this in if you want.<\/span>\r\n<\/tt>  end<\/span>\r\n<\/tt>end<\/span>\r\n<\/tt><\/pre>\n<\/td>\n<\/tr>\n<\/table>\n

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:<\/p>\n\n\n
\n
1\r\n<\/tt>2\r\n<\/tt><\/pre>\n<\/td>\n
\n
\r\n<\/tt>rake db:migrate<\/span>\r\n<\/tt><\/pre>\n<\/td>\n<\/tr>\n<\/table>\n

Now we need to modify our models to set up the associations.<\/p>\n\n\n
\n
1\r\n<\/tt>2\r\n<\/tt>3\r\n<\/tt>4\r\n<\/tt>5<\/strong>\r\n<\/tt>6\r\n<\/tt>7\r\n<\/tt>8\r\n<\/tt><\/pre>\n<\/td>\n
\n
\r\n<\/tt>class<\/span> Genre<\/span> < ActiveRecord<\/span>::Base<\/span>\r\n<\/tt>  has_many :artists<\/span>\r\n<\/tt>  has_many :songs<\/span>, :through<\/span> => :artists<\/span>\r\n<\/tt>end<\/span>\r\n<\/tt>class<\/span> Artist<\/span> < ActiveRecord<\/span>::Base<\/span>\r\n<\/tt>  has_many :songs<\/span>\r\n<\/tt>end<\/span>\r\n<\/tt><\/pre>\n<\/td>\n<\/tr>\n<\/table>\n

That should be it, now let's go to the console and see that all this works:<\/p>\n\n\n
\n
1\r\n<\/tt>2\r\n<\/tt>3\r\n<\/tt>4\r\n<\/tt>5<\/strong>\r\n<\/tt>6\r\n<\/tt>7\r\n<\/tt>8\r\n<\/tt>9\r\n<\/tt>10<\/strong>\r\n<\/tt><\/pre>\n<\/td>\n
\n
\r\n<\/tt>ruby script\/console \r\n<\/tt>Loading<\/span> development environment (Rails<\/span> 2.0<\/span>.2<\/span>)\r\n<\/tt>>> g = Genre<\/span>.find(:first<\/span>)\r\n<\/tt>=> #<Genre id: 1, name: "Genre 1", created_at: "2008-03-30 11:52:25", updated_at: "2008-03-30 11:52:25"><\/span>\r\n<\/tt>>> g.artists\r\n<\/tt>=> [#<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">]<\/span>\r\n<\/tt>>> g.songs\r\n<\/tt>=> [#<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">]<\/span>\r\n<\/tt>>> \r\n<\/tt><\/pre>\n<\/td>\n<\/tr>\n<\/table>\n

Looks like it works \ud83d\ude42 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:
\n# the controller<\/p>\n\n\n
\n
1\r\n<\/tt>2\r\n<\/tt>3\r\n<\/tt>4\r\n<\/tt>5<\/strong>\r\n<\/tt>6\r\n<\/tt>7\r\n<\/tt>8\r\n<\/tt>9\r\n<\/tt>10<\/strong>\r\n<\/tt>11\r\n<\/tt>12\r\n<\/tt>13\r\n<\/tt>14\r\n<\/tt>15<\/strong>\r\n<\/tt>16\r\n<\/tt>17\r\n<\/tt>18\r\n<\/tt>19\r\n<\/tt>20<\/strong>\r\n<\/tt>21\r\n<\/tt>22\r\n<\/tt>23\r\n<\/tt>24\r\n<\/tt>25<\/strong>\r\n<\/tt>26\r\n<\/tt>27\r\n<\/tt>28\r\n<\/tt>29\r\n<\/tt>30<\/strong>\r\n<\/tt><\/pre>\n<\/td>\n
\n
\r\n<\/tt>class<\/span> TestItController<\/span> < ApplicationController<\/span>\r\n<\/tt>  def<\/span> index<\/span>\r\n<\/tt>    @genres<\/span>  = Genre<\/span>.find(:all<\/span>)\r\n<\/tt>    @artists<\/span> = Artist<\/span>.find(:all<\/span>)\r\n<\/tt>    @songs<\/span>   = Song<\/span>.find(:all<\/span>)\r\n<\/tt>  end<\/span>\r\n<\/tt>\r\n<\/tt>  def<\/span> update_artists<\/span>\r\n<\/tt>    # updates artists and songs based on genre selected<\/span>\r\n<\/tt>    genre = Genre<\/span>.find(params[:genre_id<\/span>])\r\n<\/tt>    artists = genre.artists\r\n<\/tt>    songs   = genre.songs\r\n<\/tt>\r\n<\/tt>    render :update<\/span> do<\/span> |page|\r\n<\/tt>      page.replace_html '<\/span>artists<\/span>'<\/span><\/span>, :partial<\/span> => '<\/span>artists<\/span>'<\/span><\/span>, :object<\/span> => artists\r\n<\/tt>      page.replace_html '<\/span>songs<\/span>'<\/span><\/span>,   :partial<\/span> => '<\/span>songs<\/span>'<\/span><\/span>,   :object<\/span> => songs\r\n<\/tt>    end<\/span>\r\n<\/tt>  end<\/span>\r\n<\/tt>\r\n<\/tt>  def<\/span> update_songs<\/span>\r\n<\/tt>    # updates songs based on artist selected<\/span>\r\n<\/tt>    artist = Artist<\/span>.find(params[:artist_id<\/span>])\r\n<\/tt>    songs  = artist.songs\r\n<\/tt>\r\n<\/tt>    render :update<\/span> do<\/span> |page|\r\n<\/tt>      page.replace_html '<\/span>songs<\/span>'<\/span><\/span>, :partial<\/span> => '<\/span>songs<\/span>'<\/span><\/span>, :object<\/span> => songs\r\n<\/tt>    end<\/span>\r\n<\/tt>  end<\/span>\r\n<\/tt>end<\/span>\r\n<\/tt><\/pre>\n<\/td>\n<\/tr>\n<\/table>\n

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:
\n# the _songs partial (_songs.html.erb):<\/p>\n\n\n
\n
1\r\n<\/tt>2\r\n<\/tt>3\r\n<\/tt><\/pre>\n<\/td>\n
\n
\r\n<\/tt><%= collection_select(nil<\/span>, :song_id<\/span>, songs, :id<\/span>, :title<\/span>,\r\n<\/tt>                     {:prompt<\/span>   => "<\/span>Select a Song<\/span>"<\/span><\/span>}) %><\/span>\r\n<\/tt><\/span><\/span><\/pre>\n<\/td>\n<\/tr>\n<\/table>\n

# the _artists partial (_artists.html.erb):<\/p>\n\n\n
\n
1\r\n<\/tt>2\r\n<\/tt>3\r\n<\/tt>4\r\n<\/tt>5<\/strong>\r\n<\/tt>6\r\n<\/tt><\/pre>\n<\/td>\n
\n
\r\n<\/tt><%= collection_select(nil<\/span>, :artist_id<\/span>, artists, :id<\/span>, :name<\/span>,\r\n<\/tt>                     {:prompt<\/span>   => "<\/span>Select an Artist<\/span>"<\/span><\/span>},\r\n<\/tt>                     {:onchange<\/span> => "<\/span>#{<\/span>remote_function(:url<\/span>  => {:action<\/span> => "<\/span>update_songs<\/span>"<\/span><\/span>},\r\n<\/tt>                                                      :with<\/span> => "<\/span>'artist_id='+value<\/span>"<\/span><\/span>)}<\/span><\/span>"<\/span><\/span>}) %><\/span>\r\n<\/tt><br\/<\/span>><\/span><\/span>\r\n<\/tt><\/pre>\n<\/td>\n<\/tr>\n<\/table>\n

# and last, but not least, the index view (index.html.erb):<\/p>\n\n\n
\n
1\r\n<\/tt>2\r\n<\/tt>3\r\n<\/tt>4\r\n<\/tt>5<\/strong>\r\n<\/tt>6\r\n<\/tt>7\r\n<\/tt>8\r\n<\/tt>9\r\n<\/tt><\/pre>\n<\/td>\n
\n
\r\n<\/tt><%= javascript_include_tag :defaults<\/span> %><\/span>\r\n<\/tt><%= collection_select(nil, :genre_id,  @genres,  :id, :name,\r\n<\/tt>                      {:prompt   =<\/span>><\/span><\/span> "<\/span>Select a Genre<\/span>"<\/span><\/span>},\r\n<\/tt>                      {:onchange<\/span> => "<\/span>#{<\/span>remote_function(:url<\/span>  => {:action<\/span> => "<\/span>update_artists<\/span>"<\/span><\/span>},\r\n<\/tt>                                                       :with<\/span> => "<\/span>'genre_id='+value<\/span>"<\/span><\/span>)}<\/span><\/span>"<\/span><\/span>}) %><\/span>\r\n<\/tt><br\/<\/span>><\/span><\/span>\r\n<\/tt><div id="<\/span>artists<\/span>"<\/span><\/span>><%= render :partial<\/span> => '<\/span>artists<\/span>'<\/span><\/span>, :object<\/span> => @artists<\/span> %><\/span><\/div<\/span>><\/span><\/span>\r\n<\/tt><div id="<\/span>songs<\/span>"<\/span><\/span>><%= render :partial<\/span> => '<\/span>songs<\/span>'<\/span><\/span>,   :object<\/span> => @songs<\/span> %><\/span><\/div<\/span>><\/span><\/span>\r\n<\/tt><\/pre>\n<\/td>\n<\/tr>\n<\/table>\n

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.<\/p>\n

For now, if you have any questions, just ask me in the comments.<\/p>\n","protected":false},"excerpt":{"rendered":"

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 […]<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[6,3,5,30],"tags":[204,210],"_links":{"self":[{"href":"http:\/\/pullmonkey.com\/wp-json\/wp\/v2\/posts\/36087"}],"collection":[{"href":"http:\/\/pullmonkey.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/pullmonkey.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/pullmonkey.com\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"http:\/\/pullmonkey.com\/wp-json\/wp\/v2\/comments?post=36087"}],"version-history":[{"count":2,"href":"http:\/\/pullmonkey.com\/wp-json\/wp\/v2\/posts\/36087\/revisions"}],"predecessor-version":[{"id":57732,"href":"http:\/\/pullmonkey.com\/wp-json\/wp\/v2\/posts\/36087\/revisions\/57732"}],"wp:attachment":[{"href":"http:\/\/pullmonkey.com\/wp-json\/wp\/v2\/media?parent=36087"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/pullmonkey.com\/wp-json\/wp\/v2\/categories?post=36087"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/pullmonkey.com\/wp-json\/wp\/v2\/tags?post=36087"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}