Rails: has_many through with polymorphic association - will this work?
Asked Answered
C

2

55

A Person can have many Events and each Event can have one polymorphic Eventable record. How do I specify the relationship between the Person and the Eventable record?

Here are the models I have:

class Event < ActiveRecord::Base
  belongs_to :person
  belongs_to :eventable, :polymorphic => true
end

class Meal < ActiveRecord::Base
  has_one :event, :as => eventable
end

class Workout < ActiveRecord::Base
  has_one :event, :as => eventable
end

The main question concerns the Person class:

class Person < ActiveRecord::Base
  has_many :events
  has_many :eventables, :through => :events  # is this correct???
end

Do I say has_many :eventables, :through => :events like I did above?

Or do I have to spell them all out like so:

has_many :meals, :through => :events
has_many :workouts, :through => :events

If you see an easier way to accomplish what I'm after, I'm all ears! :-)

Crystallite answered 9/8, 2011 at 13:46 Comment(1)
+1 for asking a question without 75 lines of code relevant only to your specific project.Jive
L
88

You have to do:

class Person < ActiveRecord::Base
  has_many :events
  has_many :meals, :through => :events, :source => :eventable,
    :source_type => "Meal"
  has_many :workouts, :through => :events, :source => :eventable,
    :source_type => "Workout"
end

This will enable you to do this:

p = Person.find(1)

# get a person's meals
p.meals.each do |m|
  puts m
end

# get a person's workouts
p.workouts.each do |w|
  puts w
end

# get all types of events for the person
p.events.each do |e|
  puts e.eventable
end
Linesman answered 9/8, 2011 at 16:53 Comment(0)
V
0

Another option of this is to use a Single Table Inheritance (STI) or Multi Table Inheritance (MTI) pattern, but that requires some ActiveRecord/DB Table rework, but this may help others still finding this who are designing it for the first time.

Here is the STI method in Rails 3+: Your Eventable concept becomes a class and needs a type column (which rails automatically populates for you).

class Eventable < ActiveRecord::Base
  has_one :event
end

Then, your other two classes inherit from Eventable instead of AR::Base

class Meal < Eventable
end

class Workout < Eventable
end

And your event object is basically the same, just not polymorphic:

class Event < ActiveRecord::Base
  belongs_to :person
  belongs_to :eventable
end

This may make some of your other layers more confusing, if you've never seen this before and you're not careful. For example, a single Meal object can be accessed at /meals/1 and /eventable/1 if you make both endpoints available in the routes, and you need to be aware of the class you're using when you pull an inherited object (hint: the becomes method may be very useful if you need to override the default rails behavior)

But this is a much cleaner deliniation of responsibilities as apps scale, in my experience. Just a pattern to consider.

Vast answered 28/3, 2016 at 13:22 Comment(2)
While STI might be useful in some situations, I think it's a little presumptuous to assume that it would be the right solution here, and it detracts from the purpose of the question—there are lots of very valid reasons to use polymorphic associations for models that it would make no sense to have in the same table!Shudder
I wasn't assuming—I was just providing another option.Vast

© 2022 - 2024 — McMap. All rights reserved.