【问题标题】:Activerecord Return existing record if it exists on create or save of modelActiverecord 如果在创建或保存模型时存在,则返回现有记录
【发布时间】:2014-02-05 16:29:17
【问题描述】:

问题

我想返回一个现有的记录,如果存在的话,只要在模型上使用模型本身的验证调用 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? #=&gt; false;相反,我想在所有情况下都简单地返回现有的。

【问题讨论】:

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


    【解决方案1】:

    您想使用以下内容:find_or_create_by

    Sha1.find_or_create_by(hexdigest: '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33')
    

    它将根据 hexidigest 查找或创建您的 Sha1 记录并返回实例。

    http://api.rubyonrails.org/classes/ActiveRecord/Relation.html#method-i-find_or_create_by

    【讨论】:

      【解决方案2】:

      首先,希望您的 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
      

      这递归地保证了唯一性

      【讨论】:

        【解决方案3】:

        不幸的是,上述答案都没有真正解决我原来的问题。经过大量的试验和错误后,我发现我原来的帖子中提到的 ActiveSupport::Concern 方法似乎是我可以实现的最接近/最好的解决方案。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2016-01-18
          • 2016-01-13
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2018-10-13
          相关资源
          最近更新 更多