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! :-)
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
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.