LoginSignup
4
2

More than 3 years have passed since last update.

Rails6 のちょい足しな新機能を試す77(update_all 編)

Posted at

はじめに

Rails 6 に追加された新機能を試す第77段。 今回は、 update_all 編です。
Rails 6 では、 update_all が楽観的ロックを配慮するようになりました。

Ruby 2.6.3, Rails 6.0.0, Rails 5.2.3 で確認しました。

$ rails --version
Rails 6.0.0

プロジェクトを作る

$ bin/rails new rails_sandbox
$ cd rails_sandbox

今回は、わざと StaleObjectError が発生するような更新処理のメソッドを作って確認します。

User モデルを作る

属性として、 name と age を持つ User モデルを作ります。
楽観的ロックの機能を使うため、 lock_version も追加します。

$ bin/rails g model User name age:integer lock_version:integer

seed データを作成

seed データを作成します。

db/seeds.rb
User.create!(
  [
    { name: 'Taro', age: 10 },
    { name: 'Jiro', age: 10 },
    { name: 'Hanako', age: 10 }
  ]
)

User モデルを修正する

Taro のレコードを検索します。
Taro と Hanako の age を update_all を使って 11 に更新します。
そのあと、StaleObjectError を発生させるために、検索したTaro の age だけ12 に更新します。

app/models/user.rb
class User < ApplicationRecord
  def self.update_age
    taro = User.find_by(name: 'Taro')
    where(name: %w[Taro Hanako]).update_all(age: 11)
    taro.age = 12
    taro.save! # => StaleObjectError
  end
end

seed データの投入

DB マイグレーションを実行しデータを登録します。

bin/rails db:create db:migrate db:seed

Rails Runner で確認する

Rails Runner で User.update_age を実行するとStaleObjectError が発生します。

$ bin/rails runner 'User.update_age'
...
27: from /app/models/user.rb:6:in `update_age'
...
/usr/local/bundle/gems/activerecord-6.0.0/lib/active_record/locking/optimistic.rb:95:in `_update_row': Attempted to update a stale object: User. (ActiveRecord::StaleObjectError)`

Rails Console で確認する

rails console で各レコードを確認すると、 Taro と Hanako の age が 11 に lock_version が 1 になっていることがわかります。 ( update_all は実行されているが、 save! メソッドは実行されていない )

irb(main):001:0> User.all.map{|u| [u.name, u.age, u.lock_version]}
  User Load (0.5ms)  SELECT "users".* FROM "users"
=> [["Jiro", 10, 0], ["Taro", 11, 1], ["Hanako", 11, 1]]

Rails 5 では

Rails 5.2.3 では、エラーは発生せず、最後まで実行されます。
Rails Console で確認すると、Hanako の lock_version は 0 のままで、 Taro の lock_version は、 1 になっています。
update_all で更新したときは、lock_version は変わらず、 save! メソッドで更新したときに lock_version が更新されるためです。

irb(main):001:0> User.all.map{|u| [u.name, u.age, u.lock_version]}
  User Load (0.4ms)  SELECT "users".* FROM "users"
=> [["Jiro", 10, 0], ["Hanako", 11, 0], ["Taro", 12, 1]]

試したソース

試したソースは以下にあります。
https://github.com/suketa/rails_sandbox/tree/try077_update_all

参考情報

4
2
0

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
  3. You can use dark theme
What you can do with signing up
4
2