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.

> Location.first.notes.create(user_id: 1, content: "foobar")
 => #< Note id: 1, content: "foobar", user_id: 1, created_at: "2012-11-30 02:19:24", updated_at: "2012-11-30 02:19:24" >
> Location.first.notes
 => [#< Note id: 1, content: "foobar", user_id: 1, created_at: "2012-11-30 02:19:24", updated_at: "2012-11-30 02:19:24" >] 

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.

> Note.first.locations
 => [#< Location id: 1, name: "foobar", created_at: "2012-11-30 02:19:24", updated_at: "2012-11-30 02:19:24" >]

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:

> note = Note.create(user_id: 1, content: "foobar2")
 => #< Note id: 2, content: "foobar2", user_id: 1, created_at: "2012-11-30 02:19:24", updated_at: "2012-11-30 02:19:24" >
> Location.all.each do |location|
     note.locations << location
   end

I’m using « to push location objects into the note.locations array, and ActiveRecord will handle the creation of the NoteJoin record. Pretty neat.