Reputation: 24541
I was wondering if people could share their preferred approach for filtering the contents of a has_many relation through some other foreign key. For example, suppose you have three entities: Departments, Employees, Projects. A Department has many Employees, and a Project has many Employees. I've got a Project, and I want to get all its Employees from a given Department. My code looks like this:
# department.rb
has_many :employees
# employee.rb
belongs_to :department
belongs_to :projects
# project.rb
has_many :employees
Now I can think of four approaches to my problem:
APPROACH 1: Class query methods:
# anywhere.rb
Employee.where(:project_id => project, :department_id => department)
APPROACH 2: Helper method
# project.rb
def employees_from_department(department)
employees.select { |emp| emp.department == department }
end
APPROACH 3: Helper method on the relation
# project.rb
has_many :employees do
def from_department(department)
where(:department_id => department)
# Could also be all.select { |emp| emp.department == deparment }
end
end
APPROACH 4: Scopes
# employee.rb
scope :from_department, lambda { |department|
where(:department_id => department)
}
# anywhere.rb
project.employees.from_department(department)
I almost always choose Approach #4, because it's the most re-useable. I can apply that scope to any query that returns Employees. I can combine it with other scopes, set an ordering, etc. Plus using scopes means all my read-only, query-style code is named fairly consistently and organized at the top, so I have fewer methods. Scopes are one of my favorite Rails features. But I find myself writing them /all the time/, so that I've almost got a parameterized scope to match every :belongs_to. Is that the right approach? Also it seems like I'm generating a ton of database queries, so I'm wondering if I'm defeating any caching Rails might have done for me, because my scope is forcing Rails to go to the database every time.
In part this is a performance question, which means there is no one-size-fits-all answer, and you need to test the code in production to find the right way. But before your code is in production, what approach do you tend to choose? Or is it something else?
Upvotes: 3
Views: 2604
Reputation: 9225
project.employees.find_by_department_id(department)
(basically same as #3)
Upvotes: 1
Reputation: 10874
I personally prefer the scopes (approach 4) or class methods.
I believe in normal case, your approach 4 and approach 1 should generate the same SQL statement if you use chained scope, like:
project.employees.from_department(department_id)
You can try to add a .to_sql call to it in console, to see the actual sql generated.
For performance (sql) analyze, you could use tools such as query-reviewer or rack-bug, which are handy and useful.
Upvotes: 2