使用Rails和GraphQL创建API
版本差异不同
ruby: 2.7.1
rails: 6.0.3.4
graphql-ruby: 1.11.6
GraphQL Ruby (GraphQL-的Ruby实现)
公式网页
如果在Rails中使用GraphQL,我们可以使用上述的gem来实现API。
图形界面查询(GraphiQL)是一个用于Ruby on Rails框架的插件。
如果安装上 graphiql-rails gem,就可以在浏览器上使用一个可以检查已实施的GraphQL的IDE。在安装 graphql-ruby 时,会自动将 graphiql-rails gem 添加到 Gemfile 中。
环境搭建
gem 'graphql'
gem 'graphiql-rails' # 今回は先に入れました
当Gem安装完成后,执行rails generate graphql:install命令来生成各个文件。
生成的文件如下所示↓
$ rails generate graphql:install
create app/graphql/types
create app/graphql/types/.keep
create app/graphql/app_schema.rb
create app/graphql/types/base_object.rb
create app/graphql/types/base_argument.rb
create app/graphql/types/base_field.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/mutations/base_mutation.rb
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
在此时点的 routes.rb 文件如下所示。
Rails.application.routes.draw do
# GraphQL
if Rails.env.development?
mount GraphiQL::Rails::Engine, at: '/graphiql', graphql_path: '/graphql'
end
post '/graphql', to: 'graphql#execute'
end
做实
请创建查询
首先,需要定义与每个表对应的类型,所以我想要创建一个与以下示例用户表(users)对应的用户类型(user_type)。
create_table :users do |t|
t.string :name, null: false
t.string :email
t.timestamps
end
执行以下命令将创建user_type。
(指定类型是为GraphQL定义的用于ID的类型(实际上是字符串)
同时,以!结尾的类型不允许为空,而没有!的类型允许为空。)
$ bundle exec rails g graphql:object User id:ID! name:String! email:String
如果数据库中已经存在该表,那么它似乎会帮我们补充完成。
$ bundle exec rails g graphql:object User
↑这样也没问题
生成的文件 graphql/type/user_type.rb 的内容如下所示。
module Types
class UserType < Types::BaseObject
field :id, ID, null: false
field :name, String, null: false
field :email, String, null: true
field :created_at, GraphQL::Types::ISO8601DateTime, null: false
field :updated_at, GraphQL::Types::ISO8601DateTime, null: false
end
end
我将以下内容添加到已经生成的 graphql/type/query_type.rb 文件中。
field :users, [Types::UserType], null: false
def users
User.all
end
我认为当您在 http://localhost:3000/graphiql 上提出以下查询时,将返回响应。
{
users {
id
name
email
}
}
创建变异
接下来,我想创建一个名为 “CreateUser” 的Mutations以便创建用户。
$ bundle exec rails g graphql:mutation CreateUser
由于创建了graphql/mutations/create_user.rb,因此将根据以下方式进行修改。
module Mutations
class CreateUser < BaseMutation
field :user, Types::UserType, null: true
argument :name, String, required: true
argument :email, String, required: false
def resolve(**args)
user = User.create!(args)
{
user: user
}
end
end
end
我会向已经生成的graphql/types/mutation_type.rb文件中添加以下内容。
module Types
class MutationType < Types::BaseObject
field :createUser, mutation: Mutations::CreateUser # 追記
end
end
当在 http://localhost:3000/graphiql 上执行以下操作时,将会创建一个名为User的对象。
mutation {
createUser(
input:{
name: "user"
email: "user@email.com"
}
){
user {
id
name
email
}
}
}
协会
- 1:1の関連テーブルの場合
如果将帖子与标签进行一对一的关联的话,可以举例如下。
module Types
class LabelType < Types::BaseObject
field :id, ID, null: false
field :name, String, null: false
...
end
end
module Types
class PostType < Types::BaseObject
field :label, LabelType, null: true
end
end
可以将label定义为LabelType,就像上面的例子那样。
在这种情况下,可以将查询图像想象成…
{
posts {
id
label {
id
name
}
}
}
您可以使用查询将标签(label)作为LabelType获取所需的值。
- 1:Nの関連テーブルの場合
假设用户与帖子是一对多的关系
module Types
class PostType < Types::BaseObject
field :id, ID, null: false
field :label, LabelType, null: true
end
end
module Types
class UserType < Types::BaseObject
field :posts, [PostType], null: false
end
end
可以将上述的帖子定义为[帖子类型],并且可以作为查询
{
user(id: 1234) {
id
posts {
id
label {
id
name
}
}
}
}
可以如上所述进行调用。
GraphQL批处理
根据上述说明,可以提取1:1或1:N关联表的数据,但当前情况下可能会导致大量向数据库发出查询请求。以User和Post的1:N关系为例,如果有100条Post,那么每条都会发起100次查询请求。
因此,我們將試著引入一種解決方法,即使用 graphql-batch 來將多個請求整合在一起。
gem 'graphql-batch'
当安装了Gem之后,我们将开始创建loader。
loader将用于实现“将多个查询整合在一起”的部分。
module Loaders
class RecordLoader < GraphQL::Batch::Loader
def initialize(model)
@model = model
end
def perform(ids)
@model.where(id: ids).each { |record| fulfill(record.id, record) }
ids.each { |id| fulfill(id, nil) unless fulfilled?(id) }
end
end
end
如果这个贴子与之前的帖子是一对一的关联的话,那么就适用于它。
module Types
class PostType < Types::BaseObject
field :label, LabelType, null: true
def label
Loaders::RecordLoader.for(Label).load(object.label_id)
end
end
end
在用户与帖子之间是一对多的关系时,需要单独创建一个loader。
module Loaders
class AssociationLoader < GraphQL::Batch::Loader
def self.validate(model, association_name)
new(model, association_name)
nil
end
def initialize(model, association_name)
@model = model
@association_name = association_name
validate
end
def load(record)
raise TypeError, "#{@model} loader can't load association for #{record.class}" unless record.is_a?(@model)
return Promise.resolve(read_association(record)) if association_loaded?(record)
super
end
# We want to load the associations on all records, even if they have the same id
def cache_key(record)
record.object_id
end
def perform(records)
preload_association(records)
records.each { |record| fulfill(record, read_association(record)) }
end
private
def validate
unless @model.reflect_on_association(@association_name)
raise ArgumentError, "No association #{@association_name} on #{@model}"
end
end
def preload_association(records)
::ActiveRecord::Associations::Preloader.new.preload(records, @association_name)
end
def read_association(record)
record.public_send(@association_name)
end
def association_loaded?(record)
record.association(@association_name).loaded?
end
end
end
由于graphql-batch的存储库中有示例,请参考该示例进行实现,这样做可能更好。
按照以下的方法写,可以一次性进行查询。
module Types
class UserType < Types::BaseObject
field :posts, [PostType], null: false
def posts
Loaders::AssociationLoader.for(User, :posts).load(object)
end
end
end
从架构文件生成文件。
我想尝试从最后定义的架构文件中自动生成漂亮的文档。
找到一个方便的gem叫做graphdoc-ruby,我会试着安装它,在routes.rb中挂载它,并实现自动更新graphdoc每次部署。
请在Gemfile中添加以下内容
gem 'graphdoc-ruby'
另外,由于我们还需要 npm 包 @2fd/graphdoc,请提前在 Docker 镜像中进行安装。(如果没有使用 Docker,请在本地环境中进行安装即可)
这个房子非常漂亮。
RUN set -ex \
&& wget -qO- https://deb.nodesource.com/setup_10.x | bash - \
&& apt-get update \
&& apt-get install -y \
...
--no-install-recommends \
&& rm -rf /var/lib/apt/lists/* \
&& npm install -g yarn \
&& npm install -g @2fd/graphdoc # インストールしとく
在config/routes.rb文件中添加以下内容
Rails.application.routes.draw do
mount GraphdocRuby::Application, at: 'graphdoc'
end
如果更改了端点,请修改config/initializers/graphdoc.rb。
这间餐厅的食物非常美味。
GraphdocRuby.configure do |config|
config.endpoint = 'http://0.0.0.0:3000/api/v1/graphql'
end
只需重启Rails,然后在http://localhost:3000/graphdoc处生成文档即可。
坏的技巧
http://localhost:3000/graphiql アクセス時に以下エラーが発生する場合
Sprockets::Rails::Helper::AssetNotPrecompiled in GraphiQL::Rails::Editors#show
解決方法1
app/assets/config/manifest.js に以下を追加する
//= link graphiql/rails/application.css
//= link graphiql/rails/application.js
AssetNotPrecompiled error with Sprockets 4.0 · Issue #75 · rmosolgo/graphiql-rails
-> ただこれだとProduction時に Sprockets::FileNotFound: couldn’t find file ‘graphiql/rails/application.css’ エラーが出て使えない…
解決方法2 (うまくいった方法)
gem ‘sprocket’のバージョン3.7.2に下げる
gem ‘sprockets’, ‘~> 3.7.2’ [#1098: slowdev/knowledge/ios/FirebaseをCarthageで追加する](/posts/1098)
↑を追加し、bundle update
Rails6のAPIモードでGraphQLを使う方法(エラー対策も含む) – Qiita
graphiqlの画面にTypeError: Cannot read property ‘types’ of undefined が表示される
-> 手元の環境だとRails再起動で治りました
graphiqlの画面にSyntaxError: Unexpected token < in JSON at position 0 が表示される -> エラーが発生してる可能性がるのでログを見て修正する
有用的网站链接
-
- 【Rails】graphql-rubyでAPIを作成 – Qiita
-
- REST APIが主流のプロジェクトの中でGraphQLを導入してみた話(サーバーサイド編) – Sansan Builders Blog
-
- 「GraphQL」徹底入門 ─ RESTとの比較、API・フロント双方の実装から学ぶ – エンジニアHub|若手Webエンジニアのキャリアを考える!
-
- GraphQLを使ったAPI仕様中心開発の導入とその効果の紹介 – Kaizen Platform 開発者ブログ
-
- 雑に始める GraphQL Ruby【class-based API】 – Qiita
-
- hawksnowlog: Ruby (Sinatra) で GraphQL 入門
-
- 既存のRailsプロジェクトにGraphQL APIを追加してみた – Qiita
-
- Ruby on Rails で sprockets から Webpacker へ移行し、移行できないものは共存させる方法 – Qiita
-
- Reading: 初めてGraphQL – 型の基礎|tkhm|note
-
- https://github.com/loopstudio/rails-graphql-api-boilerplate
- https://github.com/rmosolgo/graphql-ruby-demo