コールバック

コールバックを利用するとActive Recordオブジェクトが作成、保存、更新、削除のような処理をフックに別の処理を呼び出すことができるようになります。

実際にbefore_validationコールバックを使ってすべての検証の前に一度だけ処理を実行します。Catが入力されて保存される場合、Lovely Catに変換するというメソッドです。

class Book < ApplicationRecord
  before_validation do
    self.name = self.name.gsub(/Cat/) do |matched|
      "Lovely #{matched}"
    end
  end
end
irb(main):001:0> Book.create(name: "We love Cat", publisher: Publisher.find(1), price: 999)
   (0.8ms)  SELECT sqlite_version(*)
  Publisher Load (0.5ms)  SELECT "publishers".* FROM "publishers" WHERE "publishers"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
   (0.1ms)  begin transaction
  Book Create (1.2ms)  INSERT INTO "books" ("name", "price", "created_at", "updated_at", "publisher_id") VALUES (?, ?, ?, ?, ?)  [["name", "We love Lovely Cat"], ["price", 999], ["created_at", "2021-12-14 08:11:33.182097"], ["updated_at", "2021-12-14 08:11:33.182097"], ["publisher_id", 1]]
   (1.4ms)  commit transaction
=> #<Book id: 2, name: "We love Lovely Cat", published_at: nil, price: 999, created_at: "2021-12-14 08:11:33", updated_at: "2021-12-14 08:11:33", publisher_id: 1>

次は削除後にlogに書き込む処理を実装しました。Rails.logger.infoはログファイルに指定のメッセージを出力することができるメソッドです。

Rails アプリケーションのデバッグ - Railsガイド

class Book < ApplicationRecord
	after_destroy do
    Rails.logger.info "Book is deleted: #{self.attributes}"
  end
end
irb(main):001:0> book = Book.last
   (2.6ms)  SELECT sqlite_version(*)
  Book Load (0.3ms)  SELECT "books".* FROM "books" ORDER BY "books"."id" DESC LIMIT ?  [["LIMIT", 1]]
=> #<Book id: 2, name: "We love Lovely Cat", published_at: nil, price: 999, created_at: "2021-12-14 08:11:33", updated_at: "2021-12-14 08:11:33", publisher_id: 1>
irb(main):002:0> book.destroy
   (0.1ms)  begin transaction
  Book Destroy (1.6ms)  DELETE FROM "books" WHERE "books"."id" = ?  [["id", 2]]
# ここにログが残りました。destroyが完了した後にログが出力されます
Book is deleted: {"id"=>2, "name"=>"We love Lovely Cat", "published_at"=>nil, "price"=>999, "created_at"=>Tue, 14 Dec 2021 08:11:33 UTC +00:00, "updated_at"=>Tue, 14 Dec 2021 08:11:33 UTC +00:00, "publisher_id"=>1}
   (1.5ms)  commit transaction
=> #<Book id: 2, name: "We love Lovely Cat", published_at: nil, price: 999, created_at: "2021-12-14 08:11:33", updated_at: "2021-12-14 08:11:33", publisher_id: 1>

コールバックにオプションをつけて条件を指定することも可能です。:ifというオプションをつけています。価格が5000円以上なら警告のログが出力されるようにしたコールバックを作りました。#{self.attributes}で削除されるデータのハッシュを出力しています。

class Book < ApplicationRecord
	after_destroy :if => :high_price? do
    Rails.logger.warn "Book is high price is deleted: #{self.attributes}"
    Rails.logger.warn "Please check!"
  end

  def high_price?
    price >= 5000
  end
end
irb(main):001:0> book = Book.create(name: 'High price book', price: 10000, publisher: Publisher.find(1))
   (1.7ms)  SELECT sqlite_version(*)
  Publisher Load (0.3ms)  SELECT "publishers".* FROM "publishers" WHERE "publishers"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
   (0.1ms)  begin transaction
  Book Create (1.4ms)  INSERT INTO "books" ("name", "price", "created_at", "updated_at", "publisher_id") VALUES (?, ?, ?, ?, ?)  [["name", "High price book"], ["price", 10000], ["created_at", "2021-12-14 08:50:00.880182"], ["updated_at", "2021-12-14 08:50:00.880182"], ["publisher_id", 1]]
   (1.2ms)  commit transaction
=> #<Book id: 2, name: "High price book", published_at: nil, price: 10000, created_at: "2021-12-14 08:50:00", updated_at: "2021-12-14 08:50:00", publisher_id: 1>
irb(main):002:0> book.destroy
   (5.5ms)  begin transaction
  Book Destroy (0.6ms)  DELETE FROM "books" WHERE "books"."id" = ?  [["id", 2]]
Book is high price is deleted: {"id"=>2, "name"=>"High price book", "published_at"=>nil, "price"=>10000, "created_at"=>Tue, 14 Dec 2021 08:50:00 UTC +00:00, "updated_at"=>Tue, 14 Dec 2021 08:50:00 UTC +00:00, "publisher_id"=>1}
Please check!
   (0.9ms)  commit transaction
=> #<Book id: 2, name: "High price book", published_at: nil, price: 10000, created_at: "2021-12-14 08:50:00", updated_at: "2021-12-14 08:50:00", publisher_id: 1>