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 FindInheritedValue::EmptyObjects

FindInheritedValue::EmptyObjects::EMPTY_ARRAY, FindInheritedValue::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

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Resolver::HasPayloadType

field_class, object_class, payload_type

Methods included from Member::HasFields

add_field, field, field_class, fields, get_field, global_id_field, own_fields

Methods inherited from Resolver

argument, #arguments, #authorized?, broadcastable, broadcastable?, complexity, #dataloader, extension, extensions, extras, has_max_page_size?, max_page_size, null, #ready?, resolve_method, type, type_expr, #unauthorized_object

Methods included from Member::BaseDSLMethods

#accessible?, #authorized?, #default_graphql_name, #description, #graphql_name, #introspection, #introspection?, #mutation, #name, #overridden_graphql_name, #to_graphql, #visible?

Methods included from Member::HasArguments

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

Methods included from Member::HasValidators

#validates, #validators

Methods included from Member::HasPath

#path

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

.field_optionsObject

Overriding Resolver#field_options to include subscription_scope



138
139
140
141
142
# File 'lib/graphql/schema/subscription.rb', line 138

def self.field_options
  super.merge(
    subscription_scope: subscription_scope
  )
end

.subscription_scope(new_scope = READING_SCOPE) ⇒ 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)

Returns:

  • (Symbol)


107
108
109
110
111
112
113
114
115
# File 'lib/graphql/schema/subscription.rb', line 107

def self.subscription_scope(new_scope = READING_SCOPE)
  if new_scope != READING_SCOPE
    @subscription_scope = new_scope
  elsif defined?(@subscription_scope)
    @subscription_scope
  else
    find_inherited_value(:subscription_scope)
  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.


133
134
135
# File 'lib/graphql/schema/subscription.rb', line 133

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).



89
90
91
92
93
94
# File 'lib/graphql/schema/subscription.rb', line 89

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

#resolve(**args) ⇒ Object

Implement the Resolve API



45
46
47
48
49
# File 'lib/graphql/schema/subscription.rb', line 45

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



52
53
54
55
56
57
58
59
# File 'lib/graphql/schema/subscription.rb', line 52

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



69
70
71
72
73
74
75
76
77
# File 'lib/graphql/schema/subscription.rb', line 69

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
# File 'lib/graphql/schema/subscription.rb', line 28

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


  if unsubscribed
    context.skip
  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.



64
65
66
# File 'lib/graphql/schema/subscription.rb', line 64

def subscribe(args = {})
  :no_response
end

#unsubscribeObject

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



97
98
99
100
# File 'lib/graphql/schema/subscription.rb', line 97

def unsubscribe
  context.namespace(:subscriptions)[:unsubscribed] = true
  throw :graphql_subscription_unsubscribed
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.



82
83
84
# File 'lib/graphql/schema/subscription.rb', line 82

def update(args = {})
  object
end