Question

Pourquoi ne pouvez-vous pas une clé étrangère dans une association polymorphique, comme celui représenté ci-dessous comme modèle Rails?

class Comment < ActiveRecord::Base
  belongs_to :commentable, :polymorphic => true
end

class Article < ActiveRecord::Base
  has_many :comments, :as => :commentable
end

class Photo < ActiveRecord::Base
  has_many :comments, :as => :commentable
  #...
end

class Event < ActiveRecord::Base
  has_many :comments, :as => :commentable
end
Était-ce utile?

La solution

Une clé étrangère doit faire référence à une seule table parent. Ceci est fondamental pour la théorie de la syntaxe SQL et relationnelle.

Une association polymorphique est quand une colonne donnée peut faire référence à l'une des deux ou plusieurs tables parent. Il n'y a aucun moyen que vous pouvez déclarer cette contrainte dans SQL.

Les règles brise la conception des associations polymorphiques de conception de base de données relationnelle. Je ne recommande pas l'utiliser.

Il existe plusieurs alternatives:

  • Arcs Exclusif: Créer plusieurs colonnes de clé étrangère, chaque référence à l'un des parents. Faire respecter exactement l'une de ces clés étrangères peuvent être non NULL.

  • Inverser la relation:. Utilisez trois nombreux à plusieurs tables, chacune des références Commentaires et un parent respectif

  • Béton Supertable: Au lieu de l'implicite "commentable" superclasse, créer une vraie table que chacune de vos tables de références parents. Ensuite, liez vos commentaires à ce supertable. code de pseudo-rails serait quelque chose comme ce qui suit (je ne suis pas un utilisateur Rails, donc traiter cela comme une ligne directrice, pas de code littéral):

    class Commentable < ActiveRecord::Base
      has_many :comments
    end
    
    class Comment < ActiveRecord::Base
      belongs_to :commentable
    end
    
    class Article < ActiveRecord::Base
      belongs_to :commentable
    end
    
    class Photo < ActiveRecord::Base
      belongs_to :commentable
    end
    
    class Event < ActiveRecord::Base
      belongs_to :commentable
    end
    

Je couvre aussi des associations polymorphiques dans ma présentation orienté objet pratique modèles SQL, et mon livre SQL Antipatterns: éviter les pièges de la programmation de base de données .


Re votre commentaire: Oui, je ne sais qu'il ya une autre colonne qui note le nom de la table que la clé étrangère fait pour soi-disant. Cette conception est pas pris en charge par des clés étrangères dans SQL.

Qu'est-ce qui se passe, par exemple, si vous insérez un commentaire et le nom « Vidéo » comme le nom de la table parent pour ce Comment? Aucune table « vidéo » existe. l'insert doit être interrompu avec une erreur? Qu'est-ce que la contrainte est violé? Comment le SGBDR savent que cette colonne est censé nommer une table existante? Comment faut-il gérer les noms de table insensibles à la casse?

De même, si vous laissez tomber la table Events, mais vous avez des lignes en Comments qui indiquent des événements comme leurs parents, ce qui devrait être le résultat? la table de chute doit être annulée? Si les lignes de Comments être devenus orphelins? En cas de changement de se référer à une autre table existante, comme Articles? Est-ce que les valeurs id qui ont utilisé pour pointer vers Events de sens en pointant à Articles?

Ces dilemmes sont dus au fait que les associations polymorphes dépend de l'utilisation des données (à savoir une valeur de chaîne) pour se référer aux métadonnées (un nom de table). Ce ne sont pas pris en charge par SQL. Les données et les métadonnées sont séparées.


  

