GraphQLのスキーマを分割する (Rails)

August 28, 2022

これは何?

バックエンドは1つでクライアントごとに、GraphQLのスキーマを持ちたい場合などに複数のGraphQLスキーマを設定する方法を書きました。

確認環境

$ bundle exec ruby --version
ruby 2.7.5p203 (2021-11-24 revision f69aeb8314) [x86_64-darwin19]
$ bundle exec rails --version
Rails 6.0.4.6

routing 設定

今回は、スキーマ2 を追加します。

rails/sample-6/config/routes.rb

Rails.application.routes.draw do
  if Rails.env.development?
    # スキーマ1
    mount GraphiQL::Rails::Engine, at: "/graphiql", graphql_path: "/graphql"

    # スキーマ2
    mount GraphiQL::Rails::Engine, as: "new", at: "/new/graphiql", graphql_path: "/new/graphql"
  end

  # スキーマ1
  post "/graphql", to: "graphql#execute"

  # スキーマ2
  post "/new/graphql", to: "new_graphql#execute"
  # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
end

controller 追加

使うスキーマを Sample6NewSchema で指定しています。

rails/sample-6/app/controllers/new_graphql_controller.rb

class NewGraphqlController < ApplicationController
  # If accessing from outside this domain, nullify the session
  # This allows for outside API access while preventing CSRF attacks,
  # but you'll have to authenticate your user separately
  # protect_from_forgery with: :null_session

  def execute
    variables = prepare_variables(params[:variables])
    query = params[:query]
    operation_name = params[:operationName]
    context = {
      # Query context goes here, for example:
      # current_user: current_user,
    }
    result = Sample6NewSchema.execute(query, variables: variables, context: context, operation_name: operation_name)
    render json: result
  rescue StandardError => e
    raise e unless Rails.env.development?
    handle_error_in_development(e)
  end

  private

  # Handle variables in form data, JSON body, or a blank value
  def prepare_variables(variables_param)
    case variables_param
    when String
      if variables_param.present?
        JSON.parse(variables_param) || {}
      else
        {}
      end
    when Hash
      variables_param
    when ActionController::Parameters
      variables_param.to_unsafe_hash # GraphQL-Ruby will validate name and type of incoming variables.
    when nil
      {}
    else
      raise ArgumentError, "Unexpected parameter: #{variables_param}"
    end
  end

  def handle_error_in_development(e)
    logger.error e.message
    logger.error e.backtrace.join("\n")

    render json: { errors: [{ message: e.message, backtrace: e.backtrace }], data: {} }, status: 500
  end
end

GraphQLのスキーマの設定

rails/sample-6/app/graphql/sample6_new_schema.rb

class Sample6NewSchema < GraphQL::Schema
  query(::NewSchema::Types::QueryType)

  # For batch-loading (see https://graphql-ruby.org/dataloader/overview.html)
  use GraphQL::Dataloader

  # GraphQL-Ruby calls this when something goes wrong while running a query:
  def self.type_error(err, context)
    # if err.is_a?(GraphQL::InvalidNullError)
    #   # report to your bug tracker here
    #   return nil
    # end
    super
  end

  # Union and Interface Resolution
  def self.resolve_type(abstract_type, obj, ctx)
    # TODO: Implement this method
    # to return the correct GraphQL object type for `obj`
    raise(GraphQL::RequiredImplementationMissingError)
  end

  # Relay-style Object Identification:

  # Return a string UUID for `object`
  def self.id_from_object(object, type_definition, query_ctx)
    # For example, use Rails' GlobalID library (https://github.com/rails/globalid):
    object.to_gid_param
  end

  # Given a string UUID, find the object
  def self.object_from_id(global_id, query_ctx)
    # For example, use Rails' GlobalID library (https://github.com/rails/globalid):
    GlobalID.find(global_id)
  end
end

rails/sample-6/app/graphql/new_schema/types/query_type.rb

これは Sample6NewSchema で呼び出している Query の定義です。

module NewSchema
  module Types
    class QueryType < ::Types::BaseObject
      # Add `node(id: ID!) and `nodes(ids: [ID!]!)`
      include GraphQL::Types::Relay::HasNodeField
      include GraphQL::Types::Relay::HasNodesField

      # Add root-level fields here.
      # They will be entry points for queries on your schema.
      field :new_post, ::Types::PostType, "Find a post by ID" do
        argument :id, ID
      end

      # Then provide an implementation:
      def new_post(id:)
        Post.find_by(id: id)
      end

      field :new_posts, [::Types::PostType], null: false
      def posts
        Post.all
      end
    end
  end
end

SHARE

Profile picture

Written by tamesuu