-
-
Save ssimeonov/6519423 to your computer and use it in GitHub Desktop.
| # Syntax sugar | |
| class ArrayValidator < EnumValidator | |
| end |
| # 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] || 'is invalid') | |
| 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 |
using it like that:
validates :artforms, enum: { format: { with: /\A[A-Z 0-9a-z-\u00C0-\u017F]+\z/ } }, allow_blank: true
I get
ArgumentError (The provided regular expression is using multiline anchors (^ or $), which may present a security risk. Did you mean to use \A and \z, or forgot to add the :multiline => true option?):
app/validators/enum_validator.rb:74:in new' app/validators/enum_validator.rb:74:increate_validator'
app/validators/enum_validator.rb:14:in block in initialize' app/validators/enum_validator.rb:13:ineach'
app/validators/enum_validator.rb:13:in map' app/validators/enum_validator.rb:13:ininitialize'
app/models/artwork.rb:51:in <class:Artwork>' app/models/artwork.rb:1:in<top (required)>'
app/controllers/api/v1/artworks_controller.rb:32:in `index'
I must say I don't really understand your gist right now tough, can you help me?
Thx @monfresh! This solves the problem on rails >= 4.1
Using the validator with ":on" or ":if" fails!!! Any idea?
Here an example:
validates :color, presence: true, array: { inclusion: { in: ['red', 'green'] } }, on: :update, if: :some_methodThis code works for me without using :if and :on (Rails >= 4.1):
class ArrayValidator < ActiveModel::EachValidator
def validate_each(record, attribute, values)
[values].flatten.each do |value|
options.each do |key, args|
validator_options = { attributes: attribute }
validator_options.merge!(args) if args.is_a?(Hash)
next if value.nil? && validator_options[:allow_nil]
next if value.blank? && validator_options[:allow_blank]
validator_class_name = "#{key.to_s.camelize}Validator"
validator_class = begin
validator_class_name.constantize
rescue NameError
"ActiveModel::Validations::#{validator_class_name}".constantize
end
validator = validator_class.new(validator_options)
validator.validate_each(record, attribute, value)
end
end
end
endusing:
validates :color, presence: true, array: { inclusion: { in: ['red', 'green'] } } #, on: :update, if: :some_method
# or
validates :color, array: { presence: true, inclusion: { in: ['red', 'green'] } } #, on: :update, if: :some_method Rails (4.1.7) slices default keys from options: https://github.com/rails/rails/blob/44e6d91a07c26cfd7d7b5360cc9a8184b0692859/activemodel/lib/active_model/validations/validates.rb#L106
class ArrayValidator < ActiveModel::EachValidator
def validate_each(record, attribute, values)
[values].flatten.each do |value|
options.except(:if, :unless, :on, :strict).each do |key, args|
validator_options = { attributes: attribute }
validator_options.merge!(args) if args.is_a?(Hash)
next if value.nil? && validator_options[:allow_nil]
next if value.blank? && validator_options[:allow_blank]
validator_class_name = "#{key.to_s.camelize}Validator"
validator_class = begin
validator_class_name.constantize
rescue NameError
"ActiveModel::Validations::#{validator_class_name}".constantize
end
validator = validator_class.new(validator_options)
validator.validate_each(record, attribute, value)
end
end
end
endAnd here the code for this gist:
def initialize(options)
super
@validators = options.except(:class, :if, :unless, :on, :strict).map do |(key, args)|
create_validator(key, args)
end
end
In Rails 4.1, the
optionsnow include a new:classkey (https://github.com/rails/rails/blob/7d84c3a2f7ede0e8d04540e9c0640de7378e9b3a/activemodel/lib/active_model/validator.rb#L85-94), so this will break with this error:uninitialized constant ActiveModel::Validations::ClassValidator.To fix, remove that key from the options in the
initializemethod: