web-dev-qa-db-fra.com

Mettre en place une relation polymorphe has_many: through

Rails g model Article name:string
Rails g model Category name:string
Rails g model Tag name:string taggable_id:integer taggable_type:string category_id:integer

J'ai créé mes modèles comme indiqué dans le code précédent. Les articles seront l'un des nombreux modèles qui peuvent avoir des balises. Le modèle de catégorie contiendra toutes les catégories qui peuvent être attribuées. Le modèle d'étiquette sera une table de jointure polymorphe qui représente les relations étiquetées.

class Article < ActiveRecord::Base
  has_many :tags, :as => :taggable
  has_many :categories, :through => :taggable
end

class Category < ActiveRecord::Base
  has_many :tags, :as => :taggable
  has_many :articles, :through => :taggable
end

class Tag < ActiveRecord::Base
  belongs_to :taggable, :polymorphic => true
  belongs_to :category
end

Je n'arrive pas à faire fonctionner cela, je peux le faire non polymorphe, mais je dois avoir quelque chose qui ne va pas avec la partie polymorphe. Des idées?

Edit: toujours pas ce droit:

class Article < ActiveRecord::Base
    has_many :taggables, :as => :tag
    has_many :categories, :through => :taggables, :source => :tag, :source_type => "Article"
end
class Category < ActiveRecord::Base
    has_many :taggables, :as => :tag
    has_many :articles, :through => :taggables, :source => :tag, :source_type => "Article"
end
class Tag < ActiveRecord::Base
  belongs_to :taggable, :polymorphic => true
  belongs_to :category
end
36
Serodis

Pour créer un has_many: through polymorphe, vous devez d'abord créer vos modèles. Nous utiliserons "Article", "Catégorie" et "Tag" où "Tag" est le modèle de jointure et Article est l'un des nombreux objets qui peuvent être "tagués" avec une catégorie.

Commencez par créer vos modèles "Article" et "Catégorie". Ce sont des modèles de base qui ne nécessitent aucune attention particulière, pour l'instant:

Rails g model Article name:string
Rails g model Category name:string

Maintenant, nous allons créer notre table de jointure polymorphe:

Rails g model Tag taggable_id:integer taggable_type:string category_id:integer

La table de jointure relie deux tables, ou dans notre cas une table à plusieurs autres via un comportement polymorphe. Il le fait en stockant l'ID de deux tables distinctes. Cela crée un lien. Notre table 'Category' sera toujours une 'Category' donc nous incluons 'category_id'. Les tables auxquelles il est lié varient, nous ajoutons donc un élément 'taggable_id' qui contient l'identifiant de tout élément taggable. Ensuite, nous utilisons 'taggable_type' pour compléter le lien permettant au lien de savoir à quoi il est lié, comme un article.

Maintenant, nous devons configurer nos modèles:

class Article < ActiveRecord::Base
  has_many :tags, :as => :taggable, :dependent => :destroy
  has_many :categories, :through => :tags
end
class Category < ActiveRecord::Base
  has_many :tags, :dependent => :destroy
  has_many :articles, :through => :tags, :source => :taggable, :source_type => 'Article'
end
class Tag < ActiveRecord::Base
  belongs_to :taggable, :polymorphic => true
  belongs_to :category
end

Après cela, configurez votre base de données en utilisant:

rake db:migrate

C'est tout! Maintenant, vous pouvez configurer votre base de données avec des données réelles:

Category.create :name => "Food"
Article.create :name => "Picking the right restaurant."
Article.create :name => "The perfect cherry pie!"
Article.create :name => "Foods to avoid when in a hurry!"
Category.create :name => "Kitchen"
Article.create :name => "The buyers guide to great refrigeration units."
Article.create :name => "The best stove for your money."
Category.create :name => "Beverages"
Article.create :name => "How to: Make your own soda."
Article.create :name => "How to: Fermenting fruit."

Vous avez maintenant quelques catégories et divers articles. Cependant, ils ne sont pas classés à l'aide de balises. Donc, nous devrons le faire:

a = Tag.new
a.taggable = Article.find_by_name("Picking the right restaurant.")
a.category = Category.find_by_name("Food")
a.save

Vous pouvez ensuite répéter ceci pour chacun, cela reliera vos catégories et articles. Après cela, vous pourrez accéder aux catégories de chaque article et aux articles de chaque catégorie:

Article.first.categories
Category.first.articles

Remarques:

1) Chaque fois que vous souhaitez supprimer un élément lié par un modèle de lien, assurez-vous d'utiliser "détruire". Lorsque vous détruisez un objet lié, cela détruira également le lien. Cela garantit qu'il n'y a pas de liens défectueux ou morts. C'est pourquoi nous utilisons ': dependant =>: detruire'

2) Lors de la configuration de notre modèle "Article", qui est l'un de nos modèles "taggables", il doit être lié en utilisant: as. Puisque dans l'exemple précédent, nous avons utilisé 'taggable_type' et 'taggable_id', nous utilisons: as =>: taggable. Cela aide Rails à savoir comment stocker les valeurs dans la base de données.

3) Lors de la liaison de catégories à des articles, nous utilisons: has_many: articles,: through =>: tags,: source =>: taggable,: source_type => 'Article' Cela indique au modèle de catégorie qu'il devrait avoir plusieurs: articles à travers: Mots clés. La source est: taggable, pour la même raison que ci-dessus. Le type source est "Article" car un modèle définira automatiquement taggable_type sur son propre nom.

88
Serodis

Vous ne pouvez tout simplement pas rendre la table de jointure polymorphe, au moins Rails ne prend pas en charge cela par défaut. La solution est (tirée de Obie Rails 3 way)) :

Si vous en avez vraiment besoin, has_many :through est possible avec les associations polymorphes, mais uniquement en spécifiant exactement le type d'associations polymorphes que vous souhaitez. Pour ce faire, vous devez utiliser le :source_type option. Dans la plupart des cas, vous devrez utiliser le :source, car le nom de l'association ne correspondra pas au nom de l'interface utilisé pour l'association polymorphe:

class User < ActiveRecord::Base
  has_many :comments
  has_many :commented_timesheets, :through => :comments, :source => :commentable,
           :source_type => "Timesheet"
  has_many :commented_billable_weeks, :through => :comments, :source => :commentable,
           :source_type => "BillableWeek"

C'est verbeux et l'ensemble du schéma perd de son élégance si vous suivez cette voie, mais cela fonctionne:

User.first.commented_timesheets

J'espère que j'ai aidé!

15
Gerry