近期项目中,有一个模块需要提供API,刚开始使用node.js来开发,可是开发起来很费事,可能是我不大习惯javascipt那种恐怖的callback吧,所以动点心思尝试在ruby使用异步IO的框架来提供API。
开发中我的目标有:
1. 兼容我们现在代码的model
2. 兼容我们现在的jbuilder 模板
3. 大致上符合rails开发者习惯
明确目标后,google别人已经实践过的技巧,然后综合使用。
首先我们看看我们项目大概的gem:
gem 'goliath', '~> 1.0.3'
gem 'grape', '~> 0.6.1'
gem 'mongoid', '~> 4.0.0.beta1'
gem 'jbuilder', '~> 2.0.5'
gem 'tilt', '<= 1.4.1'
gem 'tilt-jbuilder', '~> 0.5.3'
gem 'activesupport', '~> 4.0.4'
项目目录规划:
- api 用来放置 api的实现,即 grade 的代码
- config 用来放置配置文件
- application.rb 配置文件
- mongoid.yml mongoid的配置文件
- lib 放置和项目无关的代码
- core_ext 根据需要拓展内置类
- grape_ext 更具需要拓展grade
- models model放置的目录,基本上直接copy我们原来的代码
- views
- v1
- root
- **
- Gemfile
- Rakefile
- tool
- server.rb 项目入口
我们先来看看如何实现server.rb 文件
require_relative './config/application.rb'
require 'rubygems'
require 'bundler/setup'
Bundler.require
class Server < Goliath::API
require_relative 'lib/init.rb'
require_relative 'api/api.rb'
def response(env)
API.call env
end
end
之后我们需要进入lib/init.rb 写入一些配置
Dir[File.dirname(__FILE__) + '/core_ext/*.rb'].each {|file| require file }
Dir[File.dirname(__FILE__) + '/grape_ext/*.rb'].each {|file| require file }
config_path = File.join(File.dirname(__FILE__), '../config/mongoid.yml')
ENV['MONGOID_ENV'] = ENV['GOLIATH_ENV'] || 'development'
Mongoid.load! config_path
I18n.enforce_available_locales = false
Dir[File.dirname(__FILE__) + '/../models/concerns/*.rb'].each {|file| require file }
Dir[File.dirname(__FILE__) + '/../models/*.rb'].each {|file| require file }
之后我们添加lib/grape_ext/jbuilder.rb 为grade 添加jbuilder 的支持,我们查看了grape-jbuilder的源码,然后根据需要修改的:
module Grape
module Formatter
class Renderer
def initialize(view_path, template)
@view_path, @template = view_path, template
end
def render(scope, locals = {})
engine = ::Tilt.new file, nil, view_path: view_path
engine.render scope, locals
end
private
attr_reader :view_path, :template
def file
File.join view_path, "#{template}.jbuilder"
end
end
class Jbuilder
attr_reader :env, :endpoint, :object
def self.call(object, env)
new(object, env).call
end
def initialize(object, env)
@object, @env = object, env
@endpoint = env['api.endpoint']
end
def call
return Grape::Formatter::Json.call(object, env) unless template?
route_info = env['rack.routing_args'][:route_info]
namespace = if route_info.route_namespace == '/'
'root'
else
route_info.route_namespace
end
view_path = "#{AppConfig.view_path}/#{route_info.route_version}/#{namespace}/"
Renderer.new(view_path, template).render(endpoint, {})
end
def template
endpoint.options.fetch(:route_options, {})[:action]
end
def template?
!!template
end
end
end
end
之后在api/api.rb 写我们的api吧:)
class API < Grape::API
format :json
version 'v1'
formatter :json, Grape::Formatter::Jbuilder
helpers do
def current_user
@current_user ||= User.authorize!(env)
end
def authenticate!
error!('401 Unauthorized', 401) unless current_user
end
end
# 没有namespace 就会指定到我们的v1/root里面去
get '', action: :show do
@user = {name: 'manjia'}
end
# 异步http例子
get '/testhttp' do
conn = Faraday::Connection.new(:url => 'http://127.0.0.1:3000') do |builder|
builder.use Faraday::Adapter::EMSynchrony
end
conn.get "/"
end
resource :user do
post '', action: :sign_up do
@user = User.new params.permit_hash :name, :email, :password
if @user.save
@user.login
@user
else
error!({error: @user.errors}, 402)
end
end
end
end
然后在对应的目录写好jbuilder 文件即可,比如上面的:show 那么就是在views/v1/root/show.jbuilder, 上面的:sign_up 那么就是在views/v1/user/sign_up.jbuilder
ok, 这里我们加入一些task方便我们的开发
require 'rubygems'
require 'bundler/setup'
Bundler.require
desc 'open an console'
task :console do
require 'irb'
require 'irb/completion'
require_relative 'lib/init.rb'
ARGV.clear
IRB.start
end
task :server do
exec 'ruby ./server.rb -sv'
end
到这里我们的API服务项目基本搭建完毕,目标也基本实现了。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。