首页 > 解决方案 > 如何加载不直接从 Rails 5 中的 Rails 验证器类继承的自定义验证器?

问题描述

我正在为我一直从事的 Rails 5 项目编写一些自定义验证器。例如:

class EmailValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    if value.present? && !/\A[^\p{Z}]+@[^\p{Z}]+\.[^\p{Z}]+\z/.match?(value)
      record.errors.add(attribute, "must be an email")
    end
  end
end

这种方法效果很好,但是我的许多自定义验证器都在类似地检查正则表达式匹配,所以我决定创建一个抽象RegexValidator类:

class RegexValidator < ActiveModel::EachValidator
  self.abstract_class = true

  def regex
    raise "Children of this class must implement the 'regex' method."
  end

  def message(record, attribute, value)
    raise "Children of this class must implement the 'message(record, attribute, value)' method."
  end

  def validate_each(record, attribute, value)
    if value.present? && !regex.match?(value)
      record.errors.add(attribute, message(record, attribute, value))
    end
  end
end

所以现在EmailValidator是这样实现的:

class EmailValidator < RegexValidator
  def regex
    /\A[^\p{Z}]+@[^\p{Z}]+\.[^\p{Z}]+\z/
  end

  def message(record, attribute, value)
    "must be an email"
  end
end

但是,由于此更改 Rails 5 不再自动加载我EmailValidator,即使两者RegexValidatorEmailValidator被放置在适当命名的文件中,regex_validator.rb并且email_validator.rb,在app/validators之前被自动加载的文件夹中(这使得这个问题与类似的问题不同)。我怀疑这是因为EmailValidator不再直接继承自ActiveModel::EachValidator,但这无关紧要。

作为参考,我尝试生成迁移文件时出现以下错误:

