Ruby on Rails: :include on a polymorphic association with submodels
Edited Answer
I recently found out that Rails supports eager loading of polymorphic associations when you filter by the polymorphic type column. So there is no need to declare fake associations.
class Container
belongs_to :content, :polymorphic => true
end
Now query the Container
by container_type
.
containers_with_food = Container.find_all_by_content_type("Food",
:include => :content)
containers_with_thing = Container.find_all_by_content_type("Thing",
:include => :content)
Old Answer
This is a hack as there is no direct way to include the polymorphic objects in one query.
class Container
belongs_to :contents, :polymorphic => true
# add dummy associations for all the contents.
# this association should not be used directly
belongs_to :food
belongs_to :thing
end
Now query the Container
by container_type
.
containers_with_food = Container.find_all_by_content_type("Food",
:include => :food)
containers_with_thing = Container.find_all_by_content_type("Thing",
:include => :thing)
That results in two SQL calls to the database ( actually it is 4 calls as rails executes one SQL for every :include
)
There is no way to do this in one SQL as you need different column set for different content types.
Caveat: The dummy associations on Content
class should not be used directly as it will result in unexpected results.
E.g: Lets say the first object in the contents
table contains food.
Content.first.food # will work
Content.first.thing
The second call will not work. It might give you a Thing
object with the same id as the Food
object pointed by Content
.
William Jones
Updated on June 24, 2022Comments
-
William Jones almost 2 years
When working with a polymorphic association, is it possible to run an include on submodels that are only present in some types?
Example:
class Container belongs_to :contents, :polymorphic => true end class Food has_one :container belongs_to :expiration end class Things has_one :container end
In the view I'm going to want to do something like:
<% c = Containers.all %> <% if c.class == Food %> <%= food.expiration %> <% end %>
Therefore, I'd like to eager load the expirations when I load up c, because I know I will need every last one of them. Is there any way to do so? Just defining a regular :include gets me errors because not all enclosed types have a submodel expiration.
-
Harish Shetty over 13 yearsI have updated my answer based on some new information, take a look.
-
nicholaides almost 12 yearsIn what version of ActiveRecord does eager-loading of polymorphic associations work? It's not working for me in 3.2.3
-
Harish Shetty almost 12 years@nicholaides It should work in 3.2.3. The eager loading happens only when you filter by the polymorphic type column. What is the error you encountered?
-
nicholaides almost 12 years"The eager loading happens only when you filter by the polymorphic type column". Ah, I didn't catch that part. I took the liberti of updating your answer. I hope you don't mind!