我尝试给现有的Rails项目添加GraphQL API

我是PORT Advent Calendar 12日和13日的负责人,很抱歉迟来了。我是PORT株式会社的服务器端工程师,负责进行开发工作。

由于没有很好地整理好,所以打算进行添加和修正。
如果有错误的地方,请留下评论或编辑请求。

为什么要写关于 GraphQL 的内容

入职后不久,我在新人工程师聚会上谈到了我对GraphQL的一点了解,但之后一直没有深入研究。所以这次的圣诞日历活动成为我的再次挑战的契机。

GraphQL 是什么?

    • API への問合せ言語 (クエリ言語)

 

    • Facebook 製

 

    • 1 回のリクエストで必要なものだけ取得、更新できる

 

    • エンドポイントが 1 つ

 

    • 破壊的な変更が起こらないので、 v1 や v2 といったバージョンを付けなくて良い

参考: GraphQL を最速でマスターするための意識改革3ヶ条

REST API の代わり?

查询

    • データ取得

REST API の GET 相当のもの

突变

    • データ更新

REST API の POST, PUT, DELETE 相当のもの

订阅

    サーバでのデータの変化をクライアントに通知?

这个在哪里被使用的? de?)

    • Facebook

 

    • GitHub

https://developer.github.com/v4/

Twitter
Pinterest

将GraphQL集成到现有项目中

graphql-ruby只是一个GraphQL处理系统,并不会对整个web应用程序产生影响。说到Rails,只需要添加一个GraphqlController#execute动作,甚至可以通过rails generate graphql:install生成模板。因此,将GraphQL API添加到已经使用RESTful API运营的Rails应用程序中非常简单,然后可以逐步进行迁移。

参考:为现有的Rails应用添加GraphQL API – 有关Ruby的GraphQL的笔记

根据所写的内容,可以放心地进行添加,因为它不会对现有的项目产生任何影响。

安装 gem

首先,您需要安装GraphQL的gem包。

gem 'graphql'
$ bundle install

接下来,让我们进行GraphQL的设置吧。

$ bundle exec rails g graphql:install
 Running via Spring preloader in process 33086
       create  app/graphql/types
       create  app/graphql/types/.keep
       create  app/graphql/sample_app_schema.rb
       create  app/graphql/types/base_object.rb
       create  app/graphql/types/base_enum.rb
       create  app/graphql/types/base_input_object.rb
       create  app/graphql/types/base_interface.rb
       create  app/graphql/types/base_scalar.rb
       create  app/graphql/types/base_union.rb
       create  app/graphql/types/query_type.rb
 add_root_type  query
       create  app/graphql/mutations
       create  app/graphql/mutations/.keep
       create  app/graphql/types/mutation_type.rb
 add_root_type  mutation
       create  app/controllers/graphql_controller.rb
        route  post "/graphql", to: "graphql#execute"
      gemfile  graphiql-rails
        route  graphiql-rails
 Gemfile has been modified, make sure you `bundle install`

图形查询语言

通过添加名为grahigl-rails的gem,可以简单地进行操作确认。

由于在执行“$ bundle exec rails g graphql:install”命令时,graphiql-rails被添加到Gemfile中。

$ bundle install

在这里,我们要确认一下我们使用的宝石(Gem)的版本。

$ bundle exec gem list graph

*** LOCAL GEMS ***

graphiql-rails (1.5.0)
graphql (1.8.11)

执行$ bundle exec rails g graphql:install命令时,还将添加用于GraphiQL的路由。

Rails.application.routes.draw do
  if Rails.env.development?
    mount GraphiQL::Rails::Engine, at: "/graphiql", graphql_path: "/graphql"
  end

  post "/graphql", to: "graphql#execute"
  ~~
end

我们来启动服务器,并访问 localhost:3000/graphiql。

当您访问时,将会显示如下所示的画面。

image.png

您可以在此屏幕上完成实施后立即进行操作确认。此外,您还可以在右上角的”Docs”中查看文档。

因为已经定义了用于测试的 test_field,所以让我们试着执行一下。

module Types
  class QueryType < Types::BaseObject
    # Add root-level fields here.
    # They will be entry points for queries on your schema.

    # 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

通过发送以下查询,收到了 “Hello World!” 的返回。

image.png

查询

接下来,让我们确保能够发出能获取到自己想要的信息的查询。

接下来我将说明ShopType和BrandType的定义。
在已有的Rails应用程序的ActiveRecord模型中,假设已经创建了Shop和Brand,所以请根据各自的Rails应用程序模型继续阅读。

首先,创建一个对象。

$ rails g graphql:object Shop id:ID! name:String! access:String!

执行上述命令将创建 `app/graphql/types/shop_type.rb` 文件。
如果使用 `id:ID!` 这样的语法,将会使得其值不能为 null:false,而如果不加上 !,比如 `name:String`,则其值可以为 null:true。

module Types
  class ShopType < Types::BaseObject
    description '店舗'

    field :id, ID, '店舗ID', null: false
    field :name, String, '店舗名', null: false
    field :access, String, 'アクセス', null: true
    field :open_time, String, null: false
    field :close_time, String, null: false
  end
end

为了与Rails模型的列是否允许为空值保持一致,建议使用null:true和null:false。

另外,像以下的字段一样,如果写上描述,将会被添加到文档中。

此外,如果像下面的 SampleType 字段一样写上描述,将会被添加到文档中。

module Types
  class SampleType < Types::BaseObject
    description '説明説明'

    field :field_name1, '説明1', null: false
    field :field_name2, null: false, description: '説明2'
  end