/var/lib/gems/2.5.0/gems/activemodel-5.2.2/lib/active_model/validations/validates.rb:121:in `rescue in block in validates': Unknown validator: 'EmailValidator' (ArgumentError)
        from /var/lib/gems/2.5.0/gems/activemodel-5.2.2/lib/active_model/validations/validates.rb:118:in `block in validates'
        from /var/lib/gems/2.5.0/gems/activemodel-5.2.2/lib/active_model/validations/validates.rb:114:in `each'
        from /var/lib/gems/2.5.0/gems/activemodel-5.2.2/lib/active_model/validations/validates.rb:114:in `validates'
        from /home/tomeraberbach/Desktop/msf/src/app/models/user.rb:7:in `<class:User>'
        from /home/tomeraberbach/Desktop/msf/src/app/models/user.rb:4:in `<main>'
        from /var/lib/gems/2.5.0/gems/bootsnap-1.3.2/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:50:in `load'
        from /var/lib/gems/2.5.0/gems/bootsnap-1.3.2/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:50:in `load'
        from /var/lib/gems/2.5.0/gems/activesupport-5.2.2/lib/active_support/dependencies.rb:476:in `block in load_file'
        from /var/lib/gems/2.5.0/gems/activesupport-5.2.2/lib/active_support/dependencies.rb:661:in `new_constants_in'
        from /var/lib/gems/2.5.0/gems/activesupport-5.2.2/lib/active_support/dependencies.rb:475:in `load_file'
        from /var/lib/gems/2.5.0/gems/activesupport-5.2.2/lib/active_support/dependencies.rb:373:in `block in require_or_load'
        from /var/lib/gems/2.5.0/gems/activesupport-5.2.2/lib/active_support/dependencies.rb:37:in `block in load_interlock'
        from /var/lib/gems/2.5.0/gems/activesupport-5.2.2/lib/active_support/dependencies/interlock.rb:14:in `block in loading'
        from /var/lib/gems/2.5.0/gems/activesupport-5.2.2/lib/active_support/concurrency/share_lock.rb:151:in `exclusive'
        from /var/lib/gems/2.5.0/gems/activesupport-5.2.2/lib/active_support/dependencies/interlock.rb:13:in `loading'
        from /var/lib/gems/2.5.0/gems/activesupport-5.2.2/lib/active_support/dependencies.rb:37:in `load_interlock'
        from /var/lib/gems/2.5.0/gems/activesupport-5.2.2/lib/active_support/dependencies.rb:356:in `require_or_load'
        from /var/lib/gems/2.5.0/gems/bootsnap-1.3.2/lib/bootsnap/load_path_cache/core_ext/active_support.rb:46:in `block in require_or_load'
        from /var/lib/gems/2.5.0/gems/bootsnap-1.3.2/lib/bootsnap/load_path_cache/core_ext/active_support.rb:16:in `allow_bootsnap_retry'
        from /var/lib/gems/2.5.0/gems/bootsnap-1.3.2/lib/bootsnap/load_path_cache/core_ext/active_support.rb:45:in `require_or_load'
        from /var/lib/gems/2.5.0/gems/activesupport-5.2.2/lib/active_support/dependencies.rb:510:in `load_missing_constant'
        from /var/lib/gems/2.5.0/gems/bootsnap-1.3.2/lib/bootsnap/load_path_cache/core_ext/active_support.rb:58:in `block in load_missing_constant'
        from /var/lib/gems/2.5.0/gems/bootsnap-1.3.2/lib/bootsnap/load_path_cache/core_ext/active_support.rb:16:in `allow_bootsnap_retry'
        from /var/lib/gems/2.5.0/gems/bootsnap-1.3.2/lib/bootsnap/load_path_cache/core_ext/active_support.rb:57:in `load_missing_constant'
        from /var/lib/gems/2.5.0/gems/activesupport-5.2.2/lib/active_support/dependencies.rb:195:in `const_missing'
        from /var/lib/gems/2.5.0/gems/activesupport-5.2.2/lib/active_support/inflector/methods.rb:283:in `const_get'
        from /var/lib/gems/2.5.0/gems/activesupport-5.2.2/lib/active_support/inflector/methods.rb:283:in `block in constantize'
        from /var/lib/gems/2.5.0/gems/activesupport-5.2.2/lib/active_support/inflector/methods.rb:281:in `each'
        from /var/lib/gems/2.5.0/gems/activesupport-5.2.2/lib/active_support/inflector/methods.rb:281:in `inject'
        from /var/lib/gems/2.5.0/gems/activesupport-5.2.2/lib/active_support/inflector/methods.rb:281:in `constantize'
        from /var/lib/gems/2.5.0/gems/activesupport-5.2.2/lib/active_support/dependencies.rb:582:in `get'
        from /var/lib/gems/2.5.0/gems/activesupport-5.2.2/lib/active_support/dependencies.rb:613:in `constantize'
        from /var/lib/gems/2.5.0/gems/devise-4.5.0/lib/devise.rb:316:in `get'
        from /var/lib/gems/2.5.0/gems/devise-4.5.0/lib/devise/mapping.rb:83:in `to'
        from /var/lib/gems/2.5.0/gems/devise-4.5.0/lib/devise/mapping.rb:78:in `modules'
        from /var/lib/gems/2.5.0/gems/devise-4.5.0/lib/devise/mapping.rb:95:in `routes'
        from /var/lib/gems/2.5.0/gems/devise-4.5.0/lib/devise/mapping.rb:162:in `default_used_route'
        from /var/lib/gems/2.5.0/gems/devise-4.5.0/lib/devise/mapping.rb:72:in `initialize'
        from /var/lib/gems/2.5.0/gems/devise-4.5.0/lib/devise.rb:346:in `new'
        from /var/lib/gems/2.5.0/gems/devise-4.5.0/lib/devise.rb:346:in `add_mapping'
        from /var/lib/gems/2.5.0/gems/devise-4.5.0/lib/devise/rails/routes.rb:243:in `block in devise_for'
        from /var/lib/gems/2.5.0/gems/devise-4.5.0/lib/devise/rails/routes.rb:242:in `each'
        from /var/lib/gems/2.5.0/gems/devise-4.5.0/lib/devise/rails/routes.rb:242:in `devise_for'
        from /home/tomeraberbach/Desktop/msf/src/config/routes.rb:3:in `block (2 levels) in <main>'
        from /var/lib/gems/2.5.0/gems/actionpack-5.2.2/lib/action_dispatch/routing/mapper.rb:879:in `scope'
        from /home/tomeraberbach/Desktop/msf/src/config/routes.rb:2:in `block in <main>'
        from /var/lib/gems/2.5.0/gems/actionpack-5.2.2/lib/action_dispatch/routing/route_set.rb:432:in `instance_exec'
        from /var/lib/gems/2.5.0/gems/actionpack-5.2.2/lib/action_dispatch/routing/route_set.rb:432:in `eval_block'
        from /var/lib/gems/2.5.0/gems/actionpack-5.2.2/lib/action_dispatch/routing/route_set.rb:414:in `draw'
        from /home/tomeraberbach/Desktop/msf/src/config/routes.rb:1:in `<main>'
        from /var/lib/gems/2.5.0/gems/bootsnap-1.3.2/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:50:in `load'
        from /var/lib/gems/2.5.0/gems/bootsnap-1.3.2/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:50:in `load'
        from /var/lib/gems/2.5.0/gems/activesupport-5.2.2/lib/active_support/dependencies.rb:285:in `block in load'
        from /var/lib/gems/2.5.0/gems/activesupport-5.2.2/lib/active_support/dependencies.rb:257:in `load_dependency'
        from /var/lib/gems/2.5.0/gems/activesupport-5.2.2/lib/active_support/dependencies.rb:285:in `load'
        from /var/lib/gems/2.5.0/gems/railties-5.2.2/lib/rails/application/routes_reloader.rb:41:in `block in load_paths'
        from /var/lib/gems/2.5.0/gems/railties-5.2.2/lib/rails/application/routes_reloader.rb:41:in `each'
        from /var/lib/gems/2.5.0/gems/railties-5.2.2/lib/rails/application/routes_reloader.rb:41:in `load_paths'
        from /var/lib/gems/2.5.0/gems/railties-5.2.2/lib/rails/application/routes_reloader.rb:20:in `reload!'
        from /var/lib/gems/2.5.0/gems/railties-5.2.2/lib/rails/application/routes_reloader.rb:30:in `block in updater'
        from /var/lib/gems/2.5.0/gems/activesupport-5.2.2/lib/active_support/file_update_checker.rb:83:in `execute'
        from /var/lib/gems/2.5.0/gems/railties-5.2.2/lib/rails/application/routes_reloader.rb:10:in `execute'                                                      
        from /var/lib/gems/2.5.0/gems/railties-5.2.2/lib/rails/application/finisher.rb:130:in `block in <module:Finisher>'
        from /var/lib/gems/2.5.0/gems/railties-5.2.2/lib/rails/initializable.rb:32:in `instance_exec'
        from /var/lib/gems/2.5.0/gems/railties-5.2.2/lib/rails/initializable.rb:32:in `run'
        from /var/lib/gems/2.5.0/gems/railties-5.2.2/lib/rails/initializable.rb:61:in `block in run_initializers'
        from /usr/lib/ruby/2.5.0/tsort.rb:228:in `block in tsort_each'
        from /usr/lib/ruby/2.5.0/tsort.rb:350:in `block (2 levels) in each_strongly_connected_component'
        from /usr/lib/ruby/2.5.0/tsort.rb:431:in `each_strongly_connected_component_from'
        from /usr/lib/ruby/2.5.0/tsort.rb:349:in `block in each_strongly_connected_component'
        from /usr/lib/ruby/2.5.0/tsort.rb:347:in `each'
        from /usr/lib/ruby/2.5.0/tsort.rb:347:in `call'
        from /usr/lib/ruby/2.5.0/tsort.rb:347:in `each_strongly_connected_component'
        from /usr/lib/ruby/2.5.0/tsort.rb:226:in `tsort_each'
        from /usr/lib/ruby/2.5.0/tsort.rb:205:in `tsort_each'
        from /var/lib/gems/2.5.0/gems/railties-5.2.2/lib/rails/initializable.rb:60:in `run_initializers'
        from /var/lib/gems/2.5.0/gems/railties-5.2.2/lib/rails/application.rb:361:in `initialize!'
        from /home/tomeraberbach/Desktop/msf/src/config/environment.rb:5:in `<main>'
        from /var/lib/gems/2.5.0/gems/bootsnap-1.3.2/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:21:in `require'
        from /var/lib/gems/2.5.0/gems/bootsnap-1.3.2/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:21:in `block in require_with_bootsnap_lfi'
        from /var/lib/gems/2.5.0/gems/bootsnap-1.3.2/lib/bootsnap/load_path_cache/loaded_features_index.rb:65:in `register'
        from /var/lib/gems/2.5.0/gems/bootsnap-1.3.2/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:20:in `require_with_bootsnap_lfi'
        from /var/lib/gems/2.5.0/gems/bootsnap-1.3.2/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:29:in `require'
        from /var/lib/gems/2.5.0/gems/activesupport-5.2.2/lib/active_support/dependencies.rb:291:in `block in require'
        from /var/lib/gems/2.5.0/gems/activesupport-5.2.2/lib/active_support/dependencies.rb:257:in `load_dependency'
        from /var/lib/gems/2.5.0/gems/activesupport-5.2.2/lib/active_support/dependencies.rb:291:in `require'
        from /var/lib/gems/2.5.0/gems/spring-2.0.2/lib/spring/application.rb:102:in `preload'
        from /var/lib/gems/2.5.0/gems/spring-2.0.2/lib/spring/application.rb:153:in `serve'
        from /var/lib/gems/2.5.0/gems/spring-2.0.2/lib/spring/application.rb:141:in `block in run'
        from /var/lib/gems/2.5.0/gems/spring-2.0.2/lib/spring/application.rb:135:in `loop'
        from /var/lib/gems/2.5.0/gems/spring-2.0.2/lib/spring/application.rb:135:in `run'
        from /var/lib/gems/2.5.0/gems/spring-2.0.2/lib/spring/application/boot.rb:19:in `<top (required)>'
        from /usr/local/lib/site_ruby/2.5.0/rubygems/core_ext/kernel_require.rb:59:in `require'
        from /usr/local/lib/site_ruby/2.5.0/rubygems/core_ext/kernel_require.rb:59:in `require'
        from -e:1:in `<main>'

