Last active
April 20, 2024 04:31
-
-
Save jerridan/d228758527563687c65065afc2f09745 to your computer and use it in GitHub Desktop.
Implementing GraphQL Subscriptions in Rails and React
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ... | |
import ActionCable from "@rails/actioncable"; | |
import ActionCableLink from "graphql-ruby-client/subscriptions/ActionCableLink"; | |
import { split } from "@apollo/client"; | |
import { getMainDefinition } from "@apollo/client/utilities"; | |
// Setup a link for action cable | |
const cable = ActionCable.createConsumer(<YOUR_ACTION_CABLE_ENDPOINT>); | |
const actionCableLink = new ActionCableLink({ cable }); | |
// Redirect subscriptions to the action cable link, while using the HTTP link for other queries | |
const splitLink = split( | |
({ query }) => { | |
const definition = getMainDefinition(query); | |
return definition.kind === "OperationDefinition" && definition.operation === "subscription"; | |
}, | |
actionCableLink, | |
httpLink | |
); | |
// Use the new splitLink for the Apollo client | |
const client = new ApolloClient({ | |
link: splitLink, | |
...other options | |
}); | |
// ... |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ... | |
const client = new ApolloClient({ | |
uri: <YOUR_GRAPHQL_ENDPOINT>, | |
...other options | |
}); | |
// OR | |
const link = createHttpLink({ uri: <YOUR_GRAPHQL_ENDPOINT> }); | |
const client = new ApolloClient({ | |
link, | |
...other options | |
}); | |
// ... |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module Subscriptions | |
class BaseSubscription < GraphQL::Schema::Subscription | |
def current_application_context | |
context[:current_application_context] | |
end | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
development: | |
adapter: postgresql | |
test: | |
adapter: test | |
production: | |
adapter: postgresql |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module ApplicationCable | |
class Channel < ActionCable::Channel::Base | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
subscription OnCommentPosted { | |
commentPosted { | |
comment { | |
id | |
message | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# A theoretical Comment class that uses a callback to trigger a GraphQL subscription update | |
class Comment < ApplicationRecord | |
after_create :trigger_comment_posted_event | |
private | |
def trigger_comment_posted_event | |
ApplicationSubscription.trigger("commentPosted", {}, { comment: self }) | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module Subscriptions | |
class CommentPosted < BaseSubscription | |
field :comment, Types::CommentType, null: false | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module ApplicationCable | |
class Connection < ActionCable::Connection::Base | |
def current_application_context | |
@current_application_context ||= ApplicationContext.new(cookies) | |
end | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class GraphqlChannel < ApplicationCable::Channel | |
def subscribed | |
# Store all GraphQL subscriptions the consumer is listening for on this channel | |
@subscription_ids = [] | |
end | |
def execute(data) | |
query = data["query"] | |
variables = ensure_hash(data["variables"]) | |
operation_name = data["operationName"] | |
context = { | |
channel: self, | |
current_application_context: connection.current_application_context | |
} | |
result = Schema.execute({ | |
query: query, | |
context: context, | |
variables: variables, | |
operation_name: operation_name, | |
}) | |
payload = { | |
result: result.to_h, | |
more: result.subscription?, | |
} | |
# Append the subscription id | |
@subscription_ids << result.context[:subscription_id] if result.context[:subscription_id] | |
transmit(payload) | |
end | |
def unsubscribed | |
# Delete all of the consumer's subscriptions from the GraphQL Schema | |
@subscription_ids.each do |sid| | |
Schema.subscriptions.delete_subscription(sid) | |
end | |
end | |
private | |
def ensure_hash(ambiguous_param) | |
case ambiguous_param | |
when String | |
if ambiguous_param.present? | |
ensure_hash(JSON.parse(ambiguous_param)) | |
else | |
{} | |
end | |
when Hash, ActionController::Parameters | |
ambiguous_param | |
when nil | |
{} | |
else | |
raise ArgumentError, "Unexpected parameter: #{ambiguous_param}" | |
end | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Rails.application.routes.draw do | |
# ... other rails routes | |
mount ActionCable.server, at: "/cable" | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module Types | |
class SubscriptionType < Types::BaseObject | |
description "The subscription root for the GraphQL schema" | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { useSubscription } from "@apollo/client"; | |
import filter from "lodash/filter"; | |
import { GetComments, OnCommentPosted, OnCommentUpdated } from "queries/comment.gql" | |
export default useSubscriptions() { | |
// Subscribe to new comments | |
useSubscription(OnCommentPosted, { | |
onSubscriptionData: ({ subscriptionData: { data } }) => { | |
// This will be called on initial load, so we need to handle an undefined value of data | |
if (!data || !data.commentPosted) return; | |
// Read the existing comments from cache and merge in the new value | |
const newComment = data.commentPosted.comment; | |
const { comments } = client.readQuery({ query: GetComments }); | |
const updatedComments = [newComment].concat(filter(comments, (comment) => comment.id !== newComment.id)); | |
// Write the updated comments to cache | |
client.writeQuery({ | |
query: GetComments, | |
data: { comments: updatedComments }, | |
}); | |
} | |
}); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hi, just wondering what is actually in the
ApplicationContext
file?