Callback詳解

卓亦葦發表於2023-04-21

Callbacks

Callback Registration

在 Rails 中,回撥(Callbacks)是一種在模型物件的生命週期中執行特定程式碼的機制。回撥可以在模型物件的建立、更新、刪除等操作中執行特定的程式碼,例如儲存物件前執行某些邏輯,或者在物件被刪除前執行清理操作。

Rails 中的回撥分為兩種型別:前置回撥(before callbacks)和後置回撥(after callbacks)。前置回撥在操作執行之前執行,後置回撥在操作執行之後執行。可以使用 before_after_ 字首來指定回撥的型別。

以下是一些常見的回撥型別:

  • before_validationafter_validation:在驗證物件之前和之後執行回撥。
  • before_saveafter_save:在儲存物件之前和之後執行回撥。
  • before_createafter_create:在建立物件之前和之後執行回撥。
  • before_updateafter_update:在更新物件之前和之後執行回撥。
  • before_destroyafter_destroy:在刪除物件之前和之後執行回撥。

要註冊回撥,可以在模型類中使用 before_after_ 字首來指定回撥的型別,然後指定要執行的方法:

class User < ApplicationRecord
  before_save :normalize_email

  private

  def normalize_email
    self.email = email.downcase
  end
end

在上面的例子中,我們將 normalize_email 方法註冊為 before_save 回撥。這意味著在儲存 User 物件之前,Rails 將自動呼叫 normalize_email 方法。在 normalize_email 方法中,我們將 email 屬性轉換為小寫字母,以確保所有郵件地址都是小寫的。

需要注意的是,註冊回撥時必須指定要執行的方法的名稱,可以是一個例項方法或一個類方法。回撥方法中可以使用模型物件的任何屬性或方法來執行特定的邏輯,例如更新其他物件、傳送電子郵件等。

使用回撥可以讓我們更靈活地控制模型物件的行為,可以在物件的生命週期中執行任意的操作。同時,回撥也可以提高程式碼的可讀性和可維護性,使程式碼更易於理解和修改。

Available Callbacks

在 Rails 中,可以註冊多種型別的回撥來在模型物件的生命週期中執行特定程式碼。以下是可用的回撥型別:

建立和儲存物件

  • before_validation:在驗證物件之前執行回撥。
  • after_validation:在驗證物件之後執行回撥。
  • before_save:在儲存物件之前執行回撥,包括新建和更新操作。
  • around_save:在儲存物件之前和之後執行回撥,使用 yield 方法來執行儲存操作。
  • after_save:在儲存物件之後執行回撥,包括新建和更新操作。
  • before_create:在建立物件之前執行回撥。
  • around_create:在建立物件之前和之後執行回撥,使用 yield 方法來執行建立操作。
  • after_create:在建立物件之後執行回撥。

更新和刪除物件

  • before_update:在更新物件之前執行回撥。
  • around_update:在更新物件之前和之後執行回撥,使用 yield 方法來執行更新操作。
  • after_update:在更新物件之後執行回撥。
  • before_destroy:在刪除物件之前執行回撥。
  • around_destroy:在刪除物件之前和之後執行回撥,使用 yield 方法來執行刪除操作。
  • after_destroy:在刪除物件之後執行回撥。

關聯物件

  • before_add_association:在新增關聯物件之前執行回撥。
  • after_add_association:在新增關聯物件之後執行回撥。
  • before_remove_association:在刪除關聯物件之前執行回撥。
  • after_remove_association:在刪除關聯物件之後執行回撥。

其他

  • after_initialize:在例項化物件之後執行回撥。
  • after_find:在從資料庫中查詢物件之後執行回撥。

要註冊回撥,可以在模型類中使用相應的回撥方法來指定回撥的型別,然後指定要執行的方法。例如,要在儲存物件之前執行特定的邏輯,可以使用 before_save 方法:

