ActiveRecord query across three models?
I have three models:
class User < ActiveRecord::Base
has_many :projects, :through => :permissions
class Permission < ActiveRecord::Base
belongs_to :user
belongs_to :project
belongs_to :role
class Project < ActiveRecord::Base
has_many :users, :through => :permissions
It's very easy with the above to get all of a Project's us开发者_如何学Cers: @project.users
But what I want to do is get something like this: Get all the Users in all of the user's projects.
So if a user has 3 projects, each with 5 users. I want to query to get all 15 users across all of the user's groups.
I'm trying that with.
current_user.projects.users
but Rails isn't liking that much. current_user.projects works great, but not users.
Suggestions? Ideas? thanks!
UPDATED CODE 3 based on noodl's comments
scope :suggestedContacts, lambda { |user|
users_from_projects = user.projects.reduce([]) {|all_users,prj|
all_users + prj.users
}.uniq
}
ERRORS:
NoMethodError (undefined method `includes_values' for #):
My two solutions are:
- clean, simple standard rails meta-code, no custom finders
- efficient, fetches in one SQL query, no N+1 issues
- compliant, meaning you can still build, create etc. on the relation
1.
You can chain your relation in the user class.
As rails 3.0.x does not yet support nested has_many_through you can use this plugin until rails 3.1
class User < ActiveRecord::Base
has_many :permissions
has_many :projects, :through => :permissions
has_many :users_in_projects, :through => :projects, :source => :user # chain the relation
class Permission < ActiveRecord::Base
belongs_to :user
belongs_to :project
class Project < ActiveRecord::Base
has_many :users, :through => :permissions
current_user.users_in_projects
2.
Another way would be to eager load and reduce (like the other answers have already described, but I'll make it more explicit).
This is more work, less dependencies.
class User < ActiveRecord::Base
has_many :permissions
has_many :projects, :through => :permissions, :include => :users # eager load users
class Permission < ActiveRecord::Base
belongs_to :user
belongs_to :project
class Project < ActiveRecord::Base
has_many :users, :through => :permissions
current_user.projects.map(&:users).reduce(&:+).uniq_by(&:id)
# returns users in current_user's projects, one query, some computations
@noodl has the right idea, but yes it does cause "some" overhead. Granted unless you're talking about having millions of projects and permissions, you won't notice much of an improvement by optimizing. Regardless, here is an alternative approach which in theory, though not pretty, gives you what you want "efficiently" (assuming your tables are indexed properly).
class User
has_many :project_users, :class_name => "User", :finder_sql => 'select u.* from users u join permissions pp on pp.user_id=u.id join projects p on pp.project_id=p.id where project_id in (select project_id from permissions pp2 where user_id=#{id}) and pp.user_id != #{id} group by u.id'
end
It's not a scope, but does allow you to say:
user = User.find(1)
=> #<User id: 1, name: "Stephen">
user.project_users
=> [#<User id: 2, name: "Kathleen">, #<User id: 3, name: "Anne">]
Since the comment threads are getting a little laboured I'll try and spell out how I'd solve it directly.
users = current_user.projects.map(&:users).flatten.uniq
To your User class add:
has_many :users, :through => :permissions
So it will look like:
class User < ActiveRecord::Base
has_many :projects, :through => :permissions
has_many :users, :through => :permissions
Since projects is an aggregate, it's going to return Enumerable objects. You can iterate across them using (for example) each:
current_user.projects.each {|prj|
prj.users.each{|usr|
#do stuff
}
}
If you want to collect these together into one enumerable (and get rid of repeats), then
users_from_projects = current_user.projects.reduce([]) {|all_users,prj|
all_users = all_users + prj.users
}.uniq
EDIT to correct, thanks for pointing it out noodl.
You need to eager-load your associations
class User < ActiveRecord::Base
has_many :projects, :through => :permissions, :include => :users
has_many :permissions, :include => {:project => :users}
…
end
精彩评论