Activerecord如果在创建或保存模型时存在,则返回现有记录

时间:2014-02-05 16:29:17

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

问题

我想返回一条现有记录,如果存在,只要在模型上使用模型本身的验证调用save(!)或create(!), WITHOUT 使用first_or_create来自外部控制器或相关模型。如果不存在记录,则应创建/保存新记录并将其返回。

详细

我正在开发一个Ruby on Rails 4项目,我有一个非常简单的模型来存储Sha1哈希。除了默认列之外,它还有一个名为“hexdigest”的列,它是一个唯一索引。 Sha1与其他几个模型有一个has_many关系,每个模型可能包含零个或多个与单个Sha相关的记录。我遇到的问题是,我希望在任何其他模型/控制器等调用Sha1#saveSha1.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在createsave上关闭了不同的执行路径。因此,以下情况仍然失败,但有例外:

Sha1.new(hexdigest: '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33').save
Sha1.new(hexdigest: '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33').save # results in a record not unique error

它已经感觉像是一个覆盖创建的黑客,但我还没有找到更好的方法来做到这一点。现在看来我还需要覆盖savesave!,但尚未取得成功。最好使用回调,但回调只允许通过返回false或引发异常来转义创建/保存,这会导致在通过关联自动保存/创建Sha1时回滚整个事务。

我已经做了几次尝试,并且BelongsToSha问题一直是最好的解决方案,但并不适用于如上所述的所有情况。我不想在尝试保存或创建重复记录时引发错误或valid? #=> false;相反,我想在所有情况下简单地返回现有的。

3 个答案:

答案 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方法似乎是我能达到的最接近/最好的解决方案。