<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                # 4.5 模型中的回調(Callback) ## 概要: 本課時將講解 ActiveRecord 中常用的回調方法。 ## 知識點: 1. ActiveModel 中的回調 1. ActiveRecord 中的回調 1. 編寫回調 1. 觸發回調 1. 使用回調計算庫存 ## 正文 ### 4.5.1 ActiveModel 中的回調 [ActiveModel](https://github.com/rails/rails/tree/master/activemodel) 提供了多個實用的功能,它可以讓一個普通的類,具備如屬性校驗,回調,顯示字段 I18n 值等眾多功能。 比如,我們可以為 Person 類增加了一個回調方法: ~~~ class Person extend ActiveModel::Callbacks define_model_callbacks :create end ~~~ 所謂回調,是指在某個方法前(before)、后(after)、前后(around),執行某個方法。上面的例子里,Person 擁有了三個標準的回調方法:before_create、after_create、around_create。 我們還需要為這個回調方法增加邏輯代碼: ~~~ class Person extend ActiveModel::Callbacks define_model_callbacks :create # 定義 create 方法代碼 def create run_callbacks :create do puts "I am in create method." end end # 開始定義回調 before_create :action_before_create def action_before_create puts "I am in before action of create." end after_create :action_after_create def action_after_create puts "I am in after action of create." end around_create :action_around_create def action_around_create puts "I am in around action of create." yield puts "I am in around action of create." end end ~~~ 進入到 Rails 的終端里,我們測試下這個類: ~~~ % rails c > person = Person.new > person.create I am in before action of create. I am in around action of create. I am in create method. I am in around action of create. I am in after action of create. ~~~ 在 ActionModel 中有許多的 Ruby 元編程知識,如果你感興趣,可以讀一讀《[Ruby 元編程(第二版)](https://pragprog.com/book/ppmetr2/metaprogramming-ruby-2)》這本書。 ActiveRecord 中的 [回調](http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html) 將常用的 `find`,`create`,`update`,`destroy` 等方法進行包裝。 Rails 在 controller 也有回調,我們下一章會介紹。 ### 4.5.2 ActiveRecord 中的回調 我們在 Rails 中使用的 Model 回調,是通過調用 ActiveRecord 中定義的 `實例方法` 來實現的,比如 `before_validation` 方法,實現了在 `validate` 方法前的回調。 所謂 `回調`,就是在目標方法上,再執行其他的方法代碼。 ActiveRecord 提供了眾多回調方法,包含了一個 model 實例在數據庫操作中的各個時期。按照數據庫操作的不同,可以將它們劃分為五種情形的回調方法。 #### 第一種,創建對象時的回調。 - before_validation - after_validation - before_save - around_save - before_create - around_create - after_create - after_save - after_commit/after_rollback #### 第二種,更新對象時的回調。 - before_validation - after_validation - before_save - around_save - before_update - around_update - after_update - after_save - after_commit/after_rollback #### 第三種,刪除對象時的回調。 - before_destroy - around_destroy - after_destroy - after_commit/after_rollback #### 第四種,初始化和查找時的回調。 - after_find - after_initialize after_initialize 會在一個實例使用 new 創建,或從數據庫讀取時觸發。這樣避免直接覆寫實例的 initialize 方法。 當從數據庫讀取數據時,會觸發 after_find 回調: - all - first - find - find_by - find*by** - find*by**! - find_by_sql - last after_find 執行優先于 after_initialize。 #### 第五種,touch 回調。 - after_touch 執行實例的 `touch` 方法觸發該回調。 #### 回調執行順序 我們觀察一下以上每個回調的執行的順序,這里做一個簡單的例子: ~~~ class Product < ActiveRecord::Base before_validation do puts "before_validation" end after_validation do puts "after_validation" end before_save do puts "before_save" end around_save :test_around_save def test_around_save puts "begin around_save" yield puts "end around_save" end before_create do puts "before_create" end around_create :test_around_create def test_around_create puts "begin around_create" yield puts "end around_create" end after_create do puts "after_create" end after_save do puts "after_save" end after_commit do puts "after_commit" end after_rollback do puts "after_rollback" end end ~~~ 進入終端試驗下: ~~~ product = Product.new(name: "TTT") product.save (0.1ms) begin transaction before_validation after_validation before_save begin around_save before_create begin around_create SQL (0.6ms) INSERT INTO "products" ("name", "created_at", "updated_at") VALUES (?, ?, ?) [["name", "TTT"], ["created_at", "2015-06-16 02:49:20.871384"], ["updated_at", "2015-06-16 02:49:20.871384"]] end around_create after_create end around_save after_save (0.7ms) commit transaction after_commit => true ~~~ 可以看到,create 回調是最接近 sql 執行的,并且 validation、save、create 回調被包含在一個 transaction 事務中,最后,是 after_commit 回調。 我們在設計邏輯的過程中,需要了解它執行的順序。當需要在回調中操作保存到數據庫后的實例,需要把代碼放到 在 `after_commit` 中。 ### 4.5.3 編寫回調 上面列出的,是回調的方法名,我們還需要編寫具體的回調代碼。 #### 4.5.3.1 符號和方法 ~~~ class Topic < ActiveRecord::Base before_destroy :delete_parents [1] private [2] def delete_parents [3] self.class.delete_all "parent_id = #{id}" end end ~~~ [1] 用符號定義回調執行的方法名稱[2] private 或 protected 方法均可作為回調執行方法[3] 執行的方法名,和定義的符號一致 對于 `round_` 回調,我們需要在方法中使用 `yield`,上面的例子已經看到: ~~~ around_create :test_around_create def test_around_create puts "begin around_create" yield puts "end around_create" end ~~~ #### 4.5.3.2 代碼塊(Block) ~~~ before_create do self.name = login.capitalize if name.blank? end ~~~ 回調執行時,self 指的是它本身。在注冊的時候,我們可能不需要填寫 name,而要填寫 login,所以默認把 name 改成 login 的首字母大寫形式。 上面例子也可以改寫成: ~~~ before_create { |record| record.name = record.login.capitalize if record.name.blank? } ~~~ #### 4.5.3.3 在特定方法上使用回調 在一些注冊和修改的邏輯中,注冊時默認填寫的數據,在修改時不做處理,所以回調方法只在 create 上生效,下面的例子就是這種情形: ~~~ before_validation(on: :create) do self.number = number.gsub(/[^0-9]/, "") end ~~~ 或者: ~~~ before_validation :normalize_name, on: :create ~~~ #### 4.5.3.4 有條件的回調 和校驗一樣,回調也可以增加 if 或 unless 判斷: ~~~ before_save :normalize_card_number, if: :paid_with_card? ~~~ #### 4.5.3.5 字符串形式的回調 ~~~ class Topic < ActiveRecord::Base before_destroy 'self.class.delete_all "parent_id = #{id}"' end ~~~ `before_destroy` 既可以接受符號定義的方法名,也可以接受字符串。這種方式要被廢棄掉了。 #### 4.5.3.6 回調的繼承 一個類集成自另一個類,也會繼承它的回調,比如: ~~~ class Topic < ActiveRecord::Base before_destroy :destroy_author end class Reply < Topic before_destroy :destroy_readers end ~~~ 在執行 `Reply#destroy` 的時候,兩個回調都會被執行,為了避免這種情況,可以覆寫 `before_destroy`: ~~~ class Reply < Topic def before_destroy() destroy_readers end end ~~~ 但是,這是非常不好的解決方案!這個代碼只是一個例子,來自 [這里](http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html)。 回調雖然可以解決問題,但是它功能太過強大,當項目代碼變得復雜,回調的維護會造成很大的技術難度。建議使用回調解決小問題,過多的業務邏輯應該單獨處理,或者使用單獨的回調類。 #### 4.5.3.6 單獨的回調類 我們可以用一個類作為 `回調類`,使用它的的實例方法實現回調邏輯: ~~~ class BankAccount < ActiveRecord::Base before_save EncryptionWrapper.new end class EncryptionWrapper def before_save(record) [1] record.credit_card_number = encrypt(record.credit_card_number) end end ~~~ [1] 該方法僅能接受一個參數,為該 model 實例。 還可以使用 `回調類` 的類方法,來定義回調邏輯: ~~~ class PictureFileCallbacks def self.after_destroy(picture_file) ... end end ~~~ 在使用上: ~~~ class PictureFile < ActiveRecord::Base after_destroy PictureFileCallbacks end ~~~ 使用單獨的回調類,可以方便我們維護回調代碼,但是使用起來也需慎重考慮,不要增加后期的維護難度。 ### 4.5.4 觸發回調 在我們前面講解中,更新一個記錄時,destroy 方法會觸發校驗和回調,而 delete 方法不會。在這里詳細的列出,ActiveRecord 方法中,哪些會觸發回調,哪些不會。 觸發回調: - create - create! - decrement! - destroy - destroy! - destroy_all - increment! - save - save! - save(validate: false) - toggle! - update_attribute - update - update! - valid? 不觸發回調: - decrement - decrement_counter - delete - delete_all - increment - increment_counter - toggle - touch - update_column - update_columns - update_all - update_counters ### 4.5.5 回調的失敗 所有的回調,在動作執行的過程中,是順序觸發的。在 `before_xxx` 回調中,如果返回 `false`, 這個回調過程會被終止,并且觸發數據庫事務的 `rollback`,以及 `after_rollback` 回調。 但是,對于 `after_xxx` 回調,就只能用 `raise` 拋出異常的方式,來終止它。這里拋出的異常必須是 `ActiveRecord::Rollback`。我們修改下 `after_create` 回調: ~~~ after_create do puts "after_create" raise ActiveRecord::Rollback end ~~~ 在終端里: ~~~ > Product.create (0.1ms) begin transaction before_validation after_validation before_save begin around_save before_create begin around_create SQL (0.4ms) INSERT INTO "products" ("created_at", "updated_at") VALUES (?, ?) [["created_at", "2015-08-03 15:30:20.552783"], ["updated_at", "2015-08-03 15:30:20.552783"]] end around_create after_create (8.5ms) rollback transaction after_rollback => #<Product id: nil, name: nil, price: nil, description: nil, created_at: "2015-08-03 15:30:20", updated_at: "2015-08-03 15:30:20", top: nil, hot: nil> ~~~ `ActiveRecord::Rollback` 終止了數據庫事務,返回了一個沒有保存到數據庫中的實例。如果我們不拋出這個異常,比如拋出一個標準的異常類: ~~~ after_create do puts "after_create" raise StandardError end ~~~ 雖然它也會終止事務,沒有把保存數據,但是它再次拋出這個異常,而不是返回我們想要的未保存實例。 ~~~ ... after_rollback StandardError: StandardError from /PATH/shop/app/models/product.rb:40:in `block in <class:Product>' ... ~~~ ### 4.5.6 `after_commit`中的實例 當我們在回調中使用當前實例的時候,它并沒有保存到數據庫中,只有當數據庫事務 `commit` 之后,這個實例才會被保存,所以我們在 `after_commit` 回調中讀取它數據庫中的 id,并在這里設置它和其他實例的關聯。 ### 4.5.7 回調計算庫存 使用回調可以適當精簡邏輯代碼,比如我們購買一個商品類型時,在創建訂單后,應減少該商品類型的庫存數量。該 `減少數量` 的動作雖然屬于整體邏輯,但是和訂單邏輯是分開的,而它的觸發點正好在訂單 `create` 動作完成后,所以我們把它放到 `after_create` 中。 首先我們給 variants 增加 on_hand 屬性,表示當前持有的數量: ~~~ rails g migration add_on_hand_to_variants on_hand:integer ~~~ 在 order.rb 中編寫回調: ~~~ after_create do line_items.each do |line_item| line_item.variant.decrement!(:on_hand, line_item.quantity) end end ~~~
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看