【问题标题】:Custom Validator on: :create not running on rails app自定义验证器::创建不在 Rails 应用程序上运行
【发布时间】:2020-10-01 21:32:00
【问题描述】:

我有一个用于创建卷的 Rails 应用程序,并使用 ActiveModel::Validator 编写了两个自定义验证器。

volume.rb:

    class Volume < ActiveRecord::Base
      include UrlSafeCode
      include PgSearch::Model
      include ActiveModel::Validations
      validates :user_id, presence: true
      validates_with Validators::VolumeValidator
      validates_with Validators::CreateVolumeValidator, on: :create

    def self.digest text
      Digest::SHA256.hexdigest(text)
    end

    def text=(new_text)
      new_text.rstrip!
      new_text.downcase!
      self.text_digest = Volume.digest(new_text)
      super(new_text)
    end

我的问题: CreateVolumeValidator 检查具有相同 text_digest 的记录是否已在数据库中。我只想在创建新卷时运行它,以便我仍然可以更新现有卷。但是,将 on: :create 添加到 CustomVolumeValidator 会导致验证器停止工作。

我已经阅读了很多关于类似问题的其他条目,但没有找到解决方案。我很确定我错过了一些关于何时创建、验证和保存不同属性的信息,但我没有太多使用自定义验证,我迷路了。

这是其他相关代码。

volumes_controller.rb

  def new
    @volume = Volume.new
  end

  def create
    our_params = params
      .permit(:text, :description)

    if params[:text].nil?
      render :retry
      return
    end

    text = params[:text].read.to_s
    text_digest = Volume.digest(text)   

    @description = our_params[:description]

    begin
      @volume = Volume.where(text_digest: text_digest)
        .first_or_create(text: text, user: current_user, description: our_params[:description])
    rescue ActiveRecord::RecordNotUnique
      retry
    end

    if @volume.invalid?
      render :retry
      return
    end

    render :create

  end

  def edit
    get_volume
  end

  def update

    get_volume
    unless @volume
      render nothing: true, status: :not_found
      return
    end

    @volume.update(params.require(:volume).permit(:text, :description))

    if @volume.save
      redirect_to volume_path(@volume.code)
    else
      flash[:notice] = @volume.errors.full_messages.join('\n')
      render :edit
    end

  end

  def get_volume
    @volume = Volume.where(code: params.require(:code)).first
  end

create_volume_validator.rb

class Validators::CreateVolumeValidator < ActiveModel::Validator
    def validate(volume)
        existing_volume = Volume.where(text_digest: volume.text_digest).first
        if existing_volume 
          existing_volume_link = "<a href='#{Rails.application.routes.url_helpers.volume_path(existing_volume.code)}'>here</a>."
          volume.errors.add :base, ("This volume is already part of the referral archive and is available " + existing_volume_link).html_safe
        end
    end
end

【问题讨论】:

    标签: ruby-on-rails validation activerecord rails-activerecord customvalidator


    【解决方案1】:

    如果您的目标是让所有 Volume 记录都具有唯一的 text_digest,那么最好使用简单的 :uniqueness 验证器(以及相关的数据库唯一索引)。

    但是,您现有的代码不起作用的原因是:

    Volume.where(text_digest: text_digest).first_or_create(...)
    

    这将返回第一个 Volume 与匹配的 text_digest 或创建一个新的。但这意味着如果存在冲突,则不会创建任何对象,因此您的 (on: :create) 验证不会运行。相反,它只是将@volume 设置为现有对象,根据定义,它是有效的。如果没有匹配的记录,它确实调用您的验证器,但没有什么要验证的,因为您已经证明不存在text_digest 冲突。

    您可以通过将 first_or_create 替换为 create 来解决问题,但同样,您最好使用唯一的索引和验证器(如果您愿意,可以使用自定义消息)。

    【讨论】:

    • 感谢您的解释!将 first_or_create 更改为 create 有效,并且我正在努力创建您所描述的唯一性验证。
    猜你喜欢
    • 2020-02-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-04-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-03-31
    相关资源
    最近更新 更多