Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
72
Help us understand the problem. What is going on with this article?

More than 1 year has passed since last update.

@k-penguin-sato

【Rails】graphql-rubyでAPIを作成

概要

GraphQL Rubyのバージョン1.8~以降で導入されたClass-basedの書き方を使用し、簡単なAPIを作成する方法を紹介します。
ネット上でClass-basedの書き方に関する記事が少なかったので、誰かの助けになれば幸いです。

GraphQL,GraphQL Rubyの基本概念等は以下で学習して頂けます。
- GraphQL入門 - 使いたくなるGraphQL - Qiita
- GraphQL Rubyに関して
- GraphQL公式
- How to GraphQL - The Fullstack Tutorial for GraphQL

GraphQL with Ruby on Rails

ユーザーが記事(post)の取得、作成、更新、削除を行える簡単なapiをRuby on RailsGraphQLを用いて作成します。
ソースコードはこちらから確認する事ができます。

Installation

まず、以下のコマンドでapiモードでrailsアプリケーションを作成します。

$ rails new blog --api

Gemfilegraphqlgraphql-rail を加えます。

Gemfile
gem 'graphql'
gem 'graphiql-rails' #開発環境でgraphiqlを使用可能にします。こちらは必須ではありません。

通常のrailsアプリならこれだけでセットアップは完了ですが、apiモードとして作成する場合は、config/application.rbに以下の文を加えます。
(開発環境でgraphiqlを使用する可能にする為です。こちらが必要ない場合は下記を追加する必要はありません。)

config/application.rb
require "sprockets/railtie"

必要なgem等を追加したら、以下のコマンドを流しましょう。
2つめのコマンドを流すとapp/graphqlディレクトリが作成され、GraphQL関連のファイルはこのディレクトリ下に配置されます。

$ bundle install
$ rails generate graphql:install

Queries

大雑把に説明するとGraphQLでは全てがType(オブジェクトの設計書のようなもの)を持っており、そのTypeに従い、Queriesを使用して、データの取得を行い、Mutationsを使用して、データの作成・更新を行います。
まず、データの取得を行うQueriesからみていきましょう。

Postモデルを作成し、 rake db:migrateを流します。

$ rails g model Post title:string description:text
$ rake db:migrate

Queryでデータを取得できる事を確認するため、 rails consoleを使用して事前にデータを作成しましょう。

$ rails c
$ Post.create(title: "What is Ruby?", description:"Ruby is a programming language")
$ Post.create(title: "How to learn Ruby", description:"Read some books brah")

GraphQLでは各Typeを基にQueryの実行を行なっていきます。
そこで、PostTypeを定義するため、以下のコマンドを流しPost Typeを作成します。

$ rails g graphql:object Post id:ID! title:String! description:String!

以下が上記のコマンドで作成されたファイルの中身です。

app/graphql/types/post_type.rb
module Types
  class PostType < Types::BaseObject
    field :id, ID, null: false
    field :title, String, null: false
    field :description, String, null: false
  end
end

class~endを使用してよりSytaxが以前と比べてよりわかりやすくなっています。
Class Based Syntaxについて詳しく知りたい方は公式でわかりやすく説明されています。

Query Resolver

Typeは定義されましたが、サーバはこのタイプをどのように扱えば良いかわかりません。
そこで、実際にqueriesを実行するためにresolverを作成していきます。

先ほどrails g graphql:installを流した際に作成された app/graphql/types/query_type.rbに以下のコードを追加する事で、全てのPostの取得と特定のidを持つPostの取得ができます。

# app/graphql/types/query_type.rb
module Types
  class QueryType < Types::BaseObject
    field :posts, [Types::PostType], null: false
    def posts
      Post.all
    end

    field :post, Types::PostType, null: false do
      argument :id, Int, required: false
    end
    def post(id:)
      Post.find(id)
    end
  end
end

GraphiQLで確認

以下をroutes.rbに追加します。

Rails.application.routes.draw do
  if Rails.env.development?
    # add the url of your end-point to graphql_path.
    mount GraphiQL::Rails::Engine, at: "/graphiql", graphql_path: "/graphql" 
  end
  post '/graphql', to: 'graphql#execute'
end

上記のif~endを加える事でhttp://localhost:3000/graphiqlにアクセスし、実際に先ほど定義したqueryの確認を行う事ができます。

実際に rails sをコンソールで流し、http://localhost:3000/graphiqlで以下のquery を実行してみましょう。

# 全てのPost取得
{
 posts {
   id
   title
   description
 }
}

