Eager loading of polymorphic associations in Activ

2019-03-16 09:00发布

This is my first time using Rails and I was wondering if it's possible to load a has one polymorphic association in one SQL query? The models and associations between them are basic enough: An Asset model/table can refer to a content (either an Image, Text, or Audio) through a polymorphic association, i.e.

class Asset < ActiveRecord::Base
  :belongs_to :content, :polymorphic => true
end

and the Image, Text, Audio are defined like this:

class Image < ActiveRecord::Base
  :has_one :asset, :as => :content
end

When I try to load an Image, say like this:

Image.first(
      :conditions => {:id => id},
      :include => :asset
)

It specifies two queries, one to retrieve the Image and another to retrieve the Asset (FYI, this happens also if I specify a :joins). Based on my understanding, ActiveRecord does this because it doesn't know there's a one-to-one association between Image and Asset. Is there a way to force a join and retrieve the 2 objects in one go? I've also tried using join with a custom select, but I end up having to create the ActiveRecord models manually and their associations.

Does ActiveRecord provide a way to do this?

3条回答
对你真心纯属浪费
2楼-- · 2019-03-16 09:45

(This is for Rails 3 syntax.)

MetaWhere is an awesome gem for making complex queries that are outside of ActiveRecord's usual domain easy and ruby-like. (@wayne: It supports outer joins as well.)

https://github.com/ernie/meta_where

Polymorphic joins are a little trickier. Here's how I did mine with MetaWhere:

Image.joins(:asset.type(AssetModel)).includes(:asset)

In fact, I made a convenience method in my polymorphic-association-having class:

  def self.join_to(type)
    joins(:asset.type(type)).includes(:asset)
  end

So it will always query & eager load a particular type of asset. I haven't tried mixing it with multiple types of join in one query. Because polymorphism works using the same foreign key to reference different tables, on the SQL level you have to specify it as separate joins because one join only joins to one table.

This only works with ActiveRecord 3 because you have access to the amazing power of AREL.

查看更多
爷的心禁止访问
3楼-- · 2019-03-16 09:46

After digging through the Rails source I've discovered that you can force a join by referencing a table other than the current model in either the select, conditions or order clauses.

So, if I specify an order on the Asset table:

Image.first(
      :conditions => {:id => id},
      :include => :asset,
      :order => "asset.id"
)

The resulting SQL will use a left outer join and everything in one statement. I would have preferred an inner join, but I guess this will do for now.

查看更多
兄弟一词,经得起流年.
4楼-- · 2019-03-16 09:52

I ran up against this issue myself. ActiveRecord leans more toward the end of making it easy for Rubyists (who may not even be all too familiar with SQL) to interface with the database, than it does with optimized database calls. You might have to interact with the database at a lower level (e.g. DBI) to improve your performance. Using ActiveRecord will definitely affect how you design your schema.

The desire for SQL efficiency got me thinking about using other ORMs. I haven't found one to suit my needs. Even those that move more toward transact SQL itself (e.g. Sequel) have a heavy Ruby API. I would be content without a Ruby-like API and just manually writing my T-SQL. The real benefit I'm after with an ORM is the M, mapping the result set (of one or more tables) into the objects.

查看更多
登录 后发表回答