ruby 钩子方法的介绍和应用

浏览: 216 发布日期: 2017-06-11 分类: ruby

Class Extension Mixin

在 class 与 module 的关系里提到了,当一个 class 引入(include)一个 module 时,module 中的方法会变成
class 的实例方法,而不是类方法. extend 方法会把 module 中的方法放入 eigenclass 中,变成类方法.

这里使用钩子方法来实现.即当在一个class中include一个 module 时,使 module 中的方法变成class的类方法.

module MyMixin  
  def self.included(base)  
    base.extend(self)  
  end  
  
  def a_method  
    "a_method"  
  end  
end  
  
class MyClass  
  include MyMixin  
end  
  
puts MyClass.a_method  ## a_method
puts MyClass.new.a_method  ## a_method

在class include 一个方法是用到的是module的included方法.拓展included方法可以实现将module方法变为class 的类方法和实例方法.included 就是一个钩子方法


Hooks Method(钩子方法)

什么是钩子方法

钩子方法提供了一种用于在程序运行时扩展程序的行为.例如,在一个子类继承了一些特定的父类时收到通知,或者是处理一个对象上的不可调用方法而不让编译器抛出异常.这些情况就是使用钩子方法,但是它们的用法并不仅限于此.不同的框架/库使用了不同的钩子方法来实现它们的功能.

在本文中我们将会讨论如下几个钩子方法:

  • included

  • extended

  • prepended

  • inherited

  • method_missing

included, extended, perpended

在class上 include 一个module可以得到module上的方法作为class 的实例方法
included 也就是我们在上面提到的,这是Ruby提供的一个钩子方法,当你在一些 module 或者 class 中 include 了一个 module 时它会被调用.我们可以在 module 的 included 方法里增加所需要的逻辑.

  • 例如devise 的用户模型的数据验证(validatable),只需要在模型里 devise :validatable 就可以实现用户模型的验证逻辑:

def self.included(base)
  base.extend ClassMethods
  assert_validations_api!(base)

  base.class_eval do
    validates_presence_of   :email, if: :email_required?
    validates_uniqueness_of :email, allow_blank: true, if: :email_changed?
    validates_format_of     :email, with: email_regexp, allow_blank: true, if: :email_changed?

    validates_presence_of     :password, if: :password_required?
    validates_confirmation_of :password, if: :password_required?
    validates_length_of       :password, within: password_length, allow_blank: true
  end
end

extended 和 included 类似,在 class上 extend 一个 module 可以得到 module 上的方法作为 class 的类方法.这时候 extended 就会被触发.

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
User.name ## "My name is Person"

class 与 module 的引用方法还有 prepend .其对应的钩子方法是 prepended.与 include 和 extend 不同的是,include extend 类似于继承,当 class 本身有同名的方法时,以 class 自身方法为准,而 prepend 则是与之相反的,会覆盖掉 class 上的同名方法.
其钩子方法与 included 和 extended 用法一致.

module Person
  def name
    "My name belongs to Person"
  end
end

class IncludeUser
  include Person
  def name
    "My name belongs to User"
  end
end

class PrependUser
  def name
    "My name belongs to User"
  end
  prepend Person
end

IncludeUser.new.name ## 'My name belongs to User'
PrependUser.new.name ## 'My name belongs to Person'


inherited

继承是面向对象中一个最重要的概念。当父类被其他子类继承时,父类的 inherited 类方法将会被调用.

class Person
  def self.inherited(child_class)
    puts "#{child_class} inherits #{self}"
  end

  def name
    "My name is Person"
  end
end

class User < Person
end
## User inherits Person
puts User.new.name ## My name is Person

rails 里有个重要的类名为 Application,定义中 config/application.rb 文件内.这个类执行了许多不同的任务,如运行所有的Railties,引擎以及插件的初始化.关于 Application 类的一个有趣的事件是,在同一个进程中不能运行两个实例.他是这样实现的:

class << self
  def inherited(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


method_missing

method_missing 可能是Ruby中使用最广的钩子,当我们试图访问一个对象上不存在的方法时则会调用这个钩子方法.

class Person
  def method_missing(sym, *args)
     "#{sym} not defined on #{self}"
  end

  def name
    "My name is Person"
  end
end

p = Person.new

puts p.name     # => My name is Person 
puts p.address  # => address not defined on #<Person:0x007f9e8597f5b0>

Person 并没有定义 address, 这将会抛出一个异常.method_missing 钩子可以优雅地捕捉到这些未定义的方法.
method_missing 接收两个参数:被调用的方法名和传递给该方法的参数。 首先Ruby会寻找我们试图调用的方法,如果方法没找到则会寻找 method_missing 方法。 现在我们重载了 Person 中的 method_missing,因此Ruby将会调用它而不是抛出异常。


返回顶部