近期項目中,有一個模塊須要提供API,剛開始使用node.js來開發,但是開發起來很費事,多是我不大習慣javascipt那種恐怖的callback吧,因此動點心思嘗試在ruby使用異步IO的框架來提供API。java
開發中個人目標有:
1. 兼容咱們如今代碼的model
2. 兼容咱們如今的jbuilder 模板
3. 大體上符合rails開發者習慣node
明確目標後,google別人已經實踐過的技巧,而後綜合使用。json
首先咱們看看咱們項目大概的gem:api
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'
項目目錄規劃:ruby
咱們先來看看如何實現server.rb 文件app
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的源碼,而後根據須要修改的:less
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.jbuilderpost
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服務項目基本搭建完畢,目標也基本實現了。