2009-06-13 5 views
0

Ich denke über die folgende Datenbankstruktur nach, aber ich bin mir nicht sicher, welche Art von Rails-Modellbeziehungen die von mir definierten Datenbankschlüssel unterstützen würde. Könnte jemand vorschlagen, wie dies in Rails funktionieren könnte?Welche Art von Rails-Modell-Beziehung würde am besten für Aggregatschlüssel + mehrere Fremdschlüssel funktionieren?

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) 

Die obige Skizze auf die folgende Umsetzung übersetzen würde:

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; 

Antwort

11

Sie haben mehrere Möglichkeiten, wie diese in Schienen zu modellieren, aber das erste, was ich vorschlagen würde, ist, dass zu speichern, um Zeit und Ärger später sollten Sie Ihre Frage aus einem anderen Blickwinkel betrachten.

Um das Beste aus Rails herauszuholen, sollten Sie nicht mit einem Datenbankentwurf beginnen. Sie sollten mit einem Datenmodell beginnen und dann die Art und Weise betrachten, wie dieses Datenmodell einer Datenbankstruktur zugeordnet wird und nicht umgekehrt. Dies ist ein subtiler Unterschied, beinhaltet jedoch eine andere Denkweise, bei der Sie Ihre Datenbank als sekundäre Betrachtung Ihres Modells sehen und nicht umgekehrt. Dies wird das Problem auf lange Sicht einfacher verständlich machen.

Es gibt zwei ActiveRecord-Konstrukte, die in diesem Szenario verwendet werden können. Sie sind Single Table-Vererbung und polymorphe Vererbung.

Single Table Inheritance

Single Table Inheritance (STI) speichert Modelle mit viel gemeinsam genutzte Funktionalität innerhalb der gleichen zugrunde liegenden Datenbanktabelle. In Ihrem Beispiel sind Fragen und Antworten und in geringerem Maße Kommentare ähnliche Objekte. Sie haben einen Inhalt, einen Autor, einige Datetime-Felder für created_at und werden aktualisiert usw. Der einzige Unterschied zwischen einer Frage und einer Antwort besteht darin, dass Fragen zu einer Antwort "gehören". Kommentare sind etwas kniffliger, da Sie sowohl Fragen als auch Antworten und vielleicht auch Kommentare kommentieren können, obwohl Ihr Datenbankschema nicht zeigt, dass dies möglich ist.

Mit STI werden Ihre Frage- und Antwortmodelle nicht in einer separaten Tabelle, sondern in einer einzigen Tabelle gespeichert und mit den Klassennamen gekennzeichnet. Die eigentlichen Frage- und Antwortklassen erben dann von der Basisklasse, in Ihrem Fall "Post". Es gibt zahlreiche Ressourcen ou es STI diskutieren aber this one helfen kann

Polymorphe Inheritance

Dies ist das zweite Verfahren zum Modellieren ein ähnliches Verhalten in Schienen. Dies verwendet eine einzelne Tabelle, in Ihrem Fall Beiträge, um die Daten zu speichern, die den beiden Klassen gemeinsam sind. Diese Tabelle enthält Spalten, die auf den Klassennamen und die ID-Instanz des Basisobjekts verweisen. Die für das Objekt spezifischen Daten werden dann in einer separaten Tabelle pro Modell gespeichert.

Implementation (mit STI)

Um Ihre Daten unter Verwendung von STI zu modellieren dann würden Sie dies würde

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 

Ihre Modelle ein Basismodell der Beiträge wie schaffen dann folgendermaßen aussehen

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 

Und einige Beispielcode

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 

Dieser Code erzeugt die folgenden Ergebnisse

 
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! 

in der Datenbank suchen, gibt eine einzelne Beiträge Tabelle mit der folgenden Struktur ist

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

und der Dateninhalt nach dem Beispiel wie folgt: -

 
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 

Sie können es leichter finden, die Kommentare zu teilen Modell in FrageKommentare und AntwortKommentare. Dies würde gerade SQL viel einfacher machen.

+0

Das ist großartig. Danke, dass Sie sich die Zeit genommen haben, eine so umfassende Antwort zu schreiben. Obwohl ich Ihren Vorschlag, STI über einen polymorphen Ansatz zu verwenden, mag, besteht ein Nachteil von STI gegenüber Class Table Inheritance everything2.com/title/... darin, dass Sie einige ungenutzte Spalten in Klassen erhalten, die von Post abgeleitet sind. Einer Frage können beispielsweise Tags zugeordnet sein, und das könnte eine Spalte in der Tabelle "Fragen" sein, unter STI sollte es jedoch eine Spalte in der Tabelle "Beiträge" sein, und Antworten und Kommentare enden mit einer Spalte "Tags". –

+0

... aber da Rails nur STI und polymorphe Assoziationen anbietet, scheint STI die bessere Wahl zu sein, selbst wenn Sie bei diesem Ansatz mit extra unbenutzten Spalten enden. Insgesamt sind Sie jedoch richtig - ich muss das Problem aus Sicht der Rails und nicht aus einer Datenbankperspektive betrachten. Das wird etwas gewöhnungsbedürftig sein, fürchte ich. –

+0

Hallo Charlie, ich werde das kommentieren, aber ich habe gerade viele Stunden draußen getrunken, jetzt ist es nicht an der Zeit. Ich werde morgen Kommentare und/oder eine polymorphe Lösung schreiben. –

0

Ist diese Lösung auch mit einem has_and_belongs_to_many zwischen zum Beispiel:

Sportler und Trainer?

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 
Verwandte Themen