Class: ActiveStorage::AwsRecord::VariantRecord
- Inherits:
-
Object
- Object
- ActiveStorage::AwsRecord::VariantRecord
- Defined in:
- lib/active_storage/aws_record/variant_record.rb
Overview
Tracks a processed variant of a blob when config.active_storage.track_variants is on. It is co-located in its blob’s partition (+ns#Blob#<blob_id>+) with a sort key of ns#VariantRecord#<variation_digest>, so the (blob_id, variation_digest) pair is unique by construction and the blob and all its variants form one item collection. It is itself an attachment owner (+has_one_attached :image+); its owner id is a reversible, #-free encoding of the natural key so find(id) resolves it.
Defined Under Namespace
Classes: CreateConflict
Constant Summary collapse
- ID_DELIMITER =
' '
Class Method Summary collapse
-
.active_storage_find(id) ⇒ Object
Owner resolution for Active Storage (VariantRecord is an attachment owner via
image). -
.create_or_find_by!(blob_id:, variation_digest:) ⇒ Object
Race-safe creation: a conditional put on the variant’s own key, paired with a check that the source blob still exists, so a variant can never be created against a just-purged blob.
- .decode_id(id) ⇒ Object
- .encode_id(blob_id, variation_digest) ⇒ Object
-
.find(id) ⇒ Object
Contract find(id): decode the reversible id back to the natural key.
- .find_by(blob_id:, variation_digest:) ⇒ Object
-
.logical_keys_for(blob_id, variation_digest) ⇒ Object
Logical keys for a (blob_id, variation_digest) pair without an instance.
-
.where_blob(blob_id) ⇒ Object
All variant records for a blob (used by Blob#destroy’s variant sweep).
Instance Method Summary collapse
-
#id ⇒ Object
The owner id used as
record_idfor theimageattachment; reversible so VariantRecord.find(id) resolves the natural key. - #logical_keys ⇒ Object
- #save(opts = {}) ⇒ Object
-
#save!(opts = {}) ⇒ Object
Persist the variant row transactionally and run the owner save/commit callbacks, so a queued
imageattachment (from create_or_find_by!‘s block) is saved and uploaded.
Methods included from Owner
Methods included from Item
#ns_key, #physical_key, #schema, #stamp_physical_keys!
Methods included from Persistence
#==, #changed?, #dynamodb_client, #hash, #mark_destroyed!, #mark_persisted!, #read_attribute, #write_attribute
Class Method Details
.active_storage_find(id) ⇒ Object
Owner resolution for Active Storage (VariantRecord is an attachment owner via image). Delegate to the reversible-id #find: Attachable‘s default find_with_opts(hash_key => id) adapter cannot address the composite key.
48 49 50 |
# File 'lib/active_storage/aws_record/variant_record.rb', line 48 def active_storage_find(id) find(id) end |
.create_or_find_by!(blob_id:, variation_digest:) ⇒ Object
Race-safe creation: a conditional put on the variant’s own key, paired with a check that the source blob still exists, so a variant can never be created against a just-purged blob. A condition failure (the variant already exists, or the blob is gone) falls back to find. The block (used by VariantWithRecord to image.attach the processed file) runs before save so the queued image attachment is flushed by the save callbacks.
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
# File 'lib/active_storage/aws_record/variant_record.rb', line 58 def create_or_find_by!(blob_id:, variation_digest:) record = new(blob_id: blob_id.to_s, variation_digest: variation_digest) yield record if block_given? record.save! record rescue CreateConflict # Only a genuine create conflict (the row already exists) falls back to # find — a post-write failure (image attachment, blob gone) propagates. # NOTE: the marker row commits just before its image attachment is # flushed, so a *concurrent* processor that loses the race here can # briefly observe the record before its image is attached (a transient # that resolves once the winner finishes; like Active Storage's # create-then-upload, but without AR's single enclosing transaction). find_by(blob_id: blob_id, variation_digest: variation_digest) || raise(ActiveStorage::RecordNotSaved.new('Failed to create or find variant record', record)) end |
.decode_id(id) ⇒ Object
108 109 110 |
# File 'lib/active_storage/aws_record/variant_record.rb', line 108 def decode_id(id) Base64.urlsafe_decode64(id).split(ID_DELIMITER, 2) end |
.encode_id(blob_id, variation_digest) ⇒ Object
104 105 106 |
# File 'lib/active_storage/aws_record/variant_record.rb', line 104 def encode_id(blob_id, variation_digest) Base64.urlsafe_encode64("#{blob_id}#{ID_DELIMITER}#{variation_digest}", padding: false) end |
.find(id) ⇒ Object
Contract find(id): decode the reversible id back to the natural key.
32 33 34 35 36 37 38 |
# File 'lib/active_storage/aws_record/variant_record.rb', line 32 def find(id) blob_id, variation_digest = decode_id(id) find_by(blob_id: blob_id, variation_digest: variation_digest) || raise(ActiveStorage::RecordNotFound, "Couldn't find #{name} with id=#{id.inspect}") rescue ArgumentError raise ActiveStorage::RecordNotFound, "Couldn't find #{name} with id=#{id.inspect}" end |
.find_by(blob_id:, variation_digest:) ⇒ Object
40 41 42 43 |
# File 'lib/active_storage/aws_record/variant_record.rb', line 40 def find_by(blob_id:, variation_digest:) keys = logical_keys_for(blob_id.to_s, variation_digest) get_item(**keys) end |
.logical_keys_for(blob_id, variation_digest) ⇒ Object
Logical keys for a (blob_id, variation_digest) pair without an instance.
96 97 98 99 100 101 102 |
# File 'lib/active_storage/aws_record/variant_record.rb', line 96 def logical_keys_for(blob_id, variation_digest) { h: ns_key('Blob', blob_id), r: ns_key('VariantRecord', variation_digest), item_id: ns_key('Blob', blob_id, 'VariantRecord', variation_digest), } end |
.where_blob(blob_id) ⇒ Object
All variant records for a blob (used by Blob#destroy’s variant sweep). Mode A: strong base-table query under the blob partition. Mode B: GSI.
77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
# File 'lib/active_storage/aws_record/variant_record.rb', line 77 def where_blob(blob_id) schema = ActiveStorage::AwsRecord.schema partition = ns_key('Blob', blob_id) prefix = "#{ns_key('VariantRecord')}#{ActiveStorage::AwsRecord.config.separator}" opts = { key_condition_expression: '#h = :h AND begins_with(#r, :r)', expression_attribute_values: { ':h' => partition, ':r' => prefix }, } if schema.range_mode? opts[:expression_attribute_names] = { '#h' => schema.partition_attr, '#r' => schema.sort_attr } opts[:consistent_read] = true else opts[:index_name] = schema.index_name opts[:expression_attribute_names] = { '#h' => schema.index_partition_attr, '#r' => schema.index_sort_attr } end query(opts).to_a end |
Instance Method Details
#id ⇒ Object
The owner id used as record_id for the image attachment; reversible so VariantRecord.find(id) resolves the natural key.
153 154 155 156 157 |
# File 'lib/active_storage/aws_record/variant_record.rb', line 153 def id return nil if blob_id.nil? || variation_digest.nil? self.class.encode_id(blob_id, variation_digest) end |
#logical_keys ⇒ Object
113 114 115 |
# File 'lib/active_storage/aws_record/variant_record.rb', line 113 def logical_keys self.class.logical_keys_for(blob_id, variation_digest) end |
#save(opts = {}) ⇒ Object
144 145 146 147 148 149 |
# File 'lib/active_storage/aws_record/variant_record.rb', line 144 def save(opts = {}) save!(opts) true rescue ActiveStorage::RecordNotSaved, CreateConflict false end |
#save!(opts = {}) ⇒ Object
Persist the variant row transactionally and run the owner save/commit callbacks, so a queued image attachment (from create_or_find_by!‘s block) is saved and uploaded. Overrides Owner#save! to substitute the transactional create for aws-record’s plain put. If a step after the row write fails (e.g. the image attachment), the half-written row is removed so it does not become a markerless “poison” record.
123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 |
# File 'lib/active_storage/aws_record/variant_record.rb', line 123 def save!(opts = {}) marker_written = false completed = run_callbacks(:save) do stamp_physical_keys! if new_record? transactional_create! marker_written = true end true end raise ActiveStorage::RecordNotSaved.new('Save halted by a before_save callback', self) unless completed run_callbacks(:commit) self rescue CreateConflict raise rescue StandardError destroy_marker! if marker_written raise end |