12

在我的應用程序中,我有一個名爲Budget的類。預算可以有多種類型。例如,假設有兩個預算:FlatRateBudget和HourlyRateBudget。兩者都從類Budget中繼承。如何在單表繼承中運行子類的驗證?

這是我得到迄今:

class Budget < ActiveRecord::Base 
    validates_presence_of :price 
end 

class FlatRateBudget < Budget 
end 

class HourlyRateBudget < Budget 
    validates_presence_of :quantity 
end 

在控制檯,如果我這樣做:

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

爲,預計。

的問題是,在「類型」字段,在STI,來自PARAMS ..所以我需要做的是這樣的:

b = Budget.new(:type => "HourlyRateBudget", :price => 10) 
b.valid? 
=> true 

這意味着軌道運行的驗證在超一流的,而不是在設置類型後實例化子類。

我知道這是預期的行爲,因爲我正在實例化一個不需要數量字段的類,但是我不知道是否有反正告訴rails爲子類而不是超級運行驗證。

+0

當使用STI,我會避免實例從對象超類,並且只能與基類一起工作,我相信Rails將使用超類和子類獨有的適當驗證。 – firecape 2015-05-27 04:29:24

回答

9

你可以用自定義驗證器來解決這個問題,類似於這個問題的答案:Two models, one STI and a Validation但是,如果你可以簡單地實例化預定的子類型,那麼你就可以避免需要一個自定義驗證器這個案例。

正如您已經注意到的那樣,單獨設置類型字段並不會奇怪地將實例從一種類型更改爲另一種類型。雖然ActiveRecord將使用type字段來實例化適當的類,當從數據庫讀取對象時,以相反方式(實例化超類,然後手動更改類型字段)不會改變對象的效果在你的應用程序運行時鍵入 - 它不會以這種方式工作。

定製驗證方法,在另一方面,可獨立地檢查type字段,實例化適當的類型(基於type字段的值)的一個副本,然後在該對象上運行.valid?,導致即使在流程中實際創建了適當子類的實例,子類的驗證仍然是動態的。

+0

我用你的最後一段提示,它的工作。 – robertokl 2012-02-10 17:09:42

2

而不是設置類型的直接設置這樣的類型......相反,嘗試:

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,否則它只是預算。

+0

這是一種方法,但我想使用accept_nested_attributes魔術來保持我的控制器儘可能薄。 – robertokl 2012-02-10 16:44:27

+0

您絕對可以仍然這樣做。訣竅是讓你的字符串類型到一個類。 – 2012-02-10 17:53:33

3

爲尋找示例代碼,這裏就是我實現了第一個答案:

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 
+0

它對我產生這個錯誤:NoMethodError(未定義的方法'類型'爲 – Antzi 2013-04-22 14:39:36

+1

您的模型有一個「類型」字段嗎? – Bryce 2013-04-22 17:25:27

0

更重要的是,使用type.constantize.new("10"),但是這取決於從PARAMS類型必須是正確的字符串相同HourlyRateBudget.class.to_s

+0

這個答案是我要推薦的,但是你想要白名單什麼是可以接受的,所以有人不會'T傳遞一個惡意'type' 在'before_action'你可以這樣做: '呈現狀態:禁止,除非type.constantize.in?([HourlyRateBudget,FlatRateBudget])' – mackshkatz 2016-12-09 01:19:49

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 
+0

這ISN'沒有得到任何upvotes ...但它在我看來(作爲一個鐵軌noob)作爲一個非常railvil解決方案。這是否工作,這仍然是你的建議解決這個問題的方式嗎? – 2015-09-29 15:06:17

+0

很簡單的答案!你可以使用自己.type =='HourlyRateBudget'' – hackhowtofaq 2015-12-03 10:20:29

0

我也需要相同的,並與布萊斯回答的幫助下,我這樣做:

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