Подтвердить что ты не робот

Возможно ли иметь "полиморфные 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 был настроен на тег или категорию (или потенциально другую модель в будущем).

4b9b3361

Ответ 1

Я не считаю, что вам нужна ассоциация has_one, belongs_to должна быть тем, что вы ищете.

В этом случае вам понадобится столбец target_id и target_type в таблице кампании, вы можете создать их в графе с вызовом t.references :target (где t является переменной table),

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

Теперь кампанию можно связать либо с Tag, либо с Category, а @campaign.target вернет соответствующую.

Связь has_one будет использоваться, если у вас есть внешний ключ в целевой таблице, указывающий на ваш Campaign.

Например, ваши таблицы будут иметь

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

и будет иметь ассоциацию belongs_to :campaign для обоих из них. В этом случае вам придется использовать has_one :tag и has_one :category, но вы не можете использовать общий target в этой точке.

Это имеет смысл?

ИЗМЕНИТЬ

Так как target_id и target_type являются внешними ключами для другой таблицы, ваш Campaign принадлежит к одному из них. Я вижу вашу путаницу с формулировкой, потому что логически Campaign является контейнером. Думаю, вы можете думать об этом, поскольку Campaign имеет единственную цель и что Tag или Container, поэтому он принадлежит в Tag или Container.

has_one - это способ сказать, что отношение определено в целевом классе. Например, Tag был бы связан с кампанией с помощью отношения has_one, так как в этом теге не существует ничего, что идентифицирует ассоциацию. В этом случае у вас будет

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

а также для a Category. Здесь ключевое слово :as указывает рельсы, ассоциация которых связана с этим Tag. Rails не знает, как это понять, потому что нет связи с именем Tag на Campaign.

Другие две опции, которые могут вызвать дополнительную путаницу, - это опции source и source_type. Они используются только в отношениях :through, где вы фактически присоединяетесь к ассоциации through другой таблице. Документы, вероятно, описывают это лучше, но source определяет имя ассоциации, а source_type используется там, где эта связь является полиморфной. Их нужно использовать только тогда, когда целевая ассоциация (в классе :through) имеет имя, которое не является очевидным - как в случае выше с тегом target and - и нам нужно указать рельсы, которые нужно использовать.

Ответ 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-запросов на уровне базы данных.

Ответ 3

Небольшое добавление: при миграции таблицы Campaign вызов t.references :target должен иметь :polymorphic => true (по крайней мере, с рельсами 4.2)