Class: GraphQL::Subscriptions

Inherits:
Object
  • Object
show all
Defined in:
lib/graphql/subscriptions.rb,
lib/graphql/subscriptions/event.rb,
lib/graphql/subscriptions/serialize.rb,
lib/graphql/subscriptions/instrumentation.rb,
lib/graphql/subscriptions/subscription_root.rb,
lib/graphql/subscriptions/action_cable_subscriptions.rb

Direct Known Subclasses

ActionCableSubscriptions

Defined Under Namespace

Modules: Serialize, SubscriptionRoot Classes: ActionCableSubscriptions, Event, Instrumentation, InvalidTriggerError

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(schema:, **rest) ⇒ Subscriptions

Returns a new instance of Subscriptions.

Parameters:

  • schema (Class)

    the GraphQL schema this manager belongs to



36
37
38
# File 'lib/graphql/subscriptions.rb', line 36

def initialize(schema:, **rest)
  @schema = schema
end

Class Method Details

.use(defn, options = {}) ⇒ Object

See Also:

  • for options, concrete implementations may add options.


20
21
22
23
24
25
26
27
28
29
30
31
32
33
# File 'lib/graphql/subscriptions.rb', line 20

def self.use(defn, options = {})
  schema = defn.is_a?(Class) ? defn : defn.target

  if schema.subscriptions
    raise ArgumentError, "Can't reinstall subscriptions. #{schema} is using #{schema.subscriptions}, can't also add #{self}"
  end

  instrumentation = Subscriptions::Instrumentation.new(schema: schema)
  defn.instrument(:query, instrumentation)
  defn.instrument(:field, instrumentation)
  options[:schema] = schema
  schema.subscriptions = self.new(**options)
  nil
end

Instance Method Details

#build_idString

Returns A new unique identifier for a subscription.

Returns:

  • (String)

    A new unique identifier for a subscription



170
171
172
# File 'lib/graphql/subscriptions.rb', line 170

def build_id
  SecureRandom.uuid
end

#delete_subscription(subscription_id) ⇒ Object

A subscription was terminated server-side. Clean up the database.

Parameters:

  • subscription_id (String)

Returns:

  • void.

Raises:



165
166
167
# File 'lib/graphql/subscriptions.rb', line 165

def delete_subscription(subscription_id)
  raise GraphQL::RequiredImplementationMissingError
end

#deliver(subscription_id, result) ⇒ void

This method returns an undefined value.

A subscription query was re-evaluated, returning result. The result should be send to subscription_id.

Parameters:

  • subscription_id (String)
  • result (Hash)

Raises:



148
149
150
# File 'lib/graphql/subscriptions.rb', line 148

def deliver(subscription_id, result)
  raise GraphQL::RequiredImplementationMissingError
end

#each_subscription_id(event) {|subscription_id| ... } ⇒ void

This method returns an undefined value.

Get each subscription_id subscribed to event.topic and yield them

Parameters:

Yield Parameters:

  • subscription_id (String)

Raises:



131
132
133
# File 'lib/graphql/subscriptions.rb', line 131

def each_subscription_id(event)
  raise GraphQL::RequiredImplementationMissingError
end

#execute(subscription_id, event, object) ⇒ void

This method returns an undefined value.

event was triggered on object, and subscription_id was subscribed, so it should be updated.

Load subscription_id’s GraphQL data, re-evaluate the query, and deliver the result.

This is where a queue may be inserted to push updates in the background.

Parameters:

  • subscription_id (String)
  • event (GraphQL::Subscriptions::Event)

    The event which was triggered

  • object (Object)

    The value for the subscription field



88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/graphql/subscriptions.rb', line 88

def execute(subscription_id, event, object)
  # Lookup the saved data for this subscription
  query_data = read_subscription(subscription_id)
  # Fetch the required keys from the saved data
  query_string = query_data.fetch(:query_string)
  variables = query_data.fetch(:variables)
  context = query_data.fetch(:context)
  operation_name = query_data.fetch(:operation_name)
  # Re-evaluate the saved query
  result = @schema.execute(
    **{
      query: query_string,
      context: context,
      subscription_topic: event.topic,
      operation_name: operation_name,
      variables: variables,
      root_value: object,
    }
  )
  deliver(subscription_id, result)
rescue GraphQL::Schema::Subscription::NoUpdateError
  # This update was skipped in user code; do nothing.
rescue GraphQL::Schema::Subscription::UnsubscribedError
  # `unsubscribe` was called, clean up on our side
  # TODO also send `{more: false}` to client?
  delete_subscription(subscription_id)
end

#execute_all(event, object) ⇒ void

This method returns an undefined value.

Event event occurred on object, Update all subscribers.

Parameters:



121
122
123
124
125
# File 'lib/graphql/subscriptions.rb', line 121

def execute_all(event, object)
  each_subscription_id(event) do |subscription_id|
    execute(subscription_id, event, object)
  end
end

#normalize_name(event_or_arg_name) ⇒ String

Convert a user-provided event name or argument to the equivalent in GraphQL.

By default, it converts the identifier to camelcase. Override this in a subclass to change the transformation.

Parameters:

  • event_or_arg_name (String, Symbol)

Returns:

  • (String)


182
183
184
# File 'lib/graphql/subscriptions.rb', line 182

def normalize_name(event_or_arg_name)
  Schema::Member::BuildType.camelize(event_or_arg_name.to_s)
end

#read_subscription(subscription_id) ⇒ Hash

The system wants to send an update to this subscription. Read its data and return it.

Parameters:

  • subscription_id (String)

Returns:

  • (Hash)

    Containing required keys

Raises:



139
140
141
# File 'lib/graphql/subscriptions.rb', line 139

def read_subscription(subscription_id)
  raise GraphQL::RequiredImplementationMissingError
end

#trigger(event_name, args, object, scope: nil) ⇒ void

This method returns an undefined value.

Fetch subscriptions matching this field + arguments pair And pass them off to the queue.

Parameters:

  • event_name (String)
  • args (Hash<String, Symbol => Object])

    rgs [Hash<String, Symbol => Object]

  • object (Object)
  • scope (Symbol, String) (defaults to: nil)


47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/graphql/subscriptions.rb', line 47

def trigger(event_name, args, object, scope: nil)
  event_name = event_name.to_s

  # Try with the verbatim input first:
  field = @schema.get_field(@schema.subscription, event_name)

  if field.nil?
    # And if it wasn't found, normalize it:
    normalized_event_name = normalize_name(event_name)
    field = @schema.get_field(@schema.subscription, normalized_event_name)
    if field.nil?
      raise InvalidTriggerError, "No subscription matching trigger: #{event_name} (looked for #{@schema.subscription.graphql_name}.#{normalized_event_name})"
    end
  else
    # Since we found a field, the original input was already normalized
    normalized_event_name = event_name
  end

  # Normalize symbol-keyed args to strings, try camelizing them
  normalized_args = normalize_arguments(normalized_event_name, field, args)

  event = Subscriptions::Event.new(
    name: normalized_event_name,
    arguments: normalized_args,
    field: field,
    scope: scope,
  )
  execute_all(event, object)
end

#write_subscription(query, events) ⇒ void

This method returns an undefined value.

query was executed and found subscriptions to events. Update the database to reflect this new state.



157
158
159
# File 'lib/graphql/subscriptions.rb', line 157

def write_subscription(query, events)
  raise GraphQL::RequiredImplementationMissingError
end