First set things up:
# create new rails app | |
rails new dynamic_select_boxes -m https://raw.github.com/RailsApps/rails3-application-templates/master/rails3-haml-html5-template.rb | |
# create models: | |
rails g model genre name:string | |
rails g model artist name:string genre_id:integer | |
rails g model song title:string artist_id:integer | |
rake db:migrate |
I just used the html5 haml twitter bootstrap, etc template. Really useful.
If you need data, here's what I used - put this in your db/seeds.rb file:
3.times do |x| | |
genre = Genre.find_or_create_by_name(:name => "Genre #{x}") | |
3.times do |y| | |
artist = Artist.find_or_create_by_name(:name => "Artist #{x}.#{y}", :genre => genre) | |
3.times do |z| | |
Song.find_or_create_by_title(:title => "Song #{x}.#{y}.#{z}", :artist => artist) | |
end | |
end | |
end |
Next, setup your model associations:
# app/models/artist.rb | |
class Artist < ActiveRecord::Base | |
belongs_to :genre | |
has_many :songs | |
attr_accessible :genre_id, :name, :genre | |
end | |
# app/models/genre.rb | |
class Genre < ActiveRecord::Base | |
attr_accessible :name | |
has_many :artists | |
has_many :songs, :through => :artists | |
end | |
# app/models/songs.rb | |
class Song < ActiveRecord::Base | |
belongs_to :artist | |
attr_accessible :artist_id, :title, :artist | |
end |
Genres have many artists.
Artists have many songs.
Genres have many songs through artists.
I'm just using a home controller to setup variables for the index page as well as setup variables for use in the dynamic updating:
# app/controllers/home_controller.rb | |
class HomeController < ApplicationController | |
def index | |
@genres = Genre.all | |
@artists = Artist.all | |
@songs = Song.all | |
end | |
def update_artists | |
# updates artists and songs based on genre selected | |
genre = Genre.find(params[:genre_id]) | |
# map to name and id for use in our options_for_select | |
@artists = genre.artists.map{|a| [a.name, a.id]}.insert(0, "Select an Artist") | |
@songs = genre.songs.map{|s| [s.title, s.id]}.insert(0, "Select a Song") | |
end | |
def update_songs | |
# updates songs based on artist selected | |
artist = Artist.find(params[:artist_id]) | |
@songs = artist.songs.map{|s| [s.title, s.id]}.insert(0, "Select a Song") | |
end | |
end |
Now the view just has the 3 select boxes and the unobtrusive javascript (triggered onchange) to make the ajax calls for updating:
# app/views/home/index.html.haml | |
= collection_select(nil, :genre_id, @genres, :id, :name, {:prompt => "Select a Genre"}, {:id => 'genres_select'}) | |
%br | |
= collection_select(nil, :artist_id, @artists, :id, :name, {:prompt => "Select an Artist"}, {:id => 'artists_select'}) | |
%br | |
= collection_select(nil, :song_id, @songs, :id, :title, {:prompt => "Select a Song"}, {:id => 'songs_select'}) | |
:javascript | |
$(document).ready(function() { | |
$('#genres_select').change(function() { | |
$.ajax({ | |
url: "#{update_artists_path}", | |
data: { | |
genre_id : $('#genres_select').val() | |
}, | |
dataType: "script" | |
}); | |
}); | |
$('#artists_select').change(function() { | |
$.ajax({ | |
url: "#{update_songs_path}", | |
data: { | |
artist_id : $('#artists_select').val() | |
}, | |
dataType: "script" | |
}); | |
}); | |
}); |
We need our rjs files for updating the select boxes, one for the songs (when artist changes) and one for the artists and songs (when genre changes):
# app/views/home/update_artists.js.haml | |
$('#artists_select').html("#{escape_javascript(options_for_select(@artists))}"); | |
$('#songs_select').html("#{escape_javascript(options_for_select(@songs))}"); | |
# app/views/home/update_songs.js.haml | |
$('#songs_select').html("#{escape_javascript(options_for_select(@songs))}"); |
Our routes are simple:
# config/routes.rb | |
DynamicSelectBoxes::Application.routes.draw do | |
get 'home/update_artists', :as => 'update_artists' | |
get 'home/update_songs', :as => 'update_songs' | |
root :to => "home#index" | |
end |
That's it.
UPDATE: Here's an erb alternative for index.html.
# app/views/home/index.html.haml | |
<%= collection_select(nil, :genre_id, @genres, :id, :name, {:prompt => "Select a Genre"}, {:id => 'genres_select'}) %> | |
<br/> | |
<%= collection_select(nil, :artist_id, @artists, :id, :name, {:prompt => "Select an Artist"}, {:id => 'artists_select'}) %> | |
<br/> | |
<%= collection_select(nil, :song_id, @songs, :id, :title, {:prompt => "Select a Song"}, {:id => 'songs_select'}) %> | |
<script> | |
$(document).ready(function() { | |
$('#genres_select').change(function() { | |
$.ajax({ | |
url: "<%= update_artists_path %>", | |
data: { | |
genre_id : $('#genres_select').val() | |
}, | |
dataType: "script" | |
}); | |
}); | |
$('#artists_select').change(function() { | |
$.ajax({ | |
url: "<%= update_songs_path %>", | |
data: { | |
artist_id : $('#artists_select').val() | |
}, | |
dataType: "script" | |
}); | |
}); | |
}); | |
</script> |
And the js.haml can be converted to js.erb by taking #{...} and converting to <%= ... %> :
# app/views/home/update_artists.js.haml | |
$('#artists_select').html("<%= escape_javascript(options_for_select(@artists)) %>"); | |
$('#songs_select').html("<%= escape_javascript(options_for_select(@songs)) %>"); | |
# app/views/home/update_songs.js.haml | |
$('#songs_select').html("<%= escape_javascript(options_for_select(@songs)) %>"); |