gpt4 book ai didi

Association for polymorphic belongs_to of a particular type(特定类型的多态归属物关联)

转载 作者:bug小助手 更新时间:2023-10-26 20:11:24 27 4
gpt4 key购买 nike



I'm relatively new to Rails. I would like to add an association to a model that uses the polymorphic association, but returns only models of a particular type, e.g.:

我对Rails比较陌生。我想将关联添加到使用多态关联但只返回特定类型的模型的模型,例如:



class Note < ActiveRecord::Base
# The true polymorphic association
belongs_to :subject, polymorphic: true

# Same as subject but where subject_type is 'Volunteer'
belongs_to :volunteer, source_association: :subject
# Same as subject but where subject_type is 'Participation'
belongs_to :participation, source_association: :subject
end


I've tried a vast array of combinations from reading about the associations on ApiDock but nothing seems to do exactly what I want. Here's the best I have so far:

从阅读ApiDock上的关联信息中,我尝试了大量的组合,但似乎没有一种组合完全符合我的要求。以下是我到目前为止最好的:



class Note < ActiveRecord::Base
belongs_to :subject, polymorphic: true
belongs_to :volunteer, class_name: "Volunteer", foreign_key: :subject_id, conditions: {notes: {subject_type: "Volunteer"}}
belongs_to :participation, class_name: "Participation", foreign_key: :subject_id, conditions: {notes: {subject_type: "Participation"}}
end


And I want it to pass this test:

我希望它能通过这次测试:



describe Note do
context 'on volunteer' do
let!(:volunteer) { create(:volunteer) }
let!(:note) { create(:note, subject: volunteer) }
let!(:unrelated_note) { create(:note) }

it 'narrows note scope to volunteer' do
scoped = Note.scoped
scoped = scoped.joins(:volunteer).where(volunteers: {id: volunteer.id})
expect(scoped.count).to be 1
expect(scoped.first.id).to eq note.id
end

it 'allows access to the volunteer' do
expect(note.volunteer).to eq volunteer
end

it 'does not return participation' do
expect(note.participation).to be_nil
end

end
end


The first test passes, but you can't call the relation directly:

第一个测试通过,但您不能直接调用该关系:



  1) Note on volunteer allows access to the volunteer
Failure/Error: expect(note.reload.volunteer).to eq volunteer
ActiveRecord::StatementInvalid:
PG::Error: ERROR: missing FROM-clause entry for table "notes"
LINE 1: ...."deleted" = 'f' AND "volunteers"."id" = 7798 AND "notes"."s...
^
: SELECT "volunteers".* FROM "volunteers" WHERE "volunteers"."deleted" = 'f' AND "volunteers"."id" = 7798 AND "notes"."subject_type" = 'Volunteer' LIMIT 1
# ./spec/models/note_spec.rb:10:in `block (3 levels) in <top (required)>'





Why?



The reason I want to do it this way is because I'm constructing a scope based on parsing a query string including joining to various models/etc; the code used to construct the scope is considerably more complex than that above - it uses collection.reflections, etc. My current solution works for this, but it offends me I can't call the relations directly from an instance of Note.

我想这样做的原因是因为我正在构造一个基于解析查询字符串的作用域,包括连接到各种模型/等;用于构造作用域的代码比上面复杂得多-它使用集合、反射等。我目前的解决方案对此有效,但它冒犯了我,我不能直接从Note的实例调用关系。



I could solve it by splitting it into two issues: using scopes directly

我可以通过将其分为两个问题来解决它:直接使用作用域



  scope :scoped_by_volunteer_id, lambda { |volunteer_id| where({subject_type: 'Volunteer', subject_id: volunteer_id}) }
scope :scoped_by_participation_id, lambda { |participation_id| where({subject_type: 'Participation', subject_id: participation_id}) }


and then just using a getter for note.volunteer/note.participation that just returns note.subject if it has the right subject_type (nil otherwise) but I figured in Rails there must be a better way?

然后,只使用一个getter来表示note.voluent/note.参与度,它只返回note。Subject,如果它有正确的SUBJECT_TYPE(否则为空),但我认为在Rails中一定有更好的方法?


更多回答

The somewhat cryptic {notes: {subject_type: "Volunteer"}} clause has the notes: because otherwise it would query on non-existant volunteers.subject_type column, this makes it query on notes.subjects_type. I've settled on equivalent {'notes.subject_type': "Volunteer"} syntax; it also reminds me the notes should be a plural table name, not the (sometimes singular) association name...

