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

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, has_no_fields, has_no_fields?, 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?, #comment, #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

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns a new instance of Subscription.



23
24
25
26
27
28
29
30
31
32
33
# File 'lib/graphql/schema/subscription.rb', line 23

def initialize(object:, context:, field:)
  super
  # Figure out whether this is an update or an initial subscription
  @mode = context.query.subscription_update? ? :update : :subscribe
  @subscription_written = false
  @original_arguments = nil
  if (subs_ns = context.namespace(:subscriptions)) &&
    (sub_insts = subs_ns[:subscriptions])
    sub_insts[context.current_path] = self
  end
end

Class Method Details

.subscription_scope(new_scope = NOT_CONFIGURED, 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: NOT_CONFIGURED)
  • optional (Boolean) (defaults to: false)

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

Returns:

  • (Symbol)


127
128
129
130
131
132
133
134
135
136
# File 'lib/graphql/schema/subscription.rb', line 127

def self.subscription_scope(new_scope = NOT_CONFIGURED, optional: false)
  if new_scope != NOT_CONFIGURED
    @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:



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

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.


162
163
164
# File 'lib/graphql/schema/subscription.rb', line 162

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

Instance Method Details

#eventSubscriptions::Event

Returns This object is used as a representation of this subscription for the backend.

Returns:

  • (Subscriptions::Event)

    This object is used as a representation of this subscription for the backend



191
192
193
194
195
196
197
198
# File 'lib/graphql/schema/subscription.rb', line 191

def event
  @event ||= Subscriptions::Event.new(
    name: field.name,
    arguments: @original_arguments,
    context: context,
    field: field,
  )
end

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



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

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

#resolve(**args) ⇒ Object

Implement the Resolve API. You can implement this if you want code to run for both the initial subscription and for later updates. Or, implement #subscribe and #update



61
62
63
64
65
# File 'lib/graphql/schema/subscription.rb', line 61

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

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Wrap the user-defined #subscribe hook



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

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

#resolve_update(**args) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Wrap the user-provided #update hook



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

def resolve_update(**args)
  ret_val = !args.empty? ? 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

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/graphql/schema/subscription.rb', line 36

def resolve_with_support(**args)
  @original_arguments = args # before `loads:` have been run
  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.



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

def subscribe(args = {})
  :no_response
end

#subscription_written?Boolean

Returns true if #write_subscription was called already.

Returns:



186
187
188
# File 'lib/graphql/schema/subscription.rb', line 186

def subscription_written?
  @subscription_written
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



117
118
119
120
# File 'lib/graphql/schema/subscription.rb', line 117

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.



100
101
102
# File 'lib/graphql/schema/subscription.rb', line 100

def update(args = {})
  object
end

#write_subscriptionvoid

This method returns an undefined value.

Calls through to schema.subscriptions to register this subscription with the backend. This is automatically called by GraphQL-Ruby after a query finishes successfully, but if you need to commit the subscription during #subscribe, you can call it there. (This method also sets a flag showing that this subscription was already written.)

If you call this method yourself, you may also need to #unsubscribe or call subscriptions.delete_subscription to clean up the database if the query crashes with an error later in execution.



175
176
177
178
179
180
181
182
183
# File 'lib/graphql/schema/subscription.rb', line 175

def write_subscription
  if subscription_written?
    raise GraphQL::Error, "`write_subscription` was called but `#{self.class}#subscription_written?` is already true. Remove a call to `write subscription`."
  else
    @subscription_written = true
    context.schema.subscriptions.write_subscription(context.query, [event])
  end
  nil
end