typed_eav 0.6.0 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +187 -0
  3. data/README.md +258 -62
  4. data/app/models/typed_eav/field/base.rb +77 -27
  5. data/app/models/typed_eav/field/currency.rb +43 -0
  6. data/app/models/typed_eav/field/file.rb +1 -1
  7. data/app/models/typed_eav/field/image.rb +1 -1
  8. data/app/models/typed_eav/field/reference.rb +11 -0
  9. data/app/models/typed_eav/section.rb +11 -5
  10. data/app/models/typed_eav/value.rb +95 -32
  11. data/db/migrate/20260430000000_add_parent_scope_to_typed_eav_partitions.rb +1 -1
  12. data/db/migrate/20260712000000_enforce_parent_scope_invariant.rb +4 -0
  13. data/db/migrate/20260816000000_use_partial_covering_scalar_indexes.rb +198 -0
  14. data/lib/typed_eav/bulk_read.rb +13 -14
  15. data/lib/typed_eav/bulk_upsert.rb +137 -0
  16. data/lib/typed_eav/bulk_write.rb +65 -39
  17. data/lib/typed_eav/config.rb +19 -21
  18. data/lib/typed_eav/csv_mapper.rb +1 -1
  19. data/lib/typed_eav/engine.rb +16 -27
  20. data/lib/typed_eav/entity_query.rb +34 -9
  21. data/lib/typed_eav/event_dispatcher.rb +21 -30
  22. data/lib/typed_eav/field/typed_storage.rb +48 -0
  23. data/lib/typed_eav/field_deletion.rb +75 -0
  24. data/lib/typed_eav/filter_query.rb +2 -2
  25. data/lib/typed_eav/has_typed_eav/instance_methods.rb +2 -2
  26. data/lib/typed_eav/has_typed_eav.rb +1 -1
  27. data/lib/typed_eav/partition/definition_batch.rb +70 -0
  28. data/lib/typed_eav/partition.rb +2 -0
  29. data/lib/typed_eav/query_builder.rb +17 -27
  30. data/lib/typed_eav/registry.rb +7 -8
  31. data/lib/typed_eav/version.rb +1 -1
  32. data/lib/typed_eav/versioned.rb +8 -6
  33. data/lib/typed_eav/versioning/subscriber.rb +25 -29
  34. data/lib/typed_eav/versioning.rb +59 -41
  35. data/lib/typed_eav.rb +2 -0
  36. metadata +5 -1
@@ -3,16 +3,10 @@
3
3
  module TypedEAV
4
4
  # Replaces the per-type Finder class hierarchy from active_fields.
5
5
  #
6
- # Because values live in native typed columns, ActiveRecord already knows
7
- # the column types from the schema. Arel predicates (eq, gt, lt, matches, etc.)
8
- # automatically go through the column's ActiveRecord::Type for casting.
9
- #
10
- # This means:
11
- # where(integer_value: "42") -> Rails casts "42" to 42 automatically
12
- # arel[:date_value].gt(value) -> Rails casts string dates to Date objects
13
- #
14
- # No manual CAST() calls. No per-type caster classes for queries.
15
- # One module handles all field types.
6
+ # Field owns operand casting and operator validation before this layer builds
7
+ # Arel. Active Record supplies bind plumbing for the already-normalized
8
+ # typed value; no manual SQL CAST() calls or per-type query caster classes
9
+ # are needed here.
16
10
  #
17
11
  # Usage:
18
12
  # QueryBuilder.filter(field, :gt, 42)
@@ -53,6 +47,8 @@ module TypedEAV
53
47
  arel_col = values_table[col]
54
48
 
55
49
  base = value_scope(field)
50
+ excluded = %i[is_null is_not_null contains not_contains starts_with ends_with]
51
+ operand = field.cast_query_operand(operator, value) unless excluded.include?(operator)
56
52
 
57
53
  case operator
58
54
  when :eq, :currency_eq
@@ -64,9 +60,9 @@ module TypedEAV
64
60
  # dispatch resolved correctly. The operator-validation gate at
65
61
  # the top of #filter still narrows :currency_eq to Field::Currency
66
62
  # only — no other field type accepts it.
67
- eq_predicate(base, arel_col, col, value)
63
+ eq_predicate(base, arel_col, col, operand)
68
64
  when :not_eq
69
- not_eq_predicate(base, arel_col, col, value)
65
+ not_eq_predicate(base, arel_col, col, operand)
70
66
  when :references
