Associating Rails STI with Subclasses

I get weird behavior when dialing collections from has_many association with rails 3 when using STI. I have:

class Branch < ActiveRecord::Base has_many :employees, class_name: 'User::Employee' has_many :admins, class_name: 'User::BranchAdmin' end class User < ActiveRecord::Base end class User::Employee < User belongs_to :branch end class User::BranchAdmin < User::Employee end 

The desired behavior is that branch.employees returns all employees, including branch administrators. Branch administrators only seem to be β€œloaded” in this collection, when branch.admins accesses branch.admins , this is output from the console:

 Branch.first.employees.count => 2 Branch.first.admins.count => 1 Branch.first.employees.count => 3 

This can be seen in the generated SQL for the first time:

 SELECT COUNT(*) FROM "users" WHERE "users"."type" IN ('User::Employee') AND "users"."branch_id" = 1 

and second time:

 SELECT COUNT(*) FROM "users" WHERE "users"."type" IN ('User::Employee', 'User::BranchAdmin') AND "users"."branch_id" = 1 

I could solve this problem by simply specifying:

 class Branch < ActiveRecord::Base has_many :employees, class_name: 'User' has_many :admins, class_name: 'User::BranchAdmin' end 

since they are all found from their branch_id, but this creates problems in the controller, if I want to do branch.employees.build , then the default class will be User , and I have to hack something in the type column. I went around this now:

  has_many :employees, class_name: 'User::Employee', finder_sql: Proc.new{ %Q(SELECT users.* FROM users WHERE users.type IN ('User::Employee','User::BranchAdmin') AND users.branch_id = #{id}) }, counter_sql: Proc.new{ %Q(SELECT COUNT(*) FROM "users" WHERE "users"."type" IN ('User::Employee', 'User::BranchAdmin') AND "users"."branch_id" = #{id}) } 

but I would really like to avoid this, if possible. Anyone any ideas?

EDIT:

Finder_sql and counter_sql did not actually solve this for me, because it seems that the parent associations are not using it, and therefore the organisation.employees that has_many :employees, through: :branches will only include the User::Employee class in the selection again.

+6
source share
2 answers

Basically, the problem exists only in a development environment where classes are loaded as needed. (During the production process, classes are loaded and saved.)

The problem arises because the interpreter did not see that Admins is an Employee type when it first starts calling Employee.find , etc.

(Note that later it uses IN ('User::Employee', 'User::BranchAdmin') )

This happens with every use of model classes that are at a depth of more than one level, but only in dev-mode.

Subclasses always automatically load the parent hierarchy. Base classes do not load their child hierarchies.

Hack fix:

You can force the correct behavior into dev-mode by explicitly requiring all your child classes from the base class of the rb file.

+17
source

Can you use : conditions ?

 class Branch < ActiveRecord::Base has_many :employees, class_name: 'User::Employee', :conditions => {:type => "User::Employee"} has_many :admins, class_name: 'User::BranchAdmin', :conditions => {:type => "User::BranchAdmin"} end 

This will be my preferred method. Another way to do this could be to add a default region to polymorphic models.

 class User::BranchAdmin < User::Employee default_scope where("type = ?", name) end 
+2
source

Source: https://habr.com/ru/post/918553/


All Articles