iT邦幫忙

2023 iThome 鐵人賽

DAY 20
0
自我挑戰組

富士大顆系列 第 20

vol. 20 Rails 裡的 Life Cycle & Callback

  • 分享至 

  • xImage
  •  

你好,我是富士大顆 Aiko
本篇會談到:

  • Callback
  • 生命週期

生命週期 Life Cycle

要講 callback 得先提到生命週期是什麼。

在 Rails 的 Model 中,生命週期是指一個資料記錄(Record)從被創造(Create)到被更新(Update)和最終被刪除(Destroy)的整個過程。在這個過程中,可以使用 Callback 來在特定的時機點執行某些操作。

如何在 Article Model 中使用 Callback

假設我們有一個 Article Model ,生命週期可能如下:

  1. Create: 當 user 正在進行 create 一篇新文章。

    • before_validation: 驗證前,可以用來確保所有必填欄位都有值。

    • after_validation: 驗證後,通常是用來記錄 log;如果有錯誤,則記錄錯誤。

    • before_create: 在儲存新文章到資料庫之前,設定預設的狀態,這裡是先預設為草稿。

    • after_create: 在新文章被成功儲存後,可以發送通知給作者。

    class Article < ApplicationRecord
      before_validation :set_defaults
      after_validation :log_errors, if: -> { errors.present? }
      before_create :set_status
      after_create :notify_author
    
      private
    
      def set_defaults
        self.title ||= "Untitled"
      end
    
      def log_errors
        Rails.logger.debug "Validation failed: #{errors.full_messages.join(", ")}"
      end
    
      def set_status
        self.status = "draft"
      end
    
      def notify_author
        AuthorMailer.article_created(self).deliver_now
      end
    end
    

    Rails.logger.debug

Rails.logger.debug 是 Rails 內建的日誌(Logging)機制的一部分,用於將訊息寫入到日誌中。對於追蹤問題、監控應用程式狀態或記錄特定事件非常有用。

Rails.logger 提供了不同等級的日誌方法,包括:

  • debug: 用於記錄除錯訊息。
  • info: 用於記錄一般資訊。
  • warn: 用於記錄警告。
  • error: 用於記錄錯誤。
  • fatal: 用於記錄致命錯誤。
假設有一個 Article model,並且你想在文章驗證失敗時記錄錯誤訊息。
class Article < ApplicationRecord
  after_validation :log_errors, if: -> { errors.present? }

  private

  def log_errors
    Rails.logger.debug "Validation failed: #{errors.full_messages.join(", ")}"
  end
end

在這個例子中,after_validation Callback 會在驗證後執行。如果有錯誤,Rails.logger.debug 會將錯誤訊息寫入到日誌中。

可以在開發環境下的 log/development.log 文件或生產環境下的 log/production.log 文件中查看這些日誌。

  1. 更新(Update): 當文章被編輯或更新時。

    • before_update: 在更新文章之前,記錄更新的時間。
    • after_update: 在文章更新後,清除快取。
    class Article < ApplicationRecord
      before_update :set_updated_at
      after_update :clear_cache
    
      private
    
      def set_updated_at
        self.updated_at = Time.now
      end
    
      def clear_cache
        Rails.cache.delete([self.class.name, id])
      end
    end
    
  2. 刪除(Destroy): 當文章被刪除時。

    • before_destroy: 確保文章不是最後一篇。
    • after_destroy: 從所有(其他人)的收藏列表中移除該文章。
    class Article < ApplicationRecord
      before_destroy :ensure_not_last_article
      after_destroy :remove_from_favorites
    
      private
    
      def ensure_not_last_article
        if Article.count <= 1
          errors.add(:base, "Cannot delete the last article")
          throw :abort #Rails 會立即停止後續的 Callback 並中止目前的操作。
        end
      end
    
      def remove_from_favorites
        Favorite.where(article_id: id).destroy_all
      end
    end
    

throw :abort

在 Rails 的 Callback 中,throw :abort 是一種用來中止目前操作(例如儲存、更新或刪除)的方式。

這通常用於在某些條件不滿足時阻止記錄的儲存或刪除。

想確保某個使用者不是管理員才能被刪除,可以這樣做:
class User < ApplicationRecord
  before_destroy :ensure_not_admin

  private

  def ensure_not_admin
    if admin?
      errors.add(:base, "Cannot delete admin")
      throw :abort
    end
  end
end

before_destroy Callback 會在試圖刪除一個使用者記錄之前執行。如果該使用者是管理員,throw :abort 會被觸發,中止刪除,並新增一個錯誤訊息。

你可能會好奇,admin? 這個會指向誰,刪除者還是被刪除者?
在這個例子中確實沒有明確使用 self。然而,在 Ruby 的實體方法(Instance Method)中,即使沒有明確寫出 self,它仍然是有用 self。所以當你在這個方法中使用 admin? 時,實際上是在使用 self.admin?,其中 self 指的是當前的 User 實體,也就是被操作的這個資源。

實體方法(Instance Method)

在 Ruby(以及 Rails)中,實體方法是定義在類別(Class)內部,但是需要一個該類別的實體(Instance)來使用它的方法。換句話說,實體方法是屬於實體的方法,而不是類別本身。

以下是一個簡單的 Ruby 類別,其中包含一個實體方法 say_hello

class Person
  def say_hello
    puts "Hello!"
  end
end

要使用這個 say_hello 方法,你需要先 .new 一個 Person 類別的實體,然後使用這個實體來使用方法。

Aiko = Person.new
# Aiko 是 person class 新的實體

# 使用這個實體來使用在 person class 裡 的 say_hello 方法
Aiko.say_hello  
# Hello!

在這個例子中,say_hello 就是一個實體方法,因為需要一個 Person 的實體 Aiko 來使用它。

與類別方法(Class Method)的區別:

類別方法是直接屬於類別的,不需要實體就能使用。

class Person
  def self.say_goodbye
    puts "Goodbye!"
  end
end

# 直接使用類別名稱來調用類別方法
Person.say_goodbye  # 輸出:Goodbye!

在這個例子中,say_goodbye 是一個類別方法,因為你可以直接使用 Person 類別來使用它,而不需要一個實體。

總而言之,如果你不是 person, 而想使用 person 這個 class 有的方法,那除非你是 person 直接用,要不你就要 .new 一個實體來使用 person class 有的方法!


Callback

callback = call then back 出去了記得回來

...被主函式呼叫運算後會返回主函式),是指通過參數將函式傳遞到其它代碼的,某一塊可執行代碼的參照。
(看維基完全危機感...到底在講什麼)

在 Rails 中,Callback 是一種在 Model 的生命週期中的特定時機點自動執行的方法。
這些 Callback 方法讓你在特定的時機(例如:儲存、更新或刪除記錄)執行邏輯。

常見的 Callback 類型:

  1. before_validation
  2. after_validation
  3. before_save
  4. after_save
  5. before_create
  6. after_create
  7. before_update
  8. after_update
  9. before_destroy
  10. after_destroy

範例:

1. before_save Callback

在儲存之前自動將 email 轉換為小寫。

class User < ApplicationRecord
  before_save :downcase_email

  private

  def downcase_email
    self.email = email.downcase
  end
end
2. after_create Callback

在建立新的使用者後,自動發送歡迎信。

class User < ApplicationRecord
  after_create :send_welcome_email

  private

  def send_welcome_email
    UserMailer.welcome_email(self).deliver_now
  end
end
3. before_destroy Callback

在刪除資料之前,確保被刪的身份不是管理員。

class User < ApplicationRecord
  before_destroy :ensure_not_admin

  private

  def ensure_not_admin
    if admin?
      errors.add(:base, "Cannot delete admin")
      throw :abort
    end
  end
end
4. after_validation Callback

驗證後,記錄錯誤訊息。

class Article < ApplicationRecord
  after_validation :log_errors, if: -> { errors.present? }

  private

  def log_errors
    Rails.logger.debug "Validation failed: #{errors.full_messages.join(", ")}"
  end
end

今天就先到這邊!
下篇想來談測試!


上一篇
vol. 19 Rails 裡的 helper, Service Object, Concern 到底怎麼用?
下一篇
vol. 21 Rails 的「測試-測試-測試!」(上)
系列文
富士大顆30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言