Skip to content

Commit

Permalink
[8.0.1] active_record_callbacks.mdの更新を訳文に反映
Browse files Browse the repository at this point in the history
  • Loading branch information
hachi8833 committed Dec 30, 2024
1 parent cc43586 commit 853a414
Showing 1 changed file with 65 additions and 54 deletions.
119 changes: 65 additions & 54 deletions guides/source/ja/active_record_callbacks.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ Active Record コールバック

このガイドの内容:

* Active Recordオブジェクトのどのライフサイクルでどのイベントが発生するか
* Active Recordオブジェクトのどのライフサイクルでイベントが発生するか
* それらのイベントに応答するコールバックを登録・実行・スキップする方法
* リレーション/関連付け/条件付き/トランザクションのコールバックを作成する方法
* コールバックを再利用するためのオブジェクトを作成する方法
* コールバックを再利用するために共通の振る舞いをカプセル化するオブジェクトを作成する方法

--------------------------------------------------------------------------------

Expand Down Expand Up @@ -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`]:
Expand Down Expand Up @@ -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">
```

#### 作成時のコールバック
Expand Down Expand Up @@ -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">
```


### オブジェクトの更新

更新時のコールバックは、**既存の**レコードが背後のデータベースで永続化(保存)されるたびにトリガーされます。これらは、オブジェクトが更新される直前、更新された直後、および更新の直前直後に呼び出されます。
Expand Down Expand Up @@ -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]
```


#### コールバックを組み合わせる

欲しい振る舞いを実現するには、コールバックを組み合わせて使う必要が生じることがよくあります。たとえば、ユーザーが作成された後に確認メールを送信したいが、そのユーザーが新規で更新されていない場合のみ確認メールを送信したい場合や、ユーザー更新時に重要な情報が変更された場合は管理者に通知したい場合があります。
Expand Down Expand Up @@ -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
```

### オブジェクトの破棄
Expand Down Expand Up @@ -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
Expand All @@ -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>
```

Expand All @@ -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`と併用できます。
Expand All @@ -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

Expand All @@ -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`]:
Expand Down Expand Up @@ -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`]:
Expand All @@ -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`を利用した場合の影響の大きさを慎重に検討し、ドキュメント作成やテストを入念に実施して、意図しない副作用やテストの失敗のリスクを軽減する必要があります

コールバックの停止
コールバックを停止する
-----------------

モデルに新しくコールバックを登録すると、コールバックは実行キューに入ります。このキューには、あらゆるモデルに対するバリデーション、登録済みコールバック、実行待ちのデータベース操作が置かれます。
Expand Down Expand Up @@ -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`オプションを指定する)必要があります。
Expand All @@ -934,7 +947,6 @@ WARNING: `before_destroy`コールバックを使う場合は、レコードが`

これらのコールバックは`after_save`コールバックときわめて似ていますが、データベースの変更のコミットまたはロールバックが完了するまでトリガーされない点が異なります。これらのメソッドは、Active Recordのモデルから、データベーストランザクションの一部に含まれていない外部のシステムとやりとりしたい場合に特に便利です。


例として、`PictureFile`モデルで、対応するレコードが削除された後にファイルを1つ削除する必要があるとしましょう。

```ruby
Expand All @@ -961,7 +973,6 @@ end

`after_commit`コールバックを使えば、このような場合に対応できます。


```ruby
class PictureFile < ApplicationRecord
after_commit :delete_picture_file_from_disk, on: :destroy
Expand Down

0 comments on commit 853a414

Please sign in to comment.