We have a default_scope
on our User class which restricts users to a set of companies:
class User < ActiveRecord::Base
default_scope do
c_ids = Authorization.current_company_ids
includes(:companies).where(companies: { id: c_ids })
end
has_many :company_users
has_many :companies, through: company_users
end
class CompanyUser < ActiveRecord::Base
belongs_to :user
belongs_to :company
validates_uniqueness_of :company_id, scope: :user_id
end
class Company < ActiveRecord::Base
has_many :company_users
has_many :users, through: company_users
end
Calling User.last
or User.find_by_email('mhayes@widgetworks.com')
or User.find(55557)
all work and scope as expected.
Calling User.exists?(id)
throws an odd error:
Mysql2::Error: Unknown column 'companies.id' in 'where clause': SELECT 1 AS one FROM `users` WHERE `companies`.`id` IN (4) AND `users`.`id` = 55557 LIMIT 1
Basically, if I'm getting this, it's saying that companies
isn't a column on User
, which it is. And if I even copy the sql into a where
statement, it evaluates correctly.
User.where("SELECT 1 AS one FROM `users` WHERE `companies`.`id` IN (4) AND `users`.`id` = 66668 LIMIT 1")
It makes me think there's an order of evaluation with default_scope
and exists?
is somehow called before default_scope
.
If I call:
User.includes(:companies).where(companies: { id: [4] }).exists?(55557)
Works. And this is what the default_scope
is doing, so I know the default_scope
scope isn't failing.
I honestly don't know, but it looks to me like exists?
builds a relation directly and decides to throw out the includes
clause because exists?
decides that it doesn't need to load other objects. That happens in this method call. Whereas the chained exists?
has already calculated it properly from building an earlier relation (where the includes
get converted into a joins
).
Maybe not a bug but probably another weirdness to add to the list with default_scope
.
Probably the best solution is to make the includes
in the default_scope
into an actual joins
and the manually call includes
whenever you actually need the full records. (Since you don't actually need them for the exists?
call)
Another possibility is just chain both includes
and joins
in the default_scope
. I have no idea what this does to the arel
calculations although I doubt much. May or May Not Work.
Or if you just really want it to stay the same overall, you could do something crazy like:
def self.exists?(args)
self.scoped.exists?(args)
end
Which will basically build a relation with the default scope and then call exists?
on that built relation that already performed the includes
-> joins
magic.