Module: Rails::GraphQL::Helpers::LeafFromAr

Defined in:
lib/rails/graphql/helpers/leaf_from_ar.rb

Overview

Helper module allowing leaf values to be collected direct from ActiveRecord. It also helps AR Adapters to define the necessary methods and settings to operate with this extractor.

TODO: Implement ActiveRecord serialization

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.extended(other) ⇒ Object



12
13
14
15
16
17
18
19
20
# File 'lib/rails/graphql/helpers/leaf_from_ar.rb', line 12

def self.extended(other)
  # Defines which type exactly represents the scalar type on the
  # ActiveRecord adapter for casting purposes
  other.class_attribute :ar_adapter_type, instance_accessor: false, default: {}

  # A list of ActiveRecord aliases per adapter to skip casting
  other.class_attribute :ar_adapter_aliases, instance_accessor: false,
    default: (Hash.new { |h, k| h[k] = Set.new })
end

Instance Method Details

#ar_typeObject

Identifies the ActiveRecord type (actually it uses the ActiveModel::Type#type method, but ActiveRecord uses the same reference) of this object. When mismatching, the query must cast the value.



26
27
28
# File 'lib/rails/graphql/helpers/leaf_from_ar.rb', line 26

def ar_type
  :string
end

#define_for(adapter, **settings) ⇒ Object

Helper method that should be used for ActiveRecord adapters in order to provide the correct methods and settings for retrieving the given value direct from a query.

Options

  • :fetch - A specific function to build the arel for fetching the attribute

  • :cast - A function to cast an attribute to the correct value

  • :type - A symbol that represents the exactly database type that matches the ActiveRecord type (ie. :varchar for :string)

  • :aliases - An array of AR aliases that for the adapter they are equivalent

Raises:



88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/rails/graphql/helpers/leaf_from_ar.rb', line 88

def define_for(adapter, **settings)
  adapter = ar_adapters[adapter] if ar_adapters.key?(adapter)
  raise ArgumentError, (+<<~MSG).squish unless ar_adapters.values.include?(adapter)
    The given #{adapter.inspect} adapter is not a valid option.
    The valid options are: #{ar_adapters.to_a.flatten.map(&:inspect).to_sentence}.
  MSG

  define_singleton_method("from_#{adapter}_adapter", &settings[:fetch]) \
    if settings.key?(:fetch)

  define_singleton_method("cast_#{adapter}_attribute", &settings[:cast]) \
    if settings.key?(:cast)

  ar_adapter_type[adapter] = settings[:type] if settings.key?(:type)
  ar_adapter_aliases[adapter] += ::Array.wrap(settings[:aliases]) \
    if settings.key?(:aliases)
end

#from_ar(ar_object, attribute) ⇒ Object

Returns an Arel object that represents how this object is serialized direct from the query

This happens in 3 parts

  1. It finds a method to get the arel representation of the accessor of the given attribute

  2. If the attribute type mismatch the ar type or any of its aliases, then invoke a adapter-specific cast

  3. If necessary, adapters can describe a specific way to serialize the arel attribute to ensure equivalency

Example

Lets imagine a scenario where the adapter is the PostgreSQL, the attribute is a data field with enum type from a sample table and the result must be a binary base64 data:

  1. Sample.arel_attribute(:data) # => “samples”.“data”

  2. Arel::Nodes::NamedFunction.new(‘CAST’, [arel_object, Arel.sql(‘text’)]) # => CAST(“samples”.“data” AS text)

  3. Arel::Nodes::NamedFunction.new(‘ENCODE’, [arel_object, Arel.sql(“‘base64”)]) # => ENCODE(CAST(“samples”.“data” AS text), ’base64’)



60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/rails/graphql/helpers/leaf_from_ar.rb', line 60

def from_ar(ar_object, attribute)
  key = adapter_key(ar_object)
  method_name = "from_#{key}_adapter"
  method_name = 'from_abstract_adapter' unless respond_to?(method_name, true)

  arel_object = send(method_name, ar_object, attribute)
  return if arel_object.nil?

  arel_object = try("cast_#{key}_attribute", arel_object, ar_adapter_type[key]) \
    unless match_ar_type?(ar_object, attribute, key)
  return if arel_object.nil?

  method_name = "#{key}_serialize"
  respond_to?(method_name, true) ? try(method_name, arel_object) : arel_object
end

#from_ar?(ar_object, attribute) ⇒ Boolean

If a class extend this module, we assume that it can serialize attributes direct from the query point of view

Returns:

  • (Boolean)


32
33
34
# File 'lib/rails/graphql/helpers/leaf_from_ar.rb', line 32

def from_ar?(ar_object, attribute)
  ar_object&.has_attribute?(attribute)
end