Class: Smith::PersistenceAdapters::ActiveRecordStore
- Inherits:
-
Object
- Object
- Smith::PersistenceAdapters::ActiveRecordStore
- Defined in:
- lib/smith/persistence_adapters/active_record_store.rb
Constant Summary collapse
- TRANSIENT_ERROR_NAMES =
AR transient errors resolved via class-name guard so Smith doesn’t require activerecord at load time. Hosts that use this adapter already have activerecord in their dep tree.
%w[ ActiveRecord::ConnectionNotEstablished ActiveRecord::StatementInvalid ActiveRecord::TransactionIsolationConflict ].freeze
Class Method Summary collapse
Instance Method Summary collapse
- #delete(key) ⇒ Object
- #fetch(key) ⇒ Object
-
#initialize(model:, key_column: :key, payload_column: :payload, version_column: :lock_version) ⇒ ActiveRecordStore
constructor
A new instance of ActiveRecordStore.
-
#store(key, payload, ttl: nil) ⇒ Object
rubocop:disable Lint/UnusedMethodArgument.
-
#store_versioned(key, payload, expected_version:, ttl: nil) ⇒ Object
Optimistic locking via Rails’ built-in optimistic locking on the ‘lock_version` column.
Constructor Details
#initialize(model:, key_column: :key, payload_column: :payload, version_column: :lock_version) ⇒ ActiveRecordStore
Returns a new instance of ActiveRecordStore.
23 24 25 26 27 28 |
# File 'lib/smith/persistence_adapters/active_record_store.rb', line 23 def initialize(model:, key_column: :key, payload_column: :payload, version_column: :lock_version) @model_source = model @key_column = key_column @payload_column = payload_column @version_column = version_column end |
Class Method Details
.transient_errors ⇒ Object
15 16 17 18 19 20 21 |
# File 'lib/smith/persistence_adapters/active_record_store.rb', line 15 def self.transient_errors TRANSIENT_ERROR_NAMES.filter_map do |name| Object.const_get(name) rescue NameError nil end end |
Instance Method Details
#delete(key) ⇒ Object
47 48 49 50 51 |
# File 'lib/smith/persistence_adapters/active_record_store.rb', line 47 def delete(key) Retry.with_retries(operation: :delete, transient: self.class.transient_errors) do model_class.where(@key_column => key).delete_all end end |
#fetch(key) ⇒ Object
41 42 43 44 45 |
# File 'lib/smith/persistence_adapters/active_record_store.rb', line 41 def fetch(key) Retry.with_retries(operation: :fetch, transient: self.class.transient_errors) do model_class.find_by(@key_column => key)&.public_send(@payload_column) end end |
#store(key, payload, ttl: nil) ⇒ Object
rubocop:disable Lint/UnusedMethodArgument
30 31 32 33 34 35 36 37 38 39 |
# File 'lib/smith/persistence_adapters/active_record_store.rb', line 30 def store(key, payload, ttl: nil) # rubocop:disable Lint/UnusedMethodArgument # TTL is deferred for ActiveRecordStore — would require an # `expires_at` column + a periodic sweeper job. Ignored here; # documented as a known limitation. Retry.with_retries(operation: :store, transient: self.class.transient_errors) do record = model_class.find_or_initialize_by(@key_column => key) record.public_send(:"#{@payload_column}=", payload) record.save! end end |
#store_versioned(key, payload, expected_version:, ttl: nil) ⇒ Object
Optimistic locking via Rails’ built-in optimistic locking on the ‘lock_version` column. Requires the AR model to have a `lock_version` (or configured) integer column with default 0. If absent, raises ArgumentError directing the host to migrate.
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
# File 'lib/smith/persistence_adapters/active_record_store.rb', line 57 def store_versioned(key, payload, expected_version:, ttl: nil) # rubocop:disable Lint/UnusedMethodArgument unless model_class.column_names.include?(@version_column.to_s) raise ArgumentError, "ActiveRecordStore#store_versioned requires a #{@version_column} column on " \ "#{model_class.name}. Add via: " \ "add_column :#{model_class.table_name}, :#{@version_column}, :integer, default: 0" end Retry.with_retries(operation: :store_versioned, transient: self.class.transient_errors) do record = model_class.find_or_initialize_by(@key_column => key) if record.persisted? && record.public_send(@version_column) != expected_version raise Smith::PersistenceVersionConflict.new( key: key, expected: expected_version, actual: record.public_send(@version_column) ) end record.public_send(:"#{@payload_column}=", payload) record.save! rescue defined?(::ActiveRecord::StaleObjectError) ? ::ActiveRecord::StaleObjectError : StandardError => e raise unless defined?(::ActiveRecord::StaleObjectError) && e.is_a?(::ActiveRecord::StaleObjectError) raise Smith::PersistenceVersionConflict.new( key: key, expected: expected_version, actual: :concurrent ) end end |