views:

318

answers:

1

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.

+2  A: 

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.

Edit 1

I recently found out that Rails supports eager loading of polymorphic associations. So there is no need to declare fake associations.

class Container
  belongs_to :contents, :polymorphic => true
end

Now query the Container by container_type.

containers_with_food = Container.find_all_by_content_type("Food", 
                           :include => :contents)

containers_with_thing = Container.find_all_by_content_type("Thing", 
                           :include => :contents)
KandadaBoggu
I have updated my answer based on some new information, take a look.
KandadaBoggu