lundi 13 avril 2015

Odd behavior/possible bug: has_many :through and default_scope

We've got our basic has_many through: for Users and Companies.



class User < ActiveRecord::Base
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


Then we have this CompanyMethods lib that adds a default_scope to User (the klass.method_defined(:companies) is only called on User).



module CompanyMethods
def self.extended(klass)
klass.class_eval do
default_scope do
c_ids = Authorization.current_company_ids
includes(:companies).where(companies: { id: c_ids })
end
end
end
end


class User < ActiveRecord::Base
extends CompanyMethods


So when we call User.find_by_email('michael@widgetworks.com') we get back only users that are scoped by Authorization.current_company_ids.


Here's the sql:



SELECT DISTINCT `users`.id FROM `users` LEFT OUTER JOIN `company_users` ON `company_users`.`user_id` = `users`.`id` LEFT OUTER JOIN `companies` ON `companies`.`id` = `company_users`.`company_id` WHERE `companies`.`id` IN (4) AND `users`.`email` = 'michael@widgetworks.com' LIMIT 1


All this is fine and dandy for a lot of instances. But here's where it starts to get funky.


When another object, for example a CreditCard calls an association that goes through User, the companies.id scope gets called on the child object.



class CreditCard < ActiveRecord::Base
has_one :user, through: :user_profile
has_many :orders, through: :user
end

class UserProfile < ActiveRecord::Base
belongs_to :user
end

class User < ActiveRecord::Base
extends CompanyMethods
has_many :orders
end


Here's the sql that it generates:



SELECT `orders`.* FROM `orders` INNER JOIN `users` ON `orders`.`user_id` = `users`.`id` INNER JOIN `user_profiles` ON `users`.`id` = `user_profiles`.`user_id` WHERE `orders`.`company_id` IN (4) AND `companies`.`id` IN (4) AND `user_profiles`.`id` = 47717


credit_card.orders throws an error because of it's SQL query is calling "companies.id IN (4)" on reservation when it should only be calling it on user.


I've been able to get around the problem with a hack of instead of using a through association, I just wrote a method named reservations on CreditCard.



class CreditCard < ActiveRecord::Base
has_one :user, through: :user_profile

def orders
user.orders
end
end


That pretty much fixes the problem, but it's not a great solution.


Aucun commentaire:

Enregistrer un commentaire