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

rob*_*okl 13 validation activerecord ruby-on-rails single-table-inheritance

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

这是我到目前为止所得到的:

class Budget < ActiveRecord::Base
  validates_presence_of :price
end

class FlatRateBudget < Budget
end

class HourlyRateBudget < Budget
  validates_presence_of :quantity
end
Run Code Online (Sandbox Code Playgroud)

在控制台中,如果我这样做:

b = HourlyRateBudget.new(:price => 10)
b.valid?
=> false
b.errors.full_messages
=> ["Quantity can't be blank"]
Run Code Online (Sandbox Code Playgroud)

正如所料.

问题是STI上的"类型"字段来自params ..所以我需要做类似的事情:

b = Budget.new(:type => "HourlyRateBudget", :price => 10)
b.valid?
=> true
Run Code Online (Sandbox Code Playgroud)

这意味着rails在超类中运行验证,而不是在我设置类型后实例化子类.

我知道这是预期的行为,因为我正在实例化一个不需要数量字段的类,但我想知道是否还有告诉rails运行子类的验证而不是super.

jef*_*unt 9

您可以使用自定义验证器解决此问题,类似于此问题的答案:两个模型,一个STI和一个验证但是,如果您可以简单地实例化预期的子类型,则可以避免需要自定义在这种情况下完全是验证器.

正如您所注意到的那样,单独设置类型字段并不会将实例从一种类型神奇地更改为另一种类型.虽然ActiveRecord将type在从数据库中读取对象时使用该字段来实例化正确的类,但是反过来(实例化超类,然后手动更改类型字段)不具有更改对象类型的效果.应用程序正在运行 - 它只是不起作用.

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


fra*_*zon 6

我做了类似的事情.

使其适应您的问题:

class Budget < ActiveRecord::Base

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

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

end
Run Code Online (Sandbox Code Playgroud)