我尝试给现有的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。
当您访问时,将会显示如下所示的画面。
您可以在此屏幕上完成实施后立即进行操作确认。此外,您还可以在右上角的”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!” 的返回。
查询
接下来,让我们确保能够发出能获取到自己想要的信息的查询。
接下来我将说明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
请使用刚刚创建的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 数据。
如果不传入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
形状
在字段中,可以从以下选项中指定类型: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
可以按照以下方式进行调用。
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
可以按照下面的方式调用。
尝试使用商店名称进行模糊搜索。
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
尝试调用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。
同时获取多个信息
您可以同时获取多个信息。
突变
由于上一个版本的信息太多了,我弄不清楚,所以以后会进行调查并写下来。
总结
-
- 楽しい
-
- 結構簡単に実装できる
以前触ったときからの変更が多い
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圣诞日历还将继续进行,请拭目以待!