有点神秘{注意到:{subject_type:“Volunteer”}}子句具有notes:因为否则它将查询不存在的volunteers.subject_type列,这使得它查询notes.subjects_type。我已经确定了等价的“notes.subject_type”:“Volunteer”}语法;它还提醒我notes应该是一个复数的表名,而不是(有时是单数的)关联名.

优秀答案推荐

I had bump into the similar problem. and I finally ironed out the best and most robust solution by using a self reference association like below.

我也遇到过类似的问题。最后,我通过使用如下所示的自我引用关联,得出了最好、最可靠的解决方案。



class Note < ActiveRecord::Base
# The true polymorphic association
belongs_to :subject, polymorphic: true

# The trick to solve this problem
has_one :self_ref, :class_name => self, :foreign_key => :id

has_one :volunteer, :through => :self_ref, :source => :subject, :source_type => Volunteer
has_one :participation, :through => :self_ref, :source => :subject, :source_type => Participation
end


Clean & simple, only tested on Rails 4.1, but I guess it should work for previous versions.



I have found a hackish way of getting around this issue. I have a similar use case in a project of mine, and I found this to work. In your Note model you can add associations like this:

我已经找到了一种绕过这个问题的黑客方法。我在我的一个项目中有一个类似的用例,我发现这个方法很有效。在您的Note模型中,您可以添加如下关联:



class Note
belongs_to :volunteer,
->(note) {where('1 = ?', (note.subject_type == 'Volunteer')},
:foreign_key => 'subject_id'
end


You will need to add one of these for each model that you wish to attach notes to. To make this process DRYer I would recommend creating a module like so:

您需要为要附加注释的每个模型添加其中一个。要使此流程干燥器,我建议创建一个模块,如下所示:



 module Notable
def self.included(other)
Note.belongs_to(other.to_s.underscore.to_sym,
->(note) {where('1 = ?', note.subject_type == other.to_s)},
{:foreign_key => :subject_id})
end
end


Then include this in your Volunteer and Participation models.

然后在你的志愿者和参与模型中包括这一点。



[EDIT]

[编辑]



A slightly better lambda would be:

稍微好一点的lambda应该是:



 ->(note) {(note.subject_type == "Volunteer") ? where('1 = 1') : none}


For some reason replacing the 'where' with 'all' does not seem to work. Also note that 'none' is only available in Rails 4.

由于某种原因,用“all”代替“where”似乎不起作用。另外请注意,“none”仅在Rails 4中可用。



[MOAR EDIT]

[Moar编辑]



I'm not running rails 3.2 atm so I can't test, but I think you can achieve a similar result by using a Proc for conditions, something like:

我没有运行rails 3.2 ATM,因此无法进行测试,但我认为您可以通过对条件使用proc来实现类似的结果,如下所示:



belongs_to :volunteer, :foreign_key => :subject_id, 
:conditions => Proc.new {['1 = ?', (subject_type == 'Volunteer')]}


Might be worth a shot

也许值得一试



I was stuck on this sort of reverse association and in Rails 4.2.1 I finally discovered this. Hopefully this helps someone if they're using a newer version of Rails. Your question was the closest to anything I found in regard to the issue I was having.

我陷入了这种反向关联,在Rails4.2.1中,我终于发现了这一点。如果有人使用更新版本的Rails,希望这会对他们有所帮助。关于我遇到的问题,你的问题是我发现的最接近的问题。



belongs_to :volunteer, foreign_key: :subject_id, foreign_type: 'Volunteer'


You can do like so:

您可以这样做:



belongs_to :volunteer, -> {
where(notes: { subject_type: 'Volunteer' }).includes(:notes)
}, foreign_key: :subject_id


The includes do the left join so you have the notes relation with all volunteer and participation. And you go through subject_id to find your record.

包括做左连接,这样你就有了与所有志愿者和参与者的笔记关系。然后您通过Subject_id来查找您的记录。



I believe I have figured out a decent way to handle this that also covers most use cases that one might need.

我相信我已经想出了一个很好的方法来处理这个问题,它也涵盖了人们可能需要的大多数用例。


I will say, it is a hard problem to find an answer to, as it is hard to figure out how to ask the question, and also hard to weed out all the articles that are just standard Rails answers. I think this problem falls into the advanced ActiveRecord realm.

我会说,这是一个很难找到答案的问题,因为很难弄清楚如何提出这个问题,也很难剔除所有只是标准Rails答案的文章。我认为这个问题属于高级ActiveRecord领域。


Essentially what we are trying to do is to add a relationship to the model and only use that association if certain prerequisites are met on the model where the association is made. For example, if I have class SomeModel, and it has belongs_to association called "some_association", we might want to apply some prerequisite conditions that must be true on the SomeModel record that influence whether :some_association returns a result or not. In the case of a polymorphic relationship, the prerequisite condition is that the polymorphic type column is a particular value, and if not that value, it should return nil.

本质上,我们试图做的是将关系添加到模型中,并且只有在建立关联的模型上满足某些先决条件时才使用该关联。例如,如果我有类SomeModel,并且它具有名为“Some_Association”的beles_to关联,我们可能希望对SomeModel记录应用一些先决条件,这些条件必须为真,以影响:SomeModel是否返回结果。在多态关系的情况下,先决条件是多态类型列是一个特定值,如果不是这个值,它应该返回nil。


The difficulty of solving this problem is compounded by the different ways. I know of three different modes of access: direct access on an instance (ex: SomeModel.first.some_association), :joins (Ex: SomeModel.joins(:some_association), and :includes (Ex: SomeModel.includes(:some_association)) (note: eager_load is just a variation on joins). Each of these cases needs to be handled in a specific way.

解决这个问题的难度因不同的方法而变得更加复杂。我知道三种不同的访问模式:对实例的直接访问(例如:SomeModel.first st.Some_Association)、:Joins(Ex:SomeModel.joins(:SomeAssociation)和:Includes(Ex:SomeModel.Includes(:SomeAssociation))(注意:EAGER_LOAD只是JOINS的变体)。这些案件中的每一个都需要以特定的方式处理。


Today, as I've essentially been revisiting this problem, I came up with the following utility method that acts as a kind of wrapper method for belongs_to. I'm guessing a similar approach could be used for other association types.

今天,由于我基本上重新审视了这个问题,我提出了以下实用方法,它充当了belongs_to的一种包装方法。我猜类似的方法也可以用于其他关联类型。


  # WARNING: the joiner table must not be aliased to something else in the query,
# A parent / child relationship on the same table probably would not work here
# TODO: figure out how to support a second argument scope being passed
def self.belongs_to_with_prerequisites(name, prerequisites: {}, **options)
base_class = self
belongs_to name, -> (object=nil) {
# For the following explanation, assume we have an ActiveRecord class "SomeModel" that has a belongs_to
# relationship on it called "some_association"
# Object will be one of the following:
# * nil - when this association is loaded via an :includes.
# For example, SomeModel.includes(:some_association)
# * an model instance - when this association is called directly on the referring model
# For example: SomeModel.first.some_association, object will equal SomeModel.first
# * A JoinDependency - when we are joining this association
# For example, SomeModel.joins(:some_assocation)
if !object.is_a?(base_class)
where(base_class.table_name => prerequisites)
elsif prerequisites.all? {|name, value| object.send(name) == value}
self
else
none
end
},
options
end

That method would need to be injected into ActiveRecord::Base.

该方法需要注入到ActiveRecord::Base中。


Then we could use it like:

然后我们可以像这样使用它:


  belongs_to_with_prerequisites :volunteer,
prerequisites: { subject_type: 'Volunteer' },
polymorphic: true,
foreign_type: :subject_type,
foreign_key: :subject_id

And it would allow us to do the following:

这将使我们能够做到以下几点:


Note.first.volunteer
Note.joins(:volunteer)
Note.eager_load(:volunteer)

However, we'll get an error if we try to do this:

然而,如果我们尝试这样做,我们会得到一个错误:


Note.includes(:volunteer)

If we run that last bit of code, it will tell us that the column subject_type does not exist on the volunteers table.

如果我们运行最后一段代码,它会告诉我们subject_type列在volunteers表中不存在。


So we'd have to add a scope to the Notes class and use as follows:

因此,我们必须向Notes类添加一个作用域,并按如下方式使用:


class Note < ActiveRecord::Base
belongs_to_with_prerequisites :volunteer,
prerequisites: { subject_type: 'Volunteer' },
polymorphic: true,
foreign_type: :subject_type,
foreign_key: :subject_id
scope :with_volunteer, -> { includes(:volunteer).references(:volunteer) }
end
Note.with_volunteer

So at the end of the day, we don't have the extra join table that @stackNG's solution had, but that solution was definitely more eloquent and less hacky. Figured I'd post this anyway as it has been the result of a very thorough investigation and might help somebody else understand how this stuff works.

因此,归根结底,我们没有@stackNG的解决方案所具有的额外连接表,但该解决方案肯定更有说服力,也不那么老生常谈。我想不管怎样,我还是要把这篇文章贴出来,因为这是一项非常彻底的调查的结果,可能会帮助其他人理解这些东西是如何工作的。



Here's a different option using a parameterized scope. Based on the foreign type, the scope will be set to either all or none so that the relation returns the related model if it's of the right type and nil otherwise:

这里有一个使用参数化作用域的不同选项。根据外部类型,作用域将设置为All或None,以便关系在类型正确时返回相关模型,否则返回Nil:


class Note < ActiveRecord::Base
belongs_to :subject, polymorphic: true

belongs_to :volunteer,
-> (note) { note.subject_type == "Volunteer" ? all : none },
foreign_key: :subject_id

belongs_to :participation,
-> (note) { note.subject_type == "Participation" ? all : none },
foreign_key: :subject_id
end


I tried all the solutions listed here for Rails 7 and none of them worked so I thought I'd add yet another answer.

我尝试了这里列出的针对rails 7的所有解决方案,但它们都不起作用,所以我想我应该添加另一个答案。


class Note < ApplicationRecord
belongs_to :subject, polymorphic: true
belongs_to :volunteer, -> { includes(:note).where(notes: { source_type: :Volunteer}) }, foreign_key: :subject_id, optional: true
belongs_to :participation, -> { includes(:note).where(notes: { source_type: :Participation}) }, foreign_key: :subject_id, optional: true
end

class Volunteer < ApplicationRecord
has_one :note, :as => :subject
end
#Do Participation same as Volunteer

With this solution if you call note.participation on a Note where the subject is actually a Volunteer you will get nil. Other solutions here will give an error or, horribly, a Participation. Also importantly if you join with this association it will work. Something like note.includes(:volunteer).where(volunteer: {volunteer_attribute: value}) will actually join the tables correctly.

在此解决方案中,如果您在主题实际上是志愿者的Note上调用Note.Participation,您将得到零。这里的其他解决方案将导致错误,或者更可怕的是,会导致参与。同样重要的是,如果你加入这个协会,它就会奏效。这样的代码将会正确地联接这些表,比如not.includes(:Volilent).where(Volamerent:{SLOLAR_ATTRIBUTE:值})。


更多回答

This works well - thanks for sharing! It's a bit bizarre that this is necessary, really...

这个很好用--谢谢分享!有点奇怪,这是必须的,真的.

Unrelated to the question, but relevant to this answer: I believe using constants in association definitions is advised against. Instead of Volunteer and Participation, you'd want to use "Volunteer" or :Volunteer and "Participation" or :Participation. Discussion here: github.com/rails/rails/pull/8357

与问题无关,但与这个答案相关:我认为建议不要在关联定义中使用常量。你应该用“Volunteer”或:Volunteer和“Participation”或:Participation来代替Volunteer和Participation。此处讨论:githorb.com/rails/rails/ull/8357

Without self_ref: belongs_to : volunteer, foreign_key: : subject_id, foreign_type: : subject_type, class_name: Volunteer, polymorphic: true

不带self_ref:所属_to:志愿者,foreign_key::Subject_id,Foreign_type::SUBJECT_TYPE,CLASS_NAME:志愿者,多态:True

Assignment doesn't work correctly this way. If you do Note.create(volunteer: Volunteer.create) then subject_id and subject_type will be nil, although you can still retrieve the volunteer from the read method.

这样,赋值不能正常工作。如果您执行了Note.Create(Volunteer.create),那么Subject_id和Subject_type将为零,尽管您仍然可以从Read方法中检索志愿者。

@stackNG, this is a nice solution and probably the cleanest I've found on this problem. My one issue is that it adds an extra join that shouldn't be necessary. I wish it weren't necessary, and I am in the process of posting an alternative solution below, but it is not as simple, but does do away with extra queries.

@stackNG,这是一个很好的解决方案,可能是我在这个问题上找到的最干净的解决方案。我的一个问题是,它添加了一个不应该是必需的额外联接。我希望这不是必要的,我正在发布下面的替代解决方案,但它没有那么简单,但确实消除了额外的问题。

Interesting hack, sadly Rails 3.2 doesn't seem to allow a scope as part of a belongs_to relation. :( (Upgrading to Rails 4 is on my todo list, but there so much other stuff to do first...) Great idea though!

有趣的黑客攻击,令人遗憾的是,rails 3.2似乎不允许将作用域作为beles_to关系的一部分。:(升级到Rails4在我的待办事项清单上,但首先还有很多其他事情要做……)不过,这主意太棒了!

@Benjie Edited answer with alternate method (might work in 3.2)

@Benjie编辑的答案使用替代方法(可能在3.2中有效)

Thanks for the new attempt - sorry it's taken so long for me to try it out. Sadly it doesn't seem to work (undefined method subject_type), I think it's because: > Inside the proc, self is the object which is the owner of the association, unless you are eager loading the association, in which case self is the class which the association is within. -- guides.rubyonrails.org/3_1_release_notes.html

谢谢你的新尝试-对不起,我花了这么长时间才尝试。遗憾的是,它似乎不起作用(未定义的方法SUBJECT_TYPE),我认为这是因为:>在proc中,self是关联的所有者对象,除非您急于加载关联,在这种情况下,self是关联所在的类。--guides.rubyontras.org/3_1_Release_notes.html

For me this approach "runs" but the SQL resulting from joins doesn't actually constrain the subject_type [Rails 5.0]

对我来说,这种方法是“运行的”,但是连接产生的SQL实际上并不约束主题类型[rails 5.0]

@BeniCherniavsky-Paskin I stumbled upon the same problem, if there is another model with the same id, it just returns this event when the type doesn't match

@BeniCherniavsky-Paskin我碰巧遇到了同样的问题,如果有另一个型号的id相同,它只会在类型不匹配时返回这个事件

To solve the problem of subject_type not being included on join, I made this workaround: belongs_to :volunteer, -> { where(notes: { subject_type: :Volunteer }) }, foreign_key: :subject_id, foreign_type: :Volunteer, optional: true I hope it may be useful

为了解决subject_type在加入时不包含的问题,我做了这个变通方案:belongs_to:volunteer,-> { where(notes:{ subject_type::Volunteer })},foreign_key::subject_id,foreign_type::Volunteer,optional:true我希望它可能有用

Doesn't work for rails 6.1 anymore. foreign_type not allowed to be used.

不再适用于rails 6.1。不允许使用FORENT_TYPE。

@DmitryPolushkin is there an equivalent way to accomplish in rails 6.x?

@DmitryPolushkin在rails 6.x中有没有类似的方法来实现?

didn't you need optional: true ?

您不需要可选的:真吗?

I think you can use joins instead of includes – that should be enough.

我认为你可以使用joins而不是includes --这就足够了。

@Matt I tried it with joins (because of course that makes sense), but joins doesn't work exactly right, because it can result in duplicate rows being returned. I tried adding distinct afterward also, but that still didn't work. I don't know the details, but includes worked correctly, not returning duplicate records.

@Matt我尝试了joins(因为这当然有意义),但joins并不完全正确,因为它可能导致返回重复的行。后来我也试着添加了distinct,但仍然不起作用。我不知道细节,但包括工作正确,不返回重复的记录。

This doesn't work right when the Note's subject is a different type of object, but has the same ID as an existing Volunteer. E.g. for a Note (call it a_note) with attributes subject_type: "Participation", subject_id: 1, and given the database contains both a Participation and Volunteer record with id: 1, then a_note.volunteer will return the Volunteer record, rather than nil, even though a_note.subject_type is "Participation". Will update here if I figure out a fix for this

当Note的主题是不同类型的对象,但具有与现有志愿者相同的ID时,这不起作用。例如,对于具有属性SUBJECT_TYPE:“Participation”、SUBJECT_ID:1的笔记(称为a_note),并且给定数据库包含ID为1的Participation和Volunteer记录,则即使a_note_type是“Participation”,a_note.Volunteer也将返回志愿者记录,而不是NIL。如果我想出解决这个问题的办法,我会在这里更新

The best solution so far. Thank you.

到目前为止最好的解决方案。谢谢。

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com