Class: GraphQL::Schema::Subscription

Inherits:
Resolver
  • Object
show all
Extended by:
Member::HasFields, Resolver::HasPayloadType
Defined in:
lib/graphql/schema/subscription.rb

Overview

This class can be extended to create fields on your subscription root.

It provides hooks for the different parts of the subscription lifecycle:

  • #authorized?: called before initial subscription and subsequent updates
  • #subscribe: called for the initial subscription
  • #update: called for subsequent update

Also, #unsubscribe terminates the subscription.

Constant Summary collapse

NO_UPDATE =
:no_update
READING_SCOPE =
::Object.new

Constants included from Resolver::HasPayloadType

Resolver::HasPayloadType::NO_INTERFACES

Constants included from Member::HasFields

Member::HasFields::CONFLICT_FIELD_NAMES, Member::HasFields::GRAPHQL_RUBY_KEYWORDS, Member::HasFields::RUBY_KEYWORDS

Constants included from Member::HasArguments

Member::HasArguments::NO_ARGUMENTS

Constants included from EmptyObjects

EmptyObjects::EMPTY_ARRAY, EmptyObjects::EMPTY_HASH

Constants included from Member::GraphQLTypeNames

Member::GraphQLTypeNames::Boolean, Member::GraphQLTypeNames::ID, Member::GraphQLTypeNames::Int

Instance Attribute Summary

Attributes inherited from Resolver

#context, #field, #object

Attributes included from Member::BaseDSLMethods

#default_graphql_name, #graphql_name

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Resolver::HasPayloadType

field, field_class, object_class, payload_type, type

Methods included from Member::HasFields

add_field, all_field_definitions, field, field_class, global_id_field, own_fields

Methods inherited from Resolver

all_field_argument_definitions, any_field_arguments?, argument, #arguments, #authorized?, broadcastable, broadcastable?, #call_resolve, complexity, #dataloader, default_page_size, extension, extensions, extras, field_arguments, get_field_argument, has_default_page_size?, has_max_page_size?, max_page_size, null, #ready?, resolve_method, resolver_method, type, type_expr, #unauthorized_object

Methods included from Member::BaseDSLMethods

#authorized?, #default_relay, #description, #introspection, #introspection?, #mutation, #name, #visible?

Methods included from Member::HasArguments

#add_argument, #all_argument_definitions, #any_arguments?, #argument, #argument_class, #arguments, #arguments_statically_coercible?, #coerce_arguments, #get_argument, #own_arguments, #remove_argument, #validate_directive_argument

Methods included from Member::HasValidators

#validates, #validators

Methods included from Member::HasPath

#path

Methods included from Member::HasDirectives

add_directive, #directive, #directives, get_directives, #inherited, #remove_directive, remove_directive

Constructor Details

#initialize(object:, context:, field:) ⇒ Subscription

Returns a new instance of Subscription.



22
23
24
25
26
# File 'lib/graphql/schema/subscription.rb', line 22

def initialize(object:, context:, field:)
  super
  # Figure out whether this is an update or an initial subscription
  @mode = context.query.subscription_update? ? :update : :subscribe
end

Class Method Details

.subscription_scope(new_scope = READING_SCOPE, optional: false) ⇒ Symbol

Call this method to provide a new subscription_scope; OR call it without an argument to get the subscription_scope

Parameters:

  • new_scope (Symbol) (defaults to: READING_SCOPE)
  • optional (Boolean) (defaults to: false)

    If true, then don’t require scope: to be provided to updates to this subscription.

Returns:

  • (Symbol)


115
116
117
118
119
120
121
122
123
124
# File 'lib/graphql/schema/subscription.rb', line 115

def self.subscription_scope(new_scope = READING_SCOPE, optional: false)
  if new_scope != READING_SCOPE
    @subscription_scope = new_scope
    @subscription_scope_optional = optional
  elsif defined?(@subscription_scope)
    @subscription_scope
  else
    find_inherited_value(:subscription_scope)
  end
end

.subscription_scope_optional?Boolean

Returns:



126
127
128
129
130
131
132
# File 'lib/graphql/schema/subscription.rb', line 126

def self.subscription_scope_optional?
  if defined?(@subscription_scope_optional)
    @subscription_scope_optional
  else
    find_inherited_value(:subscription_scope_optional, false)
  end
