Ruby 干货分享:通过 Spree 开源代码学 Ruby 和 Rails

grantbb · 2016年09月29日 · 最后由 grantbb 回复于 2016年10月17日 · 4103 次阅读

最近在做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.active

self.activate方法其实不难理解,这是一个类方法,会在引入 Spree 的应用中查找app目录下面文件名中带有_decorator的文件,这些文件都是用来定制扩展 Spree 功能的文件。在这里,找到后会判断是否设置了Rails.configuration.cache_classes这个选项,一般在开发环境中,该选项是 false,这样当你修改代码后 Rails 会自动 reload 相关的文件。而在产品环境中,该选项为 true,因为我们不会在产品环境修改代码让 Rails 自动 reload。

后面requireload的最重要的区别就是,require如果发现该文件已经加载过后就不会重新载入,而load不管之前是否已经载入都会加载该文件。

重点来了,让我们看看下面这行信息量巨大的代码吧。一眼就看明白的同学,请自行绕过!

config.to_prepare &method(:activate).to_proc

method(:activate)

method方法来自于Object,会在当前的实例 (上面的例子就是 self) 上查找通过参数指定的方法。找到后返回一个Method的实例,这个 Method 的对象就是一个封装了方法所属的对象以及该对象的实例变量的闭包。

method(:activate).to_proc

接下来就是调用Method对象的to_proc方法,产生一个对应的Proc对象。

&method(:activate).to_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

config.to_prepare

这是来自 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!那就点个💖呗。谢谢

如果是做 Spree 定制开发的话,现在可以直接去用 Solidus 了。

#2 楼 @holysoros 我居然还不知道 Solidus,谢谢推荐,看他们更新蛮快的。👍!

需要 登录 后方可回复, 如果你还没有账号请 注册新账号