Module: TypedEAV::Field::TypedStorage
- Extended by:
- ActiveSupport::Concern
- Included in:
- Base
- Defined in:
- lib/typed_eav/field/typed_storage.rb
Overview
One concern owns the entire native-typed-column storage seam.
Field types declare WHICH typed column(s) hold their value via the class-level DSL (‘value_column`, `value_columns`, `operators`, `operator_column`), and override three instance methods to compose multi-cell value shapes (`read_value`, `write_value`, `apply_default`). Snapshot/change-detection helpers (`value_changed?`, `before_snapshot`, `after_snapshot`) are concrete and derive from `value_columns`; they are NOT overridable — the snapshot shape is a versioning-coupled invariant.
## Class-level DSL
value_column :integer_value # single-cell sugar; primary cell
value_columns :decimal_value, :string_value # plural form
operators :eq, :gt, :is_null # restrict supported operators
operator_column(:currency_eq) # override to route ops to cells
Both ‘value_column` and `value_columns` share the same `@value_columns` class instance variable. `value_column` returns the first element of `value_columns`, preserving the single-cell sugar shape.
## Override-point instance methods (the entire extension surface)
-
‘read_value(record)` — compose the logical value from the cells.
-
‘write_value(record, casted)` — unpack the casted value across cells.
-
‘apply_default(record)` — populate cells from the field’s default.
The default implementations target ‘value_columns.first` (single-cell behavior). Multi-cell types override ALL THREE — overriding just one creates an asymmetry where reads see the multi-cell shape but writes / defaults populate only one column (or vice versa).
## Concrete (non-overridable) snapshot helpers
-
‘value_changed?(record)` — true iff ANY value_columns column has a saved_change_to_attribute? — used by the Value :update dispatch gate so multi-cell types fire the event when only the second cell changed.
-
‘before_snapshot(record, change_type)` — per-column hash keyed by string column names. `:create` returns `{}`.
-
‘after_snapshot(record, change_type)` — per-column hash keyed by string column names. `:destroy` returns `{}`.
Snapshot keys are stringified so query patterns like ‘WHERE before_value->>’integer_value’ = ‘42’‘ work uniformly.
Constant Summary collapse
- DEFAULT_OPERATORS_BY_COLUMN =
{ boolean_value: %i[eq not_eq is_null is_not_null], string_value: %i[eq not_eq contains not_contains starts_with ends_with is_null is_not_null], text_value: %i[eq not_eq contains not_contains starts_with ends_with is_null is_not_null], integer_value: %i[eq not_eq gt gteq lt lteq between is_null is_not_null], decimal_value: %i[eq not_eq gt gteq lt lteq between is_null is_not_null], date_value: %i[eq not_eq gt gteq lt lteq between is_null is_not_null], datetime_value: %i[eq not_eq gt gteq lt lteq between is_null is_not_null], json_value: %i[contains is_null is_not_null], }.freeze
- FALLBACK_OPERATORS =
%i[eq not_eq is_null is_not_null].freeze
Instance Method Summary collapse
-
#after_snapshot(value_record, change_type) ⇒ Object
Post-change snapshot keyed by string column names.
-
#apply_default(value_record) ⇒ Object
Writes this field’s configured default to ‘value_record`.
-
#before_snapshot(value_record, change_type) ⇒ Object
Pre-change snapshot keyed by string column names.
-
#read_value(value_record) ⇒ Object
Returns the logical value for this field as stored on ‘value_record`.
-
#value_changed?(value_record) ⇒ Boolean
True iff ANY of the field’s value_columns had a saved change in the just-committed save.
-
#write_value(value_record, casted) ⇒ Object
Writes a casted value to ‘value_record`.
Instance Method Details
#after_snapshot(value_record, change_type) ⇒ Object
Post-change snapshot keyed by string column names.
-
:create / :update → => value_record
-
:destroy → {} (no after state)
193 194 195 196 197 198 199 200 201 202 |
# File 'lib/typed_eav/field/typed_storage.rb', line 193 def after_snapshot(value_record, change_type) case change_type.to_sym when :create, :update self.class.value_columns.to_h { |column| [column.to_s, value_record[column]] } when :destroy {} else raise ArgumentError, "Unsupported change_type: #{change_type.inspect}" end end |
#apply_default(value_record) ⇒ Object
Writes this field’s configured default to ‘value_record`. Default writes `default_value` to the primary cell, bypassing Value#value= to avoid re-casting an already-cast default. Override in multi-cell types to populate multiple cells from a composite default.
154 155 156 |
# File 'lib/typed_eav/field/typed_storage.rb', line 154 def apply_default(value_record) value_record[self.class.value_columns.first] = default_value end |
#before_snapshot(value_record, change_type) ⇒ Object
Pre-change snapshot keyed by string column names.
-
:create → {} (no before state)
-
:update → => attribute_before_last_save(col)
-
:destroy → => value_record (in-memory on the destroyed AR record per Phase 03 P04 live-validation)
175 176 177 178 179 180 181 182 183 184 185 186 187 188 |
# File 'lib/typed_eav/field/typed_storage.rb', line 175 def before_snapshot(value_record, change_type) case change_type.to_sym when :create {} when :update self.class.value_columns.to_h do |column| [column.to_s, value_record.attribute_before_last_save(column.to_s)] end when :destroy self.class.value_columns.to_h { |column| [column.to_s, value_record[column]] } else raise ArgumentError, "Unsupported change_type: #{change_type.inspect}" end end |
#read_value(value_record) ⇒ Object
139 140 141 |
# File 'lib/typed_eav/field/typed_storage.rb', line 139 def read_value(value_record) value_record[self.class.value_columns.first] end |
#value_changed?(value_record) ⇒ Boolean
True iff ANY of the field’s value_columns had a saved change in the just-committed save. Used by Value’s :update dispatch gate so multi-cell types correctly fire the event when only the second cell changed (regression case Phase 5 D3).
164 165 166 167 168 |
# File 'lib/typed_eav/field/typed_storage.rb', line 164 def value_changed?(value_record) self.class.value_columns.any? do |column| value_record.saved_change_to_attribute?(column) end end |
#write_value(value_record, casted) ⇒ Object
Writes a casted value to ‘value_record`. Default writes the primary cell. Override in multi-cell types to unpack the casted value across multiple cells.
146 147 148 |
# File 'lib/typed_eav/field/typed_storage.rb', line 146 def write_value(value_record, casted) value_record[self.class.value_columns.first] = casted end |