午夜精品福利视频,亚洲激情专区,免费看a网站,aa毛片,亚洲色图激情小说,亚洲一级毛片,免费一级毛片一级毛片aa

Ruby中的鉤子方法詳解 -電腦資料

電腦資料 時(shí)間:2019-01-01 我要投稿
【www.stanzs.com - 電腦資料】

    這篇文章主要介紹了Ruby中的鉤子方法詳解,本文講解了什么是鉤子方法、included、Devise中的 included、extended、ActiveRecord中的 extended、prepended、inherited等內(nèi)容,需要的朋友可以參考下

    Ruby的哲學(xué)理念是基于一個(gè)基本的要素,那就是讓程序員快樂,

Ruby中的鉤子方法詳解

。Ruby非常注重程序員的快樂,并且也提供了許多不同的方法來(lái)實(shí)現(xiàn)它。 它的元編程能力能夠讓程序員編寫在運(yùn)行時(shí)動(dòng)態(tài)生成的代碼。它的線程功能使得程序員有一種優(yōu)雅的的方式編寫多線程代碼。 它的鉤子方法能讓程序員在程序運(yùn)行時(shí)擴(kuò)展它的行為。

    上述的這些特性,以及一些其他很酷的語(yǔ)言方面,使得Ruby成為編寫代碼的優(yōu)先選擇之一。 本文將探討Ruby中的一些重要的鉤子方法。我們將從不同方面討論鉤子方法,如它們是什么,它們用于什么,以及我們?nèi)绾问褂盟鼈儊?lái)解決不同的問題。 我們同時(shí)也了解一下一些流行的Ruby框架/Gem包/庫(kù)是如何使用它們來(lái)提供非常酷的特性的。

    我們開始吧。

    什么是鉤子方法?

    鉤子方法提供了一種方式用于在程序運(yùn)行時(shí)擴(kuò)展程序的行為。 假設(shè)有這樣的功能,可以在無(wú)論何時(shí)一個(gè)子類繼承了一些特定的父類時(shí)收到通知, 或者是比較優(yōu)雅地處理一個(gè)對(duì)象上的不可調(diào)用的方法而不是讓編譯器拋出異常。 這些情況就是使用鉤子方法,但是它們的用法并不僅限于此。 不同的框架/庫(kù)使用了不同的鉤子方法來(lái)實(shí)現(xiàn)它們的功能。

    在本文中我們將會(huì)討論如下幾個(gè)鉤子方法:

    1.included

    2.extended

    3.prepended

    4.inherited

    5.method_missing

    included

    Ruby給我們提供了一種方式使用 模塊(modules) (在其他語(yǔ)言中被稱作 混入類(mixins))來(lái)編寫模塊化的代碼供其他的 模塊/類 使用。 模塊 的概念很簡(jiǎn)單,它就是一個(gè)可以在其他地方使用的獨(dú)立代碼塊。

    例如,如果我們想要編寫一些代碼在任何時(shí)候調(diào)用特定的方法都會(huì)返回一個(gè)靜態(tài)字符串。 我們姑且將這個(gè)方法稱作 name。你可能在其他地方也會(huì)想使用同一塊代碼。 這樣最好是新建一個(gè)模塊。讓我們來(lái)創(chuàng)建一個(gè):

    代碼如下:

    module Person

    def name

    puts "My name is Person"

    end

    end

    這是一個(gè)非常簡(jiǎn)單的模塊,僅有一個(gè) name 方法用于返回一個(gè)靜態(tài)字符串。在我們的程序中使用這個(gè)模塊:

    代碼如下:

    class User

    include Person

    end

    Ruby提供了一些不同的方法來(lái)使用模塊。include 是其中之一。include 所做的就是將在 module 內(nèi)定義的方法在一個(gè) class 的實(shí)例變量上可用。 在我們的例子中,是將 Person 模塊中定義的方法變?yōu)橐粋(gè) User 類實(shí)例對(duì)象的方法。 這就相當(dāng)于我們是將 name 方法寫在 User 類里一樣,但是定義在 module 里的好處是可復(fù)用。 要調(diào)用 name 方法我們需要?jiǎng)?chuàng)建一個(gè) User 的實(shí)例對(duì)象,然后再在這個(gè)對(duì)象上調(diào)用 name 方法。例如:

    代碼如下:

    User.new.name

    => My name is Person

    讓我們看看基于 include 的鉤子方法。included 是Ruby提供的一個(gè)鉤子方法,當(dāng)你在一些 module 或者 class 中 include 了一個(gè) module 時(shí)它會(huì)被調(diào)用。 更新 Person 模塊:

    代碼如下:

    module Person

    def self.included(base)

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

    end

    def name

    "My name is Person"

    end

    end

    你可以看到一個(gè)新的方法 included 被定義為 Person 模塊的類方法。當(dāng)你在其他的模塊或者類中執(zhí)行 include Person 時(shí),這個(gè) included 方法會(huì)被調(diào)用。 該方法接收的一個(gè)參數(shù)是對(duì)包含該模塊的類的引用。試試運(yùn)行 User.new.name,你會(huì)看到如下的輸出:

    代碼如下:

    User included Person

    My name is Person

    正如你所見,base 返回的是包含該模塊的類名,F(xiàn)在我們有了一個(gè)包含 Person 模塊的類的引用,我們可以通過元編程來(lái)實(shí)現(xiàn)我們想要的功能。 讓我們來(lái)看看 Devise是如何使用 included 鉤子的。

    Devise中的 included

    Devise是Ruby中使用最廣泛的身份驗(yàn)證gem包之一。它主要是由我喜歡的程序員 José Valim 開發(fā)的,現(xiàn)在是由一些了不起的貢獻(xiàn)者在維護(hù)。 Devise為我們提供了從注冊(cè)到登錄,從忘記密碼到找回密碼等等完善的功能。它可以讓我們?cè)谟脩裟P椭惺褂煤?jiǎn)單的語(yǔ)法來(lái)配置各種模塊:

    代碼如下:

    devise :database_authenticatable, :registerable, :validatable

    在我們模型中使用的 devise 方法在這里定義。 為了方便我將這段代碼粘貼在下面:

    代碼如下:

    def devise(*modules)

    ptions = 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 方法的模塊名將會(huì)作為一個(gè)數(shù)組保存在 *modules 中。 對(duì)于傳入的模塊調(diào)用 extract_options! 方法提取可能傳入的選項(xiàng)。 在11行中調(diào)用 each 方法,并且每個(gè)模塊在代碼塊中用 m 表示。 在12行中 m 將會(huì)轉(zhuǎn)化為一個(gè)常量(類名),因此使用 m.to.classify 一個(gè)例如 :validatable 這樣的符號(hào)會(huì)變?yōu)?Validatable 。 隨便說一下 classify 是ActiveSupport的方法。

    Devise::Models.const_get(m.to_classify) 會(huì)獲取該模塊的引用,并賦值給 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

    此時(shí)模型是 base。在第5行的 class_eval 代碼塊會(huì)以該類作為上下文進(jìn)行求值運(yùn)算。 通過 class_eval 編寫的代碼與直接打開該類的文件將代碼粘貼進(jìn)去效果是一樣的。 Devise是通過 class_eval 將驗(yàn)證包含到我們的用戶模型中的。

    當(dāng)我們?cè)囍褂肈evise注冊(cè)或者登錄時(shí),我們會(huì)看到這些驗(yàn)證,但是我們并沒有編寫這些驗(yàn)證代碼。 Devise是利用了 included 鉤子來(lái)實(shí)現(xiàn)這些的。非常的優(yōu)雅吧。

    extended

    Ruby也允許開發(fā)者 擴(kuò)展(extend) 一個(gè)模塊,這與 包含(include) 有點(diǎn)不同。 extend 是將定義在 模塊(module) 內(nèi)的方法應(yīng)用為類的方法,而不是實(shí)例的方法。 讓我們來(lái)看一個(gè)簡(jiǎn)單的例子:

    代碼如下:

    module Person

    def name

    "My name is Person"

    end

    end

    class User

    extend Person

    end

    puts User.name # => My name is Person

    正如你所看到的,我們將 Person 模塊內(nèi)定義的 name 方法作為了 User 的類方法調(diào)用。 extend 將 Person 模塊內(nèi)的方法添加到了 User 類中。extend 同樣也可以用于將模塊內(nèi)的方法作為單例方法(singleton methods)。 讓我們?cè)賮?lái)看另外一個(gè)例子:

    代碼如下:

    # 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 # (NoMethodError)

    我們創(chuàng)建了兩個(gè) User 的實(shí)例對(duì)象,并將 Person 作為參數(shù)在 u1 上調(diào)用 extend 方法。 使用這種調(diào)用方式,Person 的 name 方法僅對(duì) u1 有效,對(duì)于其他實(shí)例是無(wú)效的。

    正如 included 一樣,與 extend 相對(duì)應(yīng)的鉤子方法是 extended。 當(dāng)一個(gè)模塊被其他模塊或者類執(zhí)行了 extend 操作時(shí),該方法將會(huì)被調(diào)用。 讓我們來(lái)看一個(gè)例子:

    代碼如下:

    # 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

    該代碼的運(yùn)行結(jié)果是輸出 User extended Person。

    關(guān)于 extended 的介紹已經(jīng)完了,讓我們來(lái)看看 ActiveRecord 是如何使用它的。

    ActiveRecord中的 extended

    ActiveRecord 是在 Ruby 以及 Rails 中廣泛使用的ORM框架。它具有許多酷的特性, 因此使用它在很多情況下成為了ORM的首選。讓我們進(jìn)入 ActiveRecord 內(nèi)部看看 ActiveRecord 是如何使用回調(diào)的。 (我們使用的是 Rails v3.2.21)

    ActiveRecord 在這里 extend 了 ActiveRecord::Models 模塊。

    代碼如下:

    extend ActiveModel::Callbacks

    ActiveModel 提供了一套在模型類中使用的接口。它們?cè)试S ActionPack 與不是 ActiveRecord 的模型進(jìn)行交互。 在這里, ActiveModel::Callbacks 內(nèi)部你將會(huì)看到如下代碼:

    代碼如下:

    def self.extended(base)

    base.class_eval do

    include ActiveSupport::Callbacks

    end

    end

    ActiveModel::Callbacks 對(duì) base 即就是 ActiveRecord::Callbacks 調(diào)用了 class_eval 方法, 并包含了 ActiveSupport::Callbacks 模塊,

