Class: Rails::GraphQL::Request::Strategy

Inherits:
Object
  • Object
show all
Extended by:
ActiveSupport::Autoload
Defined in:
lib/rails/graphql/request/strategy.rb

Overview

GraphQL Request Strategy

This is the base class for the strategies of resolving a request.

Defined Under Namespace

Classes: CachedStrategy, DynamicInstance, MultiQueryStrategy, SequencedStrategy

Constant Summary collapse

PREPARE_XARGS =

Configurations for the prepare step

{ object?: true, reverse?: true }.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(request) ⇒ Strategy

Returns a new instance of Strategy.



37
38
39
40
41
42
# File 'lib/rails/graphql/request/strategy.rb', line 37

def initialize(request)
  @request = request
  @objects_pool = {}
  @listeners = Hash.new { |h, k| h[k] = Set.new }
  add_listeners_from(request)
end

Instance Attribute Details

#contextObject (readonly)

Returns the value of attribute context.



26
27
28
# File 'lib/rails/graphql/request/strategy.rb', line 26

def context
  @context
end

#listenersObject (readonly)

Returns the value of attribute listeners.



26
27
28
# File 'lib/rails/graphql/request/strategy.rb', line 26

def listeners
  @listeners
end

#requestObject (readonly)

Returns the value of attribute request.



26
27
28
# File 'lib/rails/graphql/request/strategy.rb', line 26

def request
  @request
end

#stageObject (readonly)

Returns the value of attribute stage.



26
27
28
# File 'lib/rails/graphql/request/strategy.rb', line 26

def stage
  @stage
end

Class Method Details

.can_resolve?(_) ⇒ Boolean

Check if the strategy can resolve the given request. By default, strategies cannot resolve a request. Override this method with a valid checker.

Returns:

  • (Boolean)


32
33
34
# File 'lib/rails/graphql/request/strategy.rb', line 32

def can_resolve?(_)
  false
end

Instance Method Details

#add_listeners_from(object) ⇒ Object

Check what kind of event listeners the object have, in order to speed up processing by avoiding unnecessary event instances



187
188
189
190
191
192
193
194
195
# File 'lib/rails/graphql/request/strategy.rb', line 187

def add_listeners_from(object)
  object.all_listeners&.each do |event_name|
    listeners[event_name] << object
  end

  if request.prepared_data_for?(object)
    listeners[:prepare] << object
  end
end

#cache_dumpObject

Build the cache object



226
227
228
# File 'lib/rails/graphql/request/strategy.rb', line 226

def cache_dump
  { class: self.class }
end

#cache_load(data) ⇒ Object

Organize from cache data



231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
# File 'lib/rails/graphql/request/strategy.rb', line 231

def cache_load(data)
  data, operations, fragments = data.values_at(:strategy, :operations, :fragments)

  collect_listeners do
    # Load all operations
    operations = operations.transform_values do |operation|
      request.build_from_cache(operation.delete(:type)).tap do |instance|
        instance.instance_variable_set(:@request, request)
        instance.cache_load(operation)
      end
    end

    # Load all fragments
    fragments = fragments&.transform_values do |fragment|
      request.build_from_cache(Component::Fragment).tap do |instance|
        instance.instance_variable_set(:@request, request)
        instance.cache_load(fragment)
      end
    end
  end

  # Mark itself as already organized
  @organized = true

  # Save operations and fragments into the request
  request.instance_variable_set(:@operations, operations)
  request.instance_variable_set(:@fragments, fragments)
end

#clearObject

Clear all strategy information



45
46
47
48
49
# File 'lib/rails/graphql/request/strategy.rb', line 45

def clear
  @listeners.clear
  @objects_pool.clear
  @stage = @context = @objects_pool = @data_pool = @listeners = nil
end

#compileObject

Simply run the organize step for compilation



221
222
223
# File 'lib/rails/graphql/request/strategy.rb', line 221

def compile
  for_each_operation { |op| collect_listeners { op.organize! } }
end

#find_directive!(directive) ⇒ Object

Find a given directive and store it on request cache



62
63
64
# File 'lib/rails/graphql/request/strategy.rb', line 62

def find_directive!(directive)
  request.nested_cache(:directives, directive) { schema.find_directive!(directive) }
end

#find_type!(type) ⇒ Object

Find a given type and store it on request cache



57
58
59
# File 'lib/rails/graphql/request/strategy.rb', line 57

def find_type!(type)
  request.nested_cache(:types, type) { schema.find_type!(type) }
end

#instance_for(klass) ⇒ Object

Check if the given class is in the pool, or add a new instance to the pool, and then set the instance as the current object



146
147
148
149
150
151
152
153
154
155
156
157
# File 'lib/rails/graphql/request/strategy.rb', line 146

def instance_for(klass)
  @objects_pool[klass] ||= begin
    @objects_pool.each_value.find do |value|
      value.is_a?(klass)
    end || begin
      instance = klass.new
      instance = DynamicInstance.new(instance) unless klass < GraphQL::Schema ||
        klass < GraphQL::Type::Object
      instance
    end
  end
end

#listeners?Boolean

