Module: ActiveRecord::Bitemporal::Persistence

Includes:
PersistenceOptionable
Included in:
Bitemporalize::InstanceMethods
Defined in:
lib/activerecord-bitemporal/bitemporal.rb

Overview

create, update, destroy に処理をフックする

Defined Under Namespace

Modules: EachAssociation, PersistenceOptionable

Instance Method Summary collapse

Methods included from PersistenceOptionable

#bitemporal_at, #bitemporal_option_merge_with_association!, #force_update, #force_update?, #transaction_at, #transaction_datetime, #valid_at, #valid_date, #valid_datetime

Methods included from Optionable

#bitemporal_option, #bitemporal_option_merge!, #with_bitemporal_option

Instance Method Details

#_create_record(attribute_names = self.attribute_names) ⇒ Object



343
344
345
346
347
348
349
# File 'lib/activerecord-bitemporal/bitemporal.rb', line 343

def _create_record(attribute_names = self.attribute_names)
  bitemporal_assign_initialize_value(valid_datetime: self.valid_datetime)

  ActiveRecord::Bitemporal.valid_at!(self[valid_from_key]) {
    super()
  }
end

#_find_recordObject

MEMO: Since Rails 7.1 #_find_record refers to a record with find_by!(@primary_key => id)

But if @primary_key is "id", it can't refer to the intended record, so we hack it to refer to the record based on self.class.bitemporal_id_key
see: https://github.com/rails/rails/blob/v7.1.0/activerecord/lib/active_record/persistence.rb#L1152-#L1171


433
434
435
436
437
438
# File 'lib/activerecord-bitemporal/bitemporal.rb', line 433

def _find_record(*)
  tmp_primary_key, @primary_key = @primary_key, self.class.bitemporal_id_key
  super
ensure
  @primary_key = tmp_primary_key
end

#_update_row(attribute_names, attempted_action = 'update') ⇒ Object



365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
# File 'lib/activerecord-bitemporal/bitemporal.rb', line 365

def _update_row(attribute_names, attempted_action = 'update')
  current_valid_record, before_instance, after_instance = bitemporal_build_update_records(valid_datetime: self.valid_datetime, force_update: self.force_update?)

  # MEMO: このメソッドに来るまでに validation が発動しているので、以後 validate は考慮しなくて大丈夫
  ActiveRecord::Base.transaction(requires_new: true) do
    current_valid_record&.update_transaction_to(current_valid_record.transaction_to)
    before_instance&.save_without_bitemporal_callbacks!(validate: false)
    # NOTE: after_instance always exists
    after_instance.save_without_bitemporal_callbacks!(validate: false)
    @previously_force_updated = self.force_update?

    # update 後に新しく生成したインスタンスのデータを移行する
    @_swapped_id_previously_was = swapped_id
    @_swapped_id = after_instance.swapped_id
    self[valid_from_key] = after_instance[valid_from_key]
    self[valid_to_key] = after_instance[valid_to_key]
    self.transaction_from = after_instance.transaction_from
    self.transaction_to = after_instance.transaction_to

    1
  # MEMO: Must return false instead of nil, if `#_update_row` failure.
  end || false
end

#destroy(force_delete: false, operated_at: nil) ⇒ Object



389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
# File 'lib/activerecord-bitemporal/bitemporal.rb', line 389

def destroy(force_delete: false, operated_at: nil)
  return super() if force_delete

  ActiveRecord::Base.transaction(requires_new: true) do
    @destroyed = false
    _run_destroy_callbacks {
      operated_at ||= Time.current
      target_datetime = valid_datetime || operated_at

      duplicated_instance = self.class.find_at_time(target_datetime, self.id).dup

      @destroyed = update_transaction_to(operated_at)
      @previously_force_updated = force_update?

      # force_update の場合は削除時の状態の履歴を残さない
      unless force_update?
        # 削除時の状態を履歴レコードとして保存する
        duplicated_instance[valid_to_key] = target_datetime
        duplicated_instance.transaction_from = operated_at
        duplicated_instance.save_without_bitemporal_callbacks!(validate: false)
        if @destroyed
          @_swapped_id_previously_was = swapped_id
          @_swapped_id = duplicated_instance.swapped_id
          self[valid_from_key] = duplicated_instance[valid_from_key]
          self[valid_to_key] = duplicated_instance[valid_to_key]
          self.transaction_from = duplicated_instance.transaction_from
          self.transaction_to = duplicated_instance.transaction_to
        end
      end
    }
    raise ActiveRecord::RecordInvalid unless @destroyed

    self
  end
rescue => e
  @destroyed = false
  @_association_destroy_exception = ActiveRecord::RecordNotDestroyed.new("Failed to destroy the record: class=#{e.class}, message=#{e.message}", self)
  @_association_destroy_exception.set_backtrace(e.backtrace)
  false
end

#saveObject



351
352
353
354
355
356
# File 'lib/activerecord-bitemporal/bitemporal.rb', line 351

def save(**)
  ActiveRecord::Base.transaction(requires_new: true) do
    self.class.where(bitemporal_id: self.id).lock!.pluck(:id) if self.id
    super
  end
end

#save!Object



358
359
360
361
362
363
# File 'lib/activerecord-bitemporal/bitemporal.rb', line 358

def save!(**)
  ActiveRecord::Base.transaction(requires_new: true) do
    self.class.where(bitemporal_id: self.id).lock!.pluck(:id) if self.id
    super
  end
end