Pregunta

¿Por qué no puedes tener una clave externa de una asociación polimórfica, como el que se representa a continuación como un modelo de rieles?

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
¿Fue útil?

Solución

Una clave externa debe hacer referencia a una sola tabla primaria. Esto es fundamental tanto para la sintaxis SQL, y la teoría relacional.

A Asociación polimórfica es cuando una columna dada puede hacer referencia a cualquiera de dos o más tablas de padres. No hay manera de que pueda declarar que la restricción en SQL.

El asociaciones polimórficas diseño rompe las reglas de diseño de base de datos relacional. No recomiendo usarlo.

Existen varias alternativas:

  • Exclusivos Arcos: Crea varias columnas de clave externa, cada una referencia a uno de los padres. Hacer cumplir esa exactamente una de estas claves externas puede ser que no sea nula.

  • Invertir la relación:. Use tres-muchos-a-muchos cuadros, cada uno hace referencia a los comentarios y un padre respectiva

  • Hormigón supertabla: En lugar de la superclase implícita "commentable", crear una tabla real de que cada una de sus tablas padre referencias. A continuación, vincule sus comentarios a la supertabla. código de pseudo-carriles sería algo así como lo siguiente (no soy un usuario rieles, por lo que tratar esto como una guía, no código literal):

    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
    

También cubro asociaciones polimórficas en mi presentación orientada a objetos Práctica modelos en SQL , y mi libro SQL antipatterns: se evitan los riesgos de programación de base de datos .


Re tu comentario: Sí, sé que hay otra columna que señala el nombre de la tabla que la clave externa supuestamente apunta. Este diseño no es compatible con claves externas de SQL.

Lo que ocurre, por ejemplo, si inserta un comentario y el nombre de "Video" como el nombre de la tabla padre para que Comment? No existe una tabla llamada "Video". En caso de que el inserto se interrumpe con un error? Lo que se viola la restricción? ¿Cómo funciona el RDBMS saben que esta columna se supone que es el nombre de una tabla existente? ¿Cómo se manejan los nombres de tabla entre mayúsculas y minúsculas?

Del mismo modo, si usted eliminar la tabla Events, pero hay filas en Comments que indican eventos como sus padres, lo que debería ser el resultado? Debería abortarse la tabla de la gota? Deben ser huérfanos filas en Comments? Deberían cambiar para hacer referencia a otra tabla existente, como Articles? ¿Los valores de ID que se utiliza para apuntar a Events tiene ningún sentido cuando se apunta a Articles?

Estos dilemas son todos debido al hecho de que las asociaciones polimórficas depende del uso de los datos (es decir, un valor de cadena) para referirse a metadatos (un nombre de tabla). Esto no es compatible con SQL. Datos y metadatos están separados.


  

Estoy teniendo dificultades para envolver mi cabeza en torno a su propuesta "concreta supertabla".

  • Definir Commentable como una tabla de SQL real, no sólo un adjetivo en la definición del modelo de rieles. No hay otras columnas son necesarias.

    CREATE TABLE Commentable (
      id INT AUTO_INCREMENT PRIMARY KEY
    ) TYPE=InnoDB;
    
  • Definir el Articles mesas, Photos y Events como "subclases" de Commentable, al hacer su clave primaria sea también un Commentable referencia de clave externa.

    CREATE TABLE Articles (
      id INT PRIMARY KEY, -- not auto-increment
      FOREIGN KEY (id) REFERENCES Commentable(id)
    ) TYPE=InnoDB;
    
    -- similar for Photos and Events.
    
  • Definir la tabla Comments con una clave externa a Commentable.

    CREATE TABLE Comments (
      id INT PRIMARY KEY AUTO_INCREMENT,
      commentable_id INT NOT NULL,
      FOREIGN KEY (commentable_id) REFERENCES Commentable(id)
    ) TYPE=InnoDB;
    
  • Cuando se desea crear una Article (por ejemplo), es necesario crear una nueva fila en Commentable también. Así también para Photos y 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(), ... );
    
  • Cuando se desea crear un Comment, utilice un valor que existe en Commentable.

    INSERT INTO Comments (id, commentable_id, ...)
    VALUES (DEFAULT, 2, ...);
    
  • Cuando se desea consultar los comentarios de un Photo dado, hacer algo de uniones:

    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;
    
  • Whes Usted tiene sólo el ID de un comentario y desea encontrar lo commentable de recursos es un comentario para. Para esto, se puede encontrar que es útil para la tabla commentable para designar qué recurso que hace referencia.

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

    A continuación, que había necesidad de ejecutar una segunda consulta para obtener los datos de la tabla de recursos correspondiente (fotografías, artículos, etc.), después de descubrir de qué tabla commentable_type para unirse a. No se puede hacerlo en la misma consulta, ya que SQL requiere que las tablas se nombran de manera explícita; no se puede unir a una mesa determinada por los resultados de datos en la misma consulta.

Es cierto que algunos de estos pasos romper las convenciones utilizadas por los rieles. Pero las convenciones carriles están mal con respecto a un diseño adecuado de base de datos relacional.

Otros consejos

Bill Karwin es correcto que las claves foráneas no se pueden utilizar con las relaciones polimórficos debido a SQL en realidad no tienen un concepto nativa relaciones polimórficas. Pero si su meta de tener una clave externa es hacer cumplir la integridad referencial se puede simular que a través de disparadores. Esto consigue específica DB, pero a continuación es algunos factores desencadenantes recientes que he creado para simular el comportamiento de eliminación en cascada una clave externa en una relación polimórfica:

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();

En mi código de un registro en la tabla brokerages o un registro en la tabla agents puede relacionarse con un registro en la tabla subscribers.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top