我想返回一条现有记录,如果存在,只要在模型上使用模型本身的验证调用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
;相反,我想在所有情况下简单地返回现有的。
答案 0 :(得分:0)
您想使用以下内容:find_or_create_by
Sha1.find_or_create_by(hexdigest: '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33')
它会找到OR根据hexidigest创建你的Sha1记录并返回实例。
http://api.rubyonrails.org/classes/ActiveRecord/Relation.html#method-i-find_or_create_by
答案 1 :(得分:0)
首先,希望您的hexdigest列上有SQL约束。 Ruby回调无法以原子方式验证唯一性。让我再说一遍。 Ruby回调不能以原子方式验证唯一性。
现在,关于你的问题,我要做的是,在你的sha1模型上创建一个新的保存方法。
def save_unique!
while
self.hexdigest = Digest::MD5.hexdigest(rand(123123)) if self.hexdigest.nil?
self.save!
break
rescue ActiveRecord::RecordNotUnique
end
这递归地保证了唯一性
答案 2 :(得分:0)
不幸的是,上述答案都没有解决我原来的问题。经过大量的反复试验后,我发现原始帖子中提到的ActiveSupport :: Concern方法似乎是我能达到的最接近/最好的解决方案。