Jon*_*ons 74 ruby-on-rails associations model-associations ruby-on-rails-4
我希望能够在一个表上使用两列来定义关系.所以使用任务应用程序作为示例.
尝试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
Run Code Online (Sandbox Code Playgroud)
那么 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
Run Code Online (Sandbox Code Playgroud)
这完全失败,因为似乎没有支持两个外键.
所以我想要的是能够说出User.tasks
并获得用户拥有和分配的任务.
基本上以某种方式建立一个等于查询的关系 Task.where(owner_id || assignee_id == 1)
那可能吗?
我不打算使用finder_sql
,但这个问题的未被接受的答案看起来接近我想要的:Rails - 多索引密钥关联
所以这个方法看起来像这样,
尝试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
Run Code Online (Sandbox Code Playgroud)
虽然我可以让它工作Rails 4
,但我不断收到以下错误:
ActiveRecord::PreparedStatementInvalid: missing value for :owner_id in :donor_id => :person_id OR assignee_id => :person_id
Run Code Online (Sandbox Code Playgroud)
Ars*_*Ali 74
class User < ActiveRecord::Base
def tasks
Task.where("owner_id = ? OR assigneed_id = ?", self.id, self.id)
end
end
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
通过这种方式,你可以调用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
Run Code Online (Sandbox Code Playgroud)
这样,它将在一个查询中获取所有结果,我们不必合并或组合任何结果.
Dwi*_*ght 33
在扩展@ DRE-HH的回答上面,我发现不再按预期工作中的Rails 5.看来Rails的5现在包含默认的where子句的效果WHERE tasks.user_id = ?
,因为没有它失败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
Run Code Online (Sandbox Code Playgroud)
dre*_*-hh 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.
Run Code Online (Sandbox Code Playgroud)
由于不再可能,您也可以使用@Arslan Ali解决方案.
导轨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
Run Code Online (Sandbox Code Playgroud)
Update1:关于@JonathanSimmons评论
必须将用户对象传递到User模型的范围内似乎是一种向后的方法
您不必将用户模型传递给此范围.当前用户实例自动传递给此lambda.像这样称呼它:
user = User.find(9001)
user.tasks
Run Code Online (Sandbox Code Playgroud)
UPDATE2:
如果可能的话,你可以扩展这个答案来解释发生了什么吗?我想更好地理解它,所以我可以实现类似的东西.谢谢
调用has_many :tasks
ActiveRecord类会将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
Run Code Online (Sandbox Code Playgroud)
Jon*_*ons 13
我为此制定了一个解决方案.我愿意接受任何关于如何让它变得更好的指示.
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
Run Code Online (Sandbox Code Playgroud)
这基本上覆盖了has_many关联,但仍然返回ActiveRecord::Relation
我正在寻找的对象.
所以现在我可以这样做:
User.first.tasks.completed
结果是所有已完成的任务拥有或分配给第一个用户.
归档时间: |
|
查看次数: |
39713 次 |
最近记录: |