lib/rails/graphql/request/component/field.rb in rails-graphql-0.2.1 vs lib/rails/graphql/request/component/field.rb in rails-graphql-1.0.0.beta
- old
+ new
@@ -1,10 +1,10 @@
# frozen_string_literal: true
-module Rails # :nodoc:
- module GraphQL # :nodoc:
- class Request # :nodoc:
+module Rails
+ module GraphQL
+ class Request
# = GraphQL Request Component Field
#
# This class holds information about a given field that should be
# collected from the source of where it was requested.
class Component::Field < Component
@@ -12,65 +12,77 @@
include ValueWriters
include SelectionSet
include Directives
delegate :decorate, to: :type_klass
- delegate :operation, :variables, to: :parent
- delegate :method_name, :resolver, :performer, :type_klass, :leaf_type?,
- :dynamic_resolver?, to: :field
+ delegate :operation, :variables, :request, to: :parent
+ delegate :method_name, :resolver, :performer, :type_klass,:leaf_type?,
+ :dynamic_resolver?, :mutation?, to: :field
- parent_memoize :request
-
attr_reader :name, :alias_name, :parent, :field, :arguments, :current_object
alias args arguments
- def initialize(parent, node, data)
+ def initialize(parent, node)
@parent = parent
- @name = data[:name]
- @alias_name = data[:alias]
+ @name = node[0]
+ @alias_name = node[1]
- super(node, data)
+ super(node)
end
- # Return both the field directives and the request directives
- def all_directives
- field.all_directives + super
- end
-
# Override that considers the requested field directives and also the
# definition field events, both from itself and its directives events
def all_listeners
- (request.cache(:listeners)[field] ||= field.all_listeners) + super
+ request.nested_cache(:listeners, field) do
+ if !field.listeners?
+ directive_listeners
+ elsif !directives?
+ field.all_listeners
+ else
+ local = directive_listeners
+ local.empty? ? field.all_listeners : field.all_listeners + local
+ end
+ end
end
# Override that considers the requested field directives and also the
# definition field events, both from itself and its directives events
def all_events
- @all_events ||= Helpers.merge_hash_array(
- (request.cache(:events)[field] ||= field.all_events),
- super,
- )
+ request.nested_cache(:events, field) do
+ if !field.events?
+ directive_events
+ elsif !directives?
+ field.all_events
+ else
+ Helpers.merge_hash_array(field.all_events, directive_events)
+ end
+ end
end
# Get and cache all the arguments for the field
def all_arguments
- request.cache(:arguments)[field] ||= begin
- if (result = field.all_arguments).any?
- result.each_value.map(&:gql_name).zip(result.each_value).to_h
- else
- {}
+ return unless field.arguments?
+
+ request.nested_cache(:arguments, field) do
+ field.all_arguments.each_value.with_object({}) do |argument, hash|
+ hash[argument.gql_name] = argument
end
end
end
+ # Check if the field is using a directive
+ def using?(item_or_symbol)
+ super || field.using?(item_or_symbol)
+ end
+
# Assign a given +field+ to this class. The field must be an output
# field, which means that +output_type?+ must be true. It also must be
# called exactly once per field.
- def assing_to(field)
- raise ArgumentError, <<~MSG.squish if defined?(@assigned)
+ def assign_to(field)
+ raise ArgumentError, (+<<~MSG).squish if defined?(@assigned)
The "#{gql_name}" field is already assigned to #{@field.inspect}.
MSG
@field = field
end
@@ -95,20 +107,28 @@
# need to be assigned to a filed
def assignable?
true
end
+ # Check if all the sub fields are broadcastable
+ # TODO: Maybe check for interfaces and if all types allow broadcast
+ def broadcastable?
+ value = field.broadcastable?
+ value = schema.config.default_subscription_broadcastable if value.nil?
+ value != false
+ end
+
# A little extension of the +is_a?+ method that allows checking it using
# the underlying +field+
def of_type?(klass)
super || field.of_type?(klass)
end
# When the field is invalid, there's no much to do
# TODO: Maybe add a invalid event trigger here
def resolve_invalid(error = nil)
- request.exception_to_error(error, @node) if error.present?
+ request.exception_to_error(error, self) if error.present?
validate_output!(nil)
response.safe_add(gql_name, nil)
rescue InvalidValueError
raise unless entry_point?
@@ -116,30 +136,48 @@
# When the +type_klass+ of an object is an interface or a union, the
# field needs to be redirected to the one from the actual resolved
# +object+ type
def resolve_with!(object)
+ return if skipped?
return resolve! if invalid?
old_field, @field = @field, object[@field.name]
+ request.nested_cache(:listeners, field) { strategy.add_listeners_from(self) }
@current_object = object
resolve!
ensure
@field, @current_object = old_field, nil
end
+ # Build the cache object
+ def cache_dump
+ super.merge(field: (field && all_to_gid(field)))
+ end
+
+ # Organize from cache data
+ def cache_load(data)
+ @name = data[:node][0]
+ @alias_name = data[:node][1]
+ @field = all_from_gid(data[:field])
+ super
+
+ check_authorization! unless unresolvable?
+ end
+
protected
# Perform the organization step
def organize_then(&block)
super(block) do
check_assignment!
+
+ parse_directives(@node[3])
check_authorization!
- parse_arguments
- parse_directives
- parse_selection
+ parse_arguments(@node[2])
+ parse_selection(@node[4])
end
end
# Perform the prepare step
def prepare_then(&block)
@@ -147,12 +185,11 @@
end
# Perform the resolve step
def resolve_then(&block)
stacked do
- strategy.perform(self) if field.mutation?
- send(field.array? ? 'resolve_many' : 'resolve_one', &block)
+ send((field.array? ? :resolve_many : :resolve_one), &block)
rescue StandardError => error
resolve_invalid(error)
end
end
@@ -179,49 +216,33 @@
trigger_event(:finalize)
end
end
end
- # This override allows reasigned fields to perform events. This
- # happens when fields are originally organized from interfaces. If
- # the event is stopped for the object, then it doesn't proceed to the
- # strategy implementation, ensuring compatibility
- def trigger_event(event_name, **xargs)
- return super if !defined?(@current_object) || @current_object.nil?
-
- listeners = request.cache(:listeners)[field] ||= field.all_listeners
- return super unless listeners.include?(event_name)
-
- callbacks = request.cache(:events)[field] ||= field.all_events
- old_events, @all_events = @all_events, callbacks
- super
- ensure
- @all_events = old_events
- end
-
# Check if the field was assigned correctly to an output field
def check_assignment!
- raise MissingFieldError, <<~MSG.squish if field.nil?
+ raise MissingFieldError, (+<<~MSG).squish if field.nil?
Unable to find a field named "#{gql_name}" on
#{entry_point? ? operation.kind : parent.type_klass.name}.
MSG
- raise FieldError, <<~MSG.squish unless field.output_type?
+ raise FieldError, (+<<~MSG).squish unless field.output_type?
The "#{gql_name}" was assigned to a non-output type of field: #{field.inspect}.
MSG
- empty_selection = data[:selection].nil? || data[:selection].null?
- raise FieldError, <<~MSG.squish if field.leaf_type? && !empty_selection
- The "#{gql_name}" was assigned to the #{type_klass.gql_name} which
- is a leaf type and does not have nested fields.
- MSG
+ if @node[4].nil?
+ raise FieldError, (+<<~MSG).squish if !field.leaf_type?
+ The "#{gql_name}" was assigned to the #{type_klass.gql_name} which
+ is not a leaf type and requires a selection of fields.
+ MSG
+ else
+ raise FieldError, (+<<~MSG).squish if field.leaf_type?
+ The "#{gql_name}" was assigned to the #{type_klass.gql_name} which
+ is a leaf type and does not have nested fields.
+ MSG
+ end
- raise FieldError, <<~MSG.squish if !field.leaf_type? && empty_selection
- The "#{gql_name}" was assigned to the #{type_klass.gql_name} which
- is not a leaf type and requires a selection of fields.
- MSG
-
- raise DisabledFieldError, <<~MSG.squish if field.disabled?
+ raise DisabledFieldError, (+<<~MSG).squish if field.disabled?
The "#{gql_name}" was found but it is marked as disabled.
MSG
end
end
end