使用Ruby on Rails学习GraphQL(graphql-ruby)的入门备忘录

首先

我用Ruby on Rails尝试了一下GraphQL,并写了一篇备忘录。

环境如下所示。

    • ruby: 3.0.2

 

    • rails: 6.1.4

 

    • graphql-ruby: 1.12

 

    graphiql-rails: 1.8.0

GraphQL 是一种查询语言和运行时环境,用于从客户端向服务器请求和响应数据。

GraphQL是一种用于API的查询语言,与REST API不同,具有以下优点。

    • 1つのエンドポイントのみで呼び出せる

 

    欲しい情報のみを指定でき、余分なものを取得しない

安装

    • graphql-ruby

 

    graphiql-rails

我们将参考这个进行在Rails中引入GraphQL和GraphiQL。
通过引入GraphiQL,您可以在开发环境中使用图形界面来确认和执行GraphQL的查询和变更设置,以进行测试。
它类似于phpMyAdmin的GraphQL版本。

gem 'graphql'
group :development do
  gem 'graphiql-rails'
end
bundle install
rails g graphql:install

在中文中只需要一个选项来改写以下内容:
进行GraphiQL的路由设置。
这样,您只能在本地开发环境中通过http://localhost:3000/graphiql等URL访问GraphiQL。

Rails.application.routes.draw do
  post "/graphql", to: "graphql#execute"
  # 以下を追加
  if Rails.env.development?
    mount GraphiQL::Rails::Engine, at: '/graphiql', graphql_path: '/graphql'
  end
end

获取数据 (Query)

为了获取数据,需要定义类型(Type)和查询(Query)。

Type的定义

Type用于定义返回数据的结构。
在生成用于Type定义的文件之前,先创建Rails模型并执行迁移,Type会根据该模式自动生成定义。

rails g model User
# migrationの設定
rails db:migrate
rails g graphql:object User

文件被创建,例如会生成以下类似的代码。

module Types
  class UserType < Types::BaseObject
    field :id, ID, null: false
    field :email, String, null: false
    field :encrypted_password, String, null: false
    field :created_at, GraphQL::Types::ISO8601DateTime, null: false
    field :updated_at, GraphQL::Types::ISO8601DateTime, null: false
  end
end

然而,Type是用于定义返回响应数据结构的。它不需要与数据库表结构匹配,您可以自由地添加或删除字段。
像密码之类的不应该放在API中的敏感信息,最好是删除相关字段。

查询的定义

我们将使用Query来定义如何获取信息。
Query的定义将写在app/graphql/types/query_type.rb中。
例如,可以编写查询以获取所有用户或获取指定ID的用户。

module Types
  class QueryType < Types::BaseObject
    field :users, [UserType], null: false
    def users
      User.all
    end

    field :user, UserType, null: false do
      argument :id, ID, required: true
    end
    def user(id:)
      User.find(id)
    end
  end
end

尝试使用GraphiQL获取数据

访问GraphiQL页面,将以下内容输入到左侧的输入框中,然后点击播放按钮选择查询,结果将显示在右侧的框中。
在定义时,字段名称使用snake_case进行编写,但在调用API时,请使用camelCase进行编写。

query users {
  users {
    id
    email
    createdAt
  }
}

query user1 {
  user(id: 1) {
    id
    email
  }
}

query user($id: ID!) {
  user(id: $id) {
    id
    email
  }
}

{
  "id": "1"
}

在下面的查询中,将参数分开写是一种方式。
型名(ID!)后面的!表示它是一个必需参数。
(因为在查询定义中设置了argument :id, ID, required: true)
此外,可以从右上角的Docs中查看查询、变异和类型的定义。

41c85dc040331a98c0eaf281be2a9a48.gif

创建、编辑、删除数据(Mutation)

创建、编辑、删除数据等具有副作用的处理将在Mutation中定义。
mutation的定义应该写在app/graphql/types/mutation_type.rb中。
但是,通过命令生成mutation后,mutation_type.rb文件将自动添加代码,实际编辑的文件将是各个mutation定义文件。

rails g graphql:mutation CreateUser
module Types
  class MutationType < Types::BaseObject
    field :create_user, mutation: Mutations::CreateUser # 自動で追記される
    # ~
  end
