本文严重参考了此文,此前未注明,请大家谅解。
Rails 一直走在技术的前沿,从最早引入 scss、coffee 等就可以说明,Rails 一直在努力引入前端的前沿的技术。
过去的一年,前端涌现出各种框架,而 Rails 社区紧随其后有了相应的解决方案,比如 react-rails、react_on_rails 等等。
本文主要介绍了一下如何在 Rails 项目中使用 GraphQL,并抛出一些关于用 GraphQL 取代 API 的一些最佳实践,未必正确,权当抛砖引玉。欢迎拍砖。
关于GraphQL的介绍大家可以参看这里。这里主要介绍怎么集成到 Rails 项目中。
$ rails new MyBlogApp
.
.
.
$ cd MyBlogApp
首先,我们把rack-cors
gem 添加到 Gemfile 中。
gem 'rack-cors', :require => 'rack/cors'
然后把以下内容添加至config/application.rb
:
# config/application.rb
module MyBlogApp
class Application < Rails::Application
# ...
config.middleware.insert_before 0, "Rack::Cors" do
allow do
origins '*'
resource '*', :headers => :any, :methods => [:get, :post, :options]
end
end
end
end
注意:在生产环境这样配置是相当危险的,具体配置请参考官方文档
然后我们生成一个 Blog model
$ rails generate model Blog title:string content:string author_id:integer
然后再添加一个 Author 的 model
rails generate model Author name:string
Blog 属于 Author:
# app/models/blog.rb
class Blog < ActiveRecord::Base
belongs_to :author
end
Author 有许多 Blogs
# app/models/author.rb
class Author < ApplicationRecord
has_many :blogs
end
然后,运行:
$ rails db:migrate
到这里为止,我们的 App 的基本内容已经告一段落了。 接下来,我们就开始创建 GraphQL API。
首先,在 Gemfile 里添加GraphQL
gem:
gem 'GraphQL'
然后运行$ bundle install
, 安装 gem。
我们创建 GraphQL API 的步骤是:
首先,我们在app
目录下创建graph
目录:
$ mkdir -p app/graph
$ mkdir -p app/graph/types
然后在config/application.rb
文件里添加以下路径:
# config.application.rb
config.autoload_paths << Rails.root.join('app', 'graph')
config.autoload_paths << Rails.root.join('app', 'graph', 'types')
让我们先创建一个Query
Type:
# app/graph/types/query_type.rb
QueryType = GraphQL::ObjectType.defile do
name "Query"
description 'The query root for this schema'
field :blog do
type BlogType
argument :id, !types.ID
resolve -> (obj, args, context) {
Blog.find args[:id]
}
end
end
作为这次查询的根,我们定义了一个字段 blog,它会根据 id 返回一个 Blog 记录。
然后我们继续定义blog_type
和 author_type
# app/graph/types/blog_type.rb
BlogType = GraphQL::ObjectType.define do
name 'Blog'
description 'A Blog'
field :title, types.String
field :content, types.String
field :author do
type AuthorType
resolve -> (obj, args, context) {
obj.author
}
end
end
# app/graph/types/author_type.rb
AuthorType = GraphQL::ObjectType.define do
name 'Author'
description 'Author of Blogs'
field :name, types.String
end
然后让我们在创建 GraphQL Schema:
# app/graph/blog_schema.rb
BlogSchema = GraphQL::Schema.define do
query QueryType
end
现在剩下的就是将我们的 GraphQL Schema 的端点暴露出来,最好使用 POST 的方式: 别忘了修改此处:
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
# Use :null_session here
protect_from_forgery with: :null_session
end
接下来,我们先生成一个QueriesController
:
$ rails generate controller Queries create
# app/controllers/queries_controller.rb
class QueriesController < ApplicationController
def create
query_string = params[:query]
query_variables = params[:variables] || {}
result = BlogSchema.execute(query_string, variables: query_variables)
render json: result
end
end
在 config/routes.rb 里添加以下路由:
resources :queries, via: [:post, :options]
That‘s it!
现在我们可以在终端下测试:
$ rails server
打开另一个终端:
$ rails console
> Blog.create title: "Intro to GraphQL", content: "Something something something. Blah blah blah. Etc etc etc."
> exit
$ curl -XPOST -d 'query={ blog(id: 1) { title content }}' http://localhost:3000/queries
返回:
{"data":{"blog":{"title":"Intro to GraphQL","content":"Something something something. Blah blah blah. Etc etc etc."}}}
几个思考:
resolve
方法,如:
定义个一个RescueFrom
类放置到app/classes
目录下面:class RescueFrom
def initialize error_superclass, resolve_func
@error_superclass = error_superclass
@resolve_func = resolve_func
end
def call obj, args, ctx
@resolve_func.call obj, args, ctx
rescue @error_superclass => err
puts err.message
end
end
然后重写blog_type.rb
:
BlogType = GraphQL::ObjectType.define do
name "Blog"
description "A Blog"
field :title, types.String
field :content, types.String
field :author do
type AuthorType
resolve RescueFrom.new(ActiveRecord::RecordNotFound, -> (obj, args, ctx) {
obj.author
})
end
end
但是,这种解决方法并不优雅,不能像在类似BaseController::ActionController
这样的基类里使用rescue_from
达到一劳永逸的效果。
总体来说,推荐指数,依然五星,希望大家尝试一下。