module Dino::Upsert

Provides a simple way to do an create or update of an ActiveRecord object.

Public Class Methods

upsert(klass, data, options = {}, &block) click to toggle source

klass: The ActiveRecord class we are upserting against. conditions: what should match the upsert options: what we are updating/inserting If provided, the block gets the object before it's saved, in case

there's special init options necessary for it.

rubocop:disable MethodLength

# File lib/dino/upsert.rb, line 21
def self.upsert(klass, data, options = {}, &block)
  retry_count = 0
  data_copy = {}
  data ||= []
  data.each do |k, v|
    v = klass.column_for_attribute(k).type_cast_for_database(v) if v.is_a?(Hash)
    data_copy[k] = v
  end
  begin
    klass.transaction(requires_new: true) do
      object = klass.where(data_copy).first_or_initialize
      block.call(object) if block
      object.tap do |t|
        t.assign_attributes(options)
        t.save! if t.changed?
      end
    end
  rescue PG::UniqueViolation, ActiveRecord::RecordNotUnique
    # If there's a unique violation, retry this. But only a certain amount
    # of times or we'll get into an infinite loop if something's messed up.
    # (like an incorrect unique index or something)
    if retry_count < 10
      retry_count += 1
      retry
    else
      raise
    end
  end
end