我正在尝试构建一个简单的调查/问卷调查应用程序。调查有Questions
;大多数问题由单个内容字段(问题本身)组成,调查接受者将在自由文本响应中写入。 (还有一些与此讨论无关的其他字段。)但是,用户还可以创建MultipleChoiceQuestions
或LikertQuestions
(例如,以1-5的比例回答)。 (如果是MultipleChoiceQuestions
,则会有另一个名为Answer
的模型,以便MultipleChoiceQuestion
has_many Answers
)。据我所知,这是我的设计选择:
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.从那以后,我无法让它工作。单表继承似乎很简单,因此我在其架构中为每个MultipleChoiceQuestion
提供了LikertQuestion
和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上有人说问题应该是一个抽象类。但如果我补充一下
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
LikertQuestion
和MultipleChoiceQuestion
仅显示 其唯一字段,并且不会从父级继承字段。
1)我在这里缺少什么?无论继承是否是最佳解决方案,我必须忽略一些明显的东西。
2)我应该使用模块方法而不是继承吗?正如我所提到的,继承看起来很简单:LikertQuestion
和MultipleChoiceQuestion
实际上是Questions
种。如果我使用模块方法,我就无法说出像survey.questions()
,survey.questions.build()
这样的东西,并且可能是其他方便的东西。在这种情况下,Rails的热点是做什么的?我会做任何事情。
StackOverflow上的帖子没有提供关于子类与mixin的优缺点的全面讨论。
使用Ruby 1.9.3(虽然考虑切换到2.0),Rails 3.2.3。
答案 0 :(得分:3)
你确实遗漏了一些明显的东西。你知道STI代表什么吗? 单表继承。您正在制作几个表,然后尝试使用STI。
如果您的表格相同或非常相似(可能是1个不同的字段),您应该只使用STI。它主要用于您想要子类化,然后提供区分行为的方法。例如,也许所有用户共享相同的属性,但其中一些属于管理员。您可以在users表中添加type
字段,然后可能会出现以下内容:
class Admin < User
def admin?
true
end
end
class NormalUser < User
def admin?
false
end
end
(这显然是一个非常简单的例子,并且可能不会保证它自己的STI。)
就抽象类而言,如果你有几个表都应该从超类继承行为,这是一个很好的决定。看起来你的情况可能有意义;但是,重要的是要注意抽象类没有表。将abstract_class
声明为真的重点是,在尝试查找不存在的表时,ActiveRecord不会感到困惑。没有它,ActiveRecord将假设您正在使用STI并尝试查找问题表。在您的情况下,您确实有一个问题表,因此将其声明为抽象类并没有多大意义。
另外一件事,你问“我应该使用模块方法而不是继承吗?”。使用模块实际上是Ruby中的一种继承形式。当你包含一个模块时,它就像超类一样被插入到祖先链中的类中(然而,模块是在超类之前插入的)。我确实认为某种形式的继承是正确的方法。在这种情况下,因为它们都是问题的类型,所以制作一个抽象的问题超类对我来说是有意义的。因为这些问题没有共享许多属性,所以在我看来,将它们存储在单独的表中是最好的解决方案。当你有几个不同的字段时,STI并不是一个好的做法,因为它会在你的数据库中导致很多null
。
为了清楚模块,我认为最好在几个不相关的模型共享某种形式的共同行为时完成。我多次使用的一个例子是Commentable
模块的概念(使用ActiveSupport :: Concern)。仅仅因为可以评论几个模型并不一定能保证超类,因为模型不相关 - 它们并不真正来自某种父对象。这是模块有意义的地方。在您的情况下,超类是有意义的,因为您的两个模型都是问题类型,因此它们都来自通用Question
基类似乎是合适的。