class User < ApplicationRecord
  before_save :normalize_email

  private

  def normalize_email
    self.email = email.downcase
  end
end

在上面的例子中,我們將 normalize_email 方法註冊為 before_save 回撥。這意味著在儲存 User 物件之前,Rails 將自動呼叫 normalize_email 方法。在 normalize_email 方法中,我們將 email 屬性轉換為小寫字母,以確保所有郵件地址都是小寫的。

需要注意的是,回撥方法中可以使用模型物件的任何屬性或方法來執行特定的邏輯,例如更新其他物件、傳送電子郵件等。使用回撥可以讓我們更靈活地控制模型物件的行為,可以在物件的生命週期中執行任意的操作。同時,回撥也可以提高程式碼的可讀性和可維護性,使程式碼更易於理解和修改。

回撥前更新屬性會怎麼辦

如果在回撥中嘗試更新屬性,可能會導致一些問題。因為回撥的執行順序是不確定的,所以在某些情況下,屬性的更新可能會被其他回撥覆蓋或被資料庫中的持久化資料覆蓋。

例如,如果我們在 before_save 回撥中嘗試更新某個屬性,而在 after_save 回撥中有另一個回撥也嘗試更新同一個屬性,那麼最終屬性值可能會是不確定的,因為最後執行的回撥會覆蓋之前的值。

為了避免這種情況,應該儘量避免在回撥中更新屬性。如果確實需要更新屬性,可以使用 update_column 方法來更新屬性,該方法可以直接將屬性更新到資料庫中,而不觸發其他回撥。但是需要注意,使用 update_column 方法將跳過所有的驗證,包括模型定義的驗證,因此需要謹慎使用。

另外,如果在回撥中需要使用其他模型物件的資料,可以將邏輯移動到控制器或服務物件中,以確保資料正確性和可維護性。

after_initialize and after_find

after_initializeafter_find 都是 ActiveRecord 模型中的回撥方法。

after_initialize 方法會在建立新的 ActiveRecord 物件或從資料庫中載入現有物件時被呼叫。該方法可以用來執行任意初始化邏輯,例如設定預設值或初始化關聯物件。與其他回撥不同,after_initialize 方法不需要接收任何引數,因為它是在物件建立之後立即呼叫的。

以下是一個示例,演示如何使用 after_initialize 方法在建立新物件時設定預設值:

class User < ApplicationRecord
  after_initialize :set_defaults

  private

  def set_defaults
    self.status ||= "active"
  end
end

在上面的例子中,我們將 set_defaults 方法註冊為 after_initialize 回撥。在 set_defaults 方法中,我們檢查 status 屬性是否為 nil,如果是,則將其設定為預設值 "active"

after_find 方法會在從資料庫中查詢 ActiveRecord 物件之後被呼叫。該方法可以用來執行任意的後處理邏輯,例如計算屬性或更新關聯物件。after_find 方法接收一個引數,即從資料庫中載入的 ActiveRecord 物件。

以下是一個示例,演示如何使用 after_find 方法計算使用者的年齡:

class User < ApplicationRecord
  after_find :calculate_age

  private

  def calculate_age
    self.age = Date.today.year - birthday.year
  end
end

在上面的例子中,我們將 calculate_age 方法註冊為 after_find 回撥。在 calculate_age 方法中,我們使用從資料庫中載入的使用者物件的生日屬性計算使用者的年齡,並將結果儲存到年齡屬性中。

需要注意的是,after_find 方法只會在從資料庫中載入物件時被呼叫,而不會在例項化新物件時被呼叫。如果需要在物件建立後執行某些邏輯,應該使用 after_initialize 方法。

after_touch什麼 意思

after_touch是Rails中的一個回撥方法,它會在一個已關聯的物件被touch操作更新後被觸發。在Rails中,touch操作指的是在更新一個物件時,同時更新關聯物件的更新時間戳(updated_at)欄位。這個操作可以用來實現快取失效、重新計算統計資料等功能。

