Как мне сделать сообщение об ошибке для validates_inclusion_of показать список разрешенных опций? - PullRequest
0 голосов
/ 16 декабря 2011

У меня есть validates inclusion: в моей модели, но я думаю, что сообщение об ошибке по умолчанию «не включено в список» совершенно бесполезно.

Как мне сделать так, чтобы отображался список разрешенных опцийв самом сообщении об ошибке?(например, "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})"

Ответы [ 2 ]

3 голосов
/ 16 декабря 2011

Вот пользовательский валидатор , который автоматически предоставляет интерполяционную переменную %{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.yml:

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)"

Или с лямбда / процесс:

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

Комментарии приветствуются!Как вы думаете, что-то подобное должно быть добавлено в ядро ​​Rails (ActiveModel)?

Есть ли лучшее имя для этого валидатора?restrict_to_options?restrict_to

1 голос
/ 16 декабря 2011

Ха! Похоже, что Rails явно исключает (с except(:in)) параметр: in, который мы передаем перед передачей параметров в I18n!

Вот источник рельсов из activemodel / lib / active_model / validations /lusion.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

Почему он это делает?

Не то чтобы было бы очень полезно в любом случае вставить необработанный массив в сообщение об ошибке. Нам нужен параметр string (построенный из массива), который мы можем интерполировать напрямую.

Мне удалось пройти тесты, когда я изменил 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 для интерполяции в сообщение).

Так, каковы наши варианты? Создайте пользовательский Validator, добавьте исправление обезьяны к существующему InclusionValidator или отправьте исправление команде Rails ...

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...