diff --git a/guides/subscriptions/subscription_classes.md b/guides/subscriptions/subscription_classes.md index af0914dd71..5261e336a3 100644 --- a/guides/subscriptions/subscription_classes.md +++ b/guides/subscriptions/subscription_classes.md @@ -137,11 +137,11 @@ subscription($roomId: ID!) { If you remove `null: false`, then you can return different data in the initial subscription and the subsequent updates. (See lifecycle methods below.) -Instead of a generated type, you can provide an already-configured type with `payload_type`: +Instead of a generated type, you can provide an already-configured type with `type` just like mutations and resolvers: ```ruby # Just return a message -payload_type Types::MessageType +type Types::MessageType ``` (In that case, don't return a hash from `#subscribe` or `#update`, return a `message` object instead.) diff --git a/lib/graphql/schema/subscription.rb b/lib/graphql/schema/subscription.rb index 8f1c07971c..9bc335a84a 100644 --- a/lib/graphql/schema/subscription.rb +++ b/lib/graphql/schema/subscription.rb @@ -15,9 +15,6 @@ class Subscription < GraphQL::Schema::Resolver extend GraphQL::Schema::Resolver::HasPayloadType extend GraphQL::Schema::Member::HasFields NO_UPDATE = :no_update - # The generated payload type is required; If there's no payload, - # propagate null. - null false # @api private def initialize(object:, context:, field:) @@ -69,7 +66,7 @@ def resolve(**args) def resolve_subscribe(**args) ret_val = !args.empty? ? subscribe(**args) : subscribe if ret_val == :no_response - context.skip + @null ? nil : context.skip else ret_val end diff --git a/spec/graphql/schema/subscription_spec.rb b/spec/graphql/schema/subscription_spec.rb index 0df9d1953e..fa4670138a 100644 --- a/spec/graphql/schema/subscription_spec.rb +++ b/spec/graphql/schema/subscription_spec.rb @@ -118,7 +118,7 @@ class UsersJoinedManualPayload < GraphQL::Schema::Object description: "Includes newly-created users, or all users on the initial load" end - payload_type UsersJoinedManualPayload + type UsersJoinedManualPayload def subscribe { users: USERS.values } @@ -137,6 +137,42 @@ class NewUsersJoined < BaseSubscription description: "Includes newly-created users, or all users on the initial load" end + # The newer API changes using type and not needing to implement the "subscribe" method + # which aligns with how relay or apollo digest the payload. + class UserChanged < BaseSubscription + type User + + argument :handle, String, loads: User, as: :user, camelize: false + + # don't need to implement the subscribe method. Returns an empty payload as + # the initial subscribe action shouldn't need to load any data, only when the event happens + + def update(user:) + user + end + + def self.visible?(context) + !context[:legacy_schema] + end + end + + # The newer API changes using type and not needing to implement the "subscribe" method + # which aligns with how relay or apollo digest the payload. + class LegacyUserChanged < BaseSubscription + null true + payload_type User + + argument :handle, String, loads: User, as: :user, camelize: false + + def update(user:) + user + end + + def self.visible?(context) + !!context[:legacy_schema] + end + end + class Subscription < GraphQL::Schema::Object field :toot_was_tooted, subscription: TootWasTooted, extras: [:path, :query] field :toot_was_tooted, subscription: LegacyTootWasTooted, extras: [:path, :query] @@ -144,6 +180,8 @@ class Subscription < GraphQL::Schema::Object field :direct_toot_was_tooted_with_optional_scope, subscription: DirectTootWasTootedWithOptionalScope field :users_joined, subscription: UsersJoined field :new_users_joined, subscription: NewUsersJoined + field :user_changed, subscription: UserChanged + field :user_changed, subscription: LegacyUserChanged end class Mutation < GraphQL::Schema::Object @@ -254,13 +292,13 @@ def in_memory_subscription_count end it "generates a return type" do - return_type = SubscriptionFieldSchema::TootWasTooted.payload_type + return_type = SubscriptionFieldSchema::TootWasTooted.type assert_equal "TootWasTootedPayload", return_type.graphql_name assert_equal ["toot", "user"], return_type.fields.keys end - it "can use a premade `payload_type`" do - return_type = SubscriptionFieldSchema::UsersJoined.payload_type + it "can use a premade `type`" do + return_type = SubscriptionFieldSchema::UsersJoined.type assert_equal "UsersJoinedManualPayload", return_type.graphql_name assert_equal ["users"], return_type.fields.keys assert_equal SubscriptionFieldSchema::UsersJoined::UsersJoinedManualPayload, return_type @@ -304,7 +342,7 @@ def in_memory_subscription_count GRAPHQL expected_response = { - "data"=>nil, + "data"=> { "tootWasTooted" => nil }, "errors"=>[ { "message"=>"You don't have permission to subscribe", @@ -328,7 +366,7 @@ def in_memory_subscription_count GRAPHQL expected_response = { - "data" => nil, + "data" => { "tootWasTooted" => nil }, "errors" => [ { "message"=>"No object found for `handle: \"jack\"`", @@ -350,7 +388,7 @@ def in_memory_subscription_count } GRAPHQL expected_response = { - "data"=>nil, + "data"=> { "tootWasTooted" => nil }, "errors"=>[ { "message"=>"Can't subscribe to private user ([\"tootWasTooted\"])", @@ -376,6 +414,34 @@ def in_memory_subscription_count assert_equal 1, in_memory_subscription_count end + it "sends a minimal initial response if :no_response is returned when using type instead of payload_type, which is the default" do + assert_equal 0, in_memory_subscription_count + + res = exec_query <<-GRAPHQL + subscription { + userChanged(handle: "matz") { + handle + } + } + GRAPHQL + assert_equal({"data" => { "userChanged" => nil }}, res) + assert_equal 1, in_memory_subscription_count + end + + it "sends no initial response if :no_response is returned when using legacy payload_type" do + assert_equal 0, in_memory_subscription_count + + res = exec_query <<-GRAPHQL, context: { legacy_schema: true } + subscription { + userChanged(handle: "matz") { + handle + } + } + GRAPHQL + assert_equal({"data" => nil}, res) + assert_equal 1, in_memory_subscription_count + end + it "works when there are no arguments" do assert_equal 0, in_memory_subscription_count @@ -411,6 +477,23 @@ def in_memory_subscription_count assert_equal "I am a C programmer", update_payload["data"]["tootWasTooted"]["toot"]["body"] end + it "updates with the returned value but no initial subscription value" do + res = exec_query <<-GRAPHQL + subscription { + userChanged(handle: "matz") { + handle + } + } + GRAPHQL + + assert_equal({"data" => { "userChanged" => nil }}, res) + assert_equal 1, in_memory_subscription_count + SubscriptionFieldSchema.subscriptions.trigger(:user_changed, { handle: "matz" }, {handle: "matz", private: false }) + + update = res.context[:subscription_mailbox].first + assert_equal "matz", update["data"]["userChanged"]["handle"] + end + it "updates with the returned value" do res = exec_query <<-GRAPHQL subscription {