开发者

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
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