构建一个 Ruby Gem 第十二章 Rails 钩子

如果你熟悉 Rails 的话,你知道最主要的三个组件是 models,controllers 和 views。
没什么好吃惊的,有对应的 Ruby 模块,它们是: ActiveRecord , ActionController 和 ActionView。

Rails 提供了钩子来访问这些类库在初始化的时候。通过添加或修改这些类库的代码,我们可以对 Rails 栈做任何事。在本章中,我们会看看最常见的方式去添加 gem 的代码到一个 Rails 应用和 ActiveSupport 是如何帮助的。

Railties

Railties 是对于配置和加载 Rails 框架的类库的钩子。它们允许我们很方便的添加或修改在 Rails 中已经存在的功能。
使用一个 Railtie 让我们有能力去添加 view 帮助方法,controller 方法,模型方法,生成器和一些列其他有用的特性。Railtie 的文档提供了这些任务的示例代码。文档很少考虑到 Rails 在表面之下做的事情。然后,事实上我们可以钩子初始化进程,这意味着如果我们知道方法处于哪个类中,我们可以改变或者扩展这个目标类。

看看第一个例子:

class MyRailtie < Rails::Railtie
    initializer "my_railtie.configure_rails_initialization" do
    # some initialization behavior
    end
end

在 Railtie 中,Initializers 并不是在 config/initializers 目录下的那类东西。
它们是特殊的代码块来钩子 Rails 初始化进程的。
为了实现这个功能,一个 initializer 需要一个专门的名字(就像上面那个)并且相关的代码被放在接踵而至的块中。
在我们深入创建一个 Railtie 之前,让我们探索一些扩展已有 Ruyb 类的方法。

Ruby Extensions

假设我们有如下的类:

module MegaLotto
  class HolidayDrawing
    def draw
      puts "This drawing is holiday-worthy!"
    end
  end
end

再想象一下我们想要添加 #jackpot 方法,但是类已经被定义了。Ruby 给了我们打猴子补丁的能力,正如我们在核心扩展中看到的那样。
最简单的实现方法就是在一个新的单独的文件中重新打开 MegaLotto::HolidayDrawing 类并且添加新方法:

module MegaLotto
  class HolidayDrawing
    def jackpot
      puts "You've won the big one!"
    end
  end
end

现在我们既可以访问原先已经定义的 #draw 方法,也可以访问新的 #jackpo 方法。

MegaLotto::HolidayDrawing.new.draw # => This drawing is holiday-worthy!
MegaLotto::HolidayDrawing.new.jackpot # => You've won the big one!

