Ruby on rails 如何在单表继承中运行子类的验证?

Ruby on rails 如何在单表继承中运行子类的验证?,ruby-on-rails,validation,activerecord,single-table-inheritance,Ruby On Rails,Validation,Activerecord,Single Table Inheritance,在我的应用程序中,我有一个名为Budget的类。预算可以有多种类型。。例如,假设有两个预算:FlatRateBudget和HourlyRateBudget。两者都继承自类预算 到目前为止,我得到的是: class Budget < ActiveRecord::Base validates_presence_of :price end class FlatRateBudget < Budget end class HourlyRateBudget < Budget v

在我的应用程序中,我有一个名为Budget的类。预算可以有多种类型。。例如,假设有两个预算:FlatRateBudget和HourlyRateBudget。两者都继承自类预算

到目前为止,我得到的是:

class Budget < ActiveRecord::Base
  validates_presence_of :price
end

class FlatRateBudget < Budget
end

class HourlyRateBudget < Budget
  validates_presence_of :quantity
end
正如所料

问题是STI上的“type”字段来自params。。所以我需要做一些类似的事情:

b = Budget.new(:type => "HourlyRateBudget", :price => 10)
b.valid?
=> true
这意味着rails在超级类中运行验证,而不是在我设置类型之后实例化子类


我知道这是预期的行为,因为我正在实例化一个不需要quantity字段的类,但我想知道是否有必要告诉rails为子类而不是超级类运行验证。

您可能可以使用自定义验证程序来解决这个问题,类似于这个问题的答案:然而,如果您可以简单地实例化想要的子类型,那么在这种情况下,您将完全不需要定制验证器

正如您所注意到的,单独设置type字段并不会神奇地将实例从一种类型更改为另一种类型。而ActiveRecord将使用
type
字段在从数据库中读取对象时实例化适当的类,反过来执行(实例化超类,然后手动更改type字段)不会在应用程序运行时更改对象的类型,只是无法以这种方式工作


另一方面,自定义验证方法可以独立检查
类型
字段,实例化适当类型的副本(基于
类型
字段的值),然后在该对象上运行
.valid?
,从而使子类上的验证以一种动态的方式运行,即使它实际上是在过程中创建相应子类的实例。

而不是直接设置类型,而是像这样设置类型。。。相反,请尝试:

new_type = params.fetch(:type)
class_type = case new_type
  when "HourlyRateBudget"
    HourlyRateBudget
  when "FlatRateBudget"
    FlatRateBudget
  else
    raise StandardError.new "unknown budget type: #{new_type}"
end
class_type.new(:price => 10)
您甚至可以通过以下方式将字符串转换为其类:
new_type.classify.constantize
但如果它来自params,那似乎有点危险


如果您这样做,那么您将得到一个HourlyRateBudget类,否则它将是Budget。

对于任何想要查看示例代码的人,下面是我如何实现第一个答案的:

validate :subclass_validations

def subclass_validations
  # Typecast into subclass to check those validations
  if self.class.descends_from_active_record?
    subclass = self.becomes(self.type.classify.constantize)
    self.errors.add(:base, "subclass validations are failing.") unless subclass.valid?
  end
end

更好的方法是使用
type.constantize.new(“10”)
,但是这取决于params中的类型必须是与
hourlyratebudent.class.to_s
相同的正确字符串,我也做了类似的事情

使其适应您的问题:

class Budget < ActiveRecord::Base

    validates_presence_of :price
    validates_presence_of :quantity, if: :hourly_rate?

    def hourly_rate?
        self.class.name == 'HourlyRateBudget'
    end

end
class预算
我也需要同样的答案,在布莱斯的帮助下,我做到了:

class  ActiveRecord::Base
  validate :subclass_validations, :if => Proc.new{ is_sti_supported_table? }

  def is_sti_supported_table?
  self.class.columns_hash.include? (self.class.inheritance_column)
  end

  def subclass_validations
      subclass = self.class.send(:compute_type, self.type)
      unless subclass == self.class
        subclass_obj= self.becomes(subclass)
        self.errors.add(:base, subclass_obj.errors.full_messages.join(', ')) unless subclass_obj.valid?
      end
  end
end

按照@franzlorenzon的回答,但使用duck类型避免在超类中引用类类型:

class Budget < ActiveRecord::Base
  validates_presence_of :price
  validates_presence_of :quantity, if: :hourly_rate?

  def hourly_rate?
    false
  end
end

class HourlyRateBudget < Budget
  def hourly_rate?
    true
  end
end
class预算
这是一种方法,但我想使用accept\u nested\u attributes magic使我的控制器尽可能薄。您仍然可以这样做。诀窍是把你的类型字符串放到一个类中。它为我产生了这样的错误:NoMethodError(你的模型是否有一个“类型”的未定义的方法'type'字段?当使用STI时,我会避免从超类实例化对象,而只使用基类,我相信Rails会使用来自超类和子类特有的适当验证。这不会得到任何支持…但在我看来(作为Rails noob)这是一个非常讽刺的解决方案。这有效吗?这仍然是你建议解决这个问题的方式吗?非常简单的答案!你也可以使用
self.type=='hourlyratebeadget'
。这个答案是我要推荐的。但是,你想把可以接受的东西列为白名单,这样就不会有人以恶意的方式传入
type
<代码>在执行操作之前可以执行以下操作:
呈现状态::禁止,除非键入.constantize.in.([HourlyRateBudget,FlatRateBudget])
class Budget < ActiveRecord::Base
  validates_presence_of :price
  validates_presence_of :quantity, if: :hourly_rate?

  def hourly_rate?
    false
  end
end

class HourlyRateBudget < Budget
  def hourly_rate?
    true
  end
end