module Person defname puts "My name is Person" end end
这是一个非常简单的模块,仅有一个 name 方法用于返回一个静态字符串。在我们的程序中使用这个模块:
1 2 3
classUser include Person end
Ruby提供了一些不同的方法来使用模块。include 是其中之一。include 所做的就是将在 module 内定义的方法在一个 class 的实例变量上可用。 在我们的例子中,是将 Person 模块中定义的方法变为一个 User 类实例对象的方法。 这就相当于我们是将 name 方法写在 User 类里一样,但是定义在 module 里的好处是可复用。 要调用 name 方法我们需要创建一个 User 的实例对象,然后再在这个对象上调用 name 方法。例如:
1 2
User.new.name => My name is Person
让我们看看基于 include 的钩子方法。included 是Ruby提供的一个钩子方法,当你在一些 module 或者 class 中 include 了一个 module 时它会被调用。 更新 Person 模块:
1 2 3 4 5 6 7 8 9
module Person defself.included(base) puts "#{base} included #{self}" end defname "My name is Person" end end
你可以看到一个新的方法 included 被定义为 Person 模块的类方法。当你在其他的模块或者类中执行 include Person 时,这个 included 方法会被调用。 该方法接收的一个参数是对包含该模块的类的引用。试试运行 User.new.name,你会看到如下的输出:
1 2
User included Person My name is Person
正如你所见,base 返回的是包含该模块的类名。现在我们有了一个包含 Person 模块的类的引用,我们可以通过元编程来实现我们想要的功能。 让我们来看看 Devise是如何使用 included 钩子的。
Devise中的 included
Devise是Ruby中使用最广泛的身份验证gem包之一。它主要是由我喜欢的程序员 José Valim 开发的,现在是由一些了不起的贡献者在维护。 Devise为我们提供了从注册到登录,从忘记密码到找回密码等等完善的功能。它可以让我们在用户模型中使用简单的语法来配置各种模块:
defdevise(*modules) options = modules.extract_options!.dup selected_modules = modules.map(&:to_sym).uniq.sort_by do |s| Devise::ALL.index(s) || -1# follow Devise::ALL order end devise_modules_hook! do include Devise::Models::Authenticatable selected_modules.each do |m| mod = Devise::Models.const_get(m.to_s.classify) if mod.const_defined?("ClassMethods") class_mod = mod.const_get("ClassMethods") extend class_mod if class_mod.respond_to?(:available_configs) available_configs = class_mod.available_configs available_configs.each do |config| nextunless options.key?(config) send(:"#{config}=", options.delete(config)) end end end include mod end self.devise_modules |= selected_modules options.each { |key, value| send(:"#{key}=", value) } end end
在我们的模型中传给 devise 方法的模块名将会作为一个数组保存在 *modules 中。 对于传入的模块调用 extract_options! 方法提取可能传入的选项。 在11行中调用 each 方法,并且每个模块在代码块中用 m 表示。 在12行中 m 将会转化为一个常量(类名),因此使用 m.to.classify 一个例如 :validatable 这样的符号会变为 Validatable 。 随便说一下 classify 是ActiveSupport的方法。 Devise::Models.const_get(m.to_classify) 会获取该模块的引用,并赋值给 mod。 在27行使用 include mod 包含该模块。 例子中的 Validatable 模块是定义在这里。 Validatable 的 included 钩子方法定义如下:
另一个使用定义在模块内部方法的方式称为 prepend。prepend 是在Ruby 2.0中引入的,并且与 include 和 extend 很不一样。 使用 include 和 extend 引入的方法可以被目标模块/类重新定义覆盖。 例如,如果我们在某个模块中定义了一个名为 name 的方法,并且在目标模块/类中也定义同名的方法。 那么这个在我们类在定义的 name 方法将会覆盖模块中的。而 prepend 是不一样的,它会将 prepend 引入的模块 中的方法覆盖掉我们模块/类中定义的方法。让我们来看一个简单的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
module Person defname "My name belongs to Person" end end
classUser include Person defname "My name belongs to User" end end
puts User.new.name => My name belongs to User
现在再来看看 prepend 的情况:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
module Person defname "My name belongs to Person" end end
classUser prepend Person defname "My name belongs to User" end end
puts User.new.name => My name belongs to Person
使用 prepend Person 会将 User 中的同名方法给覆盖掉,因此在终端输出的结果为 My name belongs to Person。 prepend 实际上是将方法添加到方法链的前端。在调用 User 类内定义的 name 方法时,会调用 super 从而调用 Person 模块的 name。
与 prepend 对应的回调名为(你应该猜到了) prepended。当一个模块被预置到另一个模块/类中时它会被调用。 我们来看下效果。更新 Person 模块的定义:
1 2 3 4 5 6 7 8 9
module Person defself.prepended(base) puts "#{self} prepended to #{base}" end
defname "My name belongs to Person" end end
你再运行这段代码应该会看到如下结果:
1 2
Person prepended to User My name belongs to Person
class << self definherited(base) raise "You cannot have more than one Rails::Application"if Rails.application super Rails.application = base.instance Rails.application.add_lib_to_load_path! ActiveSupport.run_load_hooks(:before_configuration, base.instance) end end
classPerson defname "My name is Person" end end p = Person.new puts p.name # => My name is Person puts p.address # => undefined method `address' for #<Person:0x007fb730a2b450> (NoMethodError)
我们定义了一个简单的 Person 类, 它只有一个 name 方法。然后创建一个 Person 的实例对象, 并分别调用 name 和 address 两个方法。因为 Person 中定义了 name,因此这个运行没问题。 然而 Person 并没有定义 address,这将会抛出一个异常。 method_missing 钩子可以优雅地捕捉到这些未定义的方法,避免此类异常。 让我们修改一下 Person 类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
classPerson defmethod_missing(sym, *args) "#{sym} not defined on #{self}" end defname "My name is Person" end end p = Person.new puts p.name # => My name is Person puts p.address # => address not defined on #<Person:0x007fb2bb022fe0>
method_missing 接收两个参数:被调用的方法名和传递给该方法的参数。 首先Ruby会寻找我们试图调用的方法,如果方法没找到则会寻找 method_missing 方法。 现在我们重载了 Person 中的 method_missing,因此Ruby将会调用它而不是抛出异常。
$ cd examples $ cat test.py # -*- coding: utf-8 - # # This file is part of gunicorn released under the MIT license. # See the NOTICE for more information.
def app(environ, start_response): """Simplest possible application object""" data = 'Hello, World!\n' status = '200 OK' response_headers = [ ('Content-type','text/plain'), ('Content-Length', str(len(data))) ] start_response(status, response_headers) return iter([data])