class GraphQL::Subscriptions::Event

This thing can be:

Attributes

arguments[R]

@return [GraphQL::Query::Arguments]

context[R]

@return [GraphQL::Query::Context]

name[R]

@return [String] Corresponds to the Subscription root field name

topic[R]

@return [String] An opaque string which identifies this event, derived from `name` and `arguments`

Public Class Methods

new(name:, arguments:, field: nil, context: nil, scope: nil) click to toggle source
# File lib/graphql/subscriptions/event.rb, line 21
def initialize(name:, arguments:, field: nil, context: nil, scope: nil)
  @name = name
  @arguments = arguments
  @context = context
  field ||= context.field
  scope_key = field.subscription_scope
  scope_val = scope || (context && scope_key && context[scope_key])
  if scope_key &&
      (subscription = field.resolver) &&
      (subscription.respond_to?(:subscription_scope_optional?)) &&
      !subscription.subscription_scope_optional? &&
      scope_val.nil?
    raise Subscriptions::SubscriptionScopeMissingError, "#{field.path} (#{subscription}) requires a `scope:` value to trigger updates (Set `subscription_scope ..., optional: true` to disable this requirement)"
  end

  @topic = self.class.serialize(name, arguments, field, scope: scope_val, context: context)
end
serialize(_name, arguments, field, scope:, context: GraphQL::Query::NullContext) click to toggle source

@return [String] an identifier for this unit of subscription

# File lib/graphql/subscriptions/event.rb, line 40
def self.serialize(_name, arguments, field, scope:, context: GraphQL::Query::NullContext)
  subscription = field.resolver || GraphQL::Schema::Subscription
  normalized_args = stringify_args(field, arguments.to_h, context)
  subscription.topic_for(arguments: normalized_args, field: field, scope: scope)
end

Private Class Methods

deep_sort_array_hashes(array_to_inspect) click to toggle source
# File lib/graphql/subscriptions/event.rb, line 81
def deep_sort_array_hashes(array_to_inspect)
  raise ArgumentError.new("Argument must be an Array") unless array_to_inspect.is_a?(Array)
  array_to_inspect.map do |v|
    if v.is_a?(Hash)
      deep_sort_hash_keys(v)
    elsif v.is_a?(Array)
      deep_sort_array_hashes(v)
    else
      v
    end
  end
end
deep_sort_hash_keys(hash_to_sort) click to toggle source

This method does not support cyclic references in the Hash, nor does it support Hashes whose keys are not sortable with respect to their peers ( cases where a <=> b might throw an error )

# File lib/graphql/subscriptions/event.rb, line 68
def deep_sort_hash_keys(hash_to_sort)
  raise ArgumentError.new("Argument must be a Hash") unless hash_to_sort.is_a?(Hash)
  hash_to_sort.keys.sort.map do |k|
    if hash_to_sort[k].is_a?(Hash)
      [k, deep_sort_hash_keys(hash_to_sort[k])]
    elsif hash_to_sort[k].is_a?(Array)
      [k, deep_sort_array_hashes(hash_to_sort[k])]
    else
      [k, hash_to_sort[k]]
    end
  end.to_h
end
get_arg_definition(arg_owner, arg_name, context) click to toggle source
# File lib/graphql/subscriptions/event.rb, line 138
def get_arg_definition(arg_owner, arg_name, context)
  arg_owner.get_argument(arg_name, context) || arg_owner.arguments(context).each_value.find { |v| v.keyword.to_s == arg_name }
end
stringify_args(arg_owner, args, context) click to toggle source
# File lib/graphql/subscriptions/event.rb, line 94
def stringify_args(arg_owner, args, context)
  arg_owner = arg_owner.respond_to?(:unwrap) ? arg_owner.unwrap : arg_owner # remove list and non-null wrappers
  case args
  when Hash
    next_args = {}
    args.each do |k, v|
      arg_name = k.to_s
      camelized_arg_name = GraphQL::Schema::Member::BuildType.camelize(arg_name)
      arg_defn = get_arg_definition(arg_owner, camelized_arg_name, context)

      if arg_defn
        normalized_arg_name = camelized_arg_name
      else
        normalized_arg_name = arg_name
        arg_defn = get_arg_definition(arg_owner, normalized_arg_name, context)
      end
      arg_base_type = arg_defn.type.unwrap
      # In the case where the value being emitted is seen as a "JSON"
      # type, treat the value as one atomic unit of serialization
      is_json_definition = arg_base_type && arg_base_type <= GraphQL::Types::JSON
      if is_json_definition
        sorted_value = if v.is_a?(Hash)
          deep_sort_hash_keys(v)
        elsif v.is_a?(Array)
          deep_sort_array_hashes(v)
        else
          v
        end
        next_args[normalized_arg_name] = sorted_value.respond_to?(:to_json) ? sorted_value.to_json : sorted_value
      else
        next_args[normalized_arg_name] = stringify_args(arg_base_type, v, context)
      end
    end
    # Make sure they're deeply sorted
    next_args.sort.to_h
  when Array
    args.map { |a| stringify_args(arg_owner, a, context) }
  when GraphQL::Schema::InputObject
    stringify_args(arg_owner, args.to_h, context)
  else
    args
  end
end

Public Instance Methods

fingerprint() click to toggle source

@return [String] a logical identifier for this event. (Stable when the query is broadcastable.)

# File lib/graphql/subscriptions/event.rb, line 47
def fingerprint
  @fingerprint ||= begin
    # When this query has been flagged as broadcastable,
    # use a generalized, stable fingerprint so that
    # duplicate subscriptions can be evaluated and distributed in bulk.
    # (`@topic` includes field, args, and subscription scope already.)
    if @context.namespace(:subscriptions)[:subscription_broadcastable]
      "#{@topic}/#{@context.query.fingerprint}"
    else
      # not broadcastable, build a unique ID for this event
      @context.schema.subscriptions.build_id
    end
  end
end