コールバックを利用すると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>
save
のコールバックはcreate
とupdate
両方に反応するコールバックです。around_save
などのaround
はアクションの前後に処理を実行するコールバックです。after_initialize
はモデルがインスタンス化したタイミングで実行されます。例:Book.new
やBook.find
など。after_find
などのfindはfirst
、last
、find
、find_by
などのモデルのインスタンスを生成した場合にしか実行されない。コールバックにオプションをつけて条件を指定することも可能です。: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>