Some useful links:
https://gist.github.com/bbugh/742942596156830c597aaf0ea7a2d800
https://evilmartians.com/chronicles/graphql-on-rails-3-on-the-way-to-perfection
https://github.com/rmosolgo/graphql-ruby/tree/v1.8.0/guides/subscriptions
# app/graphql/mutations/add_item_mutation.rb | |
module Mutations | |
class AddItemMutation < Mutations::BaseMutation | |
argument :attributes, Types::ItemAttributes, required: true | |
field :item, Types::ItemType, null: true | |
field :errors, [String], null: false | |
def resolve(attributes:) | |
check_authentication! | |
item = Item.new(attributes.merge(user: context[:current_user])) | |
if item.save | |
MartianLibrarySchema.subscriptions.trigger("itemAdded", {}, item, scope: context[:current_user].id) | |
{ item: item } | |
else | |
{ errors: item.errors.full_messages } | |
end | |
end | |
end | |
end |
# config/cable.yml | |
development: | |
adapter: redis | |
url: redis://localhost:6379/1 | |
test: | |
adapter: async | |
production: | |
adapter: redis | |
url: redis://localhost:6379/1 | |
channel_prefix: xyz_production |
# app/channels/application_cable/connection.rb | |
module ApplicationCable | |
class Connection < ActionCable::Connection::Base | |
identified_by :current_user | |
def connect | |
self.current_user = find_verified_user | |
end | |
private | |
def find_verified_user | |
token = request.params[:token].to_s | |
email = Base64.decode64(token) | |
current_user = User.find_by(email: email) | |
return current_user if current_user | |
reject_unauthorized_connection | |
end | |
end | |
end |
# config/environments/development.rb | |
# config/environments/production.rb | |
Rails.application.configure do | |
: | |
: | |
config.action_cable.allowed_request_origins = [ENV['UI_HOST_DOMAIN']] # <- ActionCable needs to know the domain or a pattern of domain to allow the connection | |
end |
{ Ref: https://evilmartians.com/chronicles/graphql-on-rails-3-on-the-way-to-perfection } | |
(Rails - 5, Graphql-ruby - 1.8.x) | |
# Gemfile | |
gem 'redis' |
# app/channels/graphql_channel.rb | |
class GraphqlChannel < ApplicationCable::Channel | |
def subscribed | |
@subscription_ids = [] | |
end | |
def execute(data) | |
result = execute_query(data) | |
payload = { | |
result: result.subscription? ? { data: nil } : result.to_h, | |
more: result.subscription? | |
} | |
# Track the subscription here so we can remove it | |
# on unsubscribe. | |
@subscription_ids << context[:subscription_id] if result.context[:subscription_id] | |
transmit(payload) | |
end | |
def unsubscribed | |
@subscription_ids.each { |sid| | |
Schema.subscriptions.delete_subscription(sid) | |
} | |
end | |
private | |
def execute_query(data) | |
Schema.execute( | |
query: data['query'], | |
context: context, | |
variables: ensure_hash(data['variables']), | |
operation_name: data['operationName'] | |
) | |
end | |
def context | |
{ | |
current_user_id: current_user.id, # <- `current_user_id` will be used as scope to broadcast message only to this user. | |
current_user: current_user, | |
channel: self | |
} | |
end | |
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 |
# app/graphql/martian_library_schema.rb | |
class MartianLibrarySchema < GraphQL::Schema | |
use GraphQL::Subscriptions::ActionCableSubscriptions, redis: Redis.new | |
mutation(Types::MutationType) | |
query(Types::QueryType) | |
subscription(Types::SubscriptionType) | |
end |
# app/graphql/types/subscription_type.rb | |
# Ref: https://github.com/rmosolgo/graphql-ruby/commit/a329ff3cb84cc83da15b6283cf80dcdd68f49286#diff-d206d5f8891deaca408fcccd141e6193R63 | |
Types::SubscriptionType = GraphQL::ObjectType.define do | |
field :item_added, Types::ItemType, null: false, description: "An item was added" do | |
subscription_scope :current_user_id | |
end | |
# The return value of the method is not used; | |
# only the raised error affects the behavior of the subscription. | |
# If the error is raised, it will be added to the response's `"errors"` key and | |
# the subscription won't be created. | |
# TODO: write a policy to authorize. something like | |
# context[:current_account].can_subscribe_to?(account_id) | |
def account_status | |
raise GraphQL::ExecutionError, 'Can not subscribe to this topic' if context[:current_user].blank? | |
end | |
end |