I have two models, Song and Show. A Show is an ordered list of Songs, in which the same Song can be listed multiple times.
That is, there should be an ordered array (or hash or anything) somewhere in Show that can contain Song1, Song2, Song1, Song3 and allow re-ordering, inserting, or deleting from that array.
I cannot figure out how to model this with ActiveRecord associations. I’m guessing I need some sort of special join table with a column for the index, but apart from starting to code my SQL directly, is there a way to do this with Rails associations?
Some code as I have it now (but doesn’t work properly):
class Song < ActiveRecord::Base
attr_accessible :title
has_and_belongs_to_many :shows
end
class Show < ActiveRecord::Base
attr_accessible :date
has_and_belongs_to_many :songs
end
song1 = Song.create(title: 'Foo')
song2 = Song.create(title: 'Bar')
show1 = Show.create(date: 'Tomorrow')
show1.songs << song1 << song2 << song1
puts "show1 size = #{show1.songs.size}" # 3
show1.delete_at(0) # Should delete the first instance of song1, but leave the second instance
puts "show1 size = #{show1.songs.size}" # 2
show1.reload
puts "show1 size = #{show1.songs.size}" # 3 again, annoyingly
Inserting might look like:
show1.songs # Foo, Bar, Foo
song3 = Song.create(title: 'Baz')
show1.insert(1, song3)
show1.songs # Foo, Baz, Bar, Foo
And reordering might (with a little magic) look something like:
show1.songs # Foo, Bar, Foo
show1.move_song_from(0, to: 1)
show1.songs # Bar, Foo, Foo
My current solution to this is a combination of has_many :through and acts_as_list. It was not the easiest thing to find information on combining the two correctly. One of the hurdles, for example, was that acts_as_list uses an index starting at 1, while the array-like methods created by the ActiveRecord association start at 0.
Here’s how my code ended up. Note that I had to specify explicit methods to modify the join table (for most of them anyway); I’m not sure if there’s a cleaner way to make those work.
I added a ‘position’ column to my ‘playlist_items’ table, as per the instructions that came with acts_as_list. It’s worth noting that I had to dig into the API for acts_as_list to find the insert_at method.