end
sample

请使用刚刚创建的ShopType,在Query中调用QueryType来定义shop。

在这个论点中,我们定义了作为参数要使用的东西。虽然这次只有一个,但我们可以定义多个。

module Types
   class QueryType < Types::BaseObject
     field :shop, ShopType, null: true do
       description '店舗をidで検索'
       argument :id, ID, '店舗ID', required: true
     end

     def shop(id:)
       Shop.find(id)
     end
   end
 end

当您执行以下查询时,将返回相应的 JSON 数据。

image.png

如果不传入id作为参数,则尝试返回最后一个商店。

module Types
   class QueryType < Types::BaseObject
     field :shop, ShopType, null: true do
       description '店舗をidで検索'
       argument :id, ID, '店舗ID', required: false # id がなくても検索できるように
     end

     def shop(id: nil) # id を渡さないときには nil を渡す
       id ? Shop.find(id) : Shop.last # idがなければ最後の Shop を返す
     end
   end
 end
image.png

形状

在字段中,可以从以下选项中指定类型:ID、字符串、整数、浮点数、日期、时间、日期时间、数组、对象、哈希。

日付、時間、日時は、以前触ってみた時には使えなかった。
将来的には、再度試してみます。

您也可以自行定义。

请参考 graphql-rails/lib/graphql/rails/types.rb 文件。


我也尝试按照BrandType和ShopType的方式创建了一个。

$ rails g graphql:object Brand id:ID! name:String!

顺便说一下,可以使用”rails d”命令删除Rails的模型和控制器。

$ rails d graphql:object Brand id:ID! name:String!
module Types
  class BrandType < Types::BaseObject
    description 'ブランド'

    field :id, ID, 'ブランドID', null: false
    field :name, String, 'ブランド名', null: false
  end
end

在1:1的关联情况下。

当商店和品牌一对一时,

class Shop < ApplicationRecord
  belongs_to :brand, required: true
  has_many :reviews, dependent: :destroy
  ~~
end
class Brand < ApplicationRecord
  has_many :shops, dependent: :destroy
  has_many :reviews, through: :shops, dependent: :destroy
  ~~
end

如果将 ShopType 的字段类型设为 BrandType,则

module Types
  class ShopType < Types::BaseObject
    description '店舗'

    ~~
    field :brand, BrandType, 'ブランド', null: false
  end
end

可以按照以下方式进行调用。

image.png

1: 如果是N的关联性情况

当商店和评论之间的关系是一对多时,

class Shop < ApplicationRecord
  ~~
  has_many :reviews, dependent: :destroy
  ~~
end
class Review < ApplicationRecord
  ~~
  belongs_to :shop
  ~~
end
$ be rails g graphql:object Review
module Types
  class ReviewType < Types::BaseObject
    description 'レビュー'

    field :id, ID, 'レビューID', null: false
    field :reviewer_name, String, null: false
    field :shop, ShopType, '店舗', null: false
  end
end

如果将ShopType字段的类型设为[ReviewType],

module Types
  class ShopType < Types::BaseObject
    description '店舗'

    ~~
    field :reviews, [ReviewType], 'レビュー', null: false
  end
end

可以按照下面的方式调用。

image.png

尝试使用商店名称进行模糊搜索。

module Types
  class QueryType < Types::BaseObject
    ~~
    field :shops, [ShopType], null: true do
      description '店舗名であいまい検索'
      argument :name, String, '店舗名', required: true
    end

    def shops(name:)
      Shop.where('name LIKE ?', "%#{name}%").limit(10)
    end
    ~~
  end
end
image.png

尝试调用Shop模型中定义的实例方法。

class Shop < ApplicationRecord
  ~~

  # 全レビューの総合点平均
  def overall_score
    ~~
  end
end

如果将method和field定义在字段中,就可以使用Query进行调用。

module Types
  class ShopType < Types::BaseObject
    description '店舗'

    ~~
    field :overall_score, Float, '全レビューの総合点平均', null: true, method: :overall_score
  end
end

在GraphQL中,通过蛇形命名法定义的字段应该使用驼峰命名法进行调用。例如,overall_score应该被称为overallScore。

image.png

同时获取多个信息

您可以同时获取多个信息。

image.png

突变

由于上一个版本的信息太多了,我弄不清楚,所以以后会进行调查并写下来。

总结

    • 楽しい

 

    • 結構簡単に実装できる

以前触ったときからの変更が多い

description を書いておくと勝手に Docs に追加される

引用

    • GraphQL – Welcome

 

    • Notes on GraphQL for Ruby

 

    • 雑に始める GraphQL Ruby【class-based API】

 

    • GraphQL Ruby の使い方 (基礎編)

 

    GraphQL を最速でマスターするための意識改革3ヶ条

我不太理解的部分

我会根据理解的情况进行补充。

    • Mutation

app/graphql/types/query_type.rb が増えてきたら読みづらそう

分割すれば良い

この辺

app/graphql/types/base_enum.rb
app/graphql/types/base_input_object.rb
app/graphql/types/base_interface.rb
app/graphql/types/base_eobject.rb
app/graphql/types/base_scaler.rb
app/graphql/types/base_union.rb

$ rails g graphql:xxxx
N+1 問題がよく起きるらしい

client

apollo

最后

在PORT株式会社,我们正在招聘能够支持我们自家服务的优秀Ruby工程师(不限于Ruby工程师)。
我们还会进行一起专注工作的”もくもく会”,欢迎一起加入我们!

PORT圣诞日历还将继续进行,请拭目以待!

广告
将在 10 秒后关闭
bannerAds