Jea*_*rtz 13 validation ruby-on-rails activemodel rails-activerecord
在Bryan Helmkamp的优秀博客文章" 7个模式来重构Fat ActiveRecord模型 "中,他提到使用Form Objects抽象多层形式并停止使用accepts_nested_attributes_for.
编辑:请参阅下面的解决方案.
我几乎完全复制了他的代码示例,因为我有同样的问题需要解决:
class Signup
  include Virtus
  extend ActiveModel::Naming
  include ActiveModel::Conversion
  include ActiveModel::Validations
  attr_reader :user
  attr_reader :account
  attribute :name, String
  attribute :account_name, String
  attribute :email, String
  validates :email, presence: true
  validates :account_name,
    uniqueness: { case_sensitive: false },
    length: 3..40,
    format: { with: /^([a-z0-9\-]+)$/i }
  # Forms are never themselves persisted
  def persisted?
    false
  end
  def save
    if valid?
      persist!
      true
    else
      false
    end
  end
private
  def persist!
    @account = Account.create!(name: account_name)
    @user = @account.users.create!(name: name, email: email)
  end
end
Run Code Online (Sandbox Code Playgroud)
我的代码中不同的一点是,我需要验证帐户名称(和用户电子邮件)的唯一性.但是,ActiveModel::Validations没有uniqueness验证器,因为它应该是非数据库支持的变体ActiveRecord.
我想有三种方法可以解决这个问题:
我宁愿使用最后一个.但后来我一直想知道如何实现这一点.
我可以做类似的事情(元编程,我需要修改其他一些方面):
  def persist!
    @account = Account.create!(name: account_name)
    @user = @account.users.create!(name: name, email: email)
  rescue ActiveRecord::RecordNotUnique
    errors.add(:name, "not unique" )
    false
  end
Run Code Online (Sandbox Code Playgroud)
但是现在我在我的类中运行了两个检查,首先我使用valid?然后我使用rescue语句来处理数据存储.
有谁知道处理这个问题的好方法?或许为此编写我自己的验证器会更好(但后来我对数据库有两个查询,理想情况下一个就足够了).
crf*_*ftr 10
如果这恰好是一次性要求,那么创建自定义验证器可能会过度.
一种简化的方法......
class Signup
  (...)
  validates :email, presence: true
  validates :account_name, length: {within: 3..40}, format: { with: /^([a-z0-9\-]+)$/i }
  # Call a private method to verify uniqueness
  validate :account_name_is_unique
  def persisted?
    false
  end
  def save
    if valid?
      persist!
      true
    else
      false
    end
  end
  private
  # Refactor as needed
  def account_name_is_unique
    if Account.where(name: account_name).exists?
      errors.add(:account_name, 'Account name is taken')
    end
  end
  def persist!
    @account = Account.create!(name: account_name)
    @user = @account.users.create!(name: name, email: email)
  end
end
Run Code Online (Sandbox Code Playgroud)
        布莱恩非常友好地在他的博客文章中评论我的问题.在他的帮助下,我想出了以下自定义验证器:
class UniquenessValidator < ActiveRecord::Validations::UniquenessValidator
  def setup(klass)
    super
    @klass = options[:model] if options[:model]
  end
  def validate_each(record, attribute, value)
    # UniquenessValidator can't be used outside of ActiveRecord instances, here
    # we return the exact same error, unless the 'model' option is given.
    #
    if ! options[:model] && ! record.class.ancestors.include?(ActiveRecord::Base)
      raise ArgumentError, "Unknown validator: 'UniquenessValidator'"
    # If we're inside an ActiveRecord class, and `model` isn't set, use the
    # default behaviour of the validator.
    #
    elsif ! options[:model]
      super
    # Custom validator options. The validator can be called in any class, as
    # long as it includes `ActiveModel::Validations`. You can tell the validator
    # which ActiveRecord based class to check against, using the `model`
    # option. Also, if you are using a different attribute name, you can set the
    # correct one for the ActiveRecord class using the `attribute` option.
    #
    else
      record_org, attribute_org = record, attribute
      attribute = options[:attribute].to_sym if options[:attribute]
      record = options[:model].new(attribute => value)
      super
      if record.errors.any?
        record_org.errors.add(attribute_org, :taken,
          options.except(:case_sensitive, :scope).merge(value: value))
      end
    end
  end
end
Run Code Online (Sandbox Code Playgroud)
您可以在ActiveModel类中使用它,如下所示:
  validates :account_name,
    uniqueness: { case_sensitive: false, model: Account, attribute: 'name' }
Run Code Online (Sandbox Code Playgroud)
您唯一的问题是,如果您的自定义model类也有验证.当您打电话时Signup.new.save,这些验证不会运行,因此您必须以其他方式检查这些验证.您始终可以save(validate: false)在上述persist!方法中使用,但是您必须确保所有验证都在Signup类中,并且当您更改Account或中的任何验证时,保持该类是最新的User.
|   归档时间:  |  
           
  |  
        
|   查看次数:  |  
           6224 次  |  
        
|   最近记录:  |