这似乎是一个典型的问题,但很难搜索。
我想通过has_many
选择用户拥有的项目,并通过has_many through
选择与用户相关联的项目。
考虑以下模型:
class User < ActiveRecord::Base
has_many :projects,
inverse_of: :owner
has_many :project_associations,
class_name: 'ProjectUser',
inverse_of: :user
has_many :associated_projects,
through: :project_associations,
source: :project
end
class Project < ActiveRecord::Base
belongs_to :owner,
class_name: 'User',
foreign_key: :owner_id,
inverse_of: :projects
has_many :user_associations,
class_name: 'ProjectUser',
inverse_of: :project
has_many :associated_users,
through: :user_associations,
source: :user
end
class ProjectUser < ActiveRecord::Base
belongs_to :project,
inverse_of: :user_associations
belongs_to :user,
inverse_of: :project_associations
end
使用多个查询执行此操作非常简单:
user = User.find(1)
all_projects = user.projects + user.associated_projects
但我怀疑可以使用Arel将其优化到单个查询中。
修改
我第一次尝试使用find_by_sql
方法的解决方案是:
Project.find_by_sql([
'SELECT "projects".* FROM "projects" ' \
'INNER JOIN "project_users" ' \
'ON "projects"."id" = "project_users"."project_id" ' \
'WHERE "project_users"."user_id" = :user_id ' \
'OR "projects"."owner_id" = :user_id',
{ user_id: 1 }
])
这会产生我期望的结果,但我想避免使用find_by_sql
而是让Arel构建SQL。
答案 0 :(得分:2)
这应该有效:
AsyncTask
您可以将其放在名为Project.joins(:user_associations)
.where("projects.owner_id = ? OR project_users.user_id = ?", id, id)
的方法中。
您也可以使用UNION
,虽然这里看起来有些过分。
只有一个警告:根据我的经验,连接模型的两个备用路径的结构,其中一个路径是连接表而另一个是直接外键,会导致很多麻烦。您将不得不一直处理这两种情况,而在复杂的查询中,User#all_projects
可能会混淆数据库查询计划程序。如果您能够,您可能需要重新考虑。如果您删除OR
列并在owner_id
上标识role
列的所有者,我认为您的头痛会更少。
编辑或在Arel:
project_users
答案 1 :(得分:0)
您可以使用includes
指令,如下所示:
User.includes(:projects, :associated_projects).find(1)
检查http://apidock.com/rails/ActiveRecord/QueryMethods/includes
然后,如果您在找到的实例上调用projects
和associated_projects
,则不会触发其他查询。
答案 2 :(得分:0)
我有我的解决方案,模型结构保持不变,我在名为User
的{{1}}模型上添加了一个方法。
all_projects
调用class User < ActiveRecord::Base
has_many :projects,
inverse_of: :owner
has_many :project_associations,
class_name: 'ProjectUser',
inverse_of: :user
has_many :associated_projects,
through: :project_associations,
source: :project
def all_projects
query = Project.arel_table[:owner_id].eq(id).or(ProjectUser.arel_table[:user_id].eq(id))
Project.joins(:user_associations).where(query)
end
end
版本并执行此查询:
#all_projects
解决方案并不像我想的那样优雅,理想情况下我希望将SELECT "projects".*
FROM "projects"
INNER JOIN "project_users" ON "project_users"."project_id" = "products"."id"
WHERE ("projects"."owner_id" = '1' OR "project_users"."user_id" = '1')
替换为Project.arel_table[:owner_id].eq(id)
关联构建的查询,并将has_many projects
添加到其中,但现在效果很好。