在我的Rails应用程序中,我正在实现类似调查的表单,允许用户填写动态可更改的问题集的答案。目前,我计划支持三种不同的“类型”问题:是/否,评级(1-5级)和文本。对于每种问题类型,我需要对相应的答案模型略有不同的行为(以适应不同的方法,验证要求等)。我目前正在尝试使用单表继承实现这些不同的行为,如下所示:
class Question < ActiveRecord::Base
validates_presence_of :question_type
# ...
end
class Answer < ActiveRecord::Base
belongs_to :question
validates_presence_of :answer
# ...
end
class YesNoAnswer < Answer
validates :answer, inclusion: {in: %w(Yes No N/A)}
# ...
end
class RatingAnswer < Answer
validates :answer, numericality: { only_integer: true }, inclusion: {in: 1..5}
def answer
self[:answer].to_i
end
# ...
end
class TextAnswer < Answer
validates :answer, length: { minimum: 2 }
# ...
end
问题在于,对于单表继承,所选类通常由数据库中每个记录的字段确定。 (默认情况下,此字段为"type"
,但您可以通过设置inheritance_column
来更改此字段。
在我的情况下,答案的类型应始终与其相应问题的类型相匹配,因此需要管理额外的数据库字段,如此笨拙和冗余。因此,我不想严格依赖数据库中的内容,而是想以编程方式确定应该为给定记录使用哪个类。 E.g:
class Answer < ActiveRecord::Base
# I want a value on the associated question to determine this record's type.
# Simply defining a type method as shown here doesn't work though.
def type
question.try(:question_type)
end
end
这可能吗?如果是这样,怎么样?
答案 0 :(得分:3)
inheritance_column
不确定类,它存储type
列的名称,然后确定用于给定记录的类。
因此,通过更改inheritance_column
值,您可以存储通常存储在另一列type
列中的内容。
这无法帮助您动态确定类名存储的位置,因为它只指向另一列,然后通过该列中的值确定Class。
另请参阅:http://apidock.com/rails/ActiveRecord/ModelSchema/ClassMethods/inheritance_column
单表继承(STI)特别适用于您希望保留模型的情况,这些模型有些相似,但根据类型保留不同的属性。 换句话说:除非你想在模型中保留不同的东西,否则你不应该使用STI。
如果您只是想要根据问题的类型采取不同的行为,您可以这样做:
根据问题的类型,通过在必要的Ruby模块上调用extend
,使用您喜欢的行为扩展Answer实例:
class Question < ActiveRecord::Base
validates_presence_of :kind
has_many :answers
# ... contains an attribute 'kind' to determine the kind of question
end
class Answer < ActiveRecord::Base
belongs_to :question
validates_presence_of :answer
# when a new instance of Answer is created, it automatically extends itself's
# behavior from the given Ruby Module, which is conveniently stored as a Rails Concern
def initialize
case question.kind
when 'yes_no'
self.class.send(:extend, YesNo)
when 'rating'
self.class.send(:extend, Rating)
when 'text'
self.class.send(:extend, Text)
else
# ...
end
end
end
并且在./app/models/concerns
中,您有不同的文件,其中包含根据问题类型定义要添加到Answer类的行为的模块:
# file concerns/yes_no.rb
module YesNo
validates :answer, inclusion: {in: %w(Yes No N/A)}
end
# file concerns/rating.rb
module Rating
validates :answer, numericality: { only_integer: true }, inclusion: {in: 1..5}
def answer
self[:answer].to_i
end
# ...
end
# file concerns/text.rb
module Text
validates :answer, length: { minimum: 2 }
end
为了更好地美化代码,您可以将这些答案行为放入子目录&#39; ./ concerns / answer_behavior /&#39;中,并将它们包装在模块&#39; AnswerBehavior&#39; ;
答案 1 :(得分:2)
这是single table inheritance in Rails
的绝佳资源<强> STI 强>
根据您的要求,您似乎需要考虑几件事情,从STI的工作方式开始
STI(单表继承)是一种应用程序从单个表中提取数据的方法,同时使用不同的“类”来区分这些数据。描述为什么这样做有效的最好方法是给你降低Rails的object orientated性质:
你在Rails中所做的一切都需要基于对象。这些是根据您的模型(从数据库中获取数据)构建的。 STI关系的绘制是您将能够构建围绕所需的特定数据类型的对象。
例如:
#app/models/answer.rb
Class Answer < ActiveRecord::Base
#fields id | type | question_id | other | attributes | created_at | updated_at
belongs_to :question
end
#app/models/question.rb
Class Question < ActiveRecord::Base
has_many :answers
end
#app/models/yes_no.rb
Class YesNo < Answer
... custom methods here ...
end
-
<强>用法强>
直接参考您的问题 - 您遇到的问题是您没有正确使用基于STI的模型。他们 应该像其他模型一样工作,除了从单个表中绘制数据(因此他们的名字):
#app/controllers/questionnaires_controller.rb
Class QuestionnairesController < ApplicationController
def create
@question = YesNo.new(questionnaire_params)
@question.save
end
end
只要您使用模型中的文件/设置使其按要求运行,调用模型的type
就不会有任何影响。
-
在我的情况下,我不想严格依赖数据库中的内容,而是想以编程方式确定应该为给定记录使用哪个类。这可能吗?如果是这样,怎么样?
如果这是你想要实现的功能,我认为你正在使用STI的错误。我可能是错的 - 但你肯定想要预先调用模型(如上所示),允许你按照自己的意愿操纵它们的对象?
<强>更新强>
根据您所写的内容,可以更好地仅使用Answer
模型,设置特定属性以确定最初发送请求的问题:
#app/models/answer.rb
Class Answer < ActiveRecord::Base
before_create :set_question_type
private
def set_question_type
self.question_type == "yes/no" unless question_type.present?
end
end
这将允许您根据需要设置问题类型