Eager loading of polymorphic associations in ActiveRecord - ruby-on-rails

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?

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.

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.

(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.

Related

Eager-load an association but ONLY a single column

I have a self-referential association like this:
class User
belongs_to :parent_user, class_name: "User"
has_many :child_users, class_name: "User", foreign_key: :parent_user_id
end
When I get a list of users, I would like to be able to eager-load the child_users association, but I only need the id column from it. I need to be able to do something like this without causing n+1 queries, and preferably without having to load all of the other data in this model:
users = User.preload(:child_users)
users.map(&:child_user_ids)
The above works to limit the query to two queries instead of n+1, but I'm loading a lot of full objects into memory that I would prefer to avoid when I only care about the id itself on those child assocations.
You don't want eager loading which is for loading models. You want to select an aggregate. This isn't something the ActiveRecord query interface handles so you'll need to use SQL strings or Arel and use the correct function for your DB.
For example:
# This is for Postgres, use JSON_ARRAYAGG on MySQL
User.left_joins(:child_users)
.select(
"users.*",
'json_agg("child_users_users".id) AS child_ids'
)
.group(:id)
child_users_users is the wonky alias that .left_joins(:child_users) creates.
At least on Postgres the driver (the PG gem) will automatically cast the result to a an array but YMMV on the other dbs.

ActiveRecord WHERE with namespaced models

I've two models within the same namespace/module:
module ReverseAuction
class Demand < ApplicationRecord
belongs_to :purchase_order, inverse_of: :demands, counter_cache: true
end
end
module ReverseAuction
class PurchaseOrder < ApplicationRecord
has_many :demands
end
end
note that I don't have to specify the class_name for the models cause they're in the same module and the relations are working well this way.
When I try to query a includes with the name of the relation by itself, it works fine, like:
ReverseAuction::PurchaseOrder.all.includes(:demands) # all right .. AR is able to figure out that *:demands* correspond to the 'reverse_auction_demands' table
But when I try to use a where in this query, AR seems to be unable to figure out the (namespaced) table name by itself, so:
ReverseAuction::PurchaseOrder.includes(:demands).where(demands: {user_id: 1}) # gives me error: 'ERROR: missing FROM-clause entry for table "demands"'
But if I specify the full resolved (namespaced) model name, then where goes well:
ReverseAuction::PurchaseOrder.includes(:demands).where(reverse_auction_demands: {user_id: 1}) # works pretty well
Is that normal that AR can infere table name of namespaced models from relations in includes but can't in where, or am I missing the point?
Is that normal that AR can infere table name of namespaced models from
relations in includes but can't in where?
Yes. This is an example of a leaky abstraction.
Assocations are an objection oriented abstraction around SQL joins, to let you do the fun stuff while AR worries about writing the SQL to join them and maintaining the in memory couplings between the records. .joins, .left_joins .includes and .eager_load are "aware" of your assocations and go through that abstraction. Because you have this object oriented abstraction .includes is smart enough to figure out how the module nesting should effect the class names and table names when writing joins.
.where and all the other parts of the ActiveRecord query interface are not as smart. This is just an API that generates SQL queries programmatically.
When you do .where(foo: 'bar') its smart enough to translate that into WHERE table_name.foo = 'bar' because the class is aware of its own table name.
When you do .where(demands: {user_id: 1}) the method is not actually aware of your associations, other model classes or the schema and just generates WHERE demands.user_id = 1 because that's how it translates a nested hash into SQL.
And note that this really has nothing to do with namespaces. When you do:
.where(reverse_auction_demands: {user_id: 1})
It works because you're using the right table name. If you where using a non-conventional table name that didn't line up with the model you would have the exact same issue.
If you want to create a where clause based on the class without hardcoding the table name pass a scope to where:
.where(
ReverseAuction::Demand.where(user_id: 1)
)
or use Arel:
.where(
ReverseAuction::Demand.arel_table[:user_id].eq(1)
)

Using existing tables and its relations in rails 3.2 application?

I have an existing application with lot of data and about 20 tables, how can I use them directly.
My database.yml file points to the MysQL database. Something like magic model generator.
you can do the following
connection = ActiveRecord::Base.connection()
results = connection.execute("#{your_sql_query_here}")
results.each do |row|
puts row[0]
end
However I'd recommend you to associate them in a more coherent fashion.
You would create a model for each one of them which is not much when you only have about 20.
In the model you would associate the table to them using the
set_table_name :name_of_your_table
Keep in mind the model name just have to be relavent enough to the table, because of the explicit set_table_name method they do not have to follow strict convention.
And to set relationships you would use the class_name just like this
has_many :fruits, :class_name => "CrazyFruit"
It might sound tedious but its the right way

Selectors where there are relationships

Is there a special way that you should call an object when you want to add a where clause that relates to a relationship that has been defined in the model.
An example would be that I have an image, the image belongs_to or has_many (whatever rocks your boat) a category, and I want to select all images that don't have any associated categories.
So for a simple belongs_to I could just say:
Image.where('category_id is null')
But is there a better way of doing this since the relationship has been explicitly defined in the model?
Are you feeling uncomfortable using where with a string clause? I have that feeling everytime I use where.
You can also do
Image.where(:category_id=>nil)
or use the dynamic finder method
Image.find_all_by_category_id(nil)
or load via the association
#images = Category.find(some_cat_id).images
(This one doesn't work for nil)
I think all of them should generate more or less the same SQL.
Try this:
Image.find(:all, :conditions => 'category_id is null')

How would you represent this sql query in a ActiveRecord relationship?

I have a slightly complicated query that I'd like to have as a natural ActiveRecord relationship. Currently I call #calendar.events and #calendar.shared_events then join the arrays and sort them. The consolidation is able to be done with this sql:
SELECT calendar_events.* FROM calendar_events left outer join calendar_events_calendars on calendar_events_calendars.calendar_event_id = calendar_events.id where calendar_events.calendar_id = 2 or calendar_events_calendars.calendar_id = 2
but I'm not sure how to represent this as an ActiveRecord relationship. I know I could use a custom sql finder but I'd like to be able to use scopes on the results.
Currently an event belongs to one calendar and also belongs to many other calendars via a habtm join table:
has_and_belongs_to_many :shared_events, :class_name => 'CalendarEvent', :order => 'beginning DESC, name'
has_many :events, :class_name => 'CalendarEvent', :dependent => :destroy, :order => 'beginning DESC, name'
Any pointers would be greatly appreciated :)
Can you help us understand your data structure a bit more? What are you trying to achieve with the two relationships (the has_many/belongs_to and the HABTM) that you can't achieve through the HABTM or a has_many, :through? It looks to me like a simplification of your data model is likely to yield the results you're after.
(sorry - not enough points or would have added as a comment)
--UPDATED AFTER COMMENT
I think that what you've suggested in your comment is an infinitely better solution. It is possible to do it how you've started implementing it, but it's unnecessarily complex - and in my experience, you can often tell when you're going down the wrong path with ActiveRecord when you start having crazy complex and duplicate relationship names.
Why not
a) have it all in a has_many, through relationship (in both directions)
b) have an additional field on the join table to specify that this is the main/primary attachment (or vice versa).
You can then have named scopes on the Event model for shared events, etc. which do the magic by including the condition on the specified join. This gives you:
#calendar.events #returns all events across the join
#calendar.shared_events #where the 'shared' flag on the join is set
#calendar.main_events #without the flag

Resources