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 |