Ruby中的钩子方法详解

2019-09-25 09:41:20于丽


# We are using same Person module and User class from previous example.    

u1 = User.new
u2 = User.new

u1.extend Person

puts u1.name # => My name is Person
puts u2.name # => undefined method `name' for #<User:0x007fb8aaa2ab38> (NoMethodError)

我们创建了两个 User 的实例对象,并将 Person 作为参数在 u1 上调用 extend 方法。 使用这种调用方式,Person 的 name 方法仅对 u1 有效,对于其他实例是无效的。

正如 included 一样,与 extend 相对应的钩子方法是 extended。 当一个模块被其他模块或者类执行了 extend 操作时,该方法将会被调用。 让我们来看一个例子:

# Modified version of Person module

module Person
  def self.extended(base)
    puts "#{base} extended #{self}"
  end

  def name
    "My name is Person"
  end
end

class User
  extend Person
end


该代码的运行结果是输出 User extended Person。

关于 extended 的介绍已经完了,让我们来看看 ActiveRecord 是如何使用它的。

ActiveRecord中的 extended

ActiveRecord 是在 Ruby 以及 Rails 中广泛使用的ORM框架。它具有许多酷的特性, 因此使用它在很多情况下成为了ORM的首选。让我们进入 ActiveRecord 内部看看 ActiveRecord 是如何使用回调的。 (我们使用的是 Rails v3.2.21)

ActiveRecord 在这里  extend 了 ActiveRecord::Models 模块。

extend ActiveModel::Callbacks

ActiveModel 提供了一套在模型类中使用的接口。它们允许 ActionPack 与不是 ActiveRecord 的模型进行交互。 在这里, ActiveModel::Callbacks 内部你将会看到如下代码:

def self.extended(base)
  base.class_eval do
    include ActiveSupport::Callbacks
  end
end

ActiveModel::Callbacks 对 base 即就是 ActiveRecord::Callbacks 调用了 class_eval 方法, 并包含了 ActiveSupport::Callbacks 模块。我们前面已经提到过了,对一个类调用 class_eval 与手动地将代码写在这个类里是一样的。 ActiveSupport::Callbacks 为 ActiveRecord::Callbacks 提供了 Rails 中的回调方法。

这里我们讨论了 extend 方法,以及与之对应的钩子 extended。并且也了解了 ActiveRecord / ActiveModel 是如何使用上述方法为我们提供可用功能的。

prepended

另一个使用定义在模块内部方法的方式称为 prepend。prepend 是在Ruby 2.0中引入的,并且与 include 和 extend 很不一样。 使用 include 和 extend 引入的方法可以被目标模块/类重新定义覆盖。 例如,如果我们在某个模块中定义了一个名为 name 的方法,并且在目标模块/类中也定义同名的方法。 那么这个在我们类在定义的 name 方法将会覆盖模块中的。而 prepend 是不一样的,它会将 prepend 引入的模块 中的方法覆盖掉我们模块/类中定义的方法。让我们来看一个简单的例子: