什么类型的Rails模型关系最适合聚合键+多个外键?

时间:2009-06-13 05:03:40

标签: ruby-on-rails model

我正在考虑以下数据库结构,但我不确定哪种类型的Rails模型关系会支持我定义的数据库密钥。任何人都可以建议这在Rails中如何起作用吗?

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)

上面的草图将转换为以下实现:

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;

2 个答案:

答案 0 :(得分:11)

关于如何在rails中建模,你有几个选择,但我建议的第一件事是,为了节省你自己的时间和麻烦,你应该从不同的角度处理你的问题。

为了从Rails中获得最大收益,您不应该从数据库设计开始。您应该从数据模型开始,然后查看将此数据模型映射到数据库结构的方式,而不是相反。这是一个微妙的差异,但涉及不同的思维模式,您将数据库视为模型的次要考虑因素,而不是相反。从长远来看,这将使问题更容易理解。

在这种情况下可以使用两种ActiveRecord构造,它们是单表继承和多态继承。

单表继承

单表继承(STI)在同一基础数据库表中存储具有大量共享功能的模型。在您的示例问题和答案中,在较小程度上,评论都是类似的对象。他们有一些内容,作者,created_at的一些日期时间字段和更新等。问题和答案之间的唯一区别是问题“属于”答案。评论稍微复杂一些,因为您可以对问题和答案进行评论,也可以对评论进行评论,尽管您的数据库架构并未反映这是可能的。

使用STI,您的问答模型不会存储在单独的表中,而是存储在单个表中,并使用类名进行标记。然后,实际的问答类继承自Base类,在您的情况下为“Post”。有很多资源可以讨论STI,但this one可能有帮助

多态继承

这是在rails中建模类似行为的第二种方法。这使用单个表,在您的案例帖子中存储两个类之间通用的数据。此表包含引用基类对象的类名和id实例的列。然后,特定于对象的数据将存储在每个模型的单独表中。

实施(使用STI)

要使用STI为数据建模,您可以创建像这样的帖子的基本模型

  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  

您的模型将如下所示

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  

一些示例代码

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

此代码产生以下结果

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!

查看数据库,有一个具有以下结构的帖子表

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

示例后的数据内容如下: -

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

您可能会发现将评论模型拆分为QuestionComments和AnswerComments更容易。这将使直接sql更容易。

答案 1 :(得分:0)

这个解决方案是否也可以使用例如:

之间的has_and_belongs_to_many

运动员和运动员?

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