Rails GraphQL API 2026年版: graphql-ruby、サブスクリプション、面接対策質問集

graphql-rubyを使用したRails GraphQL APIの構築方法を解説。DataLoaderによるN+1対策、ActionCableサブスクリプション、RSpecテスト、実践的な面接質問を網羅的にカバーします。

Rails GraphQL API with graphql-ruby

GraphQLは柔軟なデータ取得が必要なAPIにおいて標準的な選択肢となっており、graphql-rubyはActiveRecordとの緊密な連携によりこの機能をRailsにもたらします。2024年末にリリースされたRails 8は、改良されたサブスクリプション処理とN+1防止のための遅延実行機能を導入したgraphql-ruby 2.4と相性が良いです。

graphql-rubyがRailsに提供する機能

graphql-ruby gemは完全なGraphQL実装を提供します。Ruby DSLによるスキーマ定義、ActiveRecordモデルからの自動型推論、バッチ処理のための組み込みDataLoader、リアルタイムサブスクリプション用のActionCable連携が含まれます。

Rails 8アプリケーションでのgraphql-rubyセットアップ

gem generatorは初期スキーマ構造を作成し、GraphQLエンドポイントをマウントします。gemを追加してinstall generatorを実行し、ベースファイルをスキャフォールドします。

ruby
# Gemfile
gem 'graphql', '~> 2.4'
bash
# Terminal
bundle install
rails generate graphql:install

generatorはapp/graphql/にスキーマファイル、ベースタイプ、mutationsディレクトリを作成します。また、/graphqlにルートを追加し、開発環境ではオプションでGraphiQLをマウントします。

ruby
# app/graphql/sharpskill_schema.rb
class SharpskillSchema < GraphQL::Schema
  mutation(Types::MutationType)
  query(Types::QueryType)
  subscription(Types::SubscriptionType)

  # N+1防止のための組み込みDataLoaderを使用
  use GraphQL::Dataloader

  # サブスクリプションに必要
  use GraphQL::Subscriptions::ActionCableSubscriptions
end

スキーマファイルはエントリーポイントとして機能します。クエリ、ミューテーション、サブスクリプションを処理するタイプを宣言し、DataLoaderなどのミドルウェアを登録します。

Railsモデル用のタイプとリゾルバーの定義

APIに登場する各ActiveRecordモデルには対応するGraphQLタイプが必要です。タイプはどのフィールドが公開され、どのように解決されるかを定義します。

ruby
# app/graphql/types/user_type.rb
module Types
  class UserType < Types::BaseObject
    field :id, ID, null: false
    field :email, String, null: false
    field :created_at, GraphQL::Types::ISO8601DateTime, null: false

    # DataLoaderによる自動バッチ処理を行う関連
    field :posts, [Types::PostType], null: false

    def posts
      dataloader.with(Sources::ActiveRecordCollection, Post, :user_id).load(object.id)
    end
  end
end

dataloader.with呼び出しは複数のpostsフェッチを単一のSQLクエリにバッチ処理します。DataLoaderがなければ、50人のユーザーとその投稿を要求するクエリは51回のクエリを実行します。DataLoaderを使えば、2回で済みます。

ruby
# app/graphql/sources/active_record_collection.rb
class Sources::ActiveRecordCollection < GraphQL::Dataloader::Source
  def initialize(model, foreign_key)
    @model = model
    @foreign_key = foreign_key
  end

  def fetch(ids)
    records = @model.where(@foreign_key => ids).group_by(&@foreign_key)
    ids.map { |id| records[id] || [] }
  end
end

このパターンはすべての本格的なgraphql-rubyプロジェクトで見られます。公式DataLoaderドキュメントではキャッシュやネストされたソースなどの追加ユースケースがカバーされています。

引数とフィルタリングを使ったクエリの構築

QueryTypeはクライアントが利用可能なルートフィールドを定義します。引数によりフィルタリングとページネーションが可能になります。

ruby
# app/graphql/types/query_type.rb
module Types
  class QueryType < Types::BaseObject
    field :users, [Types::UserType], null: false do
      argument :email_contains, String, required: false
      argument :limit, Integer, required: false, default_value: 20
    end

    def users(email_contains: nil, limit:)
      scope = User.all
      scope = scope.where('email ILIKE ?', "%#{email_contains}%") if email_contains
      scope.limit(limit)
    end

    field :user, Types::UserType, null: true do
      argument :id, ID, required: true
    end

    def user(id:)
      User.find_by(id: id)
    end
  end
end

リゾルバーメソッドはフィールド定義に一致するキーワード引数を受け取ります。nullable フィールドからはnilを返すことが有効です。non-nullフィールドからエラーを発生させるとGraphQLエラーレスポンスがトリガーされます。