############## 結果 #####################
{
  "data": {
    "posts": [
      {
        "id": 1,
        "title": "title1",
        "description": "description1"
      },
      {
        "id": 2,
        "title": "title2",
        "description": "description2"
      },
      {
        "id": 3,
        "title": "title3",
        "description": "description3"
      },
     ]
   }
}

# 特定のPostの取得
{
 post(id:1) {
   id
   title
   description
 }
}

############## 結果 #####################
{
  "data": {
    "post": {
      "id": 1,
      "title": "title1",
      "description": "description1"
    }
  }
}

Mutations

上記で簡単に説明したようにGraphQLではMutationsを使用してデータの作成・更新を行います。
以下のapp/graphql/types/mutation_type.rbも最初にrails g graphql:installを流した際に作成されています。

module Types
  class MutationType < Types::BaseObject
    # TODO: remove me
    field :test_field, String, null: false,
      description: "An example field added by the generator"
    def test_field
      "Hello World"
    end
  end
end

Mutation Create

Postを作成する為に使用されるmutationを作成する為に以下のコマンドを流します。

$ rails g graphql:mutation CreatePost

上記のコマンドは以下の2つの事を行います。
- (1)graphql/mutations/create_post.rbの作成。
- (2)field :createPost, mutation: Mutations::CreatePostgraphql/types/mutations_type.rbに追記する。

graphql/mutations/create_post.rb
module Mutations
  class CreatePost < GraphQL::Schema::RelayClassicMutation
    # TODO: define return fields
    # field :post, Types::PostType, null: false

    # TODO: define arguments
    # argument :name, String, required: true

    # TODO: define resolve method
    # def resolve(name:)
    #   { post: ... }
    # end
  end
end
# app/graphql/types/mutation_type.rb
module Types
  class MutationType < Types::BaseObject
    field :createPost, mutation: Mutations::CreatePost
  end
end

graphql/mutations/create_post.rb に書かれている TODOs に従い、fieldargumentを定義して、Postの作成が行えるよう、以下のように編集していきます。

:graphql/mutations/create_post.rb
module Mutations
  class CreatePost < GraphQL::Schema::RelayClassicMutation
    graphql_name 'CreatePost'

    field :post, Types::PostType, null: true
    field :result, Boolean, null: true

    argument :title, String, required: false
    argument :description, String, required: false

    def resolve(**args)
      post = Post.create(title: args[:title], description: args[:description])
      {
        post: post,
        result: post.errors.blank?
      }
    end
  end
end

GraphiQLで確認

先ほどと同じくrails sでサーバを立ち上げ GraphQLPostの作成ができるか試してみましょう。

mutation {
  createPost(
    input:{
      title: "title1"
      description: "description1"
    }
  ){
    post {
      id
      title 
      description
    }
  }
}

############## 結果 #####################
{
  "data": {
    "createPost": {
      "post": {
        "id": 15,
        "title": "title1",
        "description": "description1"
      }
    }
  }
}

Mutation Update

Postを更新する為に使用されるmutationを作成する為に以下のコマンドを流します

$ rails g graphql:mutation UpdatePost

自動作成された /graphql/mutations/update_post.rb を以下のように変更しましょう。
/graphql/mutations/create_post.rbと非常に似ており、違いはargumentにてidを指定する箇所くらいです。

module Mutations
  class UpdatePost < GraphQL::Schema::RelayClassicMutation
    graphql_name 'UpdatePost'

    field :post, Types::PostType, null: true
    field :result, Boolean, null: true

    argument :id, ID, required: true
    argument :title, String, required: false
    argument :description, String, required: false

    def resolve(**args)
      post = Post.find(args[:id])
      post.update(title: args[:title], description: args[:description])
      {
        post: post,
        result: post.errors.blank?
      }
    end
  end
end

GraphiQLで確認

rails sでサーバを立ち上げ GraphQLPostの更新ができるか試してみましょう。

mutation {
  updatePost(
    input:{
      id: 1
      title: "Updated"
      description: "UPdated"
    }
  ){
    post {
      id
      title 
      description
    }
  }
}

############## 結果 #####################
{
  "data": {
    "updatePost": {
      "post": {
        "id": 1,
        "title": "Updated",
        "description": "UPdated"
      }
    }
  }
}

Mutation Delete

作成、更新のmutationsを作成したのとほぼ同じ工程で削除の為のmutationを作成します。
以下のコマンドを流します。

$ rails g graphql:mutation DeletePost

今回は削除の為に指定するidのみをargumentにセットします。

