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的团队...