Reputation: 726
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?
Upvotes: 0
Views: 228
Reputation: 102250
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)
)
Upvotes: 3