Graphql-ruby: what to define in a "Subscription Type" method

Created on 22 Aug 2018  路  5Comments  路  Source: rmosolgo/graphql-ruby

env

  • Darwin mbp.local 16.7.0 Darwin Kernel Version 16.7.0: Thu Jun 21 20:07:39 PDT 2018; root:xnu-3789.73.14~1/RELEASE_X86_64 x86_64
  • rails (5.2.1)
  • graphql (1.8.7)

NPM

  • "graphql-ruby-client": "^1.4.0"
  • "actioncable": "^5.2.1"
  • "@rails/webpacker": "3.5"

I am scratching my head trying to understand how to implement a subscription.

http://graphql-ruby.org/subscriptions/subscription_type.html
graphql_-_subscription_type
It says define a field like above, but the field actually needs a corresponding method like this. (otherwise it shows errors)

def post_was_published
  # ...
end

and just the below the code, it says This will be called on the initial request
graphql_-_subscription_type-2

But as far as I checked, this method will be called

  • when client initially loads its page
  • when Schema.subscriptions.trigger is called

Plus, even if I have a return value written in the method, it will not be sent to the client, but the triggered object value will be sent.

def post_was_published
  Post.first # this will never be sent to the client on either initial or trigger
end 

```rb
Schema.subscriptions.trigger("postWasPublished", {}, Post.last) # this Post.last will be sent

Here are what I am trying to figure out.

1. The SubscriptionType method is being called on initial and trigger call, shouldn't it be just on initial?
1. The return value in the SubscriptionType method won't be sent to the client, I shouldn't return any values?
1. Should I just have a job call (like sidekiq perform_async which calls `Schema.subscriptions.trigger`) in the method?, but the method is called on trigger as well, that will make a loop

ex:
```rb
def post_was_published
  SidekiqJob.perform_async # this will do the trigger call
end

Most helpful comment

Trying to cover this in #1930

All 5 comments

I am not sure if this is a legit way, but only difference I could see between initial and trigger calls was the context variable in the method.
https://github.com/rmosolgo/graphql-ruby/blob/b58bdac2d42e036788f6faa9799de11261c25582/lib/graphql/subscriptions/action_cable_subscriptions.rb#L115
On trigger, context has a key called :subscription_id, but initial call doesn't.

So I used the key to define if a call is initial one, like this.
https://github.com/github0013/graphql_subscription_actioncable/commit/55f5069e75c4c06670d5111b0360f08e6d297c16

I think I'm struggling with something very similar. I've been wracking my brain and combing the source trying to figure out what I'm missing, but I can't figure out why the object argument to Schema.subscriptions.trigger is always the result of the reloaded query.

My current setup involves a simple subscription for tracking the progress of an ActiveJob-powered upload task:

# graphql/subscriptions/upload_progress.rb
module Subscriptions
  class UploadProgress < ApplicationResolver
    description 'Stream progress from an ongoing upload.'

    type Types::UploadProgress, null: false

    argument :job, ID, required: true

    def resolve(job:)
      message = Sidekiq::Status.message(job)
      percent_complete = Sidekiq::Status.pct_complete(job)
      status = Sidekiq::Status.status(job)

      { message: message, percent_complete: percent_complete, status: status }
    end
  end
end
# graphql/types/subscription.rb
module Types
  class Subscription < ApplicationObject
    field :upload_progress, resolver: Subscriptions::UploadProgress
  end
end

I've set up ActionCable and graphql-ruby-client in the frontend and I've implemented a quick-'n'-dirty Redis backing for subscriptions (which you can gut-check here if you're so inclined) so that they're shared between ActiveJob jobs and the ActionCable server. I can trigger events and the client will respond to them.

The perplexing thing is that when I call trigger, I have to pass the full payload for UploadProgress as the object. This works, e.g.:

Schema.subscriptions.trigger(:upload_progress, { job: @provider_job_id }, message: message, percent_complete: percent_complete, status: status)

...whereas this fails with an InvalidNullError ("Cannot return null for non-nullable field Subscription.uploadProgress"):

Schema.subscriptions.trigger(:upload_progress, { job: @provider_job_id }, nil)

...and this fails with a slightly different InvalidNullError ("Cannot return null for non-nullable field UploadProgress.percentComplete"):

Schema.subscriptions.trigger(:upload_progress, { job: @provider_job_id }, {})

...and so on.

Except that I'm never returning null for UploadProgress; I'm only passing a nil for the root value. I confirmed this to my satisfaction by Prying into the resolver method while the query was being refreshed; Pry showed that I received the same argument I passed in, successfully resolved the status information, and returned a Hash:

Frame number: 0/156

From: /Users/daniel/Repos/Ignota/structur-api/app/graphql/subscriptions/upload_progress.rb @ line 16 Subscriptions::UploadProgress#resolve:

    11: def resolve(job:)
    12:   message = Sidekiq::Status.message(job)
    13:   percent_complete = Sidekiq::Status.pct_complete(job)
    14:   status = Sidekiq::Status.status(job)
    15:
 => 16:   binding.pry
    17:
    18:   { message: message, percent_complete: percent_complete, status: status }
    19: end

@arguments_by_keyword {:job=>#<GraphQL::Schema::Argument:0x00007f9463be33a0 @name="job", @type_expr="ID", @description=nil, @null=false, @default_value=:__no_default__, @owner=Subscriptions::UploadProgress, @as=nil, @keyword=:job, @prepare=nil, @type=#<GraphQL::Schema::NonNull:0x00007f946a933c98 @of_type=GraphQL::Types::ID, @graphql_definition=ID!>>}
@arguments_loads_as_type {}
@context             #<Query::Context ...>
@object              nil
job                  "9e70ccbb20c1395175c95c7d"
message              "Transcoding MP4..."
percent_complete     0
status               :working

If the maintainers have any insight into what's going on here, I'd be grateful for some guidance. Why is the object filtering back down to the client, and where are these null checks misfiring?

Thanks in advance for your thoughts! 馃檹

/cc @rmosolgo

Trying to cover this in #1930

Amazing! Looking forward to following that PR, happy to provide support or color if I can in any way.

Hi, I hope class-based subscriptions helped with this! Please give them a try and open an issue if you run into any trouble.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

jtippett picture jtippett  路  3Comments

skanev picture skanev  路  3Comments

rylanc picture rylanc  路  3Comments

dsgoers picture dsgoers  路  3Comments

jturkel picture jturkel  路  3Comments