Je vais avoir du mal à envelopper ma tête autour de votre proposition « Concrete Supertable ».

  • Définir Commentable comme une vraie table SQL, pas seulement un adjectif dans votre définition du modèle Rails. Aucune autre colonnes sont nécessaires.

    CREATE TABLE Commentable (
      id INT AUTO_INCREMENT PRIMARY KEY
    ) TYPE=InnoDB;
    
  • Définir les tables Articles, Photos et Events comme "sous-classes" de Commentable, en rendant leur clé primaire soit aussi un Commentable de référencement clé étrangère.

    CREATE TABLE Articles (
      id INT PRIMARY KEY, -- not auto-increment
      FOREIGN KEY (id) REFERENCES Commentable(id)
    ) TYPE=InnoDB;
    
    -- similar for Photos and Events.
    
  • Définir la table Comments avec une clé étrangère à Commentable.

    CREATE TABLE Comments (
      id INT PRIMARY KEY AUTO_INCREMENT,
      commentable_id INT NOT NULL,
      FOREIGN KEY (commentable_id) REFERENCES Commentable(id)
    ) TYPE=InnoDB;
    
  • Si vous voulez créer un Article (par exemple), vous devez créer une nouvelle ligne dans Commentable aussi. Donc aussi pour Photos et Events.

    INSERT INTO Commentable (id) VALUES (DEFAULT); -- generate a new id 1
    INSERT INTO Articles (id, ...) VALUES ( LAST_INSERT_ID(), ... );
    
    INSERT INTO Commentable (id) VALUES (DEFAULT); -- generate a new id 2
    INSERT INTO Photos (id, ...) VALUES ( LAST_INSERT_ID(), ... );
    
    INSERT INTO Commentable (id) VALUES (DEFAULT); -- generate a new id 3
    INSERT INTO Events (id, ...) VALUES ( LAST_INSERT_ID(), ... );
    
  • Si vous voulez créer un Comment, utilisez une valeur qui existe dans Commentable.

    INSERT INTO Comments (id, commentable_id, ...)
    VALUES (DEFAULT, 2, ...);
    
  • Si vous voulez interroger les commentaires d'un Photo, faire un peu donné Jointures:

    SELECT * FROM Photos p JOIN Commentable t ON (p.id = t.id)
    LEFT OUTER JOIN Comments c ON (t.id = c.commentable_id)
    WHERE p.id = 2;
    
  • When vous suffit l'identifiant d'un commentaire et que vous voulez trouver ce que commentable ressources, il est un commentaire pour. Pour cela, vous trouverez peut-être qu'il est utile pour la table commentable pour désigner quelle ressource il fait référence.

    SELECT commentable_id, commentable_type FROM Commentable t
    JOIN Comments c ON (t.id = c.commentable_id)
    WHERE c.id = 42;
    

    Ensuite, vous aurez besoin d'exécuter une deuxième requête pour obtenir des données de la table des ressources respectives (Photos, articles, etc.), après avoir découvert de commentable_type la table à se joindre à. Vous ne pouvez pas le faire dans la même requête, car SQL nécessite que les tables soient nommés de manière explicite; vous ne pouvez pas joindre à une table déterminée par les résultats de données dans la même requête.

Il est vrai que certaines de ces étapes Casser les conventions utilisées par Rails. Mais les conventions Rails sont fausses en ce qui concerne la conception de base de données relationnelle appropriée.

Autres conseils

Bill Karwin est exact que les clés étrangères ne peuvent pas être utilisées avec des relations polymorphes en raison de SQL ne pas vraiment avoir un concept indigène relations polymorphes. Mais si votre objectif d'avoir une clé étrangère est de faire respecter l'intégrité référentielle, vous pouvez simuler via les déclencheurs. Cela devient spécifique DB, mais ci-dessous quelques éléments déclencheurs récents que j'ai créé pour simuler la suppression en cascade le comportement d'une clé étrangère sur une relation polymorphes:

CREATE FUNCTION delete_related_brokerage_subscribers() RETURNS trigger AS $$
  BEGIN
    DELETE FROM subscribers
    WHERE referrer_type = 'Brokerage' AND referrer_id = OLD.id;
    RETURN NULL;
  END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER cascade_brokerage_subscriber_delete
AFTER DELETE ON brokerages
FOR EACH ROW EXECUTE PROCEDURE delete_related_brokerage_subscribers();


CREATE FUNCTION delete_related_agent_subscribers() RETURNS trigger AS $$
  BEGIN
    DELETE FROM subscribers
    WHERE referrer_type = 'Agent' AND referrer_id = OLD.id;
    RETURN NULL;
  END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER cascade_agent_subscriber_delete
AFTER DELETE ON agents
FOR EACH ROW EXECUTE PROCEDURE delete_related_agent_subscribers();

Dans mon code d'un enregistrement de la table de brokerages ou d'un enregistrement de la table de agents peut se rapporter à un enregistrement de la table de subscribers.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top