Module: TypedEAV::HasTypedEAV
- Extended by:
- ActiveSupport::Concern
- Defined in:
- lib/typed_eav/has_typed_eav.rb
Overview
Include this in any ActiveRecord model to give it typed custom fields.
class Contact < ApplicationRecord
has_typed_eav
end
class Contact < ApplicationRecord
has_typed_eav scope_method: :tenant_id
end
This gives you:
# Reading/writing values
contact.typed_values # => collection
contact.initialize_typed_values # => builds missing values with defaults
contact.typed_eav_attributes = [...] # => bulk assign via nested attributes
# Querying (the good stuff)
Contact.where_typed_eav(
{ name: "age", op: :gt, value: 21 },
{ name: "status", op: :eq, value: "active" }
)
# Or the short form with a hash:
Contact.with_field("age", :gt, 21)
Contact.with_field("status", "active") # :eq is default
Defined Under Namespace
Modules: ClassQueryMethods, InstanceMethods
Class Method Summary collapse
-
.definitions_by_name(defs) ⇒ Object
Indexes field definitions by name with deterministic three-way collision resolution: when global (scope=NULL, parent_scope=NULL), scope-only (scope set, parent_scope=NULL), and full-triple (both set) fields share a name, the most-specific row wins.
-
.definitions_multimap_by_name(defs) ⇒ Object
Indexes field definitions by name into a multi-map (one name → array of fields).
Class Method Details
.definitions_by_name(defs) ⇒ Object
Indexes field definitions by name with deterministic three-way collision resolution: when global (scope=NULL, parent_scope=NULL), scope-only (scope set, parent_scope=NULL), and full-triple (both set) fields share a name, the most-specific row wins.
Sort key ‘[scope.nil? ? 0 : 1, parent_scope.nil? ? 0 : 1]` orders rows:
[0, 0] global (least specific) → comes first
[1, 0] scope-only (middle)
[1, 1] full triple (most specific) → comes last
‘index_by(&:name)` keeps the LAST entry on duplicate keys (Rails convention via `Array#to_h`), so most-specific wins. The two-key sort extends the prior “scoped beats global” rule into “two-key beats one-key beats global” without changing the index_by-last-wins mechanism. The `(scope=NULL, parent_scope=NOT NULL)` slot is unreachable by construction (orphan-parent invariant in Field::Base), so the ordering is exhaustive across the three valid shapes.
‘for_entity(name, scope:, parent_scope:)` returns the union across all three shapes on a collision, and a bare `index_by(&:name)` would let DB row order pick the winner. Shared by the class-query path (ClassQueryMethods#where_typed_eav) and the instance path (InstanceMethods#typed_eav_defs_by_name) so the two can’t drift.
57 58 59 60 61 |
# File 'lib/typed_eav/has_typed_eav.rb', line 57 def self.definitions_by_name(defs) defs.to_a .sort_by { |d| [d.scope.nil? ? 0 : 1, d.parent_scope.nil? ? 0 : 1] } .index_by(&:name) end |
.definitions_multimap_by_name(defs) ⇒ Object
Indexes field definitions by name into a multi-map (one name →array of fields). Used by the class-query path under ‘TypedEAV.unscoped { }`, where the same field name may legitimately exist across multiple tenant partitions and we must OR-across all matching field_ids per filter rather than collapse to a single row.
68 69 70 |
# File 'lib/typed_eav/has_typed_eav.rb', line 68 def self.definitions_multimap_by_name(defs) defs.to_a.group_by(&:name) end |