Selectors where there are relationships - ruby-on-rails

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')

Related

Is it possible to ask for only certain columns from an ActiveRecord association?

consider
def Foo
has_one :user
end
let's say i only want a Foo's User's name, and not any of the other columns. so i want
SELECT name FROM "users" WHERE "prices"."id" = 123
but doing foo.user.name will give me
SELECT * FROM "users" WHERE "prices"."id" = 123
is there any slick way to use the association to get only one column? if not, then i have to do:
User.where(id: foo.user_id).pluck(:name).first
In general you can specify what columns you want to select using the .select method, like:
User.select(:name).where(...)
This will return just the values from the name column. You can chain this onto an association, but not onto an instance. So, as meagar very agressively pointed out by downvoting the other answers (including Mori's deleted answer), in a has_one relationship you can't chain this on the association (because it's not an association in that case). However, you could build a custom scope, like this:
class Foo < ActiveRecord::Base
has_one :bar
scope :bar_name, lambda {Bar.select(:name).where(:foo_id=> id)}
end
The above is untested so you may have to tweak it, but generally speaking that approach would allow you to do something like:
foo.bar_name
...without loading all the columns from Bar.
No, in the case of your has_one, but yes in the case of has_many.
The object returned for a has_one association isn't a scope onto which you can chain additional methods like select, like with a has_many. It's an actual instance of the model, and instantiating it will necessarily involve a select *.
If you want to select just the name, you'll have to access the User model directly and use select.
Conversely, if your Foo has many users, you could use foo.users.select("name") or any of the other chainable methods, as foo.users would be an actual ActiveRecord association, not an instance of a model.

Finding nil has_one associations in where query

This may be a simple question, but I seem to be pulling my hair out to find an elegant solution here. I have two ActiveRecord model classes, with a has_one and belongs_to association between them:
class Item < ActiveRecord::Base
has_one :purchase
end
class Purchase < ActiveRecord::Base
belongs_to :item
end
I'm looking for an elegant way to find all Item objects, that have no purchase object associated with them, ideally without resorting to having a boolean is_purchased or similar attribute on the Item.
Right now I have:
purchases = Purchase.all
Item.where('id not in (?)', purchases.map(&:item_id))
Which works, but seems inefficient to me, as it's performing two queries (and purchases could be a massive record set).
Running Rails 3.1.0
It's quite common task, SQL OUTER JOIN usually works fine for it. Take a look here, for example.
In you case try to use something like
not_purchased_items = Item.joins("LEFT OUTER JOIN purchases ON purchases.item_id = items.id").where("purchases.id IS null")
Found two other railsey ways of doing this:
Item.includes(:purchase).references(:purchase).where("purchases.id IS NULL")
Item.includes(:purchase).where(purchases: { id: nil })
Technically the first example works without the 'references' clause but Rails 4 spits deprecation warnings without it.
A more concise version of #dimuch solution is to use the left_outer_joins method introduced in Rails 5:
Item.left_outer_joins(:purchase).where(purchases: {id: nil})
Note that in the left_outer_joins call :purchase is singular (it is the name of the method created by the has_one declaration), and in the where clause :purchases is plural (here it is the name of the table that the id field belongs to.)
Rails 6.1 has added a query method called missing in the ActiveRecord::QueryMethods::WhereChain class.
It returns a new relation with a left outer join and where clause between the parent and child models to identify missing relations.
Example:
Item.where.missing(:purchase)

Squeel to execute outer join on Rails

I'm trying to find a way to create a simple outer join without too much hassle. I know I can do this manually by specifying an outer join, but I'm looking for a simple way.
Therefore, I was taking a look at Squeel, which seems to be the new alternative for Metawhere. It seems to be able to handle outer joins, but I can't get what I want.
In particular, I have three models :
City
Building
CityBuilding
I would simply like a list of all the buildings whether they exist in a city or not. CityBuilding is, of course, the model that connects a city to a building. I would like to get something like :
city 1{
TownCenter => city_building
Sawmill => city_building
Quarry => nil
}
The query is null since there is no city_building entry for this one, you get the idea.
Is there a way that Squeel does that? Or maybe another gem, without having to manually do an outer join?
I think you can try something like the below using Squeel. I am not sure about the where part. You will have to give one of the two join conditions.
Building.joins{city}.joins(city_buildings.outer).where{(buidlings.id == city_buildings.building_id) & (cities.id == city_buildings.city_id)}
or
Building.joins{city}.joins(city_buildings.outer).where{buidlings.id == city_buildings.building_id}
or
Building.joins{city}.joins(city_buildings.outer).where{cities.id == city_buildings.city_id}
The AR association includes uses LEFT OUTER JOIN. If you have a model relationship as follows, then:
class City
has_many :city_buildings
has_many :buildings, :through => :city_buildings
end
class Building
has_one :city_building
has_one :city, :through => :city_building
end
class CityBuilding
belongs_to :city
belongs_to :building
end
To get a list of buildings regardless of city link
Building.includes(:city).where(...)
To get a list of buildings with a of city link
Building.includes(:city).where("cities.id IS NOT NULL")
Note
I am assuming you want to access the city object after the query(if present).
This is not a good solution if you DO NOT want to eager load the city object associated with a building (as AR eager loads include associations after executing the OUTER JOIN).

Ruby on Rails: Is there a way to temporarily disable polymorphic associations?

I've got an association between models that is polymorphic.
Example:
class Review
belongs_to :review_subject, :polymorphic => true
end
However, I would like to make a call on this :review_subject association where I know all the results will be of a certain type.
In my case, I want to do this so I can join in the review_subject and then impose a condition upon it. Doing so on a polymorphic relation normally causes this to raise an EagerLoadPolymorphicError. The logic behind this is that it's not able to know what model to load in order to perform the join, but that does not apply in my case because I already know only one model will be involved in this query.
Example, where I know that all relevant reviews will belong_to a Book, and I want to only show reviews where the books have authors:
Review.joins(:review_subject)
.where(review_subject_type => "Book")
.where("reviewed.book_author IS NOT NULL")
Is there a way to temporarily disable the polymorphic relationship?
The best solution I've come up with is to add a second associationin the Review model, belongs_to :review_subject_books_only that is not polymorphic, and can be called on only in this situation. However, this is an ugly hack both in the model, and in that it also messes up include calls unless the views also refer to a Review's review_subject_books_only.
Do the query the other way around:
Book.joins(:reviews).where('book_author is not null')

Eager loading of polymorphic associations in ActiveRecord

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.

Resources