graphql in rails
GraphQL是个新的API标准,它比REST标准更高效,更强大且更灵活。它由Facebook开源,目前应用已经很广泛。
现在大部分程序都需要从服务端获取数据,而这些数据大部分都存在数据库中,所以API的作用就是为这些程序提供合适的接口。 不要误认为Graphql是一门数据库相关的技术,事实上它只是为API而存在的.
而在ruby的生态系统中,Github和Shopify等公司已经将GraphQL应用在了生产环境中。
在rails中使用添加gem即可:
gem 'graphql'
gem 'graphiql-rails'
使用rails generate graphql:install
将生成app/graphql/...的默认目录。这样将在我们的系统中加载一个GraphQL Server和一个GraphQL Client.(默认可通过http://localhost:3000/graphiql访问)
我们实现一个例子:
假定数据结构:
posts:
title:stirng
content:text
数据查询:
通过id查询post
# app/graphql/types/post_type.rb
module Types
class PostType < Types::BaseObject
description "query post"
field :id, ID, null: false
field :title, String, null: false
field :content, String, null: false
end
end
查询posts列表:
# app/graphql/types/query_type.rb
field :posts, [Types::PostType], null: false
def posts(options={})
Post.all.order("created_at DESC").
end
基于上面的例子,我们介绍下field方法的用法:
在GraphQL中,对象通过field方法,将对象的数据或者关联的数据暴露出去。
field :name, String, "The unique name", null: false
field第二个参数表示返回值,返回值类型为GraphQL内置类型,包括Integer, Float, ID, Boolean或者数组如[String],还可以是一个GraphQL的对象类型如关联对象PostType
null关键词指定返回值是否可以为空。
null: true, 意味着该field可为空
null: false,意味着不可为空,如果程序在该位置返回了一个空值,前端请求时将会报错。
下面是官方文档的几个示例,写的比较清晰:
field :name, String, null: true # `String`, may return a `String` or `nil`
field :id, ID, null: false # `ID!`, always returns an `ID`, never `nil`
field :teammates, [Types::User], null: false # `[User!]!`, always returns a list containing `User`s
field :scores, [Integer, null: true], null: true # `[Int]`, may return a list or `nil`, the list may contain a mix of `Integer`s and `nil`s
上面我们定义了类型,GraphQL server还不知道怎么操作该type,所以我们需要写一个resolver函数,
resolver是GraphQL中用来执行数据查询的函数,每一个field都需要对应一个resolver函数。一旦数据查询开始,服务器将调用这些resolver将其对应于指定的field.
所以我们在query_type.rb中添加:
# app/graphql/types/query_type.rb
field :post, PostType, null: true do
description "Find a post by id"
argument :id, ID, required: true
end
def post(id:)
Post.find(id)
end
同理field表示返回的对象记录,其中argument表示需要传入的参数,其参数意味着,参数名,参数类型,是否必须存在。
完成上面的代码,你将可以通过http://localhost:3000/graphiql浏览器工具进行查询。
查询代码为:
# 单条post查询
query{
race(id: 1) {
id
title
content
}
}
# post列表查询
query{
races {
id
title
content
}
}
数据修改:
mutation的声明类似query的声明,graphql目录下默认生成了一个mutations目录和一个mutation_type.rb文件,该文件即为mutation的根类型.
# app/graphql/mutations/create_post.rb
module Mutations
class CreatePost < Mutations::BaseMutation
graphql_name 'CreatePost'
# define return fields
field :post, Types::PostType, null: false
field :success, Boolean, null: true
# define arguments
argument :title, String, required: true
argument :content, String, required: true
# define resolve method
def resolve(**args)
post = Post.new(title: args[:title], content: args[:content])
post.save
# 注: 下面{}内的key必须与上面field指定的参数一致.
{
post: post,
success: post.errors.blank?
}
end
end
end
将mutation暴露给mutation类型:
module Types
class MutationType < BaseObject
field :createPost, mutation: Mutations::CreatePost
end
end
完成上面的代码,你将可以通过http://localhost:3000/graphiql浏览器工具进行操作。
mutation {
createPost(
input:{
title: "this is a test title"
content: "this is a test content"
}
) {
post
success
}
}
Graphql中mutation是一些特殊的fields,它的作用是完成程序中更改操作。
比如数据的增删改,数量增加,对文件的增删改以及清除缓存等操作。
mutation的field方法通过接收inputs和调用arguments,然后通过fields返回值。
context的使用
通过上面的mutation我们可以在系统中创建用户实现注册,登录等,但是如何获取current_user呢?
app/controllers/graphql_controller.rb:
def execute
...
context = {
current_user: current_user
}
...
rescue => e
raise e unless Rails.env.development?
handle_error_in_development e
end
private
def current_user
access_token = request.headers["HTTP_X_ACCESS_TOKEN"]
User.find_by(access_token: access_token)
rescue ActiveRecord::RecordNotFound
nil
end
这样我们就可以在resolver函数中通过context[:current_user]来回去当前用户了。
GraphQL还提供了很多方便的功能,如程序错误处理, Timeout处理等等,使用起来非常方便,后期遇到好的用法,再更新。
实现对current_user的验证
由于graphql所有到请求状态都是200,所有的error信息都放在errors字段里,所以对用户权限的验证就不能走http的401状态了,作者具体解释为:
image.png
可参考写法如下:
raise GraphQL::ExecutionError, :forbidden unless context[:current_user]