大家好,我是 MARK24 。可以叫我 MARK 。这是我研究 Sinatra 的笔记。
阅读过程大约 10 分钟。
基于 Sinatra 2.1.0 进行讨论
set 系统可以让 Sinatra 在自身自由的定义 设置相关的变量。
比如定义模板所在:
set :views, settings.root + '/templates'
定义 session secret:
set :session_secret, ENV.fetch('SESSION_SECRET') { SecureRandom.hex(64) }
等等,非常自由且灵活。
set 系统这部分的源码恰巧是可以简单修改之后独立工作的。摘要如下:
# https://github.com/Mark24Code/sinatra-code-review/blob/master/lib/sinatra/base.rb#L1267 def define_singleton(name, cOntent= Proc.new) singleton_class.class_eval do undef_method(name) if method_defined? name String === content ? class_eval("def #{name}() #{content}; end") : define_method(name, &content) end end def set(option, value = (not_set = true), ignore_setter = false, &block) raise ArgumentError if block and !not_set value, not_set = block, false if block if not_set raise ArgumentError unless option.respond_to?(:each) option.each { |k,v| set(k, v) } return self end if respond_to?("#{option}=") and not ignore_setter return __send__("#{option}=", value) end setter = proc { |val| set option, val, true } getter = proc { value } case value when Proc getter = value when Symbol, Integer, FalseClass, TrueClass, NilClass getter = value.inspect when Hash setter = proc do |val| val = value.merge val if Hash === val set option, val, true end end define_singleton("#{option}=", setter) define_singleton(option, getter) # 原始代码放在一个类中, 如果我们想放在单文件执行,需要 改写为 `self.class.method_defined?` 调用到方法 # define_singleton("#{option}?", "!!#{option}") unless method_defined? "#{option}?" define_singleton("#{option}?", "!!#{option}") unless self.class.method_defined? "#{option}?" self end
Sinatra 内部实现了一套 配置系统,基于一个 DSL 语法 set 。 这是 Sinatra Class 部分初始化之后唯一的初始化的 DSL 。Sinatra 没有做很多复杂的前置工作。
一致很让我疑惑的是,这里的 getter 、setter 。我们传统理解的 是这样工作的:
class Sample def initialize() @name end # getter def name @name end # setter def name=(new_name) @name = new_name end end
但是 Sinatra 这里似乎是一个循环一样的,你会发现他的 setter 永远在调用 set 这是为什么呢?我一度非常迷惑。
setter = proc { |val| set option, val, true }
我刚开始进入这段是百思不得其解。但事实证明我格局小了。这部分其实根本不是传统的 setter, 我们观察传统的 setter 他的问题是必须要以一个实例变量为依托。所以他才必须写成这样。 如果是下面这样呢?
class Sample # getter def name "new value" end # setter def name=(new_name) # setter 的逻辑,就是覆盖式定义一个 新的 直接返回新值的 getter re_define_name_getter(new_name) end end
直接伪代码,我们每次调用 setter ,setter 的任务不是去修改一个 中间值,而是每次去重新定义 新的 getter 方法,定义的时候就塞入新的值。
这样依然保持了 getter 的功能!豁然开朗!
Sinatra 的 set 系统就是这样工作的,不论是 set 函数定义本身,还是 set 内部调用的 set ,还是用户最终在外部书写 set xxx, new_value
最终殊途同归的进入 set option, value, true
然后都会走到最后一部分,重新定义三个方法。
define_singleton("#{option}=", setter) define_singleton(option, getter) define_singleton("#{option}?", "!!#{option}") unless method_defined? "#{option}?"
setter 方法的作用就是调用 set 自身,这样只要被调用,时间上可以完成了一种循环。闭环调用(原谅我用了闭环这个词 :P ) getter 方法 是以新的值直接返回,respond_to 方法同理,以新值计算返回。
1.定义处有趣的写法 value = (not_set = true)
def set(option, value = (not_set = true), ignore_setter = false, &block) # .... end
可以通过实验证实这种写法的特点是:
如果 value 赋值 比如是 99 ,那么 value = 99, not_set = nil
如果 value 没有赋值, 那么 value = not_set = true
这里主要是没有赋值,not_set 开始发挥逻辑上的作用。