I just got
comment asking how one would go about doing a many to many relation in this dynamic select box example. For example, what if an artist belongs to multiple genres. Here we go:
The original tutorial.
Create your models and build your migrations:
1
2
3
4
5
|
ruby script/generate model genre name:string
ruby script/generate model artist name:string # no genre_id here, moved to association table
ruby script/generate model song title:string artist_id:integer
ruby script/generate model artist_association artist_id:integer genre_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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
class CreateHierarchy < ActiveRecord::Migration
def self.up
# same genres as before
g1 = Genre.create(:name => "Genre 1")
g2 = Genre.create(:name => "Genre 2")
g3 = Genre.create(:name => "Genre 3")
# same artists as before, but without a genre_id
a1 = Artist.create(:name => "Artist 1")
a2 = Artist.create(:name => "Artist 2")
a3 = Artist.create(:name => "Artist 3")
a4 = Artist.create(:name => "Artist 4")
a5 = Artist.create(:name => "Artist 5")
a6 = Artist.create(:name => "Artist 6")
# now set which artists belong to which genres
# Artist 1 belongs to all three genres
ArtistAssociation.create(:genre_id => g1.id, :artist_id => a1.id)
ArtistAssociation.create(:genre_id => g2.id, :artist_id => a1.id)
ArtistAssociation.create(:genre_id => g3.id, :artist_id => a1.id)
# the rest of the artists only belong to one association
ArtistAssociation.create(:genre_id => g1.id, :artist_id => a2.id)
ArtistAssociation.create(:genre_id => g2.id, :artist_id => a3.id)
ArtistAssociation.create(:genre_id => g2.id, :artist_id => a4.id)
ArtistAssociation.create(:genre_id => g3.id, :artist_id => a5.id)
ArtistAssociation.create(:genre_id => g3.id, :artist_id => a6.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
|
So now we need to populate the database:
Now we need to modify our models to set up the associations.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
class Genre < ActiveRecord::Base
has_many :artist_associations
has_many :artists, :through => :artist_associations
# CAN"T NEST HMTs ..... has_many :songs, :through => :artists
# do it by hand ... argh
def songs
artists.map{|a| a.songs}.flatten
end
end
class Artist < ActiveRecord::Base
has_many :artist_associations
has_many :genres, :through => :artist_associations
has_many :songs
end
class ArtistAssociation < ActiveRecord::Base
belongs_to :artist
belongs_to :genre
end
|
That should be it for the many to many relationship.
Everything else is the same as in
the last tutorial.
# 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>
|
October 24th, 2008 at 08:53 AM Thanks Charlie
am I right if I said :
<filter:code>
class Artist < ActiveRecord::Base
has_many :artist_associations
has_many :genres, :through => :artist_associations
has_many :songs
end
< /filter:code>
codes means Artist has many Genres with artist_associations as a weak entity between Genres and Artists?
and is that "through" also mean dependent?
October 24th, 2008 at 03:22 PM
@Hilman - artist_associations is a join table between genre and artist. And through does not mean dependent - I missed that in this example, but you bring up a good point. You will want to specify the :dependent => :destroy in the join table association, like this:
This will remove the association when an artist is deleted.
Hope this helps.
October 24th, 2008 at 11:48 PM Okay.. once again thanks.. that's helped me a lot..