我的User班级:

##
# A class representing a user account for this web application
# Instances of this class represent rows in the +users+ table on the database.
class User < ApplicationRecord
  # Validation
  validates_presence_of :email, :encrypted_password
  validates :email, email: true

  # Associations
  devise :database_authenticatable, :registerable, :recoverable,
         :rememberable, :trackable, :validatable
  has_and_belongs_to_many :roles

  # Events
  before_validation do
    # Removes unnecessary whitespace
    self.email = email.strip
  end
end

有任何想法吗?

标签: ruby-on-railsrubyruby-on-rails-5customvalidator

解决方案


我解决了这个问题。由于类中没有方法,因此输入的self.abstract_class = true行默默地引发了错误。我以为是因为我在课堂上看到了它,但我现在意识到这是在课堂上定义的。RegexValidatorself.abstract_classApplicationRecordActiveRecord::Base

我通过添加require_relative "../validators/email_validator"user.rb文件顶部来确定这一点。这样做之后,我能够看到之前没有出现的错误:

/home/tomeraberbach/Desktop/msf/src/app/validators/regex_validator.rb:4:in `<class:RegexValidator>': undefined method `abstract_class=' for RegexValidator:Class (NoMethodError)

拆掉self.abstract_class = true线和require_relative "../validators/email_validator"线后,问题就解决了。


推荐阅读