end

.topic_for(arguments:, field:, scope:) ⇒ String

This is called during initial subscription to get a “name” for this subscription. Later, when .trigger is called, this will be called again to build another “name”. Any subscribers with matching topic will begin the update flow.

The default implementation creates a string using the field name, subscription scope, and argument keys and values. In that implementation, only .trigger calls with exact matches result in updates to subscribers.

To implement a filtered stream-type subscription flow, override this method to return a string with field name and subscription scope. Then, implement #update to compare its arguments to the current object and return NO_UPDATE when an update should be filtered out.

Parameters:

  • arguments (Hash<String => Object>)

    The arguments for this topic, in GraphQL-style (camelized strings)

  • field (GraphQL::Schema::Field)
  • scope (Object, nil)

    A value corresponding to .trigger(... scope:) (for updates) or the subscription_scope found in context (for initial subscriptions).

Returns:

  • (String)

    An identifier corresponding to a stream of updates

See Also:

  • for how to skip updates when an event comes with a matching topic.


150
151
152
# File 'lib/graphql/schema/subscription.rb', line 150

def self.topic_for(arguments:, field:, scope:)
  Subscriptions::Serialize.dump_recursive([scope, field.graphql_name, arguments])
end

Instance Method Details

#load_application_object_failed(err) ⇒ Object

If an argument is flagged with loads: and no object is found for it, remove this subscription (assuming that the object was deleted in the meantime, or that it became inaccessible).



94
95
96
97
98
99
# File 'lib/graphql/schema/subscription.rb', line 94

def load_application_object_failed(err)
  if @mode == :update
    unsubscribe
  end
  super
end

#resolve(**args) ⇒ Object

Implement the Resolve API



50
51
52
53
54
# File 'lib/graphql/schema/subscription.rb', line 50

def resolve(**args)
  # Dispatch based on `@mode`, which will raise a `NoMethodError` if we ever
  # have an unexpected `@mode`
  public_send("resolve_#{@mode}", **args)
end

#resolve_subscribe(**args) ⇒ Object

Wrap the user-defined #subscribe hook



57
58
59
60
61
62
63
64
# File 'lib/graphql/schema/subscription.rb', line 57

def resolve_subscribe(**args)
  ret_val = args.any? ? subscribe(**args) : subscribe
  if ret_val == :no_response
    context.skip
  else
    ret_val
  end
end

#resolve_update(**args) ⇒ Object

Wrap the user-provided #update hook



74
75
76
77
78
79
80
81
82
# File 'lib/graphql/schema/subscription.rb', line 74

def resolve_update(**args)
  ret_val = args.any? ? update(**args) : update
  if ret_val == NO_UPDATE
    context.namespace(:subscriptions)[:no_update] = true
    context.skip
  else
    ret_val
  end
end

#resolve_with_support(**args) ⇒ Object



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/graphql/schema/subscription.rb', line 28

def resolve_with_support(**args)
  result = nil
  unsubscribed = true
  unsubscribed_result = catch :graphql_subscription_unsubscribed do
    result = super
    unsubscribed = false
  end


  if unsubscribed
    if unsubscribed_result
      context.namespace(:subscriptions)[:final_update] = true
      unsubscribed_result
    else
      context.skip
    end
  else
    result
  end
end

#subscribe(args = {}) ⇒ Object

The default implementation returns nothing on subscribe. Override it to return an object or :no_response to (explicitly) return nothing.



69
70
71
# File 'lib/graphql/schema/subscription.rb', line 69

def subscribe(args = {})
  :no_response
end

#unsubscribe(update_value = nil) ⇒ void

This method returns an undefined value.

Call this to halt execution and remove this subscription from the system

Parameters:

  • update_value (Object) (defaults to: nil)

    if present, deliver this update before unsubscribing



104
105
106
107
# File 'lib/graphql/schema/subscription.rb', line 104

def unsubscribe(update_value = nil)
  context.namespace(:subscriptions)[:unsubscribed] = true
  throw :graphql_subscription_unsubscribed, update_value
end

#update(args = {}) ⇒ Object

The default implementation returns the root object. Override it to return NO_UPDATE if you want to skip updates sometimes. Or override it to return a different object.



87
88
89
# File 'lib/graphql/schema/subscription.rb', line 87

def update(args = {})
  object
end