71
67
  # Phase 5 Reference field. `value` may be an Integer FK OR an
72
68
  # AR record instance — `field.cast` normalizes both to an
@@ -79,27 +75,21 @@ module TypedEAV
79
75
  # The :references operator is registered ONLY on Field::Reference
80
76
  # (the operator-validation gate above keeps it from leaking to
81
77
  # other types).
82
- fk, invalid = field.cast(value)
83
- if invalid || fk.nil?
78
+ if operand.nil?
84
79
  base.none
85
80
  else
86
- base.where(arel_col.eq(fk))
81
+ base.where(arel_col.eq(operand))
87
82
  end
88
83
  when :gt
89
- base.where(arel_col.gt(value))
84
+ base.where(arel_col.gt(operand))
90
85
  when :gteq
91
- base.where(arel_col.gteq(value))
86
+ base.where(arel_col.gteq(operand))
92
87
  when :lt
93
- base.where(arel_col.lt(value))
88
+ base.where(arel_col.lt(operand))
94
89
  when :lteq
95
- base.where(arel_col.lteq(value))
90
+ base.where(arel_col.lteq(operand))
96
91
  when :between
97
- unless value.respond_to?(:first) && value.respond_to?(:last)
98
- raise ArgumentError,
99
- ":between expects a Range or two-element Array"
100
- end
101
-
102
- base.where(arel_col.between(value.first..value.last))
92
+ base.where(arel_col.between(operand))
103
93
  when :contains
104
94
  base.where(arel_col.matches("%#{sanitize_like(value)}%"))
105
95
  when :not_contains
@@ -114,10 +104,10 @@ module TypedEAV
114
104
  base.where.not(col => nil)
115
105
  when :any_eq
116
106
  # For json_value arrays: contains the given element
117
- base.where("#{col} @> ?", [value].to_json)
107
+ base.where("#{col} @> ?", [operand].to_json)
118
108
  when :all_eq
119
109
  # For json_value arrays: contains all given elements
120
- base.where("#{col} @> ?", Array(value).to_json)
110
+ base.where("#{col} @> ?", operand.to_json)
121
111
  else
122
112
  raise ArgumentError, "Unhandled operator: #{operator}"
123
113
  end
@@ -25,12 +25,11 @@ module TypedEAV
25
25
  # Register an entity type with optional type restrictions and optional
26
26
  # versioning opt-in.
27
27
  #
28
- # `versioned:` is the per-entity Phase 04 opt-in flag. When true, AND
29
- # `Config.versioning = true` at engine load (gem-level master switch),
30
- # the Phase 04 subscriber writes a TypedEAV::ValueVersion row per
31
- # Value mutation on this entity_type. Default false — apps not using
32
- # versioning pay zero cost (one Hash#dig per write at most when
33
- # `Config.versioning = true`, nothing when off).
28
+ # `versioned:` is the per-entity opt-in flag. When true and the
29
+ # boot-latched transactional callbacks are installed, the version
30
+ # writer records a TypedEAV::ValueVersion row per Value mutation on
31
+ # this entity_type. Default false — apps not using versioning pay zero
32
+ # version-row work.
34
33
  #
35
34
  # Backward compat: existing callers `register(name, types: types)`
36
35
  # continue to work — the new kwarg defaults to false. The entry hash
@@ -69,8 +68,8 @@ module TypedEAV
69
68
  # Returns the stored boolean for opted-in entities; false for
70
69
  # unregistered entities (defensive — callers might query before
71
70
  # `has_typed_eav` runs in a particular load order). The Phase 04
72
- # subscriber calls this on every Value write when `Config.versioning =
73
- # true` performance is one Hash#dig per write, negligible.
71
+ # transactional writer calls this only after its boot-latched callback
72
+ # has been installed; this lookup remains a small Hash#dig per write.
74
73
  #
75
74
  # `entities.dig(entity_type, :versioned)` returns nil when
76
75
  # `entities[entity_type]` is missing (no register call) OR when the
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TypedEAV
4
- VERSION = "0.6.0"
4
+ VERSION = "0.7.1"
5
5
  end
@@ -62,12 +62,14 @@ module TypedEAV
62
62
 
63
63
  # Re-register with versioned: true. Preserve the existing types:
64
64
  # restriction by reading the current Registry entry.
