Pergunta

Por que você não tem uma chave estrangeira em uma associação polimórfica, como a representada abaixo como um modelo de 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
Foi útil?

Solução

Uma chave estrangeira deve referenciar apenas uma tabela pai. Isso é fundamental para ambos sintaxe SQL, ea teoria relacional.

uma associação polimórfica é quando uma dada coluna pode fazer referência a qualquer uma das duas ou mais tabelas pai. Não há nenhuma maneira você pode declarar essa restrição em SQL.

Associações O polimórficos projeto quebra as regras de design de banco de dados relacional. Eu não recomendo usá-lo.

Existem várias alternativas:

  • Exclusivo Arcs: Criar várias colunas de chave estrangeira, cada referência a um dos pais. Impor que exatamente uma dessas chaves estrangeiras pode ser não-NULL.

  • Inverta o relacionamento:. Use três muitos-para-muitos mesas, cada um referências comentários e um respectivo pai

  • Concrete Supertable: Em vez da superclasse implícita "commentable", criar uma tabela real que cada uma de suas referências tabelas pai. Em seguida, ligar os seus comentários para que supertable. Pseudo-rails código seria algo como o seguinte (eu não sou um usuário Rails, por isso, tratar isso como uma diretriz, não o 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
    

Eu também cobrir associações polimórficas na minha apresentação prática orientada a objeto modelos em SQL , e meu livro SQL antipadrões: evitar as armadilhas de programação de banco de dados .


Re seu comentário: Sim, eu sei que há uma outra coluna que notas o nome da tabela que a chave estrangeira supostamente aponta. Este projeto não é suportado por chaves estrangeiras no SQL.

O que acontece, por exemplo, se você inserir um comentário eo nome do "Video" como o nome da tabela pai para que Comment? Nenhuma tabela chamado "Video" existe. Caso a inserção ser abortada com um erro? O que restrição está sendo violado? Como é que os RDBMS sabe que esta coluna é suposto para citar uma tabela existente? Como é que lidar com nomes de tabela maiúsculas e minúsculas?

Da mesma forma, se você deixar cair a tabela Events, mas você tem linhas na Comments que indicam eventos como o seu pai, o que deve ser o resultado? Caso a mesa de queda ser abortada? Deve linhas em Comments ser órfãos? Eles devem mudar para se referir a uma outra tabela existente, como Articles? Será que os valores id que usado para apontar para Events faz qualquer sentido quando apontando para Articles?

Estes dilemas são todos devido ao fato de que associações polimórficas depende da utilização de dados (ou seja, um valor string) para se referir a metadados (um nome de tabela). Isto não é suportado pelo SQL. Os dados e metadados são separados.


Eu estou tendo um momento difícil envolver minha cabeça em torno de seu "Concrete Supertable" proposta.

  • Definir Commentable como uma tabela SQL real, não apenas um adjetivo em sua definição de modelo de Rails. Há outras colunas são necessárias.

    CREATE TABLE Commentable (
      id INT AUTO_INCREMENT PRIMARY KEY
    ) TYPE=InnoDB;
    
  • Defina a Articles tabelas, Photos e Events como "subclasses" de Commentable, fazendo sua chave primária também ser um Commentable referência de chave estrangeira.

    CREATE TABLE Articles (
      id INT PRIMARY KEY, -- not auto-increment
      FOREIGN KEY (id) REFERENCES Commentable(id)
    ) TYPE=InnoDB;
    
    -- similar for Photos and Events.
    
  • Definir tabela de Comments com uma chave estrangeira para Commentable.

    CREATE TABLE Comments (
      id INT PRIMARY KEY AUTO_INCREMENT,
      commentable_id INT NOT NULL,
      FOREIGN KEY (commentable_id) REFERENCES Commentable(id)
    ) TYPE=InnoDB;
    
  • Quando você quiser criar um Article (por exemplo), você deve criar uma nova linha na Commentable também. Assim também para Photos e 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(), ... );
    
  • Quando você quiser criar um Comment, use um valor que existe em Commentable.

    INSERT INTO Comments (id, commentable_id, ...)
    VALUES (DEFAULT, 2, ...);
    
  • Quando você quiser comentários de consulta de um determinado Photo, fazer alguma junta-se:

    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 você tem apenas o ID de um comentário e você quer encontrar o recurso commentable é um recado para. Para isso, você pode achar que é útil para a tabela commentable para designar qual recurso faz referência.

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

    Em seguida, você precisa executar uma segunda consulta para obter dados a partir da tabela de recursos respectiva (fotos, artigos, etc.), depois de descobrir a partir commentable_type qual tabela para se juntar a. Você não pode fazê-lo na mesma consulta, porque o SQL requer que as tabelas ser chamado explicitamente; você não pode juntar-se a uma tabela determinada por resultados de dados na mesma consulta.

É verdade que alguns desses passos quebrar as convenções utilizadas pelo Rails. Mas as convenções Rails estão errados com relação ao projeto apropriado banco de dados relacional.

Outras dicas

Bill Karwin é correto que as chaves estrangeiras não pode ser usado com relacionamentos polimórficos devido ao SQL realmente não ter um conceito nativo relacionamentos polimórficos. Mas se seu objetivo de ter uma chave estrangeira é para impor a integridade referencial pode simulá-lo por meio de gatilhos. Isso fica DB específico, mas abaixo é alguns gatilhos recentes I criados para simular o comportamento de exclusão em cascata de uma chave estrangeira em um relacionamento polimórfico:

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

No meu código de registro na tabela brokerages ou um registro na tabela de agents pode se relacionar a um registro na tabela subscribers.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top