Ruby-on-rails 在 Rails 中可能有“多态 has_one”关系吗?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/7342205/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-03 01:56:22  来源:igfitidea点击:

Possible to have "polymorphic has_one" relationship in rails?

ruby-on-railsactiverecordpolymorphism

提问by markquezada

I'd like to do something like this:

我想做这样的事情:

Category
--------
- id
- name

Tag
--------
- id
- tag


Campaign
--------
- id
- name
- target (either a tag *or* a category)

Is a polymorphic association the answer here? I can't seem to figure out how to use it with has_one :target, :as => :targetable.

多态关联是这里的答案吗?我似乎无法弄清楚如何将它与 has_one :target, :as => :targetable 一起使用。

Basically, I want Campaign.target to be set to a Tag or a Category (or potentially another model in the future).

基本上,我希望将 Campaign.target 设置为标签或类别(或将来可能是其他模型)。

回答by Kristian PD

I don't believe you're in need of a has_oneassociation here, the belongs_toshould be what you're looking for.

我不相信你在has_one这里需要一个协会,这belongs_to应该是你要找的。

In this case, you'd want a target_idand target_typecolumn on your Campaign table, you can create these in a rake with a t.references :targetcall (where tis the tablevariable).

在这种情况下,你会想要一个target_idtarget_type列上你的活动表,您可以用耙子创建这些t.references :target调用(其中ttable变量)。

class Campaign < ActiveRecord::Base
  belongs_to :target, :polymorphic => true
end

Now campaign can be associated to either a Tagor Categoryand @campaign.targetwould return the appropriate one.

现在,活动可以与Tag或相关联,Category并且@campaign.target会返回适当的。

The has_oneassociation would be used if you have a foreign key on the target table pointing back to your Campaign.

has_one,如果你对目标表指向回你的外键关联可能被使用Campaign

For example, your tables would have

例如,您的表将有

Tag: id, tag, campaign_idCategory: id, category, campaign_id

Tag: id, tag, campaign_idCategory: id, category, campaign_id

and would have a belongs_to :campaignassociation on both of them. In this case, you'd have to use has_one :tagand has_one :category, but you couldn't use a generic targetat this point.

并且会belongs_to :campaign在两者之间建立关联。在这种情况下,您必须使用has_one :tagand has_one :category,但此时您不能使用泛型target

Does that make more sense?

这样做更有意义吗?

EDIT

编辑

Since target_idand target_typeare effectively foreign keys to another table, your Campaignbelongs to one of them. I can see your confusion with the wording because logically the Campaignis the container. I guess you can think of it as Campaignhas a single target, and that's a Tagor a Container, therefore it belongs in a Tagor Container.

由于target_idtarget_type是另一个表的有效外键,因此您Campaign属于其中之一。我可以看到您对措辞的困惑,因为逻辑上Campaign是容器。我想您可以将其视为Campaign具有单个目标,即 aTag或 a Container,因此它属于 aTagContainer

The has_oneis the way of saying the relationship is defined on the target class. For example, a Tagwould have be associated to the campaign through a has_onerelationship since there's nothing on the tag class that identifies the association. In this case, you'd have

has_one是在目标类上定义关系的说法。例如,aTag将通过has_one关系与营销活动相关联,因为标记类中没有任何内容可以标识该关联。在这种情况下,你有

class Tag < ActiveRecord::Base
  has_one :campaign, :as => :target
end

and likewise for a Category. Here, the :askeyword is telling rails which association relates back to this Tag. Rails doesn't know how to figure this out upfront because there's no association with the name tagon the Campaign.

同样对于Category. 在这里,:as关键字告诉 rails 哪个关联与 this 相关Tag。Rails不知道怎么算出来的前期,因为有这个名字没有关联tagCampaign

The other two options that may provide further confusion are the sourceand source_typeoptions. These are only used in :throughrelationships, where you're actually joining the association throughanother table. The docs probably describe it better, but the sourcedefines the association name, and source_typeis used where that association is polymorphic. They only need to be used when the target association (on the :throughclass) has a name that isn't obvious -- like the case above with target andTag -- and we need to tell rails which one to use.

可能会造成进一步混淆的另外两个选项是sourcesource_type选项。这些仅用于:through关系中,您实际上是在其中加入关联through另一个表。文档可能对其进行了更好的描述,但source定义了关联名称,并source_type在该关联是多态的情况下使用。只有当目标关联(在:through类上)的名称不明显时才需要使用它们——就像上面使用target andTag的情况一样——并且我们需要告诉 rails 使用哪个。

回答by troelskn

The answers to this questions are great, but I just wanted to mention another way to accomplish the same. What you could do instead is create two relationships, eg.:

这些问题的答案很好,但我只是想提一下实现相同目标的另一种方法。你可以做的是创建两个关系,例如:

class Campaign < ActiveRecord::Base
  belongs_to :tag
  belongs_to :category
  validate :tag_and_category_mutually_exclusive

  def target=(tag_or_category)
    case
    when tag_or_category.kind_of?(Tag)
      self.tag = tag_or_category
      self.category = nil
    when tag_or_category.kind_of?(Category)
      self.category = tag_or_category
      self.tag = nil
    else
      raise ArgumentError, "Expected Tag or Category"
    end
  end

  def target(tag_or_category)
    tag || category
  end

  private 
  def tag_and_category_mutually_exclusive
    if tag && category
      errors.add "Can't have both a tag and a category"
    end
  end
end

The validation ensures that you don't accidentally end up with both fields set, and the targethelpers allows polymorphic access to the tag/category.

验证确保您不会意外地同时设置两个字段,并且target帮助程序允许对标签/类别进行多态访问。

The benefit of doing it like this is that you get a somewhat more correct database schema, where you can define proper foreign key constraints on the id columns. This will also lead to nicer and more efficient sql queries on the database level.

这样做的好处是您可以获得更正确的数据库模式,您可以在其中对 id 列定义适当的外键约束。这也将导致在数据库级别上更好、更高效的 sql 查询。

回答by user5390702

Slight addendum: In the migration where you created the Campaigntable, the t.references :targetcall should have :polymorphic => true(at least with rails 4.2)

轻微的附录:在您创建Campaign表的迁移中,t.references :target调用应该具有:polymorphic => true(至少在 rails 4.2 中)