美文网首页
graphql in rails

graphql in rails

作者: EvanCui | 来源:发表于2019-02-28 16:23 被阅读0次

    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]
    

    相关文章

      网友评论

          本文标题:graphql in rails

          本文链接:https://www.haomeiwen.com/subject/yrxkuqtx.html