Ruby on Railsの面接対策はできていますか?

インタラクティブなシミュレーター、flashcards、技術テストで練習しましょう。

ミューテーション: レコードの作成と更新

ミューテーションはクエリより厳格な規約に従います。各ミューテーションは独自のクラスに存在し、結果とエラーの両方を含むペイロードタイプを返します。

ruby
# app/graphql/mutations/create_post.rb
module Mutations
  class CreatePost < Mutations::BaseMutation
    argument :title, String, required: true
    argument :body, String, required: true

    field :post, Types::PostType, null: true
    field :errors, [String], null: false

    def resolve(title:, body:)
      post = context[:current_user].posts.build(title: title, body: body)

      if post.save
        { post: post, errors: [] }
      else
        { post: nil, errors: post.errors.full_messages }
      end
    end
  end
end

context[:current_user]はコントローラーから取得されます。認証はGraphQLレイヤーの前に行われ、通常DeviseやJWTライブラリを使用します。

ruby
# app/controllers/graphql_controller.rb
class GraphqlController < ApplicationController
  def execute
    context = {
      current_user: current_user,
      request: request
    }
    result = SharpskillSchema.execute(
      params[:query],
      variables: params[:variables],
      context: context,
      operation_name: params[:operationName]
    )
    render json: result
  end
end

リゾルバー内のアクセス制御はcontext[:current_user]をチェックし、権限がない場合はGraphQL::ExecutionErrorを発生させます。graphql-ruby認可ガイドではフィールドレベルとタイプレベルの認可パターンが詳述されています。

GraphQLサブスクリプションとActionCableによるリアルタイム更新

サブスクリプションにより、クライアントはサーバーサイドのイベント発生時に更新を受け取ることができます。Rails ActionCableがWebSocket接続を処理し、graphql-rubyはGraphQL::Subscriptions::ActionCableSubscriptionsを通じて連携します。

ruby
# app/graphql/types/subscription_type.rb
module Types
  class SubscriptionType < Types::BaseObject
    field :post_created, Types::PostType, null: false do
      argument :user_id, ID, required: false
    end

    def post_created(user_id: nil)
      object # triggerから渡されるオブジェクト
    end
  end
end

サブスクリプションのトリガーはアプリケーションの任意の場所から行えます。通常、モデルのコールバックやサービスオブジェクト内で実行されます。

ruby
# app/models/post.rb
class Post < ApplicationRecord
  belongs_to :user

  after_create_commit :notify_subscribers

  private

  def notify_subscribers
    SharpskillSchema.subscriptions.trigger(
      :post_created,
      { user_id: user_id },
      self
    )
  end
end

GraphQLサブスクリプションを処理するActionCableチャンネルはgemに同梱されています。

ruby
# app/channels/graphql_channel.rb
class GraphqlChannel < ApplicationCable::Channel
  def subscribed
    @subscription_ids = []
  end

  def execute(data)
    result = SharpskillSchema.execute(
      data['query'],
      variables: data['variables'],
      context: { current_user: current_user, channel: self },
      operation_name: data['operationName']
    )

    payload = { result: result.to_h, more: result.subscription? }

    @subscription_ids << result.context[:subscription_id] if result.subscription?

    transmit(payload)
  end

  def unsubscribed
    @subscription_ids.each do |sid|
      SharpskillSchema.subscriptions.delete_subscription(sid)
    end
  end
end

クライアント側では、Apollo ClienturqlなどのライブラリがActionCable WebSocketに接続し、サブスクリプション状態を管理します。ActionCableの仕組みについての面接対策は、ActionCable & WebSocketsモジュールを参照してください。

RSpecによるGraphQL APIのテスト

GraphQL APIのテストでは、スキーマに対してクエリを実行し、レスポンスの構造とデータについてアサーションを行います。

ruby
# spec/graphql/queries/users_spec.rb
RSpec.describe 'Users query' do
  let!(:user) { create(:user, email: 'test@example.com') }

  let(:query) do
    <<~GRAPHQL
      query {
        users(emailContains: "test") {
          id
          email
        }
      }
    GRAPHQL
  end

  it 'returns users matching the filter' do
    result = SharpskillSchema.execute(query)
    users = result.dig('data', 'users')

    expect(users.length).to eq(1)
    expect(users.first['email']).to eq('test@example.com')
  end
end

ミューテーションのテストでは、成功とバリデーションエラーの両方のパスを検証します。

ruby
# spec/graphql/mutations/create_post_spec.rb
RSpec.describe Mutations::CreatePost do
  let(:user) { create(:user) }
  let(:context) { { current_user: user } }

  let(:mutation) do
    <<~GRAPHQL
      mutation($title: String!, $body: String!) {
        createPost(input: { title: $title, body: $body }) {
          post { id title }
          errors
        }
      }
    GRAPHQL
  end

  it 'creates a post when valid' do
    result = SharpskillSchema.execute(
      mutation,
      variables: { title: 'Hello', body: 'World' },
      context: context
    )

    data = result.dig('data', 'createPost')
    expect(data['errors']).to be_empty
    expect(data['post']['title']).to eq('Hello')
  end

  it 'returns errors when invalid' do
    result = SharpskillSchema.execute(
      mutation,
      variables: { title: '', body: 'World' },
      context: context
    )

    data = result.dig('data', 'createPost')
    expect(data['errors']).to include("Title can't be blank")
  end
end

Railsのテストパターンについては、RSpecテストモジュールを参照してください。

Rails開発者向けGraphQL面接頻出質問

Rails環境でのGraphQL知識を問う面接官は、抽象的な型理論よりも実践的な実装上の懸念に焦点を当てることが多いです。

graphql-rubyでN+1クエリを防ぐにはどうしますか?

カスタムソースを持つDataLoaderを使用します。データベース呼び出しをバッチ処理するソースクラスを定義し、リゾルバー内でdataloader.with(SourceClass, args).load(id)を呼び出します。DataLoaderは単一のGraphQL実行で要求されたすべてのIDを収集し、1回のクエリでフェッチします。

クエリとミューテーションの違いは何ですか?

意味論の違いです。クエリはデータを読み取り、副作用を持つべきではありません。ミューテーションはデータを変更し、副作用を持つ可能性があります。GraphQLはクエリフィールドの並列実行とミューテーションフィールドの順次実行を保証するため、同じリクエスト内の2つのミューテーションは順番に実行されます。

Railsでサブスクリプションはどのように機能しますか?

サブスクリプションはWebSocket接続にActionCableを使用します。クライアントはWebSocketを通じてサブスクリプションクエリを送信します。サーバーはサブスクリプションを保存し、triggerが呼び出されると、一致するすべてのクライアントに結果をプッシュします。サブスクリプションIDによりクライアントは購読解除できます。

Rails APIでRESTではなくGraphQLを選択するのはどのような場合ですか?

GraphQLは、クライアントが異なるデータのサブセットを必要とする場合(さまざまな画面サイズを持つモバイルアプリで一般的)、オーバーフェッチを削減します。また、単一のビューが複数のモデルからのデータを必要とする場合、複数のRESTエンドポイントを不要にします。RESTは、一貫したデータ形状を持つCRUD重視のAPIや、キャッシュが重要なパブリックAPIでは依然としてシンプルです。

graphql-rubyで認証と認可をどのように扱いますか?

認証はGraphQL実行前にコントローラーで行われ、通常Devise、Warden、またはJWT検証を使用します。認証されたユーザーはcontextで渡されます。認可はリゾルバー内またはgraphql-rubyのタイプとミューテーションのauthorized?フックを通じて行われ、アクセスが拒否された場合はGraphQL::ExecutionErrorを発生させます。

Ruby on Rails面接対策については、GraphQLの知識を補完するRESTful APIデザインパターンをカバーするRails APIモードモジュールを参照してください。

今すぐ練習を始めましょう!

面接シミュレーターと技術テストで知識をテストしましょう。

RailsでプロダクションGraphQL APIを構築する: 重要なポイント

  • rails generate graphql:installでgraphql-rubyをインストールし、スキーマ、タイプ、ルーティングをスキャフォールドする
  • すべての関連にDataLoaderソースを使用し、データベースクエリをバッチ処理してN+1パフォーマンス問題を防ぐ
  • ミューテーションは戻り値の型に明示的なエラーフィールドを持つ別々のクラスで定義する
  • ActionCableを通じてサブスクリプションを連携させ、モデルコールバックやサービスオブジェクトからトリガーしてリアルタイム更新を実現する
  • RSpecでスキーマに対して直接クエリとミューテーションを実行してテストする
  • 認証はコントローラーで行い、認可チェックはcontext[:current_user]を使用してリゾルバー内で行う
  • クライアントが柔軟なデータ取得を必要とする場合はGraphQLを選択し、均一なレスポンスを持つシンプルなCRUD APIではRESTを維持する
今日のチャレンジ

Ruby on Rails のバグを見つけられますか

実際のコード、隠れたバグ、1日1回。アカウントなしで試せます。

Anthony Fillion-Maillet

執筆

Anthony Fillion-Maillet

SharpSkill 創業者

10 年以上フルスタック開発に携わっています。SharpSkill を運営し、ここで公開される内容に責任を負っています。

2026年9月19日 更新

タグ

#rails
#graphql
#api
#ruby
#interview

共有

関連記事