I'm trying to build a simple survey/questionnaire app. Surveys have Questions
; most questions consist of a single content field (the question itself), for which the survey taker will write in a free-text response. (There are also a couple of other fields not relevant to this discussion.) However, users can also create MultipleChoiceQuestions
or LikertQuestions
(e.g., answers on a 1 - 5 scale). (In the case of MultipleChoiceQuestions
, there will be another model called Answer
such that a MultipleChoiceQuestion
has_many Answers
). Here are my design choices, so far as I know:
1) Inherit from Question:
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) Use a module/mixin with the shared attributes and methods:
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
This seems a clear-cut case of inheritance, so I went with option 1. Since then, I can't get it to work. Single Table Inheritance seemed simple enough, so I gave MultipleChoiceQuestion
and LikertQuestion
each type:string
in their schema. Here are the schema for each (from 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
If I implement option 1, above, then MultipleChoiceQuestion and LikertQuestion somehow do not actually include any of their unique fields as specified in schema.rb; instead, they have only the inherited fields from Question. See console output:
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
Somebody on StackOverflow said that Question should be an abstract class. But if I add
self.abstract_class = true
to Question.rb, then I get the following:
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
and MultipleChoiceQuestion
show only their unique fields and do not inherit fields from the parent.
1) What am I missing here? Regardless of whether inheritance is the optimal solution, I must be overlooking something obvious.
2) Should I be using the module approach instead of inheritance? As I mentioned, inheritance seemed like a no-brainer: LikertQuestion
and MultipleChoiceQuestion
really are kinds of Questions
. If I use the module approach, I lose the ability to say things like survey.questions()
, survey.questions.build()
, and presumably other handy stuff. What do Rails hotshots do in this situation? I'll do whatever that is.
No posts on StackOverflow offer a very comprehensive discussion of the pros and cons of subclassing vs. mixin.
Using Ruby 1.9.3 (though thinking of switching to 2.0), Rails 3.2.3.