end

我们将如下定义mutation:
使用field定义响应、argument定义参数,使用resolve定义处理。

module Mutations
  class CreateUser < BaseMutation
    field :user, Types::UserType, null: false

    argument :email,                 String, required: true
    argument :password,              String, required: true
    argument :password_confirmation, String, required: true

    def resolve(**args)
      user = User.create!(args)
      { user: user }
    end
  end
end

试着使用GrapiQL创建数据。

通过调用mutation,可以进行以下操作。
与数据获取操作不同的是,mutation以createUser()开头,但是createUser(email: ~)变为了createUser(input:{email: ~})的结构。

mutation createUser($email: String!, $password: String!, $passwordConfirmation: String!) {
  createUser(
    input: {email: $email, password: $password, passwordConfirmation: $passwordConfirmation}
  ) {
    user {
      id
      email
    }
  }
}

{
  "email": "test2@example.com",
  "password": "abc123",
  "passwordConfirmation": "abc123"
}

编辑和删除

只需按照数据创建时的方法创建变异即可进行编辑或删除。

其他

联想

如果用户拥有多个任务数据,且想要同时获取用户信息和用户拥有的任务的方法。

我会创建一个名为Task的模型,并执行迁移和创建类型,同时设置模型的关联(用户模型和任务模型)。只需在用户类型的定义中添加tasks字段即可。

module Types
  class UserType < Types::BaseObject
    # ~
    field :tasks, [TaskType], null: false
  end
end

这样一来,相关模型的信息也能一次性获取。

query user1 {
  user(id: 1) {
    id
    email
    tasks {
      id
    }
  }
}

关联模型个数的字段添加,关于N+1问题。

当需要获取用户拥有的任务数量时,可以使用以下方法。

选项1: 将”取得方法”包括在Type定义中的方法

可以在 object 上编写对模型实例的处理。

module Types
  class UserType < Types::BaseObject
    # ~
    field :tasks_count, Int, null: false
    def tasks_count
      object.tasks.size
    end
  end
end

为了避免N+1问题,在查询定义的部分要包含(:tasks)。
即使包含了,如果在Type定义中使用object.tasks.count,仍然会导致N+1问题的发生。

module Types
  class QueryType < Types::BaseObject
    # ~
    field :users, [UserType], null: false
    def users
      User.includes(:tasks)
    end
    # ~
  end
end

选项2:在查询的定义中获取的方法。

module Types
  class UserType < Types::BaseObject
    # ~
    field :tasks_count, Int, null: false
  end
end

module Types
  class QueryType < Types::BaseObject
    # ~
    field :users, [UserType], null: false
    def users
      User.select(%|
        *,
        (SELECT COUNT (*) FROM tasks t
          WHERE t.user_id = users.id
        ) AS tasks_count
      |)
    end
    # ~
  end
end

尽管我更喜欢这种生成的SQL语句,并且性能可能更好,但是老实说,考虑到可读性以及需要针对每个查询和变异分别编写,我觉得这种方法可能有些微妙。

其他的方法 de

我还没有尝试过,但使用名为graphql-batch的gem可以更灵活地获取数据,而不会出现N+1问题?

会话、当前用户

在Query或Mutation的定义中,不能直接调用像session或devise的current_user这样的辅助方法。要能够调用它们,需要在GraphqlController中进行设置。

class GraphqlController < ApplicationController
  # ~
  def execute
    # ~
    context = {
      session: session,
      current_user: current_user,
    }
    # ~
  end
  # ~
end

设定如下,这样在查询或者变更内可以通过调用context[:session]或者context[:current_user]进行调用。

最后

这次是我第一次尝试使用GraphQL,我能够轻松地实现基本的数据获取、创建、编辑和删除等功能。然而,在解决N+1问题以及认证和授权方面,虽然本次文章没有写到,但我在实践过程中遇到了许多困难。因为我仍然对许多方面都没有完全理解,所以我希望能够继续学习GraphQL。

感谢您一直阅读到最后。

我参考了那篇文章。

    • 入門 GraphQL Ruby 〜基本のCRUD処理〜

 

    【Rails】graphql-rubyでAPIを作成
广告
将在 10 秒后关闭
bannerAds