0

我正在尝试构建一个简单的调查/问卷调查应用程序。调查有Questions;大多数问题由一个内容字段(问题本身)组成,调查人员将在自由文本回复中对此进行撰写。(还有几个其他领域与本次讨论无关。)但是,用户也可以创建MultipleChoiceQuestionsLikertQuestions(例如,1 - 5 级的答案)。(在 的情况下,将MultipleChoiceQuestions有另一个模型称为has_many )。据我所知,这是我的设计选择:AnswerMultipleChoiceQuestionAnswers

1)从问题继承:

class Question < ActiveRecord::Base
   attr_accessible :id, :content
end

class MultipleChoiceQuestion < Question
   attr_accessible :type
end

class LikertQuestion < Question
   attr_accessible :type, :min, :max, :label_min, label_max
end

2)使用具有共享属性和方法的模块/mixin:

module Question
  @content, @id
  def method1
  end
end

class MultipleChoiceQuestion < ActiveRecord::Base
  include Question
end

class LikertQuestion < ActiveRecord::Base
  include Question
  attr_accessible :type, :min, :max, :label_min, label_max
end

这似乎是一个明确的继承案例,所以我选择了选项 1。从那以后,我无法让它工作。单表继承似乎很简单,所以我在他们的模式中给出MultipleChoiceQuestionLikertQuestion每个。type:string以下是每个的架构(来自 db/schema.rb):

  create_table "questions", :force => true do |t|
    t.integer  "parent"
    t.string   "type"
    t.string   "content"
    t.datetime "created_at", :null => false
    t.datetime "updated_at", :null => false
    t.integer  "survey_id"
  end

 create_table "multiple_choice_questions", :force => true do |t|
    t.datetime "created_at", :null => false
    t.datetime "updated_at", :null => false
    t.string   "type"
  end

  create_table "likert_questions", :force => true do |t|
    t.integer  "min"
    t.integer  "max"
    t.string   "label_min"
    t.string   "label_max"
    t.datetime "created_at", :null => false
    t.datetime "updated_at", :null => false
    t.string   "type"
  end

如果我实现上面的选项 1,那么 MultipleChoiceQuestion 和 LikertQuestion 实际上并不包含 schema.rb 中指定的任何唯一字段;相反,它们只有从 Question 继承的字段。查看控制台输出:

1.9.3p392 :001 > Question
 => Question(id: integer, parent: integer, content: string, created_at: datetime, updated_at: datetime, survey_id: integer)
1.9.3p392 :002 > LikertQuestion
 => LikertQuestion(id: integer, parent: integer, content: string, created_at: datetime, updated_at: datetime, survey_id: integer)
1.9.3p392 :003 > MultipleChoiceQuestion
 => MultipleChoiceQuestion(id: integer, parent: integer, content: string, created_at: datetime, updated_at: datetime, survey_id: integer)
1.9.3p392 :004 > LikertQuestion.new(:min => 3)
ActiveRecord::UnknownAttributeError: unknown attribute: min

StackOverflow 上有人说 Question 应该是一个抽象类。但是如果我添加 self.abstract_class = true到 Question.rb,那么我会得到以下信息:

1.9.3p392 :001 > Question
 => Question(abstract)
1.9.3p392 :002 > LikertQuestion
 => LikertQuestion(id: integer, min: integer, max: integer, label_min: string, label_mid: string, label_max: string, created_at: datetime, updated_at: datetime, type: string)
1.9.3p392 :003 > MultipleChoiceQuestion
 => MultipleChoiceQuestion(id: integer, created_at: datetime, updated_at: datetime, type: string)
1.9.3p392 :004 > LikertQuestion.new(:content => "foo")
ActiveRecord::UnknownAttributeError: unknown attribute: content

LikertQuestionMultipleChoiceQuestion显示其唯一字段,并且不从父级继承字段。

1)我在这里错过了什么?不管继承是否是最佳解决方案,我都必须忽略一些显而易见的事情。

2)我应该使用模块方法而不是继承吗?正如我所提到的,继承似乎是一件轻而易举的事:LikertQuestion而且MultipleChoiceQuestion确实是Questions. 如果我使用模块方法,我将失去说出诸如survey.questions(),之类的东西的能力survey.questions.build(),并且可能还有其他方便的东西。在这种情况下,Rails 能手会做什么?我会做任何事情。

StackOverflow 上没有任何帖子对子类化与 mixin 的优缺点进行非常全面的讨论。

使用 Ruby 1.9.3(虽然考虑切换到 2.0),Rails 3.2.3。

4

1 回答 1

3

你确实遗漏了一些明显的东西。你知道 STI 代表什么吗?单表继承。您正在制作几张桌子,然后尝试使用 STI。

如果您的表相同或非常相似(可能有 1 个差异字段),您应该只使用 STI。它主要用于当您想要子类化然后提供区分行为的方法时。例如,也许所有用户共享相同的属性,但其中一些是管理员。你可以type在你的用户表中有一个字段,然后你可能会有这样的东西:

class Admin < User
  def admin?
    true
  end
end

class NormalUser < User
  def admin?
    false
  end
end

(这显然是一个非常简单的例子,可能不保证 STI 本身)。

就抽象类而言,如果您有多个表都应该从超类继承行为,那么这是一个不错的决定。在您的情况下,这似乎是有道理的;但是,重要的是要注意抽象类没有表。声明abstract_class为 true 的全部意义在于 ActiveRecord 在尝试查找不存在的表时不会感到困惑。没有它,ActiveRecord 将假定您正在使用 STI 并尝试查找问题表。在您的情况下,您确实有一个问题表,因此将其声明为抽象类并没有真正意义。

另一件事,您问“我应该使用模块方法而不是继承吗?”。使用模块实际上是 Ruby 中的一种继承形式。当您包含一个模块时,它会像超类一样插入到类祖先链中(但是,模块是在超类之前插入的)。我确实认为某种形式的继承是正确的方法。在这种情况下,因为它们都是类型的问题,所以创建一个抽象的 Question 超类对我来说是有意义的。因为这些问题的许多属性并不相同,所以我认为将它们存储在单独的表中是最好的解决方案。null当您有多个不同的字段时,STI 并不是一个真正的好习惯,因为它会在您的数据库中导致很多。

为了明确模块,我认为最好在几个原本不相关的模型共享某种形式的共同行为时完成。我多次使用的一个例子是Commentable模块的概念(使用 ActiveSupport::Concern)。仅仅因为可以评论多个模型并不一定保证超类,因为模型不相关 - 它们并不真正来自某种父对象。这就是模块有意义的地方。在您的情况下,超类是有意义的,因为您的两个模型都是问题类型,因此它们都来自通用Question基类似乎是合适的。

于 2013-05-11T20:22:49.243 回答