Class: TypedEAV::QueryBuilder
- Inherits:
-
Object
- Object
- TypedEAV::QueryBuilder
- Defined in:
- lib/typed_eav/query_builder.rb
Overview
Replaces the per-type Finder class hierarchy from active_fields.
Because values live in native typed columns, ActiveRecord already knows the column types from the schema. Arel predicates (eq, gt, lt, matches, etc.) automatically go through the column’s ActiveRecord::Type for casting.
This means:
where(integer_value: "42") -> Rails casts "42" to 42 automatically
arel[:date_value].gt(value) -> Rails casts string dates to Date objects
No manual CAST() calls. No per-type caster classes for queries. One module handles all field types.
Usage:
QueryBuilder.filter(field, :gt, 42)
# => ActiveRecord::Relation scoped to matching values
QueryBuilder.filter(field, :contains, "hello")
# => ILIKE query against the field's string_value column
Class Method Summary collapse
-
.entity_ids(field, operator, value) ⇒ Object
Convenience: returns entity IDs matching the filter.
-
.filter(field, operator, value) ⇒ Object
Returns an ActiveRecord::Relation of TypedEAV::Value records matching the given field, operator, and comparison value.
Class Method Details
.entity_ids(field, operator, value) ⇒ Object
Convenience: returns entity IDs matching the filter. Useful for subqueries: Model.where(id: QueryBuilder.entity_ids(field, :gt, 5))
95 96 97 |
# File 'lib/typed_eav/query_builder.rb', line 95 def entity_ids(field, operator, value) filter(field, operator, value).distinct.select(:entity_id) end |
.filter(field, operator, value) ⇒ Object
Returns an ActiveRecord::Relation of TypedEAV::Value records matching the given field, operator, and comparison value.
The relation is suitable for subquery use:
Model.where(id: QueryBuilder.filter(field, :gt, 5).select(:entity_id))
rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength – one operator-dispatch case statement; flattening keeps the supported-operators list scannable in one place.
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 |
# File 'lib/typed_eav/query_builder.rb', line 32 def filter(field, operator, value) col = field.class.value_column operator = operator.to_sym # Validate operator is supported by this field type supported = field.class.supported_operators unless supported.include?(operator) raise ArgumentError, "Operator :#{operator} is not supported for #{field.class.name}. " \ "Supported operators: #{supported.map { |o| ":#{o}" }.join(", ")}" end arel_col = values_table[col] base = value_scope(field) case operator when :eq eq_predicate(base, arel_col, col, value) when :not_eq not_eq_predicate(base, arel_col, col, value) when :gt base.where(arel_col.gt(value)) when :gteq base.where(arel_col.gteq(value)) when :lt base.where(arel_col.lt(value)) when :lteq base.where(arel_col.lteq(value)) when :between unless value.respond_to?(:first) && value.respond_to?(:last) raise ArgumentError, ":between expects a Range or two-element Array" end base.where(arel_col.between(value.first..value.last)) when :contains base.where(arel_col.matches("%#{sanitize_like(value)}%")) when :not_contains base.where(arel_col.does_not_match("%#{sanitize_like(value)}%")) when :starts_with base.where(arel_col.matches("#{sanitize_like(value)}%")) when :ends_with base.where(arel_col.matches("%#{sanitize_like(value)}")) when :is_null base.where(col => nil) when :is_not_null base.where.not(col => nil) when :any_eq # For json_value arrays: contains the given element base.where("#{col} @> ?", [value].to_json) when :all_eq # For json_value arrays: contains all given elements base.where("#{col} @> ?", Array(value).to_json) else raise ArgumentError, "Unhandled operator: #{operator}" end end |