0

I found the following code at How do I validate members of an array field? .

# Validates the values of an Enumerable with other validators.
# Generates error messages that include the index and value of
# invalid elements.
#
# Example:
#
#   validates :values, enum: { presence: true, inclusion: { in: %w{ big small } } }
#
class EnumValidator < ActiveModel::EachValidator

  def initialize(options)
    super
    @validators = options.map do |(key, args)|
      create_validator(key, args)
    end
  end

  def validate_each(record, attribute, values)
    helper = Helper.new(@validators, record, attribute)
    Array.wrap(values).each do |value|
      helper.validate(value)
    end
  end

  private

  class Helper

    def initialize(validators, record, attribute)
      @validators = validators
      @record = record
      @attribute = attribute
      @count = -1
    end

    def validate(value)
      @count += 1
      @validators.each do |validator|
        next if value.nil? && validator.options[:allow_nil]
        next if value.blank? && validator.options[:allow_blank]
        validate_with(validator, value)
      end
    end

    def validate_with(validator, value)
      before_errors = error_count
      run_validator(validator, value)
      if error_count > before_errors
        prefix = "element #{@count} (#{value}) "
        (before_errors...error_count).each do |pos|
          error_messages[pos] = prefix + error_messages[pos]
        end
      end
    end

    def run_validator(validator, value)
      validator.validate_each(@record, @attribute, value)
    rescue NotImplementedError
      validator.validate(@record)
    end

    def error_messages
      @record.errors.messages[@attribute]
    end

    def error_count
      error_messages ? error_messages.length : 0
    end
  end

  def create_validator(key, args)
    opts = {attributes: attributes}
    opts.merge!(args) if args.kind_of?(Hash)
    validator_class(key).new(opts).tap do |validator|
      validator.check_validity!
    end
  end

  def validator_class(key)
    validator_class_name = "#{key.to_s.camelize}Validator"
    validator_class_name.constantize
  rescue NameError
    "ActiveModel::Validations::#{validator_class_name}".constantize
  end
end

It is the only way that works i have found of validating a rails form input that is expecting an array. The problem is that the error message for invalid entries is not in a clean flash message but the typical rails error page:

Error message for invalid entry

For example, my form has a multiple select input field in which the user can enter several tags from a list. If the user enters tags that are not from the list, i want the validation to occur and for the user to be told that they must pick an item from the list. How do I change the error message into a clean flash message?

Community
  • 1
  • 1
Philip7899
  • 4,599
  • 4
  • 55
  • 114
  • It looks to me like the validation is raising an error instead of just adding to the error messages stack. Have you tried using a begin/rescue/end block around the `update_attributes` block, then in the rescue section, populate your flash message and render whatever page they posted from? – nbrew Nov 11 '13 at 19:03
  • Awesome, that worked except it is still printing a prewritten error. For example, if i include an invalid entry, it says "element 0 (rails) is not included in the list". Instead, I want to make it say "Please limit your tags to those on the list". Any idea how to change that? – Philip7899 Nov 11 '13 at 19:34
  • Jut figured it out. Thanks for your help! – Philip7899 Nov 11 '13 at 19:39
  • Actually, there is still a problem. When it renders the edit page upon a failure, the input field is no longer a select box but just a text input box. – Philip7899 Nov 11 '13 at 20:59

1 Answers1

0

It appears that the validator is raising an error instead of simply adding error messages to the errors stack. I'd recommend wrapping the update_attributes block in abegin/rescue/end` block.

For changing the message, you can take two routes:

  1. You could customize the EnumValidator to use keys from your locale file so that each entry might say something like the "element %{count} is not in the list". You should be able to customize activerecord.errors.messages.inclusion for the "is not in the list" portion of the message. For the "element 0 (rails)" portion, you could either hard-code the phrase, as is currently implemented (not recommended) or make use of the locale file again by adding a key for say, enum_element, then in your EnumValidator, call I18n.translate(:'activerecord.errors.messages.enum_element', count: @count, value: value).

  2. After validation, check errors.keys for for issues on your enumerable column. If you find errors, remove those keys and add your own message for that key.

As for your form, it sounds like you're using a dynamic form builder. If it's SimpleForm, you could try specifying the exact type the input should be with the as: :select option.

nbrew
  • 771
  • 5
  • 6