可能在rails中有“polymorphic has_one”关系?

时间:2011-09-08 01:28:31

标签: ruby-on-rails activerecord polymorphism

我想做这样的事情:

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

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


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

这是一个多态关联的答案吗?我似乎无法弄清楚如何使用has_one:target,:as => :靶向

基本上,我希望Campaign.target设置为Tag或Category(或将来可能是另一个模型)。

3 个答案:

答案 0 :(得分:77)

我认为您不需要has_one关联,belongs_to应该是您正在寻找的。

在这种情况下,您需要在广告系列表格中添加target_idtarget_type列,您可以通过t.references :target调用在其中创建这些列(其中t }是table变量。

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

现在广告系列可以与TagCategory相关联,@campaign.target会返回相应的广告系列。

如果目标表上的外键指向您的has_one,则会使用Campaign关联。

例如,您的表格会有

Tag: id, tag, campaign_id Category: id, category, campaign_id

并且两者都有belongs_to :campaign关联。在这种情况下,您必须使用has_one :taghas_one :category,但此时您无法使用通用target

这更有意义吗?

修改

由于target_idtarget_type实际上是另一个表的外键,因此Campaign属于其中一个。我可以看到你对措辞的困惑,因为逻辑上Campaign是容器。我猜您可以将其视为Campaign只有一个目标,而且是TagContainer,因此它属于TagContainer

has_one是表示在目标类上定义关系的方式。例如,Tag将通过has_one关系与广告系列相关联,因为标记类中没有任何内容可用于标识关联。在这种情况下,你有

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

同样适用于Category。在这里,:as关键字告诉我哪个关联与此Tag有关。 Rails不知道如何预先解决这个问题,因为tag上的名称Campaign没有关联。

可能会引起进一步混淆的另外两个选项是sourcesource_type选项。这些仅用于:through关系,您实际上正在加入关联through另一个表。文档可能更好地描述它,但source定义了关联名称,source_type用于该关联是多态的。只有当目标关联(在:through类)上的名称不明显时才需要使用它们 - 就像上面target and标记的情况一样 - 我们需要告诉rails一个人可以使用。

答案 1 :(得分:5)

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

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

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

这样做的好处是可以获得更正确的数据库模式,您可以在其中为id列定义正确的外键约束。这也将在数据库级别上实现更好,更有效的SQL查询。

答案 2 :(得分:1)

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