Class: GraphqlDeclarative::Filter
- Inherits:
-
Object
- Object
- GraphqlDeclarative::Filter
- Defined in:
- lib/graphql_declarative/filter.rb
Overview
Applies a FilterInput's arguments to an ActiveRecord scope.
THE IMPORTANT PART — see spec/pagination_through_associations_spec.rb.
Filters declared with through: MUST NOT be applied as a join on the scope
being paginated. Resolve them to an id subquery instead:
ids = model.joins(through).where(assoc_table => {column => value}).select(:id)
scope.where(id: ids)
The base relation then stays un-joined, so LIMIT, ORDER BY and cursors all
remain correct. Two through: filters on the same association must
intersect within ONE subquery, not chain two (that changes the meaning from
"one child matching both" to "children matching each").
Chosen semantic, stated once so it is not rediscovered by accident: ONE CHILD ROW MUST SATISFY ALL PREDICATES DECLARED FOR THAT ASSOCIATION.
Constant Summary collapse
- LIKE_PATTERNS =
Ops that compare with a LIKE pattern. The pattern is built here, from a sanitized value, and passed as a bound/quoted node — never spliced into SQL text.
{ contains: ->(v) { "%#{v}%" }, starts_with: ->(v) { "#{v}%" }, ends_with: ->(v) { "%#{v}" } }.freeze
- LIKE_ESCAPE =
LIKE's own metacharacters are escaped so a user-supplied "100%" means the literal string, not "100 followed by anything".
ESCAPE '\'is emitted explicitly because only MySQL assumes backslash by default. "\\"
Class Method Summary collapse
-
.apply(scope, filter_class, args) ⇒ Object
scope - ActiveRecord::Relation (or a model class) filter_class - a GraphqlDeclarative::FilterInput subclass args - the
filter:input, as a Hash with symbol keys, a GraphQL::Schema::InputObject, or nil.
Class Method Details
.apply(scope, filter_class, args) ⇒ Object
scope - ActiveRecord::Relation (or a model class)
filter_class - a GraphqlDeclarative::FilterInput subclass
args - the filter: input, as a Hash with symbol keys, a
GraphQL::Schema::InputObject, or nil
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 |
# File 'lib/graphql_declarative/filter.rb', line 40 def apply(scope, filter_class, args) args = normalize_args(args) relation = scope.respond_to?(:all) ? scope.all : scope return relation if args.empty? model = relation.klass index = argument_index(filter_class) direct = [] # Keyed by association name so that every predicate on one association # ends up in the SAME subquery. This grouping IS the semantic. through = Hash.new { |h, k| h[k] = [] } args.each do |key, value| definition, op = index[key.to_sym] unless definition raise Error, "unknown filter argument #{key.inspect} for #{filter_class}; " \ "known arguments: #{index.keys.sort.inspect}" end # A key that was not supplied is absent from the hash; an explicit nil # is treated as "not filtering on this", not as `IS NULL`. next if value.nil? (definition.through ? through[definition.through] : direct) << [definition, op, value] end relation = apply_direct(relation, model, direct) apply_through(relation, model, through) end |