forked from ericallam/docrails
-
-
Notifications
You must be signed in to change notification settings - Fork 275
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[8.0.1] active_record_callbacks.mdの更新を訳文に反映
- Loading branch information
Showing
1 changed file
with
65 additions
and
54 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,10 +5,10 @@ Active Record コールバック | |
|
||
このガイドの内容: | ||
|
||
* Active Recordオブジェクトのどのライフサイクルでどのイベントが発生するか | ||
* Active Recordオブジェクトのどのライフサイクルでイベントが発生するか | ||
* それらのイベントに応答するコールバックを登録・実行・スキップする方法 | ||
* リレーション/関連付け/条件付き/トランザクションのコールバックを作成する方法 | ||
* コールバックを再利用するためのオブジェクトを作成する方法 | ||
* コールバックを再利用するために共通の振る舞いをカプセル化するオブジェクトを作成する方法 | ||
|
||
-------------------------------------------------------------------------------- | ||
|
||
|
@@ -194,13 +194,15 @@ end | |
|
||
```irb | ||
irb> user = User.new(name: "", email: "[email protected]", password: "abc123456") | ||
=> #<User id: nil, email: "[email protected]", created_at: nil, updated_at: nil, name: ""> | ||
#=> #<User id: nil, email: "[email protected]", created_at: nil, updated_at: nil, name: ""> | ||
irb> user.valid? | ||
Name titleized to | ||
Validation failed: Name can't be blank | ||
=> false | ||
#=> false | ||
``` | ||
|
||
|
||
[`valid?`]: | ||
https://api.rubyonrails.org/classes/ActiveModel/Validations.html#method-i-valid-3F | ||
[`validate`]: | ||
|
@@ -239,11 +241,12 @@ end | |
|
||
```irb | ||
irb> user = User.create(name: "Jane Doe", password: "password", email: "[email protected]") | ||
Password encrypted for user with email: [email protected] | ||
Password hashed for user with email: [email protected] | ||
Saving user with email: [email protected] | ||
User saved with email: [email protected] | ||
Update Cache | ||
=> #<User id: 1, email: "[email protected]", created_at: "2024-03-20 16:02:43.685500000 +0000", updated_at: "2024-03-20 16:02:43.685500000 +0000", name: "Jane Doe"> | ||
#=> #<User id: 1, email: "[email protected]", created_at: "2024-03-20 16:02:43.685500000 +0000", updated_at: "2024-03-20 16:02:43.685500000 +0000", name: "Jane Doe"> | ||
``` | ||
|
||
#### 作成時のコールバック | ||
|
@@ -277,13 +280,15 @@ end | |
|
||
```irb | ||
irb> user = User.create(name: "John Doe", email: "[email protected]") | ||
User role set to default: user | ||
Creating user with email: [email protected] | ||
User created with email: [email protected] | ||
User welcome email sent to: [email protected] | ||
=> #<User id: 10, email: "[email protected]", created_at: "2024-03-20 16:19:52.405195000 +0000", updated_at: "2024-03-20 16:19:52.405195000 +0000", name: "John Doe"> | ||
#=> #<User id: 10, email: "[email protected]", created_at: "2024-03-20 16:19:52.405195000 +0000", updated_at: "2024-03-20 16:19:52.405195000 +0000", name: "John Doe"> | ||
``` | ||
|
||
|
||
### オブジェクトの更新 | ||
|
||
更新時のコールバックは、**既存の**レコードが背後のデータベースで永続化(保存)されるたびにトリガーされます。これらは、オブジェクトが更新される直前、更新された直後、および更新の直前直後に呼び出されます。 | ||
|
@@ -339,14 +344,16 @@ end | |
|
||
```irb | ||
irb> user = User.find(1) | ||
=> #<User id: 1, email: "[email protected]", created_at: "2024-03-20 16:19:52.405195000 +0000", updated_at: "2024-03-20 16:19:52.405195000 +0000", name: "John Doe", role: "user" > | ||
#=> #<User id: 1, email: "[email protected]", created_at: "2024-03-20 16:19:52.405195000 +0000", updated_at: "2024-03-20 16:19:52.405195000 +0000", name: "John Doe", role: "user" > | ||
irb> user.update(role: "admin") | ||
User role changed to admin | ||
Updating user with email: [email protected] | ||
User updated with email: [email protected] | ||
Update email sent to: [email protected] | ||
``` | ||
|
||
|
||
#### コールバックを組み合わせる | ||
|
||
欲しい振る舞いを実現するには、コールバックを組み合わせて使う必要が生じることがよくあります。たとえば、ユーザーが作成された後に確認メールを送信したいが、そのユーザーが新規で更新されていない場合のみ確認メールを送信したい場合や、ユーザー更新時に重要な情報が変更された場合は管理者に通知したい場合があります。 | ||
|
@@ -376,10 +383,11 @@ end | |
```irb | ||
irb> user = User.create(name: "John Doe", email: "[email protected]") | ||
Confirmation email sent to: [email protected] | ||
=> #<User id: 1, email: "[email protected]", ...> | ||
#=> #<User id: 1, email: "[email protected]", ...> | ||
irb> user.update(email: "[email protected]") | ||
Notification sent to admin about critical info update for: [email protected] | ||
=> true | ||
#=> true | ||
``` | ||
|
||
### オブジェクトの破棄 | ||
|
@@ -431,7 +439,7 @@ end | |
|
||
```irb | ||
irb> user = User.find(1) | ||
=> #<User id: 1, email: "[email protected]", created_at: "2024-03-20 16:19:52.405195000 +0000", updated_at: "2024-03-20 16:19:52.405195000 +0000", name: "John Doe", role: "admin"> | ||
#=> #<User id: 1, email: "[email protected]", created_at: "2024-03-20 16:19:52.405195000 +0000", updated_at: "2024-03-20 16:19:52.405195000 +0000", name: "John Doe", role: "admin"> | ||
irb> user.destroy | ||
Checked the admin count | ||
|
@@ -453,23 +461,23 @@ NOTE: `after_initialize`と`after_find`コールバックには、対応する`b | |
```ruby | ||
class User < ApplicationRecord | ||
after_initialize do |user| | ||
Rails.logger.info("オブジェクトは初期化されました") | ||
Rails.logger.info("You have initialized an object!") | ||
end | ||
|
||
after_find do |user| | ||
Rails.logger.info("オブジェクトが見つかりました") | ||
Rails.logger.info("You have found an object!") | ||
end | ||
end | ||
``` | ||
|
||
```irb | ||
irb> User.new | ||
オブジェクトは初期化されました | ||
You have initialized an object! | ||
=> #<User id: nil> | ||
irb> User.first | ||
オブジェクトが見つかりました | ||
オブジェクトは初期化されました | ||
You have found an object! | ||
You have initialized an object! | ||
=> #<User id: 1> | ||
``` | ||
|
||
|
@@ -478,25 +486,26 @@ irb> User.first | |
[`after_initialize`]: | ||
https://api.rubyonrails.org/classes/ActiveRecord/Callbacks/ClassMethods.html#method-i-after_initialize | ||
|
||
|
||
### `after_touch` | ||
|
||
[`after_touch`][]コールバックは、Active Recordオブジェクトがtouchされるたびに呼び出されます。詳しくはAPIドキュメントの[`touch`](https://api.rubyonrails.org/classes/ActiveRecord/Persistence.html#method-i-touch)を参照してください。 | ||
|
||
```ruby | ||
class User < ApplicationRecord | ||
after_touch do |user| | ||
Rails.logger.info("オブジェクトにtouchしました") | ||
Rails.logger.info("You have touched an object") | ||
end | ||
end | ||
``` | ||
|
||
```irb | ||
user = User.create(name: "Kuldeep") | ||
=> #<User id: 1, name: "Kuldeep", created_at: "2013-11-25 12:17:49", updated_at: "2013-11-25 12:17:49"> | ||
irb> user = User.create(name: "Kuldeep") | ||
#=> #<User id: 1, name: "Kuldeep", created_at: "2013-11-25 12:17:49", updated_at: "2013-11-25 12:17:49"> | ||
irb> user.touch | ||
オブジェクトにtouchしました | ||
=> true | ||
You have touched an object | ||
#=> true | ||
``` | ||
|
||
このコールバックは`belongs_to`と併用できます。 | ||
|
@@ -505,7 +514,7 @@ irb> user.touch | |
class Book < ApplicationRecord | ||
belongs_to :library, touch: true | ||
after_touch do | ||
Rails.logger.info("Bookがtouchされました") | ||
Rails.logger.info("A Book was touched") | ||
end | ||
end | ||
|
||
|
@@ -515,19 +524,19 @@ class Library < ApplicationRecord | |
|
||
private | ||
def log_when_books_or_library_touched | ||
Rails.logger.info("Book/Libraryがtouchされました") | ||
Rails.logger.info("Book/Library was touched") | ||
end | ||
end | ||
``` | ||
|
||
```irb | ||
irb> book = Book.last | ||
=> #<Book id: 1, library_id: 1, created_at: "2013-11-25 17:04:22", updated_at: "2013-11-25 17:05:05"> | ||
#=> #<Book id: 1, library_id: 1, created_at: "2013-11-25 17:04:22", updated_at: "2013-11-25 17:05:05"> | ||
irb> book.touch # book.library.touchがトリガーされる | ||
Bookがtouchされました | ||
Book/Libraryがtouchされました | ||
=> true | ||
irb> book.touch # triggers book.library.touch | ||
A Book was touched | ||
Book/Library was touched | ||
#=> true | ||
``` | ||
|
||
[`after_touch`]: | ||
|
@@ -716,13 +725,13 @@ WARNING: コールバックには、スキップしてはならない重要な | |
[`increment_counter`]: | ||
https://api.rubyonrails.org/classes/ActiveRecord/CounterCache/ClassMethods.html#method-i-increment_counter | ||
[`insert`]: | ||
https://api.rubyonrails.org/classes/ActiveRecord/Persistence/ClassMethods.html#method-i-insert | ||
https://api.rubyonrails.org/classes/ActiveRecord/Relation.html#method-i-insert | ||
[`insert!`]: | ||
https://api.rubyonrails.org/classes/ActiveRecord/Persistence/ClassMethods.html#method-i-insert-21 | ||
https://api.rubyonrails.org/classes/ActiveRecord/Relation.html#method-i-insert-21 | ||
[`insert_all`]: | ||
https://api.rubyonrails.org/classes/ActiveRecord/Persistence/ClassMethods.html#method-i-insert_all | ||
https://api.rubyonrails.org/classes/ActiveRecord/Relation.html#method-i-insert_all | ||
[`insert_all!`]: | ||
https://api.rubyonrails.org/classes/ActiveRecord/Persistence/ClassMethods.html#method-i-insert_all-21 | ||
https://api.rubyonrails.org/classes/ActiveRecord/Relation.html#method-i-insert_all-21 | ||
[`touch_all`]: | ||
https://api.rubyonrails.org/classes/ActiveRecord/Relation.html#method-i-touch_all | ||
[`update_column`]: | ||
|
@@ -734,45 +743,49 @@ WARNING: コールバックには、スキップしてはならない重要な | |
[`update_counters`]: | ||
https://api.rubyonrails.org/classes/ActiveRecord/Relation.html#method-i-update_counters | ||
[`upsert`]: | ||
https://api.rubyonrails.org/classes/ActiveRecord/Persistence/ClassMethods.html#method-i-upsert | ||
https://api.rubyonrails.org/classes/ActiveRecord/Relation.html#method-i-upsert | ||
[`upsert_all`]: | ||
https://api.rubyonrails.org/classes/ActiveRecord/Persistence/ClassMethods.html#method-i-upsert_all | ||
https://api.rubyonrails.org/classes/ActiveRecord/Relation.html#method-i-upsert_all | ||
|
||
コールバックを抑制する | ||
保存を抑制する | ||
--------------------- | ||
|
||
特定のシナリオでは、Railsアプリケーション内で特定のコールバックの実行を一時的に抑制しなければならなくなる場合があります。これは、コールバックを恒久的に無効にせずに、特定の操作で特定のアクションをスキップしたい場合に便利です。 | ||
|
||
Railsは、[`ActiveRecord::Suppressor`モジュール](https://api.rubyonrails.org/classes/ActiveRecord/Suppressor.html)でコールバックを抑制する(suppress)メカニズムを提供しています。コールバックを抑制したいコードブロックをこのモジュールでラップすれば、その特定の操作中はコールバックが実行されないようにできます。 | ||
ある種のシナリオでは、コールバック内でレコードが保存されないように一時的に保存を抑制する必要が生じることがあります。保存の抑制は、レコードの関連付けが複雑にネストしている状況で、コールバックを恒久的に無効にしたり複雑な条件付きロジックを導入したりせずに、特定の操作中にのみ特定のレコードの保存をスキップしたい場合に役立ちます。 | ||
|
||
`User`モデルがあり、新規ユーザーがサインアップした後に「ようこそメール」を送信するコールバックがモデルに含まれているシナリオを考えてみましょう。ただし、ようこそメールを送信せずにユーザーを作成しなければならない場合もあります(データベースにテストデータをシードするときなど)。 | ||
Railsは、[`ActiveRecord::Suppressor`モジュール](https://api.rubyonrails.org/classes/ActiveRecord/Suppressor.html)でコールバックを抑制する(suppress)メカニズムを提供しています。コールバックを抑制したいコードブロックをこのモジュールでラップすると、その操作中はコールバックが実行されなくなります。 | ||
|
||
ユーザーに対してさまざまな通知が行われるシナリオを考えてみましょう。 | ||
以下の`User`を作成すると、`Notification`レコードも自動的に作成されます。 | ||
|
||
```ruby | ||
class User < ApplicationRecord | ||
after_create :send_welcome_email | ||
has_many :notifications | ||
|
||
after_create :create_welcome_notification | ||
|
||
def send_welcome_email | ||
puts "Welcome email sent to #{self.email}" | ||
def create_welcome_notification | ||
notifications.create(event: "sign_up") | ||
end | ||
end | ||
``` | ||
|
||
この例の`after_create`コールバックは、新しいユーザーが作成されるたびに`send_welcome_email`メソッドをトリガーします。 | ||
class Notification < ApplicationRecord | ||
belongs_to :user | ||
end | ||
``` | ||
|
||
ようこそメールを送信せずにユーザーを作成するには、次のように`ActiveRecord::Suppressor`モジュールを利用します。 | ||
ユーザーを作成するときに通知を作成しないようにするには、次のように`ActiveRecord::Suppressor`モジュールを利用します。 | ||
|
||
```ruby | ||
User.suppress do | ||
Notification.suppress do | ||
User.create(name: "Jane", email: "[email protected]") | ||
end | ||
``` | ||
|
||
上記のコードでは、`User.suppress`ブロックによって、"Jane"ユーザーの作成中は`send_welcome_email`コールバックが実行されないようにし、ようこそメールを送信せずにユーザーを作成できるようにしています。 | ||
上のコードは、`Notification.suppress`ブロックにより、ユーザー"Jane"の作成中は `Notification`を保存しなくなります。 | ||
|
||
WARNING: `ActiveRecord::Suppressor`を利用すると、コールバックの実行を選択的に制御できるメリットがある反面、コードが複雑になって思わぬ振る舞いが発生する可能性もあります。コールバックを抑制すると、アプリケーションで意図したフローがわかりにくくなり、今後のコードベースの理解やメンテナンスが困難になる可能性があります。コールバックを抑制した場合の影響の大きさを慎重に検討し、ドキュメント作成やテストを入念に実施して、意図しない副作用やパフォーマンスの問題、テストの失敗のリスクを軽減する必要があります。 | ||
WARNING: `ActiveRecord::Suppressor`を利用すると、コールバックの実行を選択的に制御できるメリットがある反面、コードが複雑になって思わぬ振る舞いが発生する可能性もあります。コールバックを抑制すると、アプリケーションで意図したフローがわかりにくくなり、今後のコードベースの理解やメンテナンスが困難になる可能性があります。`ActiveRecord::Suppressor`を利用した場合の影響の大きさを慎重に検討し、ドキュメント作成やテストを入念に実施して、意図しない副作用やテストの失敗のリスクを軽減する必要があります。 | ||
|
||
コールバックの停止 | ||
コールバックを停止する | ||
----------------- | ||
|
||
モデルに新しくコールバックを登録すると、コールバックは実行キューに入ります。このキューには、あらゆるモデルに対するバリデーション、登録済みコールバック、実行待ちのデータベース操作が置かれます。 | ||
|
@@ -915,12 +928,12 @@ end | |
|
||
```irb | ||
irb> user = User.first | ||
=> #<User id: 1> | ||
#=> #<User id: 1> | ||
irb> user.articles.create! | ||
=> #<Article id: 1, user_id: 1> | ||
#=> #<Article id: 1, user_id: 1> | ||
irb> user.destroy | ||
Article destroyed | ||
=> #<User id: 1> | ||
#=> #<User id: 1> | ||
``` | ||
|
||
WARNING: `before_destroy`コールバックを使う場合は、レコードが`dependent: :destroy`で削除される前に実行されるように、`dependent: :destroy`関連付けの前に配置する(または`prepend: true`オプションを指定する)必要があります。 | ||
|
@@ -934,7 +947,6 @@ WARNING: `before_destroy`コールバックを使う場合は、レコードが` | |
|
||
これらのコールバックは`after_save`コールバックときわめて似ていますが、データベースの変更のコミットまたはロールバックが完了するまでトリガーされない点が異なります。これらのメソッドは、Active Recordのモデルから、データベーストランザクションの一部に含まれていない外部のシステムとやりとりしたい場合に特に便利です。 | ||
|
||
|
||
例として、`PictureFile`モデルで、対応するレコードが削除された後にファイルを1つ削除する必要があるとしましょう。 | ||
|
||
```ruby | ||
|
@@ -961,7 +973,6 @@ end | |
|
||
`after_commit`コールバックを使えば、このような場合に対応できます。 | ||
|
||
|
||
```ruby | ||
class PictureFile < ApplicationRecord | ||
after_commit :delete_picture_file_from_disk, on: :destroy | ||
|