【问题标题】:Rails 4: Validating associated ActiveRecord models when using a Form ObjectRails 4:使用表单对象时验证关联的 ActiveRecord 模型
【发布时间】:2015-05-17 17:55:59
【问题描述】:

我正在使用基于 ActiveModel 的表单对象来处理应用程序的注册(注册)。 signup 类抽象出accountuser(帐户的主要用户)的信息。

但是,我发现我在 signup 类中复制了 accountuser 的验证逻辑。在编写规范时(使用 rspec),我意识到这种重复可能表明我处理此问题的方式存在问题。

有没有办法将signup 类中的验证传递给accountuser 模型而不复制它?这样,验证将保留在这些模型中,我可以在 signup 类中引用/调用它。

下面是 signup 类,我有它的作品,但似乎是重复代码...

class Signup
  include ActiveModel::Model

  # Scopes
  #----------------------------------------------------------------------

  # NOOP

  # Macros
  #----------------------------------------------------------------------

  attr_accessor :slug, :email, :password, :password_confirmation

  # Associations
  #----------------------------------------------------------------------

  # NOOP

  # Validations
  #----------------------------------------------------------------------

  validate :verify_unique_email
  validate :verify_unique_slug
  validates :email, presence: true, format: { with: /@/, message: "is invalid" }
  validates :password, presence: true, length: { minimum: 8 }, confirmation: true
  validates :password_confirmation, presence: true
  validates :slug,
    presence: true,
    format: { with: /\A[\w-]+\z/, message: "is invalid" },
    exclusion: { in: %w[signup signups login] }


  # Methods
  #----------------------------------------------------------------------

  def account
    @account ||= Account.new
  end

  def user
    @user ||= account.build_primary_user
  end

  def save
    account.active = true
    account.slug = slug

    user.email = email
    user.password = password
    user.password_confirmation = password_confirmation

    if valid?
      ActiveRecord::Base.transaction do
        account.save!
        user.save!
      end

      true
    else
      false
    end
  end

  def save!
    save
  end

  private

  def verify_unique_email
    if User.exists?(email: email)
      errors.add :email, "is invalid"
    end
  end

  def verify_unique_slug
    if Account.exists?(slug: slug)
      errors.add :slug, "has already been taken"
    end
  end
end

这是account 模型,注意重复验证:

class Account < ActiveRecord::Base
  has_one :primary_user, -> { where(primary: true) }, class_name: User
  has_many :users, dependent: :destroy

  validates :slug,
    uniqueness: true,
    presence: true,
    format: { with: /\A[\w-]+\z/, message: "is invalid" },
    exclusion: { in: %w[signup signups login] }
end

【问题讨论】:

    标签: ruby validation ruby-on-rails-4


    【解决方案1】:

    我喜欢你使用form object 所做的事情。 validates_associated :user, :account 可能会有所帮助,但错误消息可能有点奇怪。相反,我可能会使用mixins for common validations

    class Account < ActiveRecord::Base
      module Validations
        extend ActiveSupport::Concern
        included do
          validates :slug, presence: true
        end
      end
      include Validations
    end
    
    class Signup
      include ActiveModel::Model
      include Account::Validations
      extend Forwardable
      def_delegators :account, :slug
    end
    

    【讨论】:

    • 不幸的是,我倾向于同意包含的模块可能是解决我的特定问题的最干净的 DRY 解决方案。我希望有一种方法可以说相当于:signup.errors = account.errors + user.errors,但似乎没有一种干净的方法来实现它,而不可能没有猴子修补 Rails,我有相当严格的政策反对。我会测试你的解决方案,如果可行,我会接受它。
    • 对于我的具体情况,我决定坚持重复验证,因为我可能希望稍后在 signup 类中进行单独的验证,但是将冗长的重复验证提取到单独的模块中是最好的解决方案到目前为止我已经看到了。我已经研究过将验证委托给关联对象、复制实际的 object.errors 数组等......,它们都不是“干净”的解决方案。
    • 您是否尝试过 validates_associated 可能与自定义消息?
    • @Dan L 上面的方法你试过了吗?
    • @TimmyVonHeiss:我想我实际上只是在多个模型中使用了重复验证。这样一来,所有内容都在同一个文件中,其他开发人员可以更轻松地了解正在发生的事情,而不是在多个文件之间跳转。
    【解决方案2】:

    最简单的方法是:

    def valid?
      user.valid? && account.valid? && super
    end
    

    然后您可以摆脱重复的验证。

    仅当您需要在UserAccount 之外的其他验证时,才需要super。这也会将错误添加到UserAccount 对象上,这可能比Signup 类更有用。

    【讨论】:

    • 在我的特定用例中,我实际上希望错误显示在 signup 对象本身上,因为它是 accountuser 模型的抽象。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-06-18
    • 2011-05-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多