我希望能够在一个表上使用两列来定义关系。所以使用任务应用程序作为示例。
尝试1:
class User < ActiveRecord::Base
has_many :tasks
end
class Task < ActiveRecord::Base
belongs_to :owner, class_name: "User", foreign_key: "owner_id"
belongs_to :assignee, class_name: "User", foreign_key: "assignee_id"
end
那么Task.create(owner_id:1, assignee_id: 2)
这允许我执行Task.first.owner
返回用户1 和Task.first.assignee
返回用户2 但User.first.task
不返回任何内容。这是因为任务不属于用户,属于所有者和受让人。所以,
尝试2:
class User < ActiveRecord::Base
has_many :tasks, foreign_key: [:owner_id, :assignee_id]
end
class Task < ActiveRecord::Base
belongs_to :user
end
由于两个外键似乎得不到支持,因此完全失败了。
所以我想要的是能够说User.tasks
并获得用户拥有和分配的任务。
基本上以某种方式建立一个等于查询Task.where(owner_id || assignee_id == 1)
这可能吗?
我不打算使用finder_sql
,但这个问题的未接受答案看起来接近我想要的:Rails - Multiple Index Key Association
所以这个方法看起来像这样,
尝试3:
class Task < ActiveRecord::Base
def self.by_person(person)
where("assignee_id => :person_id OR owner_id => :person_id", :person_id => person.id
end
end
class Person < ActiveRecord::Base
def tasks
Task.by_person(self)
end
end
虽然我可以在Rails 4
中使用它,但我一直收到以下错误:
ActiveRecord::PreparedStatementInvalid: missing value for :owner_id in :donor_id => :person_id OR assignee_id => :person_id
答案 0 :(得分:69)
class User < ActiveRecord::Base
def tasks
Task.where("owner_id = ? OR assigneed_id = ?", self.id, self.id)
end
end
删除has_many :tasks
课程中的User
。
使用has_many :tasks
完全没有意义,因为我们在表user_id
中没有任何名为tasks
的列。
我在案件中解决问题的方法是:
class User < ActiveRecord::Base
has_many :owned_tasks, class_name: "Task", foreign_key: "owner_id"
has_many :assigned_tasks, class_name: "Task", foreign_key: "assignee_id"
end
class Task < ActiveRecord::Base
belongs_to :owner, class_name: "User", foreign_key: "owner_id"
belongs_to :assignee, class_name: "User", foreign_key: "assignee_id"
# Mentioning `foreign_keys` is not necessary in this class, since
# we've already mentioned `belongs_to :owner`, and Rails will anticipate
# foreign_keys automatically. Thanks to @jeffdill2 for mentioning this thing
# in the comment.
end
这样,您可以拨打User.first.assigned_tasks
以及User.first.owned_tasks
。
现在,您可以定义一个名为tasks
的方法,该方法会返回assigned_tasks
和owned_tasks
的组合。
就可读性而言,这可能是一个很好的解决方案,但从性能的角度来看,它现在不会那么好,为了获得tasks
,将发出两个查询一次,然后,这两个查询的结果也需要加入。
因此,为了获取属于用户的任务,我们将按以下方式在tasks
类中定义自定义User
方法:
def tasks
Task.where("owner_id = ? OR assigneed_id = ?", self.id, self.id)
end
这样,它将在一个查询中获取所有结果,我们不必合并或组合任何结果。
答案 1 :(得分:24)
Rails 5:
你需要取消默认的where子句 如果你还想要一个has_many关联,请参阅@Dwight回答。
虽然User.joins(:tasks)
给了我
ArgumentError: The association scope 'tasks' is instance dependent (the scope block takes an argument). Preloading instance dependent scopes is not supported.
由于不再可能,您也可以使用@Arslan Ali解决方案。
Rails 4:
class User < ActiveRecord::Base
has_many :tasks, ->(user){ where("tasks.owner_id = :user_id OR tasks.assignee_id = :user_id", user_id: user.id) }
end
UPDATE1: 关于@JonathanSimmons评论
必须将用户对象传递到User模型的范围内,这似乎是一种向后的方法
您不必将用户模型传递给此范围。 当前用户实例自动传递给此lambda。 这样称呼:
user = User.find(9001)
user.tasks
Update2:
如果可能,你可以扩展这个答案来解释发生了什么吗?我想更好地理解它,所以我可以实现类似的东西。感谢
在ActiveRecord类上调用has_many :tasks
会将lambda函数存储在某个类变量中,这只是一种在其对象上生成tasks
方法的奇特方法,该方法将调用此lambda。生成的方法看起来类似于以下伪代码:
class User
def tasks
#define join query
query = self.class.joins('tasks ON ...')
#execute tasks_lambda on the query instance and pass self to the lambda
query.instance_exec(self, self.class.tasks_lambda)
end
end
答案 2 :(得分:22)
在上面的@dre -hh的答案中进行了扩展,我发现它在Rails 5中不再正常工作。看来Rails 5现在包含WHERE tasks.user_id = ?
效果的默认where子句,由于没有此方案中的user_id
列。
我发现它仍然可以使用has_many
关联,你只需要取消Rails添加的这个额外的where子句。
class User < ApplicationRecord
has_many :tasks, ->(user) { unscope(:where).where("owner_id = :id OR assignee_id = :id", id: user.id) }
end
答案 3 :(得分:14)
我为此制定了解决方案。我对如何做到这一点的任何指示持开放态度。
class User < ActiveRecord::Base
def tasks
Task.by_person(self.id)
end
end
class Task < ActiveRecord::Base
scope :completed, -> { where(completed: true) }
belongs_to :owner, class_name: "User", foreign_key: "owner_id"
belongs_to :assignee, class_name: "User", foreign_key: "assignee_id"
def self.by_person(user_id)
where("owner_id = :person_id OR assignee_id = :person_id", person_id: user_id)
end
end
这基本上覆盖了has_many关联,但仍然返回我正在寻找的ActiveRecord::Relation
对象。
所以现在我可以这样做:
User.first.tasks.completed
,结果是所有已完成的任务拥有或分配给第一个用户。
答案 4 :(得分:2)
从Rails 5开始,您还可以执行ActiveRecord更安全的方法:
Dimensions: (index: 10)
Coordinates:
* index (index) MultiIndex
- lat (index) int64 6 8 5 17 15 16 9 19 11 14
- lon (index) int64 15 7 9 17 18 1 12 2 6 8
Data variables:
dv_v (index) int64 5 14 6 1 19 12 16 10 0 11
rxy (index) int64 15 8 6 2 0 1 4 16 7 19
rxyz (index) int64 15 17 18 5 14 13 16 2 10 9
depth (index) int64 11 18 5 19 3 14 7 17 0 4
答案 5 :(得分:1)
至于你的代码,这是我的修改
class User < ActiveRecord::Base
has_many :tasks, ->(user) { unscope(where: :user_id).where("owner_id = ? OR assignee_id = ?", user.id, user.id) }, class_name: 'Task'
end
class Task < ActiveRecord::Base
belongs_to :owner, class_name: "User", foreign_key: "owner_id"
belongs_to :assignee, class_name: "User", foreign_key: "assignee_id"
end
警告:强> 如果您正在使用RailsAdmin并且需要创建新记录或编辑现有记录,请不要按照我的建议进行操作。因为当您执行以下操作时此hack会导致问题:
current_user.tasks.build(params)
原因是rails会尝试使用current_user.id来填充task.user_id,但却发现没有像user_id这样的内容。
所以,考虑一下我的黑客方法,但不要这样做。
答案 6 :(得分:0)
更好的方法是使用多态关联:
task.rb
class Task < ActiveRecord::Base
belongs_to :taskable, polymorphic: true
end
assigned_task.rb
class AssignedTask < Task
end
owned_task.rb
class OwnedTask < Task
end
user.rb
class User < ActiveRecord::Base
has_many :assigned_tasks, as: :taskable, dependent: :destroy
has_many :owned_tasks, as: :taskable, dependent: :destroy
end
结果,我们可以这样使用它:
new_user = User.create(...)
AssignedTask.create(taskable: new_user, ...)
OwnedTask.create(taskable: new_user, ...)
pp user.assigned_tasks
pp user.owned_tasks