如何在 Sinatra 中使用 DSL 元编程

How to use DSL metaprogramming with Sinatra

我正在尝试使用 DSL 来管理同一路由中的不同区域设置,例如 get "/test"

这是学习如何扩展 Sinatra 的练习,因此 Rack::Locale 或类似工具不是有效答案。

根据请求的正文 JSON 正文,假设我收到 JSON 作为 POST 或 PUT,我想用特定的语言环境进行响应。

我目前有一个我认为需要的准系统脚本:

class Locale
  attr_reader :locale_id
  attr_reader :described_class

  alias :current_locale :locale_id

  def initialize(locale_id, &block)
    @locale_id = locale_id
    instance_eval &block
  end

end

def locale(locale_id, &block)
  Locale.new(locale_id, &block)
end

我缺少根据输入的 request.body JSON 中的语言环境进行响应的功能,而这里的 class 还有一些我还没有看到的东西需要或丢失。

如何使用它的一个例子是:

get '/' do 
   locale 'cs-CS' do 
     "Czech"
     #or db query or string
   end 
   locale 'en-UK' do 
     "British english"
     #or db query or string
   end
end

因此,为了更清楚地阐明,我将尝试使用 TDD 方法:

作为用户,当我发送 JSON 包含:"locale": "cs-CS" 结果是捷克语。

你读过Extending The DSL and the Conditions section of the README了吗?

现在,您并没有真正扩展 DSL。我会稍微重新设计它,因为它看起来像您想要匹配 case 语句,但这意味着创建很多 类 或丑陋的匹配语句。但是,Sinatra 已经有一些非常好的方法来匹配路线和条件。所以,像这样的东西会更惯用:

post '/', :locale => "Czech" do
  "Czech"
end

post '/', :locale => "British English" do
  "British"
end

post '/', :locale => "en-GB" do
  "cs-CS"
end

post '/', :locale => "cs-CS" do
  "cs-CS"
end

如何做到这一点?首先,您需要一个过滤器来转换进入的 JSON:

before do
  if request.media_type == "application/json"
    request.body.rewind
    @json = JSON.parse request.body.read
    @locale = @json["locale"] && Locales[@json["locale"]]
  end
end

然后你需要一个条件来检查:

set(:locale) {|value|
  condition {
    !!@locale && (@locale == value || @json["locale"] == value)
  }
}

全部(app.rb):

require 'sinatra'

Locales = {
  'cs-CS' => "Czech",
  'en-GB' => "British English"
}

before do
  if request.media_type == "application/json"
    request.body.rewind
    @json = JSON.parse request.body.read
    @locale = @json["locale"] && Locales[@json["locale"]]
  end
end


set(:locale) {|value|
  condition {
    !!@locale && (@locale == value || @json["locale"] == value)
  }
}


post '/', :locale => "en-GB" do
  "cs-CS"
end


post '/', :locale => "cs-CS" do
  "cs-CS"
end

可以,但不能作为扩展。所以,依靠我在顶部发布的文档:

require 'sinatra/base'

module Sinatra
  module Localiser

    Locales = {
      'cs-CS' => "Czech",
      'en-GB' => "British English"
    }

    def localise!(locales=Locales)
      before do
        if request.media_type == "application/json"
          request.body.rewind
          @json = JSON.parse request.body.read
          @locale = @json["locale"] && locales[@json["locale"]]
        end
      end

      set(:locale) {|value|
        condition {
          !!@locale && (@locale == value || @json["locale"] == value)
        }
      }
    end
  end
  register Localiser
end

现在它将扩展 DSL。例如:

require "sinatra/localiser"
class Localised < Sinatra::Base
  register Sinatra::Localiser

  localise!


  post '/', :locale => "Czech" do
    "Czech"
  end


  post '/', :locale => "British English" do
    "British"
  end


  ["get","post"].each{|verb|
    send verb, "/*" do
      "ELSE"
    end
  }

  run! if app_file == [=16=]
end

希望这能帮助您澄清一些事情。