2009-06-13 7 views
0

Je considère la structure de base de données suivante, mais je ne suis pas sûr quel type de relations de modèle Rails prendrait en charge les clés de base de données que j'ai définies. Quelqu'un pourrait-il suggérer comment cela pourrait fonctionner dans Rails?Quel type de relation de modèle Rails fonctionnerait le mieux pour les clés agrégées + plusieurs clés étrangères?

Posts 
id 
post_type -- must be 'Q' or 'A' 
author 
date 
content 
UNIQUE KEY (post_id, post_type) -- to support foreign keys 

Questions 
id 
post_id 
post_type -- must be 'Q' 
FOREIGN KEY (post_id, post_type) REFERENCES Posts(post_id, post_type) 

Answers  
id 
post_id 
post_type -- must be 'A' 
question_id 
FOREIGN KEY (post_id, post_type) REFERENCES Posts(post_id, post_type) 
FOREIGN KEY (question_id) REFERENCES Questions(post_id) 

Comments  
id 
post_id 
author 
date 
content 
FOREIGN KEY (post_id) REFERENCES Posts(post_id) 

Le croquis ci-dessus se traduirait par la mise en œuvre suivante:

CREATE TABLE Posts (
    post_id  SERIAL PRIMARY KEY, 
    post_type CHAR(1),    -- must be 'Q' or 'A' 
    -- other columns common to both types of Post 
    UNIQUE KEY (post_id, post_type) -- to support foreign keys 
) ENGINE=InnoDB; 

CREATE TABLE Comments (
    comment_id SERIAL PRIMARY KEY, 
    post_id  BIGINT UNSIGNED NOT NULL, 
    -- other columns for comments (e.g. date, who, text) 
    FOREIGN KEY (post_id) REFERENCES Posts(post_id) 
) ENGINE=InnoDB; 

CREATE TABLE Questions (
    post_id  BIGINT UNSIGNED PRIMARY KEY, 
    post_type CHAR(1),    -- must be 'Q' 
    -- other columns specific to Questions 
    FOREIGN KEY (post_id, post_type) REFERENCES Posts(post_id, post_type) 
) ENGINE=InnoDB; 

CREATE TABLE Answers (
    post_id  BIGINT UNSIGNED PRIMARY KEY, 
    post_type CHAR(1),    -- must be 'A' 
    question_id BIGINT UNSIGNED NOT NULL, 
    -- other columns specific to Answers 
    FOREIGN KEY (post_id, post_type) REFERENCES Posts(post_id, post_type) 
    FOREIGN KEY (question_id) REFERENCES Questions(post_id) 
) ENGINE=InnoDB; 

Répondre

11

Vous avez plusieurs options quant à la façon de modéliser cela dans les rails, mais la première chose que je suggère est que pour sauver vous-même le temps et les problèmes plus tard, vous devriez aborder votre question sous un angle différent.

Afin de tirer le meilleur parti de Rails, vous ne devriez pas commencer par une conception de base de données. Vous devez commencer par un modèle de données, puis examiner la manière de mapper ce modèle de données à une structure de base de données, et non l'inverse. Ceci est une différence subtile, mais implique un état d'esprit différent où vous voyez votre base de données comme une considération secondaire à votre modèle, et non l'inverse. Cela rendra le problème plus facile à comprendre à long terme.

Deux constructions ActiveRecord peuvent être utilisées dans ce scénario: héritage de table unique et héritage polymorphique.

unique Héritage de Table

magasins unique Héritage de Table (STI) Les modèles avec des fonctionnalités bien partagées au sein de la même table de base de données sous-jacente. Dans votre exemple, les questions et réponses, et dans une moindre mesure les commentaires sont tous des objets similaires. Ils ont du contenu, un auteur, des champs datetime pour created_at et mis à jour à etc. La seule différence entre une question et une réponse est que les questions "appartiennent à" une réponse. Les commentaires sont légèrement plus compliqués car vous pouvez commenter à la fois les questions et réponses, et peut-être aussi sur les commentaires, bien que votre schéma de base de données ne reflète pas que c'est possible.

Avec STI vos questions et réponses ne sont pas stockées dans une table séparée mais dans une seule table et marqués avec les noms de classe. Les classes de questions et réponses réelles héritent alors de la classe de base, dans votre cas "Post". Il existe de nombreuses ressources òû il discuter des IST mais this one peuvent aider

polymorphes héritage

C'est la deuxième méthode de modélisation des comportements similaires dans les rails. Cela utilise une seule table, dans vos messages de cas pour stocker les données qui sont communes entre les deux classes. Cette table contient des colonnes qui référencent le nom de classe et l'instance d'ID de l'objet de base. Les données spécifiques à l'objet seraient alors stockées dans une table distincte par modèle.

mise en œuvre (en utilisant STI)

Pour modéliser vos données en utilisant STI alors vous créer un modèle de base des postes comme ce

