ActiveRecord WHERE with namespaced models - ruby-on-rails

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

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.

ORDER BY and DISTINCT ON (...) in Rails

I am trying to ORDER by created_at and then get a DISTINCT set based on a foreign key.
The other part is to somehow use this is ActiveModelSerializer. Specifically I want to be able to declare:
has_many :somethings
In the serializer. Let me explain further...
I am able to get the results I need with this custom sql:
def latest_product_levels
sql = "SELECT DISTINCT ON (product_id) client_product_levels.product_id,
client_product_levels.* FROM client_product_levels WHERE client_product_levels.client_id = #{id} ORDER BY product_id,
client_product_levels.created_at DESC";
results = ActiveRecord::Base.connection.execute(sql)
end
Is there any possible way to get this result but as a condition on a has_many relationship so that I can use it in AMS?
In pseudo code: #client.products_levels
Would do something like: #client.order(created_at: :desc).select(:product_id).distinct
That of course fails for reasons that are beyond me.
Any help would be great.
Thank you.
A good way to structure this is to split your query into two parts: the first part manages the filtering of rows so that you get only your latest client product levels. The second part uses a standard has_many association to connect Client with ClientProductLevel.
Starting with your ClientProductLevel model, you can create a scope to do the latest filtering:
class ClientProductLevel < ActiveRecord::Base
scope :latest, -> {
select("distinct on(product_id) client_product_levels.product_id,
client_product_levels.*").
order("product_id, created_at desc")
}
end
You can use this scope anywhere that you have a query that returns a list of ClientProductLevel objects, e.g., ClientProductLevel.latest or ClientProductLevel.where("created_at < ?", 1.week.ago).latest, etc.
If you haven't already done so, set up your Client class with a has_many relationship:
class Client < ActiveRecord::Base
has_many :client_product_levels
end
Then in your ActiveModelSerializer try this:
class ClientSerializer < ActiveModel::Serializer
has_many :client_product_levels
def client_product_levels
object.client_product_levels.latest
end
end
When you invoke the ClientSerializer to serialize a Client object, the serializer sees the has_many declaration, which it would ordinarily forward to your Client object, but since we've got a locally defined method by that name, it invokes that method instead. (Note that this has_many declaration is not the same as an ActiveRecord has_many, which specifies a relationship between tables: in this case, it's just saying that the serializer should present an array of serialized objects under the key `client_product_levels'.)
The ClientSerializer#client_product_levels method in turn invokes the has_many association from the client object, and then applies the latest scope to it. The most powerful thing about ActiveRecord is the way it allows you to chain together disparate components into a single query. Here, the has_many generates the `where client_id = $X' portion, and the scope generates the rest of the query. Et voila!
In terms of simplification: ActiveRecord doesn't have native support for distinct on, so you're stuck with that part of the custom sql. I don't know whether you need to include client_product_levels.product_id explicitly in your select clause, as it's already being included by the *. You might try dumping it.

Legacy Database Schema - Key/Value Tables

I've got a legacy database schema which consists of objects similar to the following:
table=car
oid, something, something_else, ...
has many properties -> car_properties
table=car_properties
oid, car_id, keyname, value, ...
belongs to car
The Car object is actually (logically) a combination of the columns in the "car" table, and multiple rows in the "car_properties" table.
I'm looking at doing a parallel rewrite of the application which uses this schema, so I need some way to map this table schema back to a nice ActiveRecord object. Ideally I'd like each of the properties in the _properties table to be accessible as a method on the "Car" class, so I can change the underlying table later without breaking things.
I can statically define the methods in the Car class, but I want to ensure that the ActiveRecord magic works, so things like .save work, and I can change the underlying schema at a later date (realising this will probably be an outage to the application).
How would I go about doing this in ActiveRecord?
To Clarify:
Basically, I want the following to work
#car = Car.first
#car.something = something
#car.someprop = something
However in the above, #code.someprop is actually #car.properties.where( "keyname = ?", "someprop" ).value
Obviously I don't want to be doing a SQL Query every time for this though, so I'm looking for a nice way to do this.
Unless I'm underthinking it, it should be something as simple as:
class CarProperty < ActiveRecord::Base
set_primary_key :oid
belongs_to :car
end
class Car < ActiveRecord::Base
set_table_name :car
set_primary_key :oid
has_many :car_properties
accepts_nested_attributes_for :car_properties
end

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)

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