65
- # has_typed_eav already called register(name, types: types,
66
- # versioned: false) we overwrite with versioned: true while
67
- # keeping the same types. If the entry doesn't exist (defensive
68
- # — shouldn't happen post-has_typed_eav), default types to nil.
69
- existing = TypedEAV.registry.entities[name] || {}
70
- TypedEAV.registry.register(name, types: existing[:types], versioned: true)
65
+ # has_typed_eav already registered the canonical Rails polymorphic
66
+ # name. Reuse it here so including this concern on an STI subclass
67
+ # enables versioning for the base entity_type actually stored on Value.
68
+ # If the entry doesn't exist (defensive — shouldn't happen post-
69
+ # has_typed_eav), default types to nil.
70
+ entity_type = polymorphic_name
71
+ existing = TypedEAV.registry.entities[entity_type] || {}
72
+ TypedEAV.registry.register(entity_type, types: existing[:types], versioned: true)
71
73
  end
72
74
  end
73
75
  end
@@ -2,15 +2,15 @@
2
2
 
3
3
  module TypedEAV
4
4
  module Versioning
5
- # The Phase 04 internal subscriber. Conditionally registered with
6
- # EventDispatcher.register_internal_value_change at engine boot via
7
- # `TypedEAV::Versioning.register_if_enabled`. When registered, runs
8
- # at slot 0 of the value-change subscriber chain.
5
+ # The transactional version writer. Conditionally installed on Value's
6
+ # create/update `after` and destroy `before` callbacks at engine boot via
7
+ # `TypedEAV::Versioning.register_if_enabled`; every write is therefore
8
+ # part of the source transaction.
9
9
  #
10
10
  # ## Contract
11
11
  #
12
- # `call(value, change_type, context)` — called by EventDispatcher.
13
- # Returns nil (return value is ignored by EventDispatcher; the
12
+ # `call(value, change_type, context)` — called by Value's lifecycle
13
+ # callback. Returns nil (the return value is ignored; the
14
14
  # method's job is the side effect of writing a ValueVersion row).
15
15
  #
16
16
  # Two-gate short-circuit (the master switch is enforced at
@@ -54,16 +54,13 @@ module TypedEAV
54
54
  # nil").
55
55
  module Subscriber
56
56
  class << self
57
- # Public entry point. EventDispatcher calls this with the locked
58
- # 3-arg signature `(value, change_type, context)`.
57
+ # Public entry point. Value's transactional callbacks call this with
58
+ # the locked 3-arg signature `(value, change_type, context)`.
59
59
  #
60
- # NOTE: there is NO `Config.versioning` gate here. The subscriber
61
- # is only registered with EventDispatcher when `Config.versioning`
62
- # was true at engine `config.after_initialize` time (see
63
- # `TypedEAV::Versioning.register_if_enabled`, invoked from
64
- # lib/typed_eav/engine.rb's `config.after_initialize` block). If
65
- # versioning is off, the subscriber is never registered and never
66
- # reached. The remaining gates are:
60
+ # NOTE: there is NO `Config.versioning` gate here. The subscriber is
61
+ # installed only when `Config.versioning` was true at engine
62
+ # `config.after_initialize` time. If versioning is off, the callback
63
+ # is never installed and this method is never reached. The remaining gates are:
67
64
  # 1. field-presence (orphan guard — Value's field_id may have
68
65
  # been NULLed by Phase 02's ON DELETE SET NULL cascade).
69
66
  # 2. per-entity opt-in (Registry.versioned?).
@@ -72,6 +69,8 @@ module TypedEAV
72
69
  return unless TypedEAV.registry.versioned?(value.entity_type)
73
70
 
74
71
  write_version_row(value, change_type, context)
72
+ ensure
73
+ value.consume_pending_version_group_id if value.respond_to?(:consume_pending_version_group_id)
75
74
  end
76
75
 
77
76
  private
@@ -85,15 +84,9 @@ module TypedEAV
85
84
  before_value = field.before_snapshot(value, change_type)
86
85
  after_value = field.after_snapshot(value, change_type)
87
86
 
88
- # CRITICAL: for :destroy events, write `value_id: nil`.
89
- # By the time `after_commit on: :destroy` fires, the parent row
90
- # in `typed_eav_values` has already been deleted (Postgres
91
- # commits the DELETE before invoking after_commit callbacks).
92
- # The FK is `ON DELETE SET NULL`, but at the moment we INSERT
93
- # the version row, Postgres validates the FK against the
94
- # current state of typed_eav_values — which no longer contains
95
- # the parent. Writing `value.id` (still readable in-memory on
96
- # the destroyed AR record) would FK-fail at INSERT.
87
+ # CRITICAL: for :destroy events, write `value_id: nil`. The row is
88
+ # written before the source DELETE and must not retain an identity
89
+ # that the FK will null when the Value is removed.
97
90
  #
98
91
  # The audit trail for destroy events stays queryable via:
99
92
  # - entity_type + entity_id (host record identity)
@@ -102,8 +95,7 @@ module TypedEAV
102
95
  # `field_id` remains populated because destroying a Value does
103
96
  # not destroy its Field — `value.field_id` is a live reference.
104
97
  #
105
- # For :create and :update events, `value_id: value.id` is
106
- # correct (parent row exists at after_commit time).
98
+ # For :create and :update, `value_id: value.id` is correct.
107
99
  version_value_id = change_type == :destroy ? nil : value.id
108
100
 
109
101
  # Phase 06 bulk-operations correlation tag. Two delivery paths:
@@ -113,7 +105,7 @@ module TypedEAV
113
105
  # on each affected Value object BEFORE `record.save`, INSIDE
114
106
  # the per-record `with_context` block. Reading the snapshot
115
107
  # here (not `current_context`) guarantees the UUID survives
116
- # the outer-transaction `after_commit` boundary — by the time
108
+ # the source transaction boundary — by the time
117
109
  # we run, the lexical `with_context` block has unwound and
118
110
  # `current_context` would be empty, but the per-Value ivar
119
111
  # persists. Mirrors the existing in-memory ivar pattern at
@@ -127,12 +119,16 @@ module TypedEAV
127
119
  # request id). For those paths the Value ivar is unset; the
128
120
  # `||` falls through to `context[:version_group_id]`. Also
129
121
  # used as the belt-and-suspenders fallback for any future
130
- # after_commit-inside-savepoint dispatch path that bulk writes
122
+ # savepoint path that bulk writes
131
123
  # might leverage.
132
124
  #
133
125
  # When neither path supplies a UUID the column (added in
134
126
  # `db/migrate/20260506000001`) stays NULL — backward-compatible:
135
127
  # unchanged subscribers and unchanged callers continue to work.
128
+ pending_group_id = if value.respond_to?(:consume_pending_version_group_id)
129
+ value.consume_pending_version_group_id
130
+ end
131
+
136
132
  TypedEAV::ValueVersion.create!(
137
133
  value_id: version_value_id,
138
134
  field_id: value.field_id,
@@ -142,7 +138,7 @@ module TypedEAV
142
138
  before_value: before_value,
143
139
  after_value: after_value,
144
140
  context: context.to_h, # frozen → unfrozen jsonb-serializable hash
145
- version_group_id: value.pending_version_group_id || context[:version_group_id],
141
+ version_group_id: pending_group_id || context[:version_group_id],
146
142
  change_type: change_type.to_s,
147
143
  changed_at: Time.current,
148
144
  )
@@ -1,23 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TypedEAV
4
- # Phase 04 versioning namespace. Houses the Subscriber that writes
5
- # TypedEAV::ValueVersion rows in response to Value lifecycle events
6
- # dispatched by EventDispatcher.
4
+ # Transactional versioning namespace. Houses the Subscriber that writes
5
+ # TypedEAV::ValueVersion rows from Value lifecycle callbacks.
7
6
  #
8
7
  # ## Architecture
9
8
  #
10
9
  # - TypedEAV::Versioning::Subscriber.call(value, change_type, context)
11
- # is conditionally registered with
12
- # EventDispatcher.register_internal_value_change at engine boot via
13
- # `TypedEAV::Versioning.register_if_enabled`, which is invoked from
14
- # the `config.after_initialize` block in lib/typed_eav/engine.rb.
15
- # When TypedEAV.config.versioning is false (default), the helper
16
- # returns early — no callable is added to the dispatcher chain.
17
- # When true, the subscriber registers and runs FIRST in the value-
18
- # change subscriber chain (slot 0 by `after_initialize` block
19
- # declaration order — Phase 07 will declare its matview block LATER
20
- # in the same engine to keep matview at slot ≥ 1).
10
+ # is conditionally installed on Value's transactional callbacks at engine
11
+ # boot via `TypedEAV::Versioning.register_if_enabled`. The version row
12
+ # therefore shares the source transaction.
21
13
  #
22
14
  # - The subscriber is gated by TWO checks at call time (both must
23
15
  # pass for a version row to be written):
@@ -30,10 +22,9 @@ module TypedEAV
30
22
  # the callable — when false, the subscriber is never registered in
31
23
  # the first place.
32
24
  #
33
- # - Errors raised by Subscriber.call PROPAGATE per the EventDispatcher
34
- # internal-vs-user error policy (03-CONTEXT.md §User-callback error
35
- # policy). Versioning corruption must be loud — silent failure
36
- # leaves the audit log inconsistent with the live row.
25
+ # - Errors raised by Subscriber.call propagate. Versioning corruption must
26
+ # be loud silent failure leaves the audit log inconsistent with the live
27
+ # row.
37
28
  #
38
29
  # ## Public API surface
39
30
  #
@@ -52,43 +43,70 @@ module TypedEAV
52
43
  # this namespace shell — it does NOT recursively autoload nested
53
44
  # constants. Without the explicit declaration below, the engine's
54
45
  # config.after_initialize block (which references
55
- # `TypedEAV::Versioning::Subscriber.method(:call)`) raises
46
+ # `TypedEAV::Versioning::Subscriber`) raises
56
47
  # `NameError: uninitialized constant TypedEAV::Versioning::Subscriber`
57
48
  # at boot time, breaking every host that enables versioning.
58
49
  autoload :Subscriber, "typed_eav/versioning/subscriber"
59
50
 
60
- # Conditionally register the Subscriber with EventDispatcher's internal
61
- # value-change subscriber chain. Called by the engine's
62
- # `config.after_initialize` block (lib/typed_eav/engine.rb).
51
+ CALLBACKS = {
52
+ create: %i[_write_version_create after].freeze,
53
+ update: %i[_write_version_update after].freeze,
54
+ destroy: %i[_write_version_destroy before].freeze,
55
+ }.freeze
56
+
57
+ # Conditionally install the Subscriber on Value's transactional callbacks.
58
+ # Called by the engine's `config.after_initialize` block.
63
59
  #
64
60
  # Extracted into a class method (not inlined inside the after_initialize
65
- # block) for testability: specs can call this against a freshly-cleared
66
- # `EventDispatcher.value_change_internals` to exercise both branches
67
- # (versioning on/off) in-process, without booting the engine. The
68
- # slot-0 regression spec (plan 04-03 P03) and the zero-overhead
69
- # verification spec (this plan, subscriber_spec engine-boot block) both
70
- # rely on this seam.
61
+ # block) for testability: specs can call this seam in-process without
62
+ # booting a second Rails application.
71
63
  #
72
- # Idempotent — safe to call multiple times. Calling twice with
73
- # versioning on results in exactly ONE entry in
74
- # `EventDispatcher.value_change_internals`. The idempotency check uses
75
- # `Array#include?` against `Subscriber.method(:call)`; `Method#==`
76
- # compares receiver+name (semantic equality), so two fresh
77
- # `Subscriber.method(:call)` instances compare equal even though they
78
- # are different Method objects. The engine block runs this exactly
79
- # once per boot in production; the idempotency guard protects future
80
- # code paths that might re-invoke for any reason.
64
+ # Idempotent — safe to call multiple times. Pool validation happens before
65
+ # any callback is installed, so a multi-database misconfiguration fails
66
+ # closed without partial activation.
81
67
  #
82
68
  # When `TypedEAV.config.versioning` is false (default), this method is
83
- # a no-op: zero callable in `value_change_internals`, zero per-write
84
- # dispatch cost. That is the locked CONTEXT line 17 contract.
69
+ # a no-op: no callback is installed and the disabled path adds no
70
+ # per-write predicate or dispatcher work.
85
71
  def self.register_if_enabled
86
72
  return unless TypedEAV.config.versioning
87
73
 
88
- method_ref = TypedEAV::Versioning::Subscriber.method(:call)
89
- return if TypedEAV::EventDispatcher.value_change_internals.include?(method_ref)
74
+ value_pool = TypedEAV::Value.connection_pool
75
+ version_pool = TypedEAV::ValueVersion.connection_pool
76
+ unless value_pool.equal?(version_pool)
77
+ raise ArgumentError, "TypedEAV versioning requires Value and ValueVersion to share a connection pool"
78
+ end
79
+
80
+ callback_chains = callback_chains_for_installation
81
+
82
+ callback_chains.each do |event, (filter, kind, chain)|
83
+ next if chain.any? { |callback| callback.filter == filter && callback.kind == kind }
84
+
85
+ TypedEAV::Value.set_callback(event, kind, filter, prepend: true)
86
+ end
87
+ end
88
+
89
+ def self.callback_chains_for_installation
90
+ CALLBACKS.to_h do |event, (filter, kind)|
91
+ chain = TypedEAV::Value.send(:get_callbacks, event).to_a
92
+ wrong_kind = chain.find { |callback| callback.filter == filter && callback.kind != kind }
93
+ if wrong_kind
94
+ raise ArgumentError,
95
+ "TypedEAV versioning callback #{filter.inspect} on #{event} has kind " \
96
+ "#{wrong_kind.kind.inspect}; expected #{kind.inspect}"
97
+ end
98
+
99
+ [event, [filter, kind, chain]]
100
+ end
101
+ end
102
+ private_class_method :callback_chains_for_installation
90
103
 
91
- TypedEAV::EventDispatcher.register_internal_value_change(method_ref)
104
+ def self.atomic_callbacks_installed?
105
+ CALLBACKS.all? do |event, (filter, kind)|
106
+ TypedEAV::Value.send(:get_callbacks, event).to_a.any? do |callback|
107
+ callback.filter == filter && callback.kind == kind
108
+ end
109
+ end
92
110
  end
93
111
  end
94
112
  end
data/lib/typed_eav.rb CHANGED
@@ -18,6 +18,7 @@ module TypedEAV
18
18
  autoload :FilterQuery
19
19
  autoload :BulkRead
20
20
  autoload :BulkWrite
21
+ autoload :BulkUpsert
21
22
  autoload :Partition
22
23
  autoload :QueryBuilder
23
24
  autoload :SchemaPortability
@@ -27,6 +28,7 @@ module TypedEAV
27
28
  autoload :ValueVersion
28
29
  autoload :Versioned
29
30
  autoload :Versioning
31
+ autoload :FieldDeletion
30
32
 
31
33
  # Raised when a model declared `has_typed_eav scope_method: ...` but no
32
34
  # scope can be resolved at query time and `config.require_scope` is truthy.
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: typed_eav
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.7.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - dchuk
@@ -94,6 +94,7 @@ files:
94
94
  - db/migrate/20260506000001_add_version_group_id_to_typed_eav_value_versions.rb
95
95
  - db/migrate/20260507000000_add_label_to_typed_eav_fields.rb
96
96
  - db/migrate/20260712000000_enforce_parent_scope_invariant.rb
97
+ - db/migrate/20260816000000_use_partial_covering_scalar_indexes.rb
97
98
  - lib/generators/typed_eav/install/install_generator.rb
98
99
  - lib/generators/typed_eav/scaffold/scaffold_generator.rb
99
100
  - lib/generators/typed_eav/scaffold/templates/config/initializers/typed_eav.rb
@@ -145,6 +146,7 @@ files:
145
146
  - lib/generators/typed_eav/scaffold/templates/views/typed_eav/values/inputs/_url.html.erb
146
147
  - lib/typed_eav.rb
147
148
  - lib/typed_eav/bulk_read.rb
149
+ - lib/typed_eav/bulk_upsert.rb
148
150
  - lib/typed_eav/bulk_write.rb
149
151
  - lib/typed_eav/config.rb
150
152
  - lib/typed_eav/csv_mapper.rb
@@ -152,10 +154,12 @@ files:
152
154
  - lib/typed_eav/entity_query.rb
153
155
  - lib/typed_eav/event_dispatcher.rb
154
156
  - lib/typed_eav/field/typed_storage.rb
157
+ - lib/typed_eav/field_deletion.rb
155
158
  - lib/typed_eav/filter_query.rb
156
159
  - lib/typed_eav/has_typed_eav.rb
157
160
  - lib/typed_eav/has_typed_eav/instance_methods.rb
158
161
  - lib/typed_eav/partition.rb
162
+ - lib/typed_eav/partition/definition_batch.rb
159
163
  - lib/typed_eav/query_builder.rb
160
164
  - lib/typed_eav/registry.rb
161
165
  - lib/typed_eav/schema_portability.rb