class CreatePosts < ActiveRecord::Migration 
    def self.up 
     create_table :posts do |t| 
     t.string :type 
     t.string :author 
     t.text :content 
     t.integer :parent_id 
     t.timestamps 
     end 
    end 


    def self.down 
     drop_table :posts 
    end 
end 

Vos modèles alors ressembler à ceci

class Post < ActiveRecord::Base 
end 

class Question < Post 
    has_many :answers, :foreign_key => :parent_id 
    has_many :comments, :foreign_key => :parent_id 
end 

class Answer < Post 
    belongs_to :question, :foreign_key => :parent_id 
    has_many :comments, :foreign_key => :parent_id 
end 

class Comment < Post 
    belongs_to :question, :foreign_key => :parent_id 
    belongs_to :answer, :foreign_key => :parent_id 
end 

Et quelques exemples de code

q1 = Question.new(:author => 'Steve', :content => 'What is 2 + 2') 
q1c1 = q1.comments.build(:author => 'Malcolm', 
    :content => "Good question, i'd been wondering that myself")  
q1a1 = q1.answers.build(:author => 'John', :content => '2+2 = 5') 
q1a2 = q1.answers.build(:author => 'Phil', :content => '2+2 is a sum') 

q1a1c1 = q1a1.comments.build(:author => 'Chris', 
    :content => 'Sorry John it should be 4') 
q1a2c1 = q1a2.comments.build(:author => 'Steve', 
    :content => 'Hi Phil thanks for stating the obvious!') 

q1.save 

qu = Question.find(:first) 
puts "#{qu.author} asked #{qu.content}" 
qu.comments.each {|qc| puts "\t#{qc.author} commented #{qc.content}"} 
qu.answers.each do |ans| 
    puts "\t#{ans.author} answered with #{ans.content}" 
    ans.comments.each do |comm| 
    puts "\t\t#{comm.author} commented #{comm.content}" 
    end 

end 

Ce code produit les résultats suivants

 
Steve asked What is 2 + 2 
    Malcolm commented Good question, i'd been wondering that myself 
    John answered with 2+2 = 5 
    Chris commented Sorry John it should be 4 
    Phil answered with 2+2 is a sum 
    Steve commented Hi Phil thanks for stating the obvious! 

En regardant dans la base de données, il y a une table de messages unique avec la structure suivante

CREATE TABLE "posts" (
    "id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, 
    "type" varchar(255), 
    "author" varchar(255), 
    "content" text, 
    "parent_id" integer, 
    "created_at" datetime, 
    "updated_at" datetime 
); 

Et le contenu des données après l'exemple est la suivante: -

 
1|Question|Steve|What is 2 + 2||2009-06-13 09:52:20|2009-06-13 09:52:20 
2|Answer|John|2+2 = 5|1|2009-06-13 09:52:20|2009-06-13 09:52:20 
3|Comment|Chris|Sorry John it should be 4|2|2009-06-13 09:52:20|2009-06-13 09:52:20 
4|Answer|Phil|2+2 is a sum|1|2009-06-13 09:52:20|2009-06-13 09:52:20 
5|Comment|Steve|Hi Phil thanks for stating the obvious!|4|2009-06-13 09:52:20|2009-06-13 09:52:20 
6|Comment|Malcolm|Good question, i'd been wondering that myself|1|2009-06-13 09:52:20|2009-06-13 09:52:20 

Vous trouverez peut-être plus facile de diviser les commentaires modèle dans QuestionComments et AnswerComments. Cela rendrait SQL directement plus facile.

+0

C'est génial. Merci d'avoir pris le temps d'écrire une réponse aussi complète. Bien que j'aime votre suggestion d'utiliser STI sur une approche polymorphe, un inconvénient de STI par rapport à Class Table héritage tout2.com/title/... est que vous vous retrouvez avec des colonnes inutilisées dans les classes qui dérivent de Post. Par exemple, une question peut avoir des balises associées et cela pourrait être une colonne dans le tableau des questions, mais sous STI, il devrait s'agir d'une colonne dans le tableau Posts, puis Answers et Comments se retrouvent également avec une colonne Tags. –

+0

... mais puisque Rails ne propose que des associations STI et polymorphes, STI semble être le meilleur choix même si vous vous retrouvez avec des colonnes supplémentaires inutilisées dans cette approche. Globalement, vous avez raison, cependant - J'ai besoin d'aborder le problème de la perspective Rails plutôt que d'une perspective de base de données. Je vais avoir besoin de m'habituer, j'en ai peur. –

+0

Salut Charlie, je vais commenter, mais je viens de boire depuis plusieurs heures alors ce n'est pas le moment. Je vais écrire quelques commentaires et/ou une solution polymorphique demain –

0

Cette solution est également possible avec un has_and_belongs_to_many entre par exemple:

Les sportifs et les formateurs?

s1 = Sportsmen.new(:name=> “Günter”) 
t1 = Trainer.new(:name=> „Hans“) 
t2 = Trainer.new(:name=> „Joachim“) 

is this correct ? 
S1t1 = s1.t1 
S1t2 = s1.t2 
Questions connexes