Posted at

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


はじめに

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


参考情報