Module: TypedEAV::EntityQuery

Defined in:
lib/typed_eav/entity_query.rb

Overview

Class-level query orchestration extended onto host AR models by the ‘has_typed_eav` macro. Owns the `UNSET_SCOPE` / `ALL_SCOPES` sentinels and the `resolve_scope` chain; delegates the heavy lifting to `FilterQuery` (multi-filter SQL composition) and `BulkRead` (bulk per-record reads). `bulk_set_typed_eav_values` stays as a 3-line wrapper around the existing `BulkWrite` executor.

Constant Summary collapse

UNSET_SCOPE =

Sentinel for the ‘scope:` kwarg default. Distinguishes “kwarg not passed -> resolve from ambient” (UNSET_SCOPE) from “explicitly nil -> filter to global-only fields” (preserves prior behavior).

Object.new.freeze
ALL_SCOPES =

Sentinel returned by ‘resolve_scope` inside an `unscoped { }` block. Signals the caller to skip the scope filter entirely (return fields across all partitions, not just global).

Object.new.freeze

Instance Method Summary collapse

Instance Method Details

#bulk_set_typed_eav_values(records, values_by_field_name, version_grouping: :default) ⇒ Object

Bulk write API. Sets the same ‘values_by_field_name` Hash on every record in `records` inside ONE outer ActiveRecord transaction with a SAVEPOINT-PER-RECORD failure-isolation envelope. See `TypedEAV::BulkWrite` for the transaction shape, error-aggregation contract, and the `version_grouping:` semantics.



99
100
101
102
103
104
105
106
# File 'lib/typed_eav/entity_query.rb', line 99

def bulk_set_typed_eav_values(records, values_by_field_name, version_grouping: :default)
  TypedEAV::BulkWrite.execute(
    host_class: self,
    records: records,
    values_by_field_name: values_by_field_name,
    version_grouping: version_grouping,
  )
end

#typed_eav_definitions(scope: UNSET_SCOPE, parent_scope: UNSET_SCOPE) ⇒ Object

Returns field definitions for this entity type.

‘scope:` and `parent_scope:` behavior:

- omitted        -> resolve from ambient (`with_scope` -> resolver -> raise/nil)
- passed a value -> use verbatim (explicit override; admin/test path)
- passed nil     -> filter to global-only on that axis (prior behavior preserved)


75
76
77
78
79
80
81
82
83
# File 'lib/typed_eav/entity_query.rb', line 75

def typed_eav_definitions(scope: UNSET_SCOPE, parent_scope: UNSET_SCOPE)
  resolved = resolve_scope(scope, parent_scope)
  if resolved.equal?(ALL_SCOPES)
    TypedEAV::Partition.visible_fields(entity_type: name, mode: :all_partitions)
  else
    s, ps = resolved
    TypedEAV::Partition.visible_fields(entity_type: name, scope: s, parent_scope: ps)
  end
end

#typed_eav_hash_for(records) ⇒ Object

Bulk read API. Returns ‘{ record_id => { field_name => value } }` for an Enumerable of host records — the class-method bulk variant of `HasTypedEAV::InstanceMethods#typed_eav_hash`. N+1-free regardless of record count or field count. See `TypedEAV::BulkRead` for the pipeline and query bound.



90
91
92
# File 'lib/typed_eav/entity_query.rb', line 90

def typed_eav_hash_for(records)
  TypedEAV::BulkRead.new(host_class: self, records: records).to_hash
end

#where_typed_eav(*filters, scope: UNSET_SCOPE, parent_scope: UNSET_SCOPE) ⇒ Object

Query by custom field values. Accepts an array of filter hashes or a hash of hashes (from form params).

Each filter needs:

:name or :n      - the field name
:op or :operator - the operator (default: :eq)
:value or :v     - the comparison value

Contact.where_typed_eav(
  { name: "age", op: :gt, value: 21 },
  { name: "city", value: "Portland" }   # op defaults to :eq
)

‘scope:` and `parent_scope:` behavior:

- omitted        -> resolve from ambient (`with_scope` -> resolver -> raise/nil)
- passed a value -> use verbatim (explicit override; admin/test path)
- passed nil     -> filter to global-only on that axis (prior behavior)


38
39
40
41
42
43
44
45
46
47
48
# File 'lib/typed_eav/entity_query.rb', line 38

def where_typed_eav(*filters, scope: UNSET_SCOPE, parent_scope: UNSET_SCOPE)
  resolved = resolve_scope(scope, parent_scope)
  effective_scope, effective_parent = scope_pair(resolved)

  TypedEAV::FilterQuery.new(
    model: self,
    filters: filters,
    scope: effective_scope,
    parent_scope: effective_parent,
  ).to_relation
end

#with_field(name, operator_or_value = nil, value = nil, scope: UNSET_SCOPE, parent_scope: UNSET_SCOPE) ⇒ Object

Shorthand for single-field queries.

Contact.with_field("age", :gt, 21)
Contact.with_field("active", true)      # op defaults to :eq
Contact.with_field("name", :contains, "smith")

Accepts both ‘scope:` and `parent_scope:` kwargs with the same ambient/explicit/nil semantics as `where_typed_eav`. Single-scope callers (no `parent_scope:`) are unaffected.



59
60
61
62
63
64
65
66
67
# File 'lib/typed_eav/entity_query.rb', line 59

def with_field(name, operator_or_value = nil, value = nil, scope: UNSET_SCOPE, parent_scope: UNSET_SCOPE)
  filter = if value.nil? && !operator_or_value.is_a?(Symbol)
             # Two-arg form: with_field("name", "value") implies :eq
             { name: name, op: :eq, value: operator_or_value }
           else
             { name: name, op: operator_or_value, value: value }
           end
  where_typed_eav(filter, scope: scope, parent_scope: parent_scope)
end