Module: TypedEAV::HasTypedEAV::InstanceMethods
- Defined in:
- lib/typed_eav/has_typed_eav/instance_methods.rb
Overview
Per-record API mixed into host AR models by the has_typed_eav macro.
Reads/writes typed values via field name, returns scope/parent_scope
via the configured accessor methods, and builds the collision-collapsed
per-instance definition map (delegating to Partition.definitions_by_name
so the class-query path and the instance path share one source of truth).
Instance Method Summary collapse
-
#initialize_typed_values ⇒ Object
Build missing values with defaults for all available fields.
-
#set_typed_eav_value(name, value) ⇒ Object
Set a specific field's value by name.
-
#typed_eav_attributes=(attributes) ⇒ Object
(also: #typed_eav=)
Bulk assign values by field NAME.
-
#typed_eav_definitions ⇒ Object
The field definitions available for this record.
-
#typed_eav_hash ⇒ Object
Hash of all field values: { "field_name" => value, ... }.
-
#typed_eav_parent_scope ⇒ Object
Current parent_scope value (for two-level partitioning).
-
#typed_eav_scope ⇒ Object
Current scope value (for multi-tenant).
-
#typed_eav_value(name) ⇒ Object
Get a specific field's value by name.
Instance Method Details
#initialize_typed_values ⇒ Object
Build missing values with defaults for all available fields. Useful in forms to show all fields even when no value exists yet.
Iterates the collision-collapsed view (typed_eav_defs_by_name)
rather than the raw definitions list. Otherwise, when a record's
scope partition has both a global (scope=NULL) and a same-name
scoped field, for_entity returns BOTH rows and the form would
render two inputs for the same name — but only the scoped one
round-trips on save (it wins in typed_eav_defs_by_name).
48 49 50 51 52 53 54 55 56 57 58 |
# File 'lib/typed_eav/has_typed_eav/instance_methods.rb', line 48 def initialize_typed_values existing_field_ids = existing_typed_value_field_ids typed_eav_defs_by_name.each_value do |field| next if existing_field_ids.include?(field.id) typed_values.build(field: field, value: field.default_value) end typed_values end |
#set_typed_eav_value(name, value) ⇒ Object
Set a specific field's value by name
110 111 112 113 114 115 116 117 118 119 120 |
# File 'lib/typed_eav/has_typed_eav/instance_methods.rb', line 110 def set_typed_eav_value(name, value) field = typed_eav_defs_by_name[name.to_s] return unless field existing = typed_values.detect { |v| v.field_id == field.id } if existing existing.value = value else typed_values.build(field: field, value: value) end end |
#typed_eav_attributes=(attributes) ⇒ Object Also known as: typed_eav=
Bulk assign values by field NAME. Coexists with (rather than replaces)
the accepts_nested_attributes_for :typed_values setter declared
on the host model, which accepts entries keyed by field ID.
The nested-attributes setter is the standard Rails form contract
(forms post field_id as a hidden input per value row). This setter
takes entries keyed by field name and translates them to field
IDs before handing off to the nested-attributes setter. It also
enforces the types: restriction declared on has_typed_eav and
supports _destroy: true for removing a value by name.
record.typed_eav_attributes = [
{ name: "age", value: 30 },
{ name: "email", value: "test@example.com" },
{ name: "old_field", _destroy: true },
]
Pick the one that fits: forms -> typed_values_attributes=, scripting -> typed_eav_attributes=. They can't both run in the same save.
79 80 81 82 83 84 85 86 87 88 |
# File 'lib/typed_eav/has_typed_eav/instance_methods.rb', line 79 def typed_eav_attributes=(attributes) fields_by_name = typed_eav_defs_by_name values_by_field_id = typed_values.index_by(&:field_id) nested = normalize_typed_eav_attributes(attributes).filter_map do |attrs| build_or_update_typed_value(attrs, fields_by_name, values_by_field_id) end self.typed_values_attributes = nested if nested.any? end |
#typed_eav_definitions ⇒ Object
The field definitions available for this record
12 13 14 15 16 17 |
# File 'lib/typed_eav/has_typed_eav/instance_methods.rb', line 12 def typed_eav_definitions self.class.typed_eav_definitions( scope: typed_eav_scope, parent_scope: typed_eav_parent_scope, ) end |
#typed_eav_hash ⇒ Object
Hash of all field values: { "field_name" => value, ... }. Same
preload semantics as typed_eav_value — respects already-loaded
associations instead of rebuilding the relation.
Collision-safe: on a global+scoped name overlap, the value attached to the winning field_id wins (scoped). Without this guard, a stray row tied to a shadowed global field could surface here even though writes route through the scoped winner.
130 131 132 133 134 135 136 137 138 139 140 |
# File 'lib/typed_eav/has_typed_eav/instance_methods.rb', line 130 def typed_eav_hash winning_ids_by_name = typed_eav_defs_by_name.transform_values(&:id) loaded_typed_values_with_fields.each_with_object({}) do |tv, hash| # Skip orphans (`tv.field` nil — definition deleted out from under # the value) so the hash isn't crashy when stale rows linger. next unless tv.field assign_hash_value(hash, tv, winning_ids_by_name) end end |
#typed_eav_parent_scope ⇒ Object
Current parent_scope value (for two-level partitioning).
Returns nil for models that did not declare parent_scope_method: —
the method is defined unconditionally so callers (e.g. the Value-side
cross-axis validator) can respond_to? and read uniformly without
branching on parent_scope_method configuration. Mirrors the
&.to_s normalization on typed_eav_scope.
33 34 35 36 37 |
# File 'lib/typed_eav/has_typed_eav/instance_methods.rb', line 33 def typed_eav_parent_scope return nil unless self.class.typed_eav_parent_scope_method send(self.class.typed_eav_parent_scope_method)&.to_s end |
#typed_eav_scope ⇒ Object
Current scope value (for multi-tenant)
20 21 22 23 24 |
# File 'lib/typed_eav/has_typed_eav/instance_methods.rb', line 20 def typed_eav_scope return nil unless self.class.typed_eav_scope_method send(self.class.typed_eav_scope_method)&.to_s end |
#typed_eav_value(name) ⇒ Object
Get a specific field's value by name. Honors an already-loaded
typed_values association so list-page callers that preloaded
typed_values: :field don't trigger a fresh query per record.
On a global+scoped name collision, prefer the value bound to the winning field_id (scoped wins). Without this guard, a stray value row attached to a shadowed global field would surface here even though writes route through the scoped winner.
100 101 102 103 104 105 106 107 |
# File 'lib/typed_eav/has_typed_eav/instance_methods.rb', line 100 def typed_eav_value(name) winning = typed_eav_defs_by_name[name.to_s] # Skip orphans (`v.field` nil — definition deleted out from under # the value via raw SQL or a missing FK cascade) so a stray row # can't crash the read path with NoMethodError. candidates = loaded_typed_values_with_fields.select { |v| v.field && v.field.name == name.to_s } select_winning_value(candidates, winning)&.value end |