大家好,我是 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 开始发挥逻辑上的作用。