Class: GraphqlDeclarative::FilterInput

Inherits:
GraphQL::Schema::InputObject
  • Object
show all
Defined in:
lib/graphql_declarative/filter_input.rb

Overview

A GraphQL::Schema::InputObject built from filter declarations.

class Types::CourseFilter < GraphqlDeclarative::FilterInput
filter :title,       :string,   ops: [:eq, :contains, :starts_with]
filter :published,   :boolean
filter :created_at,  :datetime, ops: [:gte, :lte]
filter :author_name, :string,   through: :author, column: :name
end

Each (name, op) pair generates one argument: :title_contains, :created_at_gte. :eq generates the bare name (:title), not :title_eq.

The registry (definitions) is what Filter.apply reads back at request time. It is the ONLY source of column and association identifiers — nothing in the SQL layer is ever derived from user input. See SPEC.md section 7.

Defined Under Namespace

Classes: Definition

Constant Summary collapse

DEFAULT_OPS =
{
  string: %i[eq contains starts_with ends_with in],
  integer: %i[eq gt gte lt lte in],
  float: %i[eq gt gte lt lte],
  boolean: %i[eq],
  datetime: %i[eq gt gte lt lte]
}.freeze
GRAPHQL_TYPES =

The GraphQL scalar each declared type maps to. :in wraps this in a list and the LIKE ops (contains/starts_with/ends_with) override it to String.

{
  string: GraphQL::Types::String,
  integer: GraphQL::Types::Int,
  float: GraphQL::Types::Float,
  boolean: GraphQL::Types::Boolean,
  datetime: GraphQL::Types::ISO8601DateTime
}.freeze

Class Method Summary collapse

Class Method Details

.argument_name_for(name, op) ⇒ Object

The argument naming table from SPEC.md section 4. :eq is the bare name — title, never title_eq.



103
104
105
# File 'lib/graphql_declarative/filter_input.rb', line 103

def argument_name_for(name, op)
  (op.to_sym == :eq) ? name.to_sym : :"#{name}_#{op}"
end

.definitionsObject

=> Definition. Inheritance-safe: walking superclass on every read means a subclass sees its parent's declarations without ever holding (or mutating) the parent's hash. Adding a filter to the parent after the subclass exists is picked up too.



88
89
90
91
92
93
94
# File 'lib/graphql_declarative/filter_input.rb', line 88

def definitions
  if superclass.respond_to?(:definitions)
    superclass.definitions.merge(own_definitions)
  else
    own_definitions.dup
  end
end

.filter(name, type, ops: nil, through: nil, column: nil) ⇒ Object

Declare one filterable attribute and generate one argument per op.

Everything here fails loudly at class-definition time (SPEC.md 6.1): an unknown type or an op that makes no sense for the type is a boot error, never a per-request surprise.

Raises:



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
# File 'lib/graphql_declarative/filter_input.rb', line 46

def filter(name, type, ops: nil, through: nil, column: nil)
  name = name.to_sym
  type = type.to_sym

  valid_ops = DEFAULT_OPS[type]
  unless valid_ops
    raise Error, "unknown filter type #{type.inspect} for filter #{name.inspect}; " \
                 "expected one of #{DEFAULT_OPS.keys.inspect}"
  end

  ops = Array(ops || valid_ops).map(&:to_sym)
  raise Error, "filter #{name.inspect} declares an empty ops list" if ops.empty?

  invalid = ops - valid_ops
  unless invalid.empty?
    raise Error, "invalid op(s) #{invalid.inspect} for #{type} filter #{name.inspect}; " \
                 "valid ops for #{type} are #{valid_ops.inspect}"
  end

  definition = Definition.new(
    name: name,
    type: type,
    ops: ops.freeze,
    through: through&.to_sym,
    # `column:` defaults to the filter name; for a `through:` filter it is
    # the column on the ASSOCIATION's table, not on the base model.
    column: (column || name).to_sym
  )

  own_definitions[name] = definition

  ops.each do |op|
    argument(argument_name_for(name, op), graphql_type_for(type, op), required: false)
  end

  definition
end

.own_definitionsObject

Declarations made directly on this class, excluding inherited ones.



97
98
99
# File 'lib/graphql_declarative/filter_input.rb', line 97

def own_definitions
  @own_definitions ||= {}
end