電腦資料

Ruby中的鉤子方法詳解》(http://www.stanzs.com)。我們前面已經(jīng)提到過了,對(duì)一個(gè)類調(diào)用 class_eval 與手動(dòng)地將代碼寫在這個(gè)類里是一樣的。 ActiveSupport::Callbacks 為 ActiveRecord::Callbacks 提供了 Rails 中的回調(diào)方法。

    這里我們討論了 extend 方法,以及與之對(duì)應(yīng)的鉤子 extended。并且也了解了 ActiveRecord / ActiveModel 是如何使用上述方法為我們提供可用功能的。

    prepended

    另一個(gè)使用定義在模塊內(nèi)部方法的方式稱為 prepend。prepend 是在Ruby 2.0中引入的,并且與 include 和 extend 很不一樣。 使用 include 和 extend 引入的方法可以被目標(biāo)模塊/類重新定義覆蓋。 例如,如果我們?cè)谀硞(gè)模塊中定義了一個(gè)名為 name 的方法,并且在目標(biāo)模塊/類中也定義同名的方法。 那么這個(gè)在我們類在定義的 name 方法將會(huì)覆蓋模塊中的。而 prepend 是不一樣的,它會(huì)將 prepend 引入的模塊 中的方法覆蓋掉我們模塊/類中定義的方法。讓我們來(lái)看一個(gè)簡(jiǎn)單的例子:

    代碼如下:

    module Person

    def name

    "My name belongs to Person"

    end

    end

    class User

    include Person

    def name

    "My name belongs to User"

    end

    end

    puts User.new.name

    => My name belongs to User

    現(xiàn)在再來(lái)看看 prepend 的情況:

    代碼如下:

    module Person

    def name

    "My name belongs to Person"

    end

    end

    class User

    prepend Person

    def name

    "My name belongs to User"

    end

    end

    puts User.new.name

    => My name belongs to Person

    使用 prepend Person 會(huì)將 User 中的同名方法給覆蓋掉,因此在終端輸出的結(jié)果為 My name belongs to Person。 prepend 實(shí)際上是將方法添加到方法鏈的前端。在調(diào)用 User 類內(nèi)定義的 name 方法時(shí),會(huì)調(diào)用 super 從而調(diào)用 Person 模塊的 name。

    與 prepend 對(duì)應(yīng)的回調(diào)名為(你應(yīng)該猜到了) prepended。當(dāng)一個(gè)模塊被預(yù)置到另一個(gè)模塊/類中時(shí)它會(huì)被調(diào)用。 我們來(lái)看下效果。更新 Person 模塊的定義:

    代碼如下:

    module Person

    def self.prepended(base)

    puts "#{self} prepended to #{base}"

    end

    def name

    "My name belongs to Person"

    end

    end

    你再運(yùn)行這段代碼應(yīng)該會(huì)看到如下結(jié)果:

    代碼如下:

    Person prepended to User

    My name belongs to Person

    prepend 的引入是為了去除 alias_method_chain hack的丑陋,它曾被Rails以及其他庫(kù)廣泛地使用以達(dá)到與 prepend 相同的功能。 因?yàn)?prepend 只有在 Ruby >= 2.0 的版本中才能使用,因此如果你打算使用 prepend 的話,那么你就應(yīng)該升級(jí)你的Ruby版本。

    inherited

    繼承是面向?qū)ο笾幸粋(gè)最重要的概念。Ruby是一門面向?qū)ο蟮木幊陶Z(yǔ)言,并且提供了從基/父類繼承一個(gè)子類的功能。 我們來(lái)看一個(gè)簡(jiǎn)單的例子:

    代碼如下:

    class Person

    def name

    "My name is Person"

    end

    end

    class User < Person

    end

    puts User.new.name # => My name is Person

    我們創(chuàng)建了一個(gè) Person 類和一個(gè)子類 User。在 Person 中定義的方法也成為了 User 的一部分。 這是非常簡(jiǎn)單的繼承。你可能會(huì)好奇,是否有什么方法可以在一個(gè)類被其他類繼承時(shí)收到通知呢? 是的,Ruby有一個(gè)名為 inherited 的鉤子可以實(shí)現(xiàn)。我們?cè)倏纯催@個(gè)例子:

    代碼如下:

    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

    puts User.new.name

    正如你所見,當(dāng) Person 類被其他子類繼承時(shí) inherited 類方法將會(huì)被調(diào)用。 運(yùn)行以上代碼結(jié)果如下:

    代碼如下:

    User inherits Person

    My name is Person

    讓我們看看 Rails 在它的代碼中是如何使用 inherited 的。

    Rails中的 inherited

    Rails應(yīng)用中有一個(gè)重要的類名為 Application ,定義中 config/application.rb 文件內(nèi)。 這個(gè)類執(zhí)行了許多不同的任務(wù),如運(yùn)行所有的Railties,引擎以及插件的初始化。 關(guān)于 Application 類的一個(gè)有趣的事件是,在同一個(gè)進(jìn)程中不能運(yùn)行兩個(gè)實(shí)例。 如果我們嘗試修改這個(gè)行為,Rails將會(huì)拋出一個(gè)異常。讓我們來(lái)看看Rails是如何實(shí)現(xiàn)這個(gè)特性的。

    Application 類繼承自 Rails::Application,它是在這里定義的。 在62行定義了 inherited 鉤子,它會(huì)在我們的Rails應(yīng)用 Application 類繼承 Rails::Application 時(shí)被調(diào)用。 inherited 鉤子的代碼如下:

    代碼如下:

    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

    class << self 是Ruby中的另一個(gè)定義類方法的方式。在 inherited 中的第1行是檢查 Rails.application 是否已存在。 如果存在則拋出異常。第一次運(yùn)行這段代碼時(shí) Rails.application 會(huì)返回false然后調(diào)用 super。 在這里 super 即是 Rails::Engine 的 inherited 鉤子,因?yàn)?Rails::Application 繼承自 Rails::Engine。

    在下一行,你會(huì)看到 Rails.application 被賦值為 base.instance 。其余就是設(shè)置Rails應(yīng)用了。

    這就是Rails如何巧妙地使用 inherited 鉤子來(lái)實(shí)現(xiàn)我們的Rails Application 類的單實(shí)例。

    method_missing

    method_missing 可能是Ruby中使用最廣的鉤子。在許多流行的Ruby框架/gem包/庫(kù)中都有使用它。 當(dāng)我們?cè)噲D訪問一個(gè)對(duì)象上不存在的方法時(shí)則會(huì)調(diào)用這個(gè)鉤子方法。 讓我們來(lái)看一個(gè)簡(jiǎn)單的例子:

    代碼如下:

    class Person

    def name

    "My name is Person"

    end

    end

    p = Person.new

    puts p.name    # => My name is Person

    puts p.address # => undefined method `address‘ for # (NoMethodError)

    我們定義了一個(gè)簡(jiǎn)單的 Person 類, 它只有一個(gè) name 方法。然后創(chuàng)建一個(gè) Person 的實(shí)例對(duì)象, 并分別調(diào)用 name 和 address 兩個(gè)方法。因?yàn)?Person 中定義了 name,因此這個(gè)運(yùn)行沒問題。 然而 Person 并沒有定義 address,這將會(huì)拋出一個(gè)異常。 method_missing 鉤子可以優(yōu)雅地捕捉到這些未定義的方法,避免此類異常。 讓我們修改一下 Person 類:

    代碼如下:

    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 #

    method_missing 接收兩個(gè)參數(shù):被調(diào)用的方法名和傳遞給該方法的參數(shù)。 首先Ruby會(huì)尋找我們?cè)噲D調(diào)用的方法,如果方法沒找到則會(huì)尋找 method_missing 方法。 現(xiàn)在我們重載了 Person 中的 method_missing,因此Ruby將會(huì)調(diào)用它而不是拋出異常。

    讓我們來(lái)看看 Rake 是如何使用 method_missing 的。

    Rake中的 method_missing

    Rake 是Ruby中使用最廣泛的gem包之一。Rake 使用 method_missing 來(lái)提供訪問傳遞給Rake任務(wù)的參數(shù)。 首先創(chuàng)建一個(gè)簡(jiǎn)單的rake任務(wù):

    代碼如下:

    task :hello do

    puts "Hello"

    end

    如果你通過調(diào)用 rake hello 來(lái)執(zhí)行這個(gè)任務(wù),你會(huì)看到輸出 Hello。 讓我們擴(kuò)展這個(gè)rake任務(wù),以便接收一個(gè)參數(shù)(一個(gè)人名)并向他打招呼:

    代碼如下:

    task :hello, :name do |t, args|

    puts "Hello #{args.name}"

    end

    t 是任務(wù)名,args 保存了傳遞過來(lái)的參數(shù)。正如你所見,我們調(diào)用 args.name 來(lái)獲取傳遞給 hello 任務(wù)的 name 參數(shù)。 運(yùn)行該任務(wù),并傳遞一個(gè)參數(shù):

    代碼如下:

    rake hello["Imran Latif"]

    => Hello Imran Latif

    讓我們來(lái)看看 Rake 是如何使用 method_missing 為我們提供了傳遞給任務(wù)的參數(shù)的。

    在上面任務(wù)中的 args 對(duì)象是一個(gè) Rake::TaskArguments 實(shí)例,它是在這里所定義。 這個(gè)類負(fù)責(zé)管理傳遞給Rake任務(wù)的參數(shù)。查看 Rake::TaskArguments 的代碼,你會(huì)發(fā)現(xiàn)并沒有定義相關(guān)的方法將參數(shù)傳給任務(wù)。 那么 Rake 是如何將參數(shù)提供給任務(wù)的呢?答案是 Rake 是使用了 method_missing 巧妙地實(shí)現(xiàn)了這個(gè)功能。 看看第64行 method_missing 的定義:

    代碼如下:

    def method_missing(sym, *args)

    lookup(sym.to_sym)

    end

    在這個(gè)類中定義 method_missing 是為了保證能夠訪問到那些未定義的方法,而不是由Ruby拋出異常。 在 method_missing 中它調(diào)用了 lookup 方法:

    代碼如下:

    def lookup(name)

    if @hash.has_key?(name)

    @hash[name]

    elsif @parent

    @parent.lookup(name)

    end

    end

    method_missing 調(diào)用 lookup,并將方法名以 Symbol(符號(hào)) 的形式傳遞給它。 lookup 方法將會(huì)在 @hash 中進(jìn)行查找,它是在 Rake::TaskArguments 的構(gòu)造函數(shù)中創(chuàng)建的。 如果 @hash 中包含該參數(shù)則返回,如果在 @hash 中沒有則 Rake 會(huì)嘗試調(diào)用 @parent 的 lookup。 如果該參數(shù)沒有找到,則什么都不返回。

    這就是 Rake 如何巧妙地使用 method_missing 提供了訪問傳遞給Rake任務(wù)的參數(shù)的。 感謝Jim Weirich編寫了Rake。

    結(jié)束語(yǔ)

    我們討論了5個(gè)重要的Ruby鉤子方法,探索了它們是如何工作的,以及一些流行的框架/gem包是如何使用它們來(lái)提供一些優(yōu)雅的功能。 我希望你能喜歡這篇文章。請(qǐng)?jiān)谠u(píng)論中告訴我們你所喜歡的Ruby鉤子,以及你使用它們所解決的問題。

最新文章