Check if any listener were actually added

Returns:

  • (Boolean)


67
68
69
# File 'lib/rails/graphql/request/strategy.rb', line 67

def listeners?
  listeners.any?
end

#listening_to?(event_name) ⇒ Boolean

Check if any object is listening to a given event_name

Returns:

  • (Boolean)


72
73
74
# File 'lib/rails/graphql/request/strategy.rb', line 72

def listening_to?(event_name)
  listeners? && listeners.key?(event_name.to_sym)
end

#perform(field, data = nil) ⇒ Object

When a field has a perform step, run it under the context of the prepared value from the data pool



84
85
86
87
88
89
90
# File 'lib/rails/graphql/request/strategy.rb', line 84

def perform(field, data = nil)
  context.stacked(data || @data_pool[field]) do
    safe_store_data(field) do
      Event.trigger(:perform, field, self, &field.performer)
    end
  end
end

#prepare(field, &block) ⇒ Object

Execute the prepare step for the given field and execute the given block using context stack



94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/rails/graphql/request/strategy.rb', line 94

def prepare(field, &block)
  check_fragment_multiple_prepare!(field)
  value = safe_store_data(field) do
    prepared = request.prepared_data_for(field)
    if prepared.is_a?(PreparedData)
      field.prepared_data!
      prepared.all
    else
      Event.trigger(:prepare, field, self, **PREPARE_XARGS)
    end
  end

  perform(field, value) if field.mutation?

  value = @data_pool[field]
  context.stacked(value, &block) unless value.nil?
end

#prepared_data_for(field, with_null: false) ⇒ Object

Get the prepared data for the given field, getting ready for resolve, while ensuring to check prepared data on request



210
211
212
213
214
215
216
217
218
# File 'lib/rails/graphql/request/strategy.rb', line 210

def prepared_data_for(field, with_null: false)
  if field.prepared_data?
    request.prepared_data_for(field).next
  elsif @data_pool.key?(field)
    @data_pool[field]
  elsif with_null
    PreparedData::NULL
  end
end

#resolve(field, *args, array: false, decorate: false, &block) ⇒ Object

Resolve a value for a given object, It uses the args to prevent problems with nil values.



114
115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/rails/graphql/request/strategy.rb', line 114

def resolve(field, *args, array: false, decorate: false, &block)
  resolve_data_for(field, args)

  value = args.last
  value = field.decorate(value) if decorate
  context.stacked(value) do |current|
    if !array
      block.call(current)
      field.write_value(current)
    else
      field.write_array(current, &block)
    end
  end
end

#resolve!Object

Executes the strategy in the normal mode

Raises:

  • (NotImplementedError)


52
53
54
# File 'lib/rails/graphql/request/strategy.rb', line 52

def resolve!
  raise NotImplementedError
end

#resolve_data_for(field, args) ⇒ Object

Get the resolved data for a given field



130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/rails/graphql/request/strategy.rb', line 130

def resolve_data_for(field, args)
  return unless args.size.zero?

  if field.try(:dynamic_resolver?)
    extra = prepared_data_for(field, with_null: true)
    extra = extra === PreparedData::NULL ? EMPTY_HASH : { prepared: extra }
    args << Event.trigger(:resolve, field, self, **extra, &field.resolver)
  elsif field.prepared_data?
    args << prepared_data_for(field)
  else
    data_for(args, field)
  end
end

#safe_store_data(field, value = nil) ⇒ Object

Only store a given value for a given field if it is not set yet



203
204
205
206
# File 'lib/rails/graphql/request/strategy.rb', line 203

def safe_store_data(field, value = nil)
  value ||= yield if block_given?
  @data_pool[field] ||= value unless value.nil?
end

#stacked(object, &block) ⇒ Object

When running an stacked operation, make sure that the object was added to the list of the listeners



78
79
80
# File 'lib/rails/graphql/request/strategy.rb', line 78

def stacked(object, &block)
  request.stacked(object, &block)
end

#store_data(field, value) ⇒ Object

Store a given resolve value for a given field



198
199
200
# File 'lib/rails/graphql/request/strategy.rb', line 198

def store_data(field, value)
  @data_pool[field] = value
end

#trigger_event(event_name, **xargs) ⇒ Object

Trigger an event using a set of filtered objects from request.stack. trigger_all. The filter is based on the listeners that were collected by the strategy.



163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
# File 'lib/rails/graphql/request/strategy.rb', line 163

def trigger_event(event_name, **xargs)
  return unless listening_to?(event_name)

  # A simpler attempt to remove select less objects (or even none) by
  # assuming that the first item will work as exclusive and
  # non-exclusive, and the others, only non-exclusive or anything
  # different than a +Callback+
  event_name = event_name.to_sym
  list = listeners[event_name]
  objects = request.stack.select.with_index do |obj, idx|
    next unless list.include?(obj)
    next true if idx == 0

    obj.all_events.try(:[], event_name)&.any? do |ev|
      !(ev.is_a?(Callback) && ev.exclusive?)
    end
  end

  # Now trigger with more for all the selected objects
  Event.trigger(event_name, objects, self, **xargs) if objects.present?
end