Class: Woods::Console::ScopePredicateParser

Inherits:
Object
  • Object
show all
Defined in:
lib/woods/console/scope_predicate_parser.rb

Overview

Parses Ransack-style predicate suffixes in scope hashes and builds safe Arel predicates without string interpolation.

Supported suffixes:

_eq, _not_eq           — equality / inequality
_gt, _gteq, _lt, _lteq — numeric/date comparisons
_in, _not_in           — set membership (value must be Array)
_null, _not_null       — IS NULL / IS NOT NULL (value must be boolean)
_present               — IS NOT NULL AND != '' (value must be boolean)
_blank                 — IS NULL OR = '' (value must be boolean)
_matches               — LIKE pattern

Plain hash keys (no recognised suffix) are treated as equality conditions and handled by the standard ActiveRecord where(hash) path.

Every column reference is validated through ModelValidator#validate_column! before an Arel node is built — SQL injection via column names is impossible.

Examples:

parser = ScopePredicateParser.new(model_name: 'Order', model_validator: validator)
relation = parser.parse(Order, { status: 'paid', total_refund_gt: 0 })

Constant Summary collapse

SUPPORTED_SUFFIXES =
%w[
  _eq _not_eq
  _gt _gteq _lt _lteq
  _in _not_in
  _null _not_null
  _present _blank
  _matches
].freeze
SUFFIX_PATTERN =

Suffix pattern — longest suffix match wins because we scan the full list.

/(_eq|_not_eq|_gteq|_lteq|_gt|_lt|_not_in|_not_null|_in|_null|_present|_blank|_matches)\z/
SUFFIX_HINT =
"Supported suffixes: #{SUPPORTED_SUFFIXES.join(', ')}.".freeze

Instance Method Summary collapse

Constructor Details

#initialize(model_name:, model_validator:) ⇒ ScopePredicateParser

Returns a new instance of ScopePredicateParser.

Parameters:

  • model_name (String)

    ActiveRecord model name (e.g. ‘Order’)

  • model_validator (ModelValidator)

    Validates column names



46
47
48
49
# File 'lib/woods/console/scope_predicate_parser.rb', line 46

def initialize(model_name:, model_validator:)
  @model_name = model_name
  @model_validator = model_validator
end

Instance Method Details

#parse(relation, scope_hash) ⇒ ActiveRecord::Relation

Parse a scope hash and return an ActiveRecord::Relation.

Keys without a recognised suffix are collected into a plain equality hash and applied via relation.where(hash) — the fast path. Keys with a recognised suffix are validated and built via Arel.

Parameters:

  • relation (ActiveRecord::Relation, Class)

    Starting relation

  • scope_hash (Hash)

    Scope conditions, possibly with predicate suffixes

Returns:

  • (ActiveRecord::Relation)

Raises:



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/woods/console/scope_predicate_parser.rb', line 61

def parse(relation, scope_hash)
  equality = {}
  arel_nodes = []

  scope_hash.each do |raw_key, value|
    key = raw_key.to_s
    match = SUFFIX_PATTERN.match(key)

    if match
      suffix = match[1]
      column = key.delete_suffix(suffix)
      @model_validator.validate_column!(@model_name, column)
      arel_nodes << build_node(relation, column, suffix, value)
    else
      equality[raw_key] = value
    end
  end

  relation = relation.where(equality) if equality.any?
  arel_nodes.each { |node| relation = relation.where(node) }
  relation
end