最近在做Spree
的定制开发,翻看其中的代码,发现其中有下面这么一段代码信息量是非常的大。所以就想通过这一小段代码来好好学习一下相关的 Ruby 知识。
# spree/api/lib/spree/api/engine.rb
def self.activate
Dir.glob(File.join(File.dirname(__FILE__), "../../../app/**/*_decorator*.rb")) do |c|
Rails.configuration.cache_classes ? require(c) : load(c)
end
end
config.to_prepare &method(:activate).to_proc
就是这样的一段代码却包含了大量的 Ruby 和 Rails 的知识。其中最后一行config.to_prepare &method(:activate).to_proc
是本文重点解释的对象,接下来我们就一个一个来学习。
self.activate
方法其实不难理解,这是一个类方法,会在引入 Spree 的应用中查找app
目录下面文件名中带有_decorator
的文件,这些文件都是用来定制扩展 Spree 功能的文件。在这里,找到后会判断是否设置了Rails.configuration.cache_classes
这个选项,一般在开发环境中,该选项是 false,这样当你修改代码后 Rails 会自动 reload 相关的文件。而在产品环境中,该选项为 true,因为我们不会在产品环境修改代码让 Rails 自动 reload。
后面require
和load
的最重要的区别就是,require
如果发现该文件已经加载过后就不会重新载入,而load
不管之前是否已经载入都会加载该文件。
重点来了,让我们看看下面这行信息量巨大的代码吧。一眼就看明白的同学,请自行绕过!
config.to_prepare &method(:activate).to_proc
method
方法来自于Object
,会在当前的实例 (上面的例子就是 self) 上查找通过参数指定的方法。找到后返回一个Method
的实例,这个 Method 的对象就是一个封装了方法所属的对象以及该对象的实例变量的闭包。
接下来就是调用Method
对象的to_proc
方法,产生一个对应的Proc
对象。
在Proc
对象前面加上&
符号,作为参数传递,相当于给方法传递了一个block
,然后方法内部通过yield
调用block
,例如:
def to_prepare
yield if block_given?
puts 'Done prepare!'
end
to_prepare(&proc)
相反,如果在接收的block
参数前面加上&
符号,那就相当于给方法传递了一个Proc
的对象,然后方法内部通过proc.call
来调用
def config(&block)
block.call
puts 'done config!'
end
config do
puts 'I am block!'
end
这是来自 Rails 配置里面功能,该配置是全局性的,会在所有的 initializers 运行之后运行。很重要的一点是,该配置中的代码,在 production 和 test 环境中默认只会执行一次,而在开发环境 dev 中,会在每次修改文件,重新发出请求时,Rails 完成 reload,在实际进行请求处理之前执行这段代码。(好绕的流程)
所以,你会看到上面的代码就是来重新加载一些定制 Spree 功能的代码。
在 Rails 中你还可以手动调用ActionDispatch::Reloader.to_prepare
来实现同样地功能。
另外还有一个与之对应的ActionDispatch::Reloader.to_cleanup
,区别是该callback
会在请求处理完成后执行。
上面提到开发环境的不同,主要是受到config.cache_classes
配置的控制。开发环境 dev 下,该属性为false
,所以 Rails 会发现有文件修改后,自动 realod。
介绍完这些基本的知识点后,你可能会觉得还是有点糊涂,为什么 Spree 中要这么做呢?
这种做法其实在 Ruby 的世界是一种常见的用法,主要就是利用闭包的特点,把相关的逻辑更好的组织和封装。关于这一点上面 Spree 的例子不是很完美。
下面有给出一段利用闭包中封装的实例变量的例子。
class MethodTest
class << self
attr_accessor :root_path
def activate
puts @root_path
puts helper
end
def helper
"I am helper method. #{@root_path}"
end
end
end
def to_prepare
yield if block_given?
puts 'Done prepare!'
end
MethodTest.root_path = '/root'
to_prepare &MethodTest.method(:activate).to_proc
看完后,有学到新东西吗?Yes!那就点个💖呗。谢谢