另一个给 Ruby 类添加方法的选项是使用 extend 或者 include 方法。虽然这篇博文有点老了,但是这模式在今天依然和适合 Ruby。
通常当使用 extend 或者 include 时,对于要包含目标方法(在我们的例子用是 #jackpot)放在一个单独的 Ruby 模块中:

module MegaLotto
  module Jackpot
    def jackpot
      puts "You've won the big one!"
    end
  end
end

为了添加 #jackpo 方法到 MegaLotto::HolidayDrawing,我们再一次重构耐心打开 MegaLotto::HolidayDrawing 类并且包含那个模块:

module MegaLotto
  class HolidayDrawing
    include MegaLotto::Jackpot
  end
end

这产生了和之前一样的结果,但是具体实现被抽象到了一个模块:

MegaLotto::HolidayDrawing.new.jackpot # => You've won the big one!

再进一步,包含一个类方法到一个标准的 Ruby 类中,我们可以 5 行代码缩减为:

MegaLotto::HolidayDrawing.include(MegaLotto::Jackpot)

不过,当我们尝试时,我们得到了下面的错误:

NoMethodError: private method `include` called for
MegaLotto::HolidayDrawing:Class from (irb):36
from /Users/bhilkert/.rbenv/versions/2.0.0-p247/bin/irb:12:in `<main>`

这说明包含了一个私有方法,所以用 Ruby 的 send 方法避开 Ruby 的可见性限制,这样就能访问到私有方法了。send 方法接收一个方法名(符号或字符串)以及可选的参数。
看上去就像这样:

MegaLotto::HolidayDrawing.send(:include, MegaLotto::Jackpot)

调用 #jackpot 的结果和预期一样:

MegaLotto::HolidayDrawing.new.jackpot # => You've won the big one!

注意:extend 的用法类似,除了我们不需要使用 send 因为方法是 public 的。使用 extend 对源对象添加了类方法。所以它不适合我们这个例子。这些加载的方式是需要一个 Rails 初始化钩子的。如果我们使用上面的例子,一个例子 Railtie 可以像这样:

class MyRailtie < Rails::Railtie
  initializer "my_railtie.configure_rails_initialization" do
    MegaLotto::HolidayDrawing.send(:include, MegaLotto::Jackpot)
  end
end

现在,MegaLotto::HolidayDrawing 命名空间不被 Rails 启动时包含,所以这个例子没有帮助。然而,如果我们把它应用到 ActionView::Base 中,你可以看到它是如何立刻变的有用的:

class MyRailtie < Rails::Railtie
  initializer "my_railtie.configure_rails_initialization" do
    ActionView::Base.send(:include, MegaLotto::Jackpot)
  end
end

这是一个常见模式对于 gems 包含 view helpers。然后,我们可以通过改变 ActiveSupport 的 on_load 方法来做的更好。

Active Support Load Hooks

一个扩展原生 Rails 的方法是通过 Active Support gem。
正如前面所提到的,Active Support 包含了一些列帮助和扩展方法。
Active Support 有一个 .on_load 方法来保持加载钩子的跟踪。
当一个特定的 Rails 类被加载时,它的相关的加载钩子会被执行。
.on_load 的源码只是一个标准的 Ruby Hash,包含一个 key 对于目标 Rails 类。
让我们看看和 Railtie 等价的 Active Support .on_load 方法。
对于我们的 initializer 它看起来像这样:

class MyRailtie < Rails::Railtie
  initializer "my_railtie.configure_rails_initialization" do
    # some initialization behavior
  end
end

使用 initializer,不需要使用 include 或者 extend 来添加方法到一个已经存在的类(就像我们上面看到的),我们会触发包含的方法当一个特定的 Rails 类库加载时(这里的例子是 action_view):

class MyRailtie < Rails::Railtie
  initializer "my_railtie.configure_rails_initialization" do
    ActiveSupport.on_load(:action_view) do
      # some initialization behavior, but in the
      # context of the base `ActionView` class
    end
  end
end

这种方法的好处是它看傻姑娘去更像原生的 Ruby 并且避开了不得不使用 send 来让类包含额外的模块。
我们将会对将要到来的 Rails 集成使用这种方式。

真实世界的例子

WillPaginate gem 提供了一个很好的例子关于 ActiveSupport.on_load 钩子的价值。
这是我见过的很复杂的 Railtie 之一。它让分页影响了 views,controllers 和 models 当查询和展示数据给用户看时。注意 .on_load 钩子对于 ActionView,ActionController 和 ActiveModel 。
它说明了 ActionController 它自己使用 .on_load 方法来加载它的内部。即使 Rails 内部使用 Railtie 钩子提供给了 gem 的作者!

总结

在 Rails 初始化时访问 Rails 类库给了我们很大的灵活性,让 gem 的创造者可以集成 Rails 栈的任何组件。
很明显 Rails 核心团队让 gem 集成和访问 Rails 内部有一个优先权。
这允许我们,作为 gem 开发者,通过分离开发类库来构建有价值的功能。
希望这个章节让我们知道了集成和 Rails 初始化进程加钩子是多么简单。
接着 Rails 集成的话题,下面我们会看看我们如何给我们的 gem 加上 Rails 的 view 帮助方法。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,830评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,992评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,875评论 0 331
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,837评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,734评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,091评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,550评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,217评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,368评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,298评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,350评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,027评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,623评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,706评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,940评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,349评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,936评论 2 341

推荐阅读更多精彩内容