LoginSignup
2

More than 1 year has passed since last update.

posted at

updated at

[Rails]has_oneやbelongs_toをリロード!削除されたときの挙動がreload_associationとreloadで違うので注意!

ActiveRecordのアソシエーションは1度取得するとキャッシュしてくれるため、2度目からはSQLが発行されません。
この機能はとても便利なのですが、たまに最新の情報にリロードしたいときがあります。

例えば下記のようにuser.profile.addressが別で更新された場合です。

irb(main):001:0> user = User.first

# profileは初回アクセスなので、SQLが発行される
irb(main):003:0> user.profile.address
  Profile Load (0.9ms)  SELECT `profiles`.* FROM `profiles` WHERE `profiles`.`user_id` = 2 LIMIT 1
=> "住所だよ"

# profileはキャッシュしているのでSQLは発行されない
irb(main):004:0> user.profile.address
=> "住所だよ"

# 別プロセスでprofile.addressを更新
irb(main):005:0> Profile.find(1).update!(address: '住所だよ!!!!!')
  Profile Load (1.6ms)  SELECT `profiles`.* FROM `profiles` WHERE `profiles`.`id` = 1 LIMIT 1
  TRANSACTION (0.7ms)  BEGIN
  User Load (0.9ms)  SELECT `users`.* FROM `users` WHERE `users`.`id` = 2 LIMIT 1
  Profile Update (1.1ms)  UPDATE `profiles` SET `profiles`.`address` = '住所だよ!!!!!', `profiles`.`updated_at` = '2021-10-29 07:44:20.414663' WHERE `profiles`.`id` = 1
  TRANSACTION (3.5ms)  COMMIT
=> true

# キャッシュから取得するため、更新されていない
irb(main):006:0> user.profile.address
=> "住所だよ"

そんなときに2つのやり方があります。

1つ目はActiveRecordのreloadを使います。

reloadを使うと、下記のようにSQLが発行されて最新の情報に更新されます。

irb(main):007:0> user.profile.address
=> "住所だよ"
irb(main):008:0> user.profile.reload
  Profile Load (0.9ms)  SELECT `profiles`.* FROM `profiles` WHERE `profiles`.`id` = 1 LIMIT 1
=> #<Profile:0x000055ba8153a7a0 id: 1, user_id: 2, address: "住所だよ!!!!!", created_at: Wed, 20 Oct 2021 02:12:12.898532000 UTC +00:00, updated_at: Fri, 29 Oct 2021 07:44:20.414663000 UTC +00:00>
irb(main):009:0> user.profile.address
=> "住所だよ!!!!!"

2つ目はアソシエーションを設定したら自動生成されるreload_#{アソシエーション名}です。
has_oneとbelongs_toのどちらにも同様に生成されます。

irb(main):013:0> user.profile.address
=> "住所だよ"
irb(main):014:0> user.reload_profile
  Profile Load (0.9ms)  SELECT `profiles`.* FROM `profiles` WHERE `profiles`.`user_id` = 2 LIMIT 1
=> #<Profile:0x000055ba8206ec48 id: 1, user_id: 2, address: "住所だよ!!!!!", created_at: Wed, 20 Oct 2021 02:12:12.898532000 UTC +00:00, updated_at: Fri, 29 Oct 2021 08:04:07.284792000 UTC +00:00>
irb(main):015:0> user.profile.address
=> "住所だよ!!!!!"

上記の通り、データが存在しているときはどちらを使っても同じように再取得されるのですが、削除されていた場合は挙動が違うので注意が必要です。

reloadを使った場合

irb(main):018:0> Profile.find(1).destroy
  Profile Load (1.1ms)  SELECT `profiles`.* FROM `profiles` WHERE `profiles`.`id` = 1 LIMIT 1
  Profile Destroy (1.1ms)  DELETE FROM `profiles` WHERE `profiles`.`id` = 1

irb(main):019:0> user.profile.reload
  Profile Load (0.8ms)  SELECT `profiles`.* FROM `profiles` WHERE `profiles`.`id` = 1 LIMIT 1
/usr/local/bundle/gems/activerecord-6.1.4.1/lib/active_record/relation/finder_methods.rb:357:in `raise_record_not_found_exception!': Couldn't find Profile with 'id'=1 (ActiveRecord::RecordNotFound)

reload_#{アソシエーション名}を使った場合

irb(main):003:0> Profile.find(1).destroy
  Profile Load (0.7ms)  SELECT `profiles`.* FROM `profiles` WHERE `profiles`.`id` = 1 LIMIT 1
  Profile Destroy (0.9ms)  DELETE FROM `profiles` WHERE `profiles`.`id` = 1

irb(main):004:0> user.reload_profile
  Profile Load (0.8ms)  SELECT `profiles`.* FROM `profiles` WHERE `profiles`.`user_id` = 2 LIMIT 1
=> nil

reloadを使った場合、データが見つからないとActiveRecord::RecordNotFoundが発生します。
一方、reload_#{アソシエーション名}を使った場合はデータが見つからないとnilになります。

削除される可能性があるデータを扱う場合はご注意ください!!

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
2