Ruby Sinatra 扩展机制原理分析

Ice-storm · 发布于 2017年07月15日 · 1757 次阅读
7bb237
本帖已被设为精华帖!

最近读Sinatra源码受益良多,文章中有问题的地方还请多多指教

Sinatra通过helpersregister函数进行扩展 首先看一下helpers函数,用来是来扩展Base类的实例方法。

def helpers(*extensions, &block)
  class_eval(&block)   if block_given?
  include(*extensions) if extensions.any?
end

在看一下register函数,用来扩展Base类的类方法

def register(*extensions, &block)
  extensions << Module.new(&block) if block_given?
  @extensions += extensions
  extensions.each do |extension|
    extend extension
    extension.registered(self) if extension.respond_to?(:registered)
  end
end

两个方法都支持通过代码块扩展,也支持通过Module来扩展。

两种扩展方法对应Sinatra的两种编程风格。

Sinatra编程的两种风格:

经典风格:

require'sinatra'

get '/' do
  "hello world"
end

模块化风格:

require'sinatra/base'

class Hello < Sinatra::Base

  get "/" do
    "hello world"
  end

  run!
end

为两种风格编写扩展:

Sinatra扩展也分为两种:

helper 型 dsl 型

helper型:

require 'sinatra/base'

module Sinatra
  module FormatHelper
    def escape_html(text)
      Rack::Utils.escape_html(text)
    end
  end

  helpers FormatHelper 
end

classic style 使用extension

require 'sinatra'

get '/' do
  escape_html("x > y")
end

modular style 使用extension

require 'sinatra/base'

class Hello < Sinatra::Base

  helpers Sinatra::FormatHelper

  get '/' do
    escape_html("x > y")
  end

  run!
end

这里的helpers其实相当于include

dsl型:

require 'sinatra/base'

module Sinatra
  module Devise
    def authenticate!
      before {
        halt 403, "You Bastards!"
      }
    end
  end

  register Devise
end

classic style 使用 dsl extension

require 'sinatra'

authenticate!

get '/' do
  escape_html("x > y")
end

modular style 使用 dsl extension

require 'sinatra/base'

class Hello < Sinatra::Base
  register Sinatra::Devise

  authenticate!

  get '/' do
    "hello world"
  end

  run!
end

上面就是两种扩展的用法,我们在深入一点

Sinatra/main的最后几行可以看到extend Sinatra::Delegator,其实当 require 'sinatra'的时候就是执行的上面那行代码,作为整个Sinatra的入口。

看一下Delegator模块,默认的target是Application,也就是helpersregister等Sinatra的一干关键字get post ...等都是通过动态派发给Application类执行的。

module Delegator
  def self.delegate(*methods)
    methods.each do |method_name|
      define_method(method_name) do |*args, &block|
        return super(*args, &block) if respond_to? method_name
        Delegator.target.send(method_name, *args, &block)
      end
      private method_name
    end
  end

  delegate :get, :patch, :put, :post, :delete, :head, :options, :link, :unlink,
           :template, :layout, :before, :after, :error, :not_found, :configure,
           :set, :mime_type, :enable, :disable, :use, :development?, :test?,
           :production?, :helpers, :settings, :register

  class << self
    attr_accessor :target
  end

  self.target = Application
end

Delegator模块功能就是定义派发给Application的函数(不管实现,只是转发)

整个通过Delegator.delegate注册的方法的实现都在Helper这个模块里,在Base类里include各种方法的实现,然后Application在继承Base,有点绕~。

class Base
  include Rack::Utils
  include Helpers
  include Templates
  ...
end

通过一张图理清楚他们的关系

定义自己的关键字,像get post这样。

扩展方法首先在Delegator模块调用delegate方法的时候添加你扩展的关键字名称,类似delegate :get, :patch, ... , :my_fun, 然后在Helper模块里定义my_fun的实现

module Helpers
  my_fun
    p "my_fun"
  end
  ...
end

这样就可以像关键字一样使用my_fun了,当然也可以通过上面介绍的两个方法进行扩展。

参考 https://ruby-china.org/topics/2110 http://saito.im/note/Sinatra-Extensions/

共收到 0 条回复
1107 jasl 将本帖设为了精华贴 07月17日 00:13
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册