Je voudrais faire quelque chose comme ça:
Category
--------
- id
- name
Tag
--------
- id
- tag
Campaign
--------
- id
- name
- target (either a tag *or* a category)
Une association polymorphe est-elle la réponse ici? Je n'arrive pas à comprendre comment l'utiliser avec has_one: target,: as =>: targetable.
Fondamentalement, je veux que Campaign.target soit défini sur un tag ou une catégorie (ou potentiellement un autre modèle à l'avenir).
Je ne pense pas que vous ayez besoin d'un has_one
l'association ici, la belongs_to
devrait être ce que vous cherchez.
Dans ce cas, vous souhaitez un target_id
et target_type
colonne sur votre table de campagne, vous pouvez les créer dans un râteau avec un t.references :target
appel (où t
est la variable table
).
class Campaign < ActiveRecord::Base
belongs_to :target, :polymorphic => true
end
La campagne peut désormais être associée à un Tag
ou Category
et @campaign.target
renverrait celui qui convient.
Le has_one
l'association serait utilisée si vous avez une clé étrangère sur la table cible pointant vers votre Campaign
.
Par exemple, vos tables auraient
Tag: id, tag, campaign_id
Category: id, category, campaign_id
et aurait un belongs_to :campaign
association sur les deux. Dans ce cas, vous devez utiliser has_one :tag
et has_one :category
, mais vous ne pouvez pas utiliser de générique target
à ce stade.
Est-ce que ça fait plus de sens?
MODIFIER
Puisque target_id
et target_type
sont en fait des clés étrangères à une autre table, votre Campaign
appartient à l'une d'entre elles. Je peux voir votre confusion avec le libellé car logiquement le Campaign
est le conteneur. Je suppose que vous pouvez le considérer comme Campaign
a une seule cible, et c'est un Tag
ou un Container
, donc il appartient à un Tag
ou Container
.
Le has_one
est la façon de dire que la relation est définie sur la classe cible. Par exemple, un Tag
aurait été associé à la campagne via un has_one
relation car il n'y a rien sur la classe de balise qui identifie l'association. Dans ce cas, vous auriez
class Tag < ActiveRecord::Base
has_one :campaign, :as => :target
end
et de même pour un Category
. Ici le :as
le mot clé indique Rails quelle association se rapporte à cette Tag
. Rails ne sait pas comment comprendre cela à l'avance car il y a aucune association avec le nom tag
sur le Campaign
.
Les deux autres options qui peuvent créer une confusion supplémentaire sont les source
et source_type
options. Ils ne sont utilisés que dans :through
relations, où vous rejoignez réellement l'association through
une autre table. Les documents le décrivent probablement mieux, mais le source
définit le nom de l'association et source_type
est utilisé lorsque cette association est polymorphe. Ils ne doivent être utilisés que lorsque l'association cible (sur le :through
class) a un nom qui n'est pas évident - comme dans le cas ci-dessus avec target and
Tag - et nous devons dire Rails lequel utiliser).
Les réponses à ces questions sont excellentes, mais je voulais juste mentionner une autre façon d'y parvenir. Ce que vous pourriez faire à la place, c'est créer deux relations, par exemple:
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
La validation garantit que vous ne vous retrouvez pas accidentellement avec les deux champs définis, et les assistants target
permettent un accès polymorphe à la balise/catégorie.
L'avantage de le faire comme ceci est que vous obtenez un schéma de base de données un peu plus correct, où vous pouvez définir des contraintes de clé étrangère appropriées sur les colonnes id. Cela conduira également à des requêtes SQL plus agréables et plus efficaces au niveau de la base de données.
Petit ajout: dans la migration où vous avez créé la table Campaign
, la t.references :target
l'appel devrait avoir :polymorphic => true
(au moins avec Rails 4.2)