graphql/mutations/delete_post.rb
module Mutations
  class DeletePost < GraphQL::Schema::RelayClassicMutation
    graphql_name 'DeletePost'

    field :post, Types::PostType, null: true
    field :result, Boolean, null: true

    argument :id, ID, required: true

    def resolve(**args)
      post = Post.find(args[:id])
      post.destroy
      {
        post: post,
        result: post.errors.blank?
      }
    end
  end
end

GraphiQLで確認

rails sでサーバを立ち上げ GraphQLPostの削除ができるか試してみましょう。

mutation {
  deletePost(
    input:{
      id: 1
    }
  ){
    post {
      id
      title 
      description
    }
  }
}

############## 結果 #####################
{
  "data": {
    "posts": [
      {
        "id": 2,
        "title": "How to learn Ruby"
      },
      {
        "id": 3,
        "title": "title1"
      }]
  }
}

Connection fields(1)

GraphQLではtypes,queries,mutationsの働きがわかればアソーシエーションも簡単に対処できます。
実際に各ポストが持つコメントを取得できるようにしてみましょう。

まず、Commentモデルを作成し、Post多対1(has_many/belongs_to)の関係をもたせます。
また、ここで確認の為のデータを作成しておきましょう。

$ rails g model Comment content:string post:references
$ rake db:migrate
# app/models/comment.rb
class Comment < ApplicationRecord
  belongs_to :post
end

# app/models/post.rb
class Post < ApplicationRecord
  has_many :comments, dependent: :destroy
end
db/seeds.rb
Post.new.tap do |post|
  post.title = 'title'
  post.description = 'description'
  post.comments.build(content: 'comment1')
  post.save!
end
$ rake db:seed

次に、以下のコマンドでComment typeを作成しましょう。

$ rails g graphql:object Comment id:ID! content:String!

上記のコマンドは以下のファイルを作成します。

app/graphql/types/comment_type.rb
module Types
  class CommentType < Types::BaseObject
    description 'Comment'

    field :id, ID, null: false
    field :content, String, null: false
  end
end

そして、commentsというfieldPost typeに追加しましょう。

app/graphql/types/post_type.rb
module Types
  class PostType < Types::BaseObject
    description 'Post'

    field :id, Int, null: false
    field :title, String, null: false
    field :description, String, null: false
    field :comments, [Types::CommentType], null: false 
  end
end

GraphiQLで確認

rails sでサーバを立ち上げ GraphQLPostcommentsが取得できるか試してみましょう。

{
  posts {
    id
    title
    comments {
      id
      content
    }
  }
}

############## 結果 #####################
{
  "data": {
    "posts": [
      {
        "id": "1",
        "title": "title",
        "comments": [
          {
            "id": "1",
            "content": "comment1"
          }
        ]
      }
    ]
  }
}

Connection fields(2)

Commentを作成できるようにしてみましょう。
以下のコマンドを流して/graphql/types/mutation_type.rbを作成します。

$ rails g graphql:mutation CreateComment

次のfield :post, Types::PostType, null: falseapp/graphql/types/comment_type.rbに追加しましょう。

app/graphql/types/comment_type.rb
module Types
  class CommentType < Types::BaseObject
    description 'Comment'
    field :id, ID, null: false
    field :content, String, null: false
    field :post, Types::PostType, null: false
  end
end

そして、 app/graphql/mutations/create_comment.rbを以下のように変更します。

app/graphql/mutations/create_comment.rb
module Mutations
  class CreateComment < GraphQL::Schema::RelayClassicMutation
    graphql_name 'CreateComment'

    field :comment, Types::CommentType, null: true
    field :result, Boolean, null: true

    argument :post_id, ID, required: true
    argument :content, String, required: true

    def resolve(**args)
      post = Post.find(args[:post_id])
      comment = post.comments.build(content: args[:content])
      comment.save
      {
        comment: comment,
        result: post.errors.blank?
      }
    end
  end
end

GraphiQLで確認

rails sでサーバを立ち上げ GraphQLPostcommentsが作成できるか試してみましょう。

mutation {
  createComment(
    input:{
      postId: 1
      content: "NEW COMMENT"
    }
  ){
    comment {
      id
      content
      post {
        id
        title
        comments {
          id
          content
        }
      }
    }
  }
}

############## 結果 #####################
{
  "data": {
    "createComment": {
      "comment": {
        "id": "2",
        "content": "NEW COMMENT",
        "post": {
          "id": "1",
          "title": "title",
          "comments": [
            {
              "id": "1",
              "content": "comment1"
            },
            {
              "id": "2",
              "content": "NEW COMMENT"
            }
          ]
        }
      }
    }
  }
}

Commentの更新や削除を行いたい場合はPostの更新、削除の方法を真似て作成してみてください。

参考

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
72
Help us understand the problem. What is going on with this article?