Rails GraphQL API у 2026: graphql-ruby, підписки та питання на співбесіді

Повний посібник зі створення продакшн-рівня GraphQL API з Rails 8 та graphql-ruby. Проєктування схеми, мутації, підписки з ActionCable та підготовка до співбесіди.

Rails GraphQL API у 2026: graphql-ruby, підписки та питання на співбесіді

GraphQL став стандартним вибором для API, що потребують гнучкого отримання даних, а graphql-ruby привносить цю функціональність у Rails із тісною інтеграцією з ActiveRecord. Rails 8, випущений наприкінці 2024 року, чудово поєднується з graphql-ruby 2.4, який представив покращену обробку підписок та краще ліниве виконання для запобігання проблем N+1.

Що graphql-ruby дає Rails

Gem graphql-ruby забезпечує повну реалізацію GraphQL: визначення схеми за допомогою Ruby DSL, автоматичне виведення типів з моделей ActiveRecord, вбудований DataLoader для пакетної обробки та інтеграцію з ActionCable для підписок реального часу.

Налаштування graphql-ruby в додатку Rails 8

Генератор gem'у створює початкову структуру схеми та монтує endpoint GraphQL. Почніть з додавання gem'у та запуску інсталяційного генератора для створення базових файлів.

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

Генератор створює каталог app/graphql/ з файлом схеми, базовими типами та директорією мутацій. Він також додає маршрут за адресою /graphql та опціонально монтує GraphiQL для розробки.

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

  # Використання вбудованого DataLoader для запобігання N+1
  use GraphQL::Dataloader

  # Необхідно для підписок
  use GraphQL::Subscriptions::ActionCableSubscriptions
end

Файл схеми виступає точкою входу. Він оголошує, які типи обробляють запити, мутації та підписки, та реєструє middleware, такий як DataLoader.

Визначення типів і резолверів для моделей Rails

Кожна модель ActiveRecord, що з'являється в API, потребує відповідного типу 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 об'єднує кілька запитів постів в один 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

Методи резолверів отримують іменовані аргументи, що відповідають визначенню поля. Повернення nil з nullable поля є допустимим; викидання помилки з non-null поля викликає відповідь з помилкою GraphQL.

Готовий до співбесід з Ruby on Rails?

Практикуйся з нашими інтерактивними симуляторами, flashcards та технічними тестами.

Мутації: створення та оновлення записів

Мутації підпорядковуються суворішим конвенціям, ніж запити. Кожна мутація живе у власному класі та повертає тип payload, що містить як результат, так і можливі помилки.

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

Запуск підписки відбувається з будь-якого місця в додатку, зазвичай у callback'у моделі або сервісному об'єкті.

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

ActionCable канал для обробки GraphQL підписок постачається разом з 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 Client або urql, підключаються до ActionCable WebSocket та керують станом підписок. Для підготовки до співбесіди щодо механіки ActionCable дивіться модуль ActionCable & WebSockets.

Тестування GraphQL API з RSpec

Тестування 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.

Поширені питання на співбесіді з GraphQL для Rails-розробників

Інтерв'юери, що перевіряють знання GraphQL у контексті Rails, часто фокусуються на практичних аспектах реалізації, а не на абстрактній теорії типів.

Як запобігти запитам N+1 у graphql-ruby?

Використовуйте DataLoader з власними джерелами. Визначте клас джерела, який пакетує виклики до бази даних, і викликайте dataloader.with(SourceClass, args).load(id) у резолверах. DataLoader збирає всі ID, запитані в одному виконанні GraphQL, та отримує їх одним запитом.

Яка різниця між запитом та мутацією?

Семантика: запити читають дані і не повинні мати побічних ефектів. Мутації модифікують дані і можуть мати побічні ефекти. GraphQL гарантує паралельне виконання полів запитів та послідовне виконання полів мутацій, тому дві мутації в одному запиті виконуються по черзі.

Як працюють підписки в Rails?

Підписки використовують ActionCable для WebSocket-з'єднань. Клієнт надсилає запит підписки через WebSocket. Сервер зберігає підписку і, коли викликається trigger, надсилає результат усім відповідним клієнтам. Ідентифікатор підписки дозволяє клієнтам відписатися.

Коли обрати GraphQL замість REST для Rails API?

GraphQL зменшує надмірне отримання даних, коли клієнтам потрібні різні підмножини даних, що є поширеним у мобільних додатках з різними розмірами екранів. Він також усуває потребу в кількох REST endpoint'ах, коли один view потребує даних з кількох моделей. REST залишається простішим для API з великою кількістю CRUD-операцій із консистентними формами даних та для публічних API, де кешування є критичним.

Як обробляти автентифікацію та авторизацію в graphql-ruby?

Автентифікація відбувається в контролері перед виконанням GraphQL, зазвичай через Devise, Warden або перевірку JWT. Автентифікований користувач передається в context. Авторизація відбувається в резолверах або через хук authorized? graphql-ruby на типах та мутаціях, викидаючи GraphQL::ExecutionError при відмові в доступі.

Більше про підготовку до співбесіди Ruby on Rails дивіться у модулі Rails API Mode, який охоплює патерни проєктування REST API, що доповнюють знання GraphQL.

Починай практикувати!

Перевір свої знання з нашими симуляторами співбесід та технічними тестами.

Створення продакшн GraphQL API з Rails: ключові висновки

  • Встановлення graphql-ruby з rails generate graphql:install створює схему, типи та маршрутизацію
  • Використання джерел DataLoader для всіх асоціацій дозволяє пакетувати запити до бази даних та запобігати проблемам продуктивності N+1
  • Мутації визначаються в окремих класах з явним полем помилок у типі повернення
  • Інтеграція підписок через ActionCable забезпечує оновлення в реальному часі, що запускаються з callback'ів моделей або сервісних об'єктів
  • Тестування запитів та мутацій здійснюється шляхом їх виконання безпосередньо до схеми в RSpec
  • Автентифікація належить контролеру; перевірки авторизації відбуваються всередині резолверів з використанням context[:current_user]
  • Обирайте GraphQL, коли клієнтам потрібне гнучке отримання даних; залишайтеся з REST для простих CRUD API з однорідними відповідями
Щоденний виклик

Чи знайдеш ти помилку в Ruby on Rails?

Справжній фрагмент коду, прихована помилка, одна спроба на день. Щоб спробувати, акаунт не потрібен.

Anthony Fillion-Maillet

Автор:

Anthony Fillion-Maillet

Засновник SharpSkill

Fullstack-розробник понад 10 років. Керує SharpSkill і відповідає за все, що тут публікується.

Оновлено 19 вересня 2026 р.

Поділитися

Пов'язані статті