Here’s an interesting problem I ran into today.
Polymorphic associations in Ruby on Rails are actually quite easy to do, especially in Rails 3.2. If you need a refresher, there’s a great screencast over at Railscasts, which does require a subscription which I highly highly recommend: http://railscasts.com/episodes/154-polymorphic-association-revised.
However, my problem was a little different, and maybe a special case because I can’t think of many applications this would apply to.
Say I have a Location and a Checkpoint, and the Location and Checkpoint can have notes, posted by Users. I would use a polymorphic association for the Notes to the Location and Checkpoint. But, I also want a single Note, to be posted to many Locations or Checkpoints, which for a single model-to-model relationship I could simply use a join table.
Example, two Locations which are near each other, maybe they’re coordinates, could share a single Note describing the general area, and with the same Note model, one Note may describe multiple Checkpoints.
Solution: Make the join table polymorphic.
Notes model that contains the content and user_id, which I use to associate the User model with.
Note join model that is also polymorphic. notable_id and notable_type is the polymorphic attributes used by ActiveRecord.
Next I add the model associations to Location and Checkpoint models.
Above I set the note_joins model as the polymorphic association :notable, which will use the notable_id and notable_type attributes to assign which model and ID the note join belongs to. Then I set the has_many association on the Note model, through note_joins. This will let me use such methods as Location.first.notes to pull up all the notes that belong to that location. The same applies to Checkpoint.
In the NoteJoin model I need specify that it belongs to the notable polymorphic association and that it also belongs to a Note. I do so by adding the following.
Now for the Note model, it should belong to the notable polymorphic association, and also belong to a user(through the user_id attribute).
At this point everything should work, and through the NoteJoin model I can have a single Note belong to many different Locations and even Checkpoints.
Let’s quickly test this in console.
Great it works, but now I want to see what Locations or Checkpoints this Note belongs to through NoteJoin. To do that I need to update my Note model to include a has_many locations and checkpoints. Notice I’m using the source and source_type option, to pass my polymorphic association :notable, since that’s how we translate which model it belongs to.
And now I can find the locations which my note belongs to.
Another problem came up after this point. If I were to create a Note, how could I easily add many Locations to it? My first thought was to create a method that would update the join table, but I knew there had to be an easier way already built in ActiveRecord to do this.
I did some digging around and found a simple solution:
I’m using « to push location objects into the note.locations array, and ActiveRecord will handle the creation of the NoteJoin record. Pretty neat.