Class: Cells::TransactionRecord

Inherits:
Object
  • Object
show all
Defined in:
app/models/cells/transaction_record.rb

Overview

This class is a fake ActiveRecord::Base which implements the interface partially so that it can be used with ActiveRecord::ConnectionAdapters::Transaction#add_record github.com/rails/rails/blob/v7.1.5.2/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb#L147-L155 From there, several methods are required to be implemented:

  • #before_committed!

    • Normally implemented by ActiveRecord::Transactions

  • #committed!

    • Normally implemented by ActiveRecord::Transactions

  • #rolledback!

    • Normally implemented by ActiveRecord::Transactions

  • #trigger_transactional_callbacks?

    • Normally implemented by ActiveRecord::Transactions

    • Overridden by Cells::TransactionRecord to avoid more interfaces needed

  • #destroyed?

    • Normally implemented by ActiveRecord::Persistence but we ignore it

  • #_new_record_before_last_commit

    • Normally implemented by ActiveRecord::Transactions

We ignore some methods because they won’t be called under specific conditions, specifically when the following both return true:

  • #trigger_transactional_callbacks?

Defined Under Namespace

Modules: TransactionExtension

Constant Summary collapse

Error =
Class.new(RuntimeError)
TIMEOUT_IN_SECONDS =
0.2

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(connection, transaction) ⇒ TransactionRecord

Returns a new instance of TransactionRecord.



61
62
63
64
65
66
67
68
# File 'app/models/cells/transaction_record.rb', line 61

def initialize(connection, transaction)
  @connection = connection
  @transaction = transaction
  @create_records = []
  @destroy_records = []
  @outstanding_lease = nil
  @done = false
end

Class Method Details

.current_transaction(connection) ⇒ Object

Extend the transaction class with an accessor to store a TransactionRecord in cells_current_transaction_record. Each transaction object is unique, and Rails manages their lifecycle, so explicit cleanup isn’t required. Nested transactions aren’t supported or expected in this design.



38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'app/models/cells/transaction_record.rb', line 38

def self.current_transaction(connection)
  return unless Current.cells_claims_leases?

  # The Cells::OutstandingLease requires a transaction to be open
  # to ensure that the lease is only created if the transaction
  # within a transaction and not outside of one
  if connection.current_transaction.closed?
    raise Error, 'The Cells::TransactionRecord requires transaction to be open'
  end

  current_transaction = connection.current_transaction

  instance = current_transaction.cells_current_transaction_record
  return instance if instance

  TransactionRecord.new(connection, current_transaction).tap do |instance|
    current_transaction.cells_current_transaction_record = instance
    # https://api.rubyonrails.org/v7.1.5.2/classes/ActiveRecord/ConnectionAdapters/DatabaseStatements.html#method-i-add_transaction_record
    # https://github.com/rails/rails/blob/v7.1.5.2/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb#L147-L155
    current_transaction.add_record(instance)
  end
end

Instance Method Details

#before_committed!Object



90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'app/models/cells/transaction_record.rb', line 90

def before_committed!
  raise Error, 'Already done' if done
  raise Error, 'Already created lease' if outstanding_lease
  raise Error, 'Attributes can now only be claimed on main DB' if Cells::OutstandingLease.connection != @connection

  @outstanding_lease = Cells::OutstandingLease.create_from_request!(
    create_records: sanitize_records_for_grpc(create_records),
    destroy_records: sanitize_records_for_grpc(destroy_records),
    deadline: deadline
  )
rescue GRPC::BadStatus => e
  raise_committing_error!(e)
  raise ActiveRecord::Rollback
end

#committed!(should_run_callbacks: true) ⇒ Object

rubocop:disable Lint/UnusedMethodArgument – this needs to follow the interface

Raises:



117
118
119
120
121
122
123
124
# File 'app/models/cells/transaction_record.rb', line 117

def committed!(should_run_callbacks: true) # rubocop:disable Lint/UnusedMethodArgument -- this needs to follow the interface
  raise Error, 'Already done' if done
  raise Error, 'No lease created' unless outstanding_lease

  outstanding_lease.send_commit_update!(deadline: deadline)
  outstanding_lease.destroy! # the lease is no longer needed
  @done = true
end

#create_record(metadata) ⇒ Object

Raises:



70
71
72
73
74
# File 'app/models/cells/transaction_record.rb', line 70

def create_record()
  raise Error, 'Lease already created' if outstanding_lease

  create_records << 
end

#destroy_record(metadata) ⇒ Object

Raises:



76
77
78
79
80
# File 'app/models/cells/transaction_record.rb', line 76

def destroy_record()
  raise Error, 'Lease already created' if outstanding_lease

  destroy_records << 
end

#rolledback!(force_restore_state: false, should_run_callbacks: true) ⇒ Object

rubocop:disable Lint/UnusedMethodArgument – this needs to follow the interface

Raises:



105
106
107
108
109
110
111
112
113
114
115
# File 'app/models/cells/transaction_record.rb', line 105

def rolledback!(force_restore_state: false, should_run_callbacks: true) # rubocop:disable Lint/UnusedMethodArgument -- this needs to follow the interface
  raise Error, 'Already done' if done

  # It is possible that lease might be not created yet,
  # since the transaction might be rolledback prematurely
  return unless outstanding_lease

  outstanding_lease.send_rollback_update!(deadline: deadline)
  outstanding_lease.destroy! # the lease is no longer needed
  @done = true
end

#trigger_transactional_callbacks?Boolean

Always trigger callbacks. See: ActiveRecord::ConnectionAdapters::Transaction#

prepare_instances_to_run_callbacks_on

github.com/rails/rails/blob/v7.1.5.2/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb#L269

Returns:

  • (Boolean)


86
87
88
# File 'app/models/cells/transaction_record.rb', line 86

def trigger_transactional_callbacks?
  true
end