Custom Validator on: :create not 运行 on rails app
Custom Validator on: :create not running on rails app
我有一个用于创建卷的 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 的记录。我只想在创建新卷时 运行 这样我仍然可以更新现有卷。但是,添加: :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
如果您的目标是让所有 Volume
记录都具有唯一性 text_digest
,您最好使用简单的 :uniqueness
验证器(以及关联的数据库唯一索引)。
但是,您现有的代码无法正常工作的原因是:
Volume.where(text_digest: text_digest).first_or_create(...)
此 returns 可以是第一个 Volume
与匹配的 text_digest
或创建一个新的。但这意味着如果存在冲突,则不会创建任何对象,因此您的 (on: :create
) 验证不会 运行。相反,它只是将 @volume
设置为现有对象,根据定义,该对象是有效的。如果没有匹配的记录,它 会 调用你的验证器,但是没有什么要验证的,因为你已经证明没有 text_digest
冲突。
您可以通过将 first_or_create
替换为 create
来解决,但同样,使用唯一索引和验证器(如果您愿意,可以使用自定义消息)会更好。
我有一个用于创建卷的 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 的记录。我只想在创建新卷时 运行 这样我仍然可以更新现有卷。但是,添加: :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
如果您的目标是让所有 Volume
记录都具有唯一性 text_digest
,您最好使用简单的 :uniqueness
验证器(以及关联的数据库唯一索引)。
但是,您现有的代码无法正常工作的原因是:
Volume.where(text_digest: text_digest).first_or_create(...)
此 returns 可以是第一个 Volume
与匹配的 text_digest
或创建一个新的。但这意味着如果存在冲突,则不会创建任何对象,因此您的 (on: :create
) 验证不会 运行。相反,它只是将 @volume
设置为现有对象,根据定义,该对象是有效的。如果没有匹配的记录,它 会 调用你的验证器,但是没有什么要验证的,因为你已经证明没有 text_digest
冲突。
您可以通过将 first_or_create
替换为 create
来解决,但同样,使用唯一索引和验证器(如果您愿意,可以使用自定义消息)会更好。