【发布时间】:2014-02-05 16:29:17
【问题描述】:
问题
我想返回一个现有的记录,如果存在的话,只要在模型上使用模型本身的验证调用 save(!) 或 create(!),WITHOUT 使用 first_or_create 从外部控制器或相关模型。如果不存在记录,则应创建/保存新记录并返回。
详情
我正在开发一个 Ruby on Rails 4 项目,我有一个非常简单的模型来存储 Sha1 哈希。除了默认列之外,它还有一个名为“hexdigest”的列,它是一个唯一索引。 Sha1 与其他几个模型有 has_many 关系,每个模型可能包含与单个 Sha 相关的零个或多个记录。我遇到的问题是我想始终返回现有记录,如果存在的话,任何其他模型/控制器/等都调用Sha1#save 或Sha1.create。我一直在尝试在模型本身中执行此操作,但还没有找到解决此问题的好方法。
下面是模型的简单表示(实际模型几乎没有额外的复杂性):
class Sha1 < ActiveRecord::Base
has_many :git_repos
validates :hexdigest,
presence: true,
length: { is: 40 },
format: { with: /\A[a-f0-9]+\z/ }
end
目前,我正在使用关注点管理关联模型的 Sha1 记录的唯一性:
module BelongsToSha
extend ActiveSupport::Concern
included do
belongs_to :sha1
before_validation :get_sha1
validates :sha1, presence: true
accepts_nested_attributes_for :sha1
end
def get_sha1
if !sha1.nil? && sha1.new_record?
self.sha1 = Sha1.find_or_create_by(hexdigest: self.sha1.hexdigest)
end
end
end
这具有确保新记录的关联 Sha1 在保存时解析为现有 sha1(如果存在)的简单效果。
但是,如果我尝试从其他地方(在测试、控制器或其他代码中)手动创建/保存 Sha1,它会失败,因为 first_or_create 没有直接在模型上解析:
Sha1.create(hexdigest: '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33')
Sha1.create(hexdigest: '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33') # results in a record not unique error
我设法通过覆盖 Sha1 模型上的 create 解决了 create 上的问题:
def self.create(attributes, &block)
first || super
end
def self.create!(attributes, &block)
first || super
end
但是,保存时问题仍然存在,因为 ActiveRecord 在 create 和 save 上的执行路径不同。所以以下仍然失败,但有一个例外:
Sha1.new(hexdigest: '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33').save
Sha1.new(hexdigest: '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33').save # results in a record not unique error
覆盖创建已经感觉像是一种黑客攻击,但我还没有找到更好的方法来做到这一点。现在看来,我还需要覆盖save 和save!,但目前还没有成功。最好使用回调,但回调只允许通过返回 false 或引发异常来转义创建/保存,这将导致在通过关联自动保存/创建 Sha1 时回滚整个事务。
我已经对此进行了多次尝试,并且 BelongsToSha 问题是迄今为止最好的解决方案,但并非在上述所有情况下都有效。我不想在尝试保存或创建重复记录时引发错误或valid? #=> false;相反,我想在所有情况下都简单地返回现有的。
【问题讨论】:
标签: ruby-on-rails activerecord ruby-on-rails-4