0

我在我的模型中有一個validates inclusion:,但我認爲「不包含在列表中」的默認錯誤消息完全沒有用處。如何爲validates_inclusion_of顯示允許的選項列表的錯誤消息?

如何讓它顯示錯誤消息中允許的選項列表本身? (例如,"is not one of the allowed options (option 1, option 2, or option 3)"

更具體而言,什麼是最優雅的方式來獲得以下測試通過:?,

describe Person do 
    describe 'validation' do 
    describe 'highest_degree' do 
     # Note: Uses matchers from shoulda gem 
     it { should  allow_value('High School').  for(:highest_degree) } 
     it { should  allow_value('Associates').  for(:highest_degree) } 
     it { should  allow_value('Bachelors').   for(:highest_degree) } 
     it { should  allow_value('Masters').   for(:highest_degree) } 
     it { should  allow_value('Doctorate').   for(:highest_degree) } 
     it { should_not allow_value('Elementary School'). for(:highest_degree).with_message('is not one of the allowed options (High School, Associates, Bachelors, Masters, or Doctorate)') } 
     it { should_not allow_value(nil).     for(:highest_degree).with_message('is required') } 
     it { subject.valid?; subject.errors[:highest_degree].grep(/is not one of/).should be_empty } 
    end 
    end 
end 

給出以下模型:

class Person 
    DegreeOptions = ['High School', 'Associates', 'Bachelors', 'Masters', 'Doctorate'] 
    validates :highest_degree, inclusion: {in: DegreeOptions}, allow_blank: true, presence: true 
end 

這是我在我的配置/ locales/en.yml目前:

en: 
    activerecord: 
    errors: 
     messages: 
     blank: "is required" 
     inclusion: "is not one of the allowed options (%{in})" 

回答

3

這裏有一個custom Validator,可自動提供%{allowed_options}插值的用在你的錯誤消息:

class RestrictToValidator < ActiveModel::EachValidator 
    ErrorMessage = "An object with the method #include? or a proc or lambda is required, " << 
        "and must be supplied as the :allowed_options option of the configuration hash" 

    def initialize(*args) 
    super 
    @allowed_options = options[:allowed_options] 
    end 

    def check_validity! 
    unless [:include?, :call].any?{ |method| options[:allowed_options].respond_to?(method) } 
     raise ArgumentError, ErrorMessage 
    end 
    end 

    def allowed_options(record) 
    @allowed_options.respond_to?(:call) ? @allowed_options.call(record) : @allowed_options 
    end 
    def allowed_options_string(record) 
    allowed_options = allowed_options(record) 
    if allowed_options.is_a?(Range) 
     "#{allowed_options}" 
    else 
     allowed_options.to_sentence(last_word_connector: ', or ') 
    end 
    end 

    def validate_each(record, attribute, value) 
    allowed_options = allowed_options(record) 
    inclusion_method = inclusion_method(allowed_options) 
    unless allowed_options.send(inclusion_method, value) 
     record.errors.add(attribute, :restrict_to, 
         options.except(:in).merge!(
          value: value, 
          allowed_options: allowed_options_string(record) 
         ) 
    ) 
    end 
    end 

private 

    # In Ruby 1.9 <tt>Range#include?</tt> on non-numeric ranges checks all possible values in the 
    # range for equality, so it may be slow for large ranges. The new <tt>Range#cover?</tt> 
    # uses the previous logic of comparing a value with the range endpoints. 
    def inclusion_method(enumerable) 
    enumerable.is_a?(Range) ? :cover? : :include? 
    end 
end 

納入你的config/locales/en。陽明:

en: 
    activerecord: 
    errors: 
     messages: 
     restrict_to: "is not one of the allowed options (%{allowed_options})" 

您可以使用它像這樣:

DegreeOptions = ['High School', 'Associates', 'Bachelors', 'Masters', 'Doctorate'] 
    validates :highest_degree, restrict_to: {allowed_options: DegreeOptions}, 
    allow_blank: true, presence: true 
    # => "highest_degree is not one of the allowed options (High School, Associates, Bachelors, Masters, or Doctorate)" 

或者用範圍:

validates :letter_grade, restrict_to: {allowed_options: 'A'..'F'} 
    # => "letter_grade is not one of the allowed options (A..F)" 

或者是lambda/proc中:

validates :address_state, restrict_to: { 
    allowed_options: ->(person){ Carmen::states(country) 
    } 

評論受歡迎的!你認爲像這樣的東西應該添加到Rails(ActiveModel)核心?

這個驗證器有更好的名字嗎? restrict_to_optionsrestrict_to

+0

我很驚訝沒有人對此評論過。幹得不錯!我認爲這是一段很棒的代碼。 :+1: – 2014-02-11 21:45:16

1

唉!看起來Rails明確地排除(在except(:in)之內):在將參數傳遞給I18n之前我們通過的in選項!

這裏是activemodel的/ lib目錄/ active_model /驗證/ inclusion.rb導軌來源:

class InclusionValidator < EachValidator 
    def validate_each(record, attribute, value) 
    delimiter = options[:in] 
    exclusions = delimiter.respond_to?(:call) ? delimiter.call(record) : delimiter 
    unless exclusions.send(inclusion_method(exclusions), value) 
     record.errors.add(attribute, :inclusion, options.except(:in).merge!(:value => value)) 
    end 
    end 
end 

爲什麼會這樣?

不是說將原始數組內插到錯誤消息中是非常有用的。我們需要的是字符串 param(由數組構建),我們可以直接插入。

我設法測試通過時,我改變了validates這樣:

validates :highest_degree, inclusion: { 
    in:    DegreeOptions, 
    allowed_options: DegreeOptions.to_sentence(last_word_connector: ', or ')} 
    }, allow_blank: true, presence: true 

和改變en.yml這樣:

 inclusion: "is not one of the allowed options (%{allowed_options})" 

但有通過傳遞DegreeOptions的醜陋兩個不同的散列鍵。

我認爲驗證器本身應該爲我們構建該密鑰(並將它傳遞給I18n以插入消息中)。

那麼我們有什麼選擇?創建自定義驗證,猴補丁現有InclusionValidator,或提交補丁到Rails的團隊...