如何验证数组字段的成员?

发布于 2024-11-01 18:49:33 字数 425 浏览 2 评论 0原文

我有这个模型:

class Campaign

  include Mongoid::Document
  include Mongoid::Timestamps

  field :name, :type => String
  field :subdomain, :type => String
  field :intro, :type => String
  field :body, :type => String
  field :emails, :type => Array
end

现在我想验证 emails 数组中的每封电子邮件的格式是否正确。我阅读了 Mongoid 和 ActiveModel::Validations 文档,但没有找到如何执行此操作。

你能给我指点一下吗?

I have this model:

class Campaign

  include Mongoid::Document
  include Mongoid::Timestamps

  field :name, :type => String
  field :subdomain, :type => String
  field :intro, :type => String
  field :body, :type => String
  field :emails, :type => Array
end

Now I want to validate that each email in the emails array is formatted correctly. I read the Mongoid and ActiveModel::Validations documentation but I didn't find how to do this.

Can you show me a pointer?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

晚雾 2024-11-08 18:49:33

您可以定义自定义ArrayValidator。将以下内容放入 app/validators/array_validator.rb 中:

class ArrayValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, values)
    Array(values).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
end

您可以在模型中像这样使用它:

class User
  include Mongoid::Document
  field :tags, Array

  validates :tags, array: { presence: true, inclusion: { in: %w{ ruby rails } }
end

它将根据 每个 验证数组中的每个元素在array 哈希中指定的strong> 验证器。

You can define custom ArrayValidator. Place following in app/validators/array_validator.rb:

class ArrayValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, values)
    Array(values).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
end

You can use it like this in your models:

class User
  include Mongoid::Document
  field :tags, Array

  validates :tags, array: { presence: true, inclusion: { in: %w{ ruby rails } }
end

It will validate each element from the array against every validator specified within array hash.

只为守护你 2024-11-08 18:49:33

Milovan 的答案得到了我的支持,但实现存在一些问题:

  1. 展平嵌套数组会更改行为并隐藏无效值。

  2. nil 字段值被视为 [nil],这似乎不正确。

  3. 所提供的示例,使用 presence: true 将生成 NotImplementedError 错误,因为 PresenceValidator 未实现 validate_each .

  4. 在每次验证时为数组中的每个值实例化一个新的验证器实例效率相当低。

  5. 生成的错误消息不会显示数组元素无效的原因,这会造成较差的用户体验。

    生成

这是一个更新的可枚举和数组验证器,可以解决所有这些问题。为了方便起见,代码包含在下面。

# 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

Milovan's answer got an upvote from me but the implementation has a few problems:

  1. Flattening nested arrays changes behavior and hides invalid values.

  2. nil field values are treated as [nil], which doesn't seem right.

  3. The provided example, with presence: true will generate a NotImplementedError error because PresenceValidator does not implement validate_each.

  4. Instantiating a new validator instance for every value in the array on every validation is rather inefficient.

  5. The generated error messages do not show why element of the array is invalid, which creates a poor user experience.

Here is an updated enumerable and array validator that addresses all these issues. The code is included below for convenience.

# 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
皇甫轩 2024-11-08 18:49:33

您可能想为电子邮件字段定义自己的自定义验证器。

因此,您将在类定义之后添加,

validate :validate_emails

def validate_emails
  invalid_emails = self.emails.map{ |email| email.match(/^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i) }.select{ |e| e != nil }
  errors.add(:emails, 'invalid email address') unless invalid_emails.empty?
end

正则表达式本身可能并不完美,但这是基本思想。您可以按如下方式查看 Rails 指南:

http:// /guides.rubyonrails.org/v2.3.8/activerecord_validations_callbacks.html#creating-custom-validation-methods

You'll probably want to define your own custom validator for the emails field.

So you'll add after your class definition,

validate :validate_emails

def validate_emails
  invalid_emails = self.emails.map{ |email| email.match(/^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i) }.select{ |e| e != nil }
  errors.add(:emails, 'invalid email address') unless invalid_emails.empty?
end

The regex itself may not be perfect, but this is the basic idea. You can check out the rails guide as follows:

http://guides.rubyonrails.org/v2.3.8/activerecord_validations_callbacks.html#creating-custom-validation-methods

雨后咖啡店 2024-11-08 18:49:33

发现自己刚才正在尝试解决这个问题。我稍微修改了 Tim O 的答案,得出以下内容,它为错误对象提供了更清晰的输出和更多信息,然后您可以在视图中向用户显示这些信息。

validate :validate_emails

def validate_emails
  emails.each do |email|
    unless email.match(/^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i)
      errors.add(:emails, "#{email} is not a valid email address.")
    end
  end
end

Found myself trying to solve this problem just now. I've modified Tim O's answer slightly to come up with the following, which provides cleaner output and more information to the errors object that you can then display to the user in the view.

validate :validate_emails

def validate_emails
  emails.each do |email|
    unless email.match(/^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i)
      errors.add(:emails, "#{email} is not a valid email address.")
    end
  end
end
谁对谁错谁最难过 2024-11-08 18:49:33

下面是一个可能对 Rails api 文档有所帮助的示例: http://apidock.com/ Rails/ActiveModel/Validations/ClassMethods/validates

当在给定属性的一次调用中使用自定义验证器和默认验证器时,验证方法的威力就会显现出来,例如

class EmailValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    record.errors[attribute] << (options[:message] || "is not an email") unless
      value =~ /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
  end
end

class Person
  include ActiveModel::Validations
  attr_accessor :name, :email

  validates :name, :presence => true, :uniqueness => true, :length => { :maximum => 100 }
  validates :email, :presence => true, :email => true
end

Here's an example that might help out of the rails api docs: http://apidock.com/rails/ActiveModel/Validations/ClassMethods/validates

The power of the validates method comes when using custom validators and default validators in one call for a given attribute e.g.

class EmailValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    record.errors[attribute] << (options[:message] || "is not an email") unless
      value =~ /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
  end
end

class Person
  include ActiveModel::Validations
  attr_accessor :name, :email

  validates :name, :presence => true, :uniqueness => true, :length => { :maximum => 100 }
  validates :email, :presence => true, :email => true
end
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文