Ruby中的钩子方法详解

   这篇文章主要介绍了Ruby中的钩子方法详解,本文讲解了什么是钩子方法、included、Devise中的 included、extended、ActiveRecord中的 extended、prepended、inherited等内容,需要的朋友可以参考下

  Ruby的哲学理念是基于一个基本的要素,那就是让程序员快乐。Ruby非常注重程序员的快乐,并且也提供了许多不同的方法来实现它。 它的元编程能力能够让程序员编写在运行时动态生成的代码。它的线程功能使得程序员有一种优雅的的方式编写多线程代码。 它的钩子方法能让程序员在程序运行时扩展它的行为。

  上述的这些特性,以及一些其他很酷的语言方面,使得Ruby成为编写代码的优先选择之一。 本文将探讨Ruby中的一些重要的钩子方法。我们将从不同方面讨论钩子方法,如它们是什么,它们用于什么,以及我们如何使用它们来解决不同的问题。 我们同时也了解一下一些流行的Ruby框架/Gem包/库是如何使用它们来提供非常酷的特性的。

  我们开始吧。

  什么是钩子方法?

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

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

  1.included

  2.extended

  3.prepended

  4.inherited

  5.method_missing

  included

  Ruby给我们提供了一种方式使用 模块(modules) (在其他语言中被称作 混入类(mixins))来编写模块化的代码供其他的 模块/类 使用。 模块 的概念很简单,它就是一个可以在其他地方使用的独立代码块。

  例如,如果我们想要编写一些代码在任何时候调用特定的方法都会返回一个静态字符串。 我们姑且将这个方法称作 name。你可能在其他地方也会想使用同一块代码。 这样最好是新建一个模块。让我们来创建一个:

   代码如下:

  module Person

  def name

  puts "My name is Person"

  end

  end

  这是一个非常简单的模块,仅有一个 name 方法用于返回一个静态字符串。在我们的程序中使用这个模块:

  代码如下:

  class User

  include Person

  end

  Ruby提供了一些不同的方法来使用模块。include 是其中之一。include 所做的就是将在 module 内定义的方法在一个 class 的实例变量上可用。 在我们的例子中,是将 Person 模块中定义的方法变为一个 User 类实例对象的方法。 这就相当于我们是将 name 方法写在 User 类里一样,但是定义在 module 里的好处是可复用。 要调用 name 方法我们需要创建一个 User 的实例对象,然后再在这个对象上调用 name 方法。例如:

   代码如下:

  User.new.name

  => My name is Person

  让我们看看基于 include 的钩子方法。included 是Ruby提供的一个钩子方法,当你在一些 module 或者 class 中 include 了一个 module 时它会被调用。 更新 Person 模块:

   代码如下:

  module Person

  def self.included(base)

  puts "#{base} included #{self}"

  end

  def name

  "My name is Person"

  end

  end

  你可以看到一个新的方法 included 被定义为 Person 模块的类方法。当你在其他的模块或者类中执行 include Person 时,这个 included 方法会被调用。 该方法接收的一个参数是对包含该模块的类的引用。试试运行 User.new.name,你会看到如下的输出:

   代码如下:

  User included Person

  My name is Person

  正如你所见,base 返回的是包含该模块的类名。现在我们有了一个包含 Person 模块的类的引用,我们可以通过元编程来实现我们想要的功能。 让我们来看看 Devise是如何使用 included 钩子的。

  Devise中的 included

  Devise是Ruby中使用最广泛的身份验证gem包之一。它主要是由我喜欢的程序员 José Valim 开发的,现在是由一些了不起的贡献者在维护。 Devise为我们提供了从注册到登录,从忘记密码到找回密码等等完善的功能。它可以让我们在用户模型中使用简单的语法来配置各种模块:

   代码如下:

  devise :database_authenticatable, :registerable, :validatable

  在我们模型中使用的 devise 方法在这里定义。 为了方便我将这段代码粘贴在下面:

   代码如下:

  def devise(*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|

  next unless 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 钩子方法定义如下:

   代码如下:

  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

  此时模型是 base。在第5行的 class_eval 代码块会以该类作为上下文进行求值运算。 通过 class_eval 编写的代码与直接打开该类的文件将代码粘贴进去效果是一样的。 Devise是通过 class_eval 将验证包含到我们的用户模型中的。

  当我们试着使用Devise注册或者登录时,我们会看到这些验证,但是我们并没有编写这些验证代码。 Devise是利用了 included 钩子来实现这些的。非常的优雅吧。

  extended

  Ruby也允许开发者 扩展(extend) 一个模块,这与 包含(include) 有点不同。 extend 是将定义在 模块(module) 内的方法应用为类的方法,而不是实例的方法。 让我们来看一个简单的例子:

  代码如下:

  module Person

  def name

  "My name is Person"

  end

  end

  class User

  extend Person

  end

  puts User.name # => My name is Person

  正如你所看到的,我们将 Person 模块内定义的 name 方法作为了 User 的类方法调用。 extend 将 Person 模块内的方法添加到了 User 类中。extend 同样也可以用于将模块内的方法作为单例方法(singleton methods)。 让我们再来看另外一个例子:

   代码如下:

  # 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 #

""

""

""

""

>

""

""

>

""

""

>

""

<

>

""

""

<

<<

""

<<

""

>

>

""

""

>

>

""

""

""

>

时间: 2024-09-17 04:00:51

Ruby中的钩子方法详解的相关文章

Android 中 onSaveInstanceState()使用方法详解

Android 中 onSaveInstanceState()使用方法详解 覆盖onSaveInstanceState方法,并在onCreate中检测savedInstanceState和获取保存的值 @Override protected void onSaveInstanceState(Bundle outState) { outState.putInt("currentposition", videoView.getCurrentPosition()); super.onSave

实例讲解Ruby中的钩子方法及对方法调用添加钩子_ruby专题

钩子方法有些类似事件驱动装置,可以在特定的事件发生后执行特定的回调函数,这个回调函数就是钩子方法(更形象的描述: 钩子方法可以像钩子一样,勾住一个特定的事件.),在Rails中before\after函数就是最常见的钩子方法. Class#inherited方法也是这样一个钩子方法,当一个类被继承时,Ruby会调用该方法.默认情况下,Class#inherited什么都不做,但是通过继承,我们可以拦截该事件,对感兴趣的继承事件作出回应. class String def self.inherit

ThinkPHP中where()使用方法详解_php实例

本文介绍ThinkPHP的where()方法的用法.where方法可以用于对数据库操作的结果进行筛选.即SQL查询语句中的where子句. 今天来给大家讲下查询最常用但也是最复杂的where方法,where方法也属于模型类的连贯操作方法之一,主要用于查询和操作条件的设置. where方法的用法是ThinkPHP查询语言的精髓,也是ThinkPHP ORM的重要组成部分和亮点所在,可以完成包括普通查询.表达式查询.快捷查询.区间查询.组合查询在内的查询操作.where方法的参数支持字符串和数组,虽

ThinkPHP中order()使用方法详解_php实例

本文介绍ThinkPHP的order()方法的用法.order方法可以用于对数据库操作的结果进行排序.即相当于是在select语句中一个order by的子句. order方法属于模型的连贯操作方法之一,用于对数据库操作的结果进行排序.即相当于是在select语句中一个order by的子句. 用法 $Model->where('status=1')->order('id desc')->limit(5)->select(); 注意:连贯操作方法没有顺序,可以在select方法调用

ThinkPHP中limit()使用方法详解_php实例

本文介绍ThinkPHP的limit()方法的用法.limit方法可以用于对数据库操作的结果进行取指定范围的条数.即相当于是在mysql查询语句中的limit子句. limit方法也是模型类的连贯操作方法之一,主要用于指定查询和操作的数量,特别在分页查询的时候使用较多.ThinkPHP的l imit方法可以兼容所有的数据库驱动类的. 用法一.限制结果数量 例如获取满足要求的10个用户,如下调用即可: $User = M('User'); $User->where('status=1')->fi

javascript正则表达式中的replace方法详解_javascript技巧

前面的文章我已经介绍了正则的四个基本方法,当时也提到过replace方法 我们来回顾一下replace方法的使用: 先定义一个正则对象:var re=/中间写匹配的条件/; replace():正则匹配字符串,若是匹配成功,将匹配成功的字符串用新的字符串来替换 语法:字符串.replace(re,新的字符串): 举个例子:网络中经常会遇到,不文明的词会被*代替,我们来试一下: <!DOCTYPE> <html> <head> <meta charset='utf-

Android中HorizontalScrollView使用方法详解_Android

由于移动设备物理显示空间一般有限,不可能一次性的把所有要显示的内容都显示在屏幕上.所以各大平台一般会提供一些可滚动的视图来向用户展示数据.Android平台框架中为我们提供了诸如ListView.GirdView.ScrollView等滚动视图控件,这几个视图控件也是我们平常使用最多的.下面介绍一下HorizontalScrollView的使用和需要注意的点:  HorizontalScrollView是一个FrameLayout  ,这意味着你只能在它下面放置一个子控件,这个子控件可以包含很多

Android数据持久化之读写SD卡中内容的方法详解

本文实例讲述了Android数据持久化之读写SD卡中内容的方法.分享给大家供大家参考,具体如下: 前面文章里讲的那三个方法:openFileOutput .openFileInput 虽然都能通过流对象OutputStream和InputStream可以处理任意文件中的数据,但与 SharedPreferences 一样,只能在手机内存的指定目录下建立文件,因此,在实际的开发使用中有很大的局限性,那么在这一节中,我们来看一个比较高级的方法来实现数据的持久化--读写SD卡上的内容. --读取ass

深入PHP5中的魔术方法详解_php技巧

从PHP 5以后的版本,PHP中的类就可以使用魔术方法了.其规定以两个下划线(__)开头的方法都保留为魔术方法,所以建议大家函数名最好不用__开头,除非是为了重载已有的魔术方法. 1.__get.__set这两个方法是为在类和他们的父类中没有声明的属性而设计的.◆__get( $property ) 当调用一个未定义的属性时,此方法会被触发,传递的参数是被访问的属性名.◆__set( $property, $value ) 给一个未定义的属性赋值时,此方法会被触发,传递的参数是被设置的属性名和值