例如,假設你有一個User模型和一個Post模型,一個使用者可以擁有多篇文章。當你更新某篇文章時,你可能需要更新相關使用者的更新時間戳,以便在使用者列表或其他地方正確地排序。你可以使用touch選項來實現這一點,如下所示:

class Post < ApplicationRecord
  belongs_to :user, touch: true
end

class User < ApplicationRecord
  has_many :posts
  after_touch :update_sorting

  def update_sorting
    # 更新使用者排序,例如更新`updated_at`欄位
    self.touch
  end
end

這段程式碼定義了兩個Active Record模型,PostUser,它們之間存在一個一對多的關聯關係。

Post模型中,使用belongs_to :user, touch: true宣告瞭一個屬於關聯,表示一篇文章屬於一個使用者。touch: true選項表示當文章被更新時,自動更新與之關聯的使用者的updated_at欄位,以便在使用者列表或其他地方正確地排序。

User模型中,使用has_many :posts宣告瞭一個擁有多個關聯,表示一個使用者可以擁有多篇文章。after_touch :update_sorting宣告瞭一個after_touch回撥方法,表示當與之關聯的一篇文章被touch操作更新時,自動呼叫update_sorting方法更新使用者排序,例如更新updated_at欄位。

update_sorting方法中,使用self.touch方法更新使用者的updated_at欄位,以便在使用者列表或其他地方正確地排序。

需要注意的是,這段程式碼中使用了touch操作來實現自動更新關聯物件的更新時間戳,這可以用來實現快取失效、重新計算統計資料等功能。在Rails中,touch操作是一種常見的技巧,可以用來簡化程式碼並提高效能。

需要注意的是,after_touch回撥方法只會在touch操作觸發更新時被呼叫。如果你手動更新了updated_at欄位,after_touch回撥方法不會被呼叫。

執行回撥

這些是Rails中常用的Active Record模型操作方法,下面分別介紹它們的作用:

  1. create(attributes = {}):建立一個新的模型物件並將其儲存到資料庫中。可以傳入一個雜湊表引數attributes表示要建立的模型物件的屬性值。

  2. create!(attributes = {}):與create相同,但如果儲存失敗會丟擲異常。

  3. destroy:從資料庫中刪除當前模型物件。

  4. destroy!:與destroy相同,但如果刪除失敗會丟擲異常。

  5. destroy_all:刪除符合條件的所有模型物件,不進行任何回撥和驗證。

  6. destroy_by(conditions):根據條件刪除符合條件的單個模型物件,不進行任何回撥和驗證。

  7. save:將當前模型物件的屬性值儲存到資料庫中。如果物件不存在,則建立一個新的物件。

  8. save!:與save相同,但如果儲存失敗會丟擲異常。

  9. save(validate: false):與save相同,但不進行模型物件的驗證。

  10. toggle!:將當前模型物件的布林型別屬性取反並儲存到資料庫中。

  11. touch:更新當前模型物件的updated_at欄位,並儲存到資料庫中。這個方法通常用於更新快取或觸發回撥方法。

  12. update_attribute(name, value):更新當前模型物件的單個屬性值,並直接儲存到資料庫中,不進行任何驗證。

  13. update(attributes):更新當前模型物件的屬性值,並儲存到資料庫中。可以傳入一個雜湊表引數attributes表示要更新的屬性值。

  14. update!(attributes):與update相同,但如果更新失敗會丟擲異常。

  15. valid?:檢查當前模型物件是否透過驗證。如果驗證失敗,可以使用errors方法檢視錯誤資訊。

這些方法是Rails中常用的Active Record模型操作方法,可以用於建立、更新、刪除和驗證模型物件。需要注意的是,這些方法中有些會觸發回撥方法、進行驗證或丟擲異常,具體使用時需要根據實際情況選擇合適的方法。

相關文章