Module: TypedEAV::BulkWrite

Defined in:
lib/typed_eav/bulk_write.rb

Overview

Internal executor for host-class bulk typed-value writes.

Host models keep the public ‘bulk_set_typed_eav_values` (uniform values-per-record) and `bulk_set_typed_eav_values_per_record` (per-record-varying values) APIs; this module owns the transaction shape, savepoint isolation, error aggregation, field name resolution delegation, and version-group stamping.

## Internal shape (G1, issue #18)

Both public executors (‘execute` and `execute_per_record`) are thin adapters: they validate their inputs, resolve the version grouping, allocate field UUIDs, and then hand off to `execute_pairs(pairs, effective_grouping, field_uuids)` — a single shared loop that takes ordered `[record, vbn]` pairs and runs the outer-transaction-plus- savepoint-per-record envelope.

Pair-shaped (not Hash-shaped) so ‘execute`’s ‘[record, vbn]` list can carry duplicate in-memory instances of the same persisted row without silently collapsing them via Hash-key collision — preserving `execute`’s byte-for-byte behavior contract.

Class Method Summary collapse

Class Method Details

.apply_record_save(record:, vbn:, effective_grouping:, uuids:, accumulator:) ⇒ Object



64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/typed_eav/bulk_write.rb', line 64

def apply_record_save(record:, vbn:, effective_grouping:, uuids:, accumulator:)
  push_uuid = case effective_grouping
              when :per_record then uuids[:record]
              when :per_field  then uuids[:field].values.first
              end

  do_save = lambda do
    record.typed_eav_attributes = vbn.map { |name, value| typed_eav_entry_for(name, value) }
    stamp_pending_version_group_ids(record, effective_grouping, uuids)

    if record.save
      accumulator[:successes] << record
    else
      accumulator[:errors_by_record][record] = record.errors.messages.transform_keys(&:to_s)
      raise ActiveRecord::Rollback
    end
  end

  if push_uuid
    TypedEAV.with_context(version_group_id: push_uuid, &do_save)
  else
    do_save.call
  end
end

.execute(host_class:, records:, values_by_field_name:, version_grouping: :default) ⇒ Object



27
28
29
30
31
32
33
34
35
36
37
38
39
40
# File 'lib/typed_eav/bulk_write.rb', line 27

def execute(host_class:, records:, values_by_field_name:, version_grouping: :default)
  validate_inputs!(records, values_by_field_name, version_grouping)

  records = records.to_a
  return { successes: [], errors_by_record: {} } if records.empty?

  validate_record_classes!(host_class, records)

  effective_grouping = resolve_grouping(version_grouping)
  vbn = values_by_field_name.transform_keys(&:to_s)
  field_uuids = effective_grouping == :per_field ? vbn.keys.index_with { SecureRandom.uuid } : nil

  execute_pairs(records.map { |r| [r, vbn] }, effective_grouping, field_uuids)
end

.execute_per_record(host_class:, values_by_record:, version_grouping: :default) ⇒ Object

Per-record-varying sibling to ‘execute`. Accepts a `Hash<host_record, Hash<field_name, value>>` and routes each record’s value-set through the same shared ‘execute_pairs` envelope.

Empty ‘values_by_record` short-circuits to the empty result without opening a transaction (matches `execute`’s empty-records contract).



48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/typed_eav/bulk_write.rb', line 48

def execute_per_record(host_class:, values_by_record:, version_grouping: :default)
  validate_per_record_inputs!(values_by_record, version_grouping)

  return { successes: [], errors_by_record: {} } if values_by_record.empty?

  validate_record_classes!(host_class, values_by_record.keys, method: :bulk_set_typed_eav_values_per_record)

  effective_grouping = resolve_grouping(version_grouping)
  pairs = values_by_record.map { |record, vbn| [record, vbn.transform_keys(&:to_s)] }
  field_uuids = if effective_grouping == :per_field
                  pairs.flat_map { |(_record, vbn)| vbn.keys }.uniq.index_with { SecureRandom.uuid }
                end

  execute_pairs(pairs, effective_grouping, field_uuids)
end