web-dev-qa-db-ja.com

レールで「多態的なhas_one」関係を持つことは可能ですか?

私はこのようなことをしたいと思います:

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

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


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

多態的な関連はここでの答えですか? has_one:target、:as =>:targetableでそれを使用する方法を理解できないようです。

基本的には、Campaign.targetをタグまたはカテゴリ(または将来的には別のモデルになる可能性がある)に設定する必要があります。

39
markquezada

ここでhas_oneの関連付けが必要だとは思わないので、belongs_toを探してください。

この場合、キャンペーンテーブルにtarget_id列とtarget_type列が必要です。t.references :target呼び出し(ttable変数)を使用して、これらをレーキで作成できます。

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

これで、キャンペーンをTagまたはCategoryのいずれかに関連付けることができ、@campaign.targetは適切なものを返します。

Campaignを指す外部キーがターゲットテーブルにある場合、has_one関連付けが使用されます。

たとえば、テーブルには

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

両方にbelongs_to :campaign関連付けがあります。この場合、has_one :taghas_one :categoryを使用する必要がありますが、この時点では一般的なtargetを使用できませんでした。

それはもっと理にかなっていますか?

[〜#〜]編集[〜#〜]

target_idtarget_typeは事実上別のテーブルへの外部キーであるため、Campaignはそれらのいずれかに属しています。論理的にはCampaignがコンテナであるため、この文言の混乱を理解できます。 Campaignには単一のターゲットがあり、TagまたはContainerであるため、TagまたはContainerに属していると考えることができます。

has_oneは、関係がターゲットクラスで定義されていることを示す方法です。たとえば、タグクラスには関連付けを識別するものがないため、Taghas_one関係を介してキャンペーンに関連付けられます。この場合、

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

同様に、Categoryの場合も同様です。ここで、:asキーワードは、どの関連付けがこのTagに関連しているかをRailsに伝えています。 Railsは、tagCampaignという名前との関連付けがないため、これを前もって把握する方法を知りません。

さらに混乱を招く可能性のある他の2つのオプションは、sourceおよびsource_typeオプションです。これらは:throughリレーションシップでのみ使用され、ここで実際にthrough関連付けを別のテーブルに結合します。ドキュメントはおそらくそれをよりよく説明しますが、sourceは関連名を定義し、source_typeはその関連が多態性である場合に使用されます。これらは、ターゲットの関連付け(:throughクラス上)に、上記のtarget andTagの場合のように明確ではない名前があり、Railsどちらを使用するか。

79
Kristian PD

この質問への回答は素晴らしいですが、同じことを達成する別の方法についてお話ししたいと思います。代わりにできることは、2つの関係を作成することです。

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クエリも可能になります。

7
troelskn

わずかな補遺:Campaignテーブルを作成した移行では、t.references :target呼び出しには:polymorphic => true(少なくともRails 4.2)

1
user5390702