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.:createreturns{}.after_snapshot(record, change_type)— per-column hash keyed by string column names.:destroyreturns{}.
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[col]
- :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[col] (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
Returns the logical value for this field as stored on value_record.
Default reads the primary cell. Override in multi-cell types to
compose a hash (e.g., Field::Currency returns
{amount: r[:decimal_value], currency: r[:string_value]}).
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 |