どうもおはようポテト(@ohayoupoteto22)です。
Railsで排他制御ってどうやるん?
ということで今回は
「Railsの排他制御についてざっくり知りたい」
「楽観ロックってなんぞや」
という方に向けてRailsで「lock_versionを使って排他制御(楽観ロック)を行う方法」をまとめました。
初学者の備忘録ゆえ至らない点もあると思いますが参考になれば幸いです⸝⸝- ̫ -⸝⸝
ブログ主
お品書き
そもそも楽観ロックって?
「競合とはなんぞや」という方に向けて、図にするとこんな感じ。
ユーザー二人が同じタイミングで商品の在庫を取得する
右の人が2つ購入して商品の在庫は2つに更新。
左の人が1つ購入して商品の在庫は3つに… ファッ !?
ってな具合です。
右の人が購入し終わってから左の人が商品の在庫数を取得すればこんなことにはならないのですが、同じタイミングで在庫数を取得しているためこのように矛盾が生じてしまいます。
くるみ
それを防ぐのが排他処理であり、今回利用するのはそのうちの一つである「楽観ロック」。
データの参照時には何もせず、更新時に競合しているかチェックしてくれるものです。
ブログ主
楽観ロックの手順
そこまで難しいものではありません。
くるみ
Railsでlock_versionを使って排他制御(楽観ロック)を行う方法
下準備
$ rails new opti_test
まずアプリを作成します。今回は「opti_test」という名前で。
$ cd opti_test
アプリのディレクトリに移動。
$ rails generate scaffold food
便利なScaffolding機能を使ってサクッと簡易なアプリを作ります。
テーブル名は「foods」で。料理を取り扱うモデルです。
参考 rails g scaffoldとは?Qiitalock_versionカラムを定義
class CreateFoods < ActiveRecord::Migration[5.2]
def change
create_table :foods do |t|
t.string :name
t.integer :price
t.integer :lock_version, default: 0
t.timestamps
end
end
end
マイグレーションファイルにてカラムを定義していきます。
今回は料理の名前と値段用のカラム、nameとpriceを定義。さらに今回の楽観ロックの肝となるlock_virsionも定義します。
lock_virsionとは
行のバージョンを管理するためのレコード
更新時に値が異なっていた(他のユーザーによる更新と競合した)場合にエラーを発生してくれる
簡単にまとめるとこんな感じ。
$ rails db:migrate
マイグレーションを実行してあげます。
$ rails c
irb(main):001:0> Food.column_names
=> ["id", "name", "price", "lock_version", "created_at", "updated_at"]
コンソールモードでカラムを確認してあげて、ちゃんと登録されてればおけです。
irb(main):001:0> Food.create(name:"竜田揚げ",price:700)
そしてレコードが一つも無いと何もできないので、適当に一つ作ってあげます。
くるみ
ビューをいじる
<%= form_with(model: food, local: true) do |form| %>
(省略)
<%= form.text_field :name%>
<%= form.number_field :price%>
<%= form.hidden_field :lock_version%>
<%= form.submit("値段を修正する") %>
<%end%>
_form.html.erbに上のように追記してあげます。
nameとpriceに対しては普通にフィールドを設置してあげて、lock_versionはhidden_fieldにして値をコントローラーに受け渡せるようにします。
def food_params
params.require(:food).permit(:name, :price, :lock_version)
end
更にfoods_controller.rbのfood_paramsメソッドに上のように記述してあげれば、パラメーターとして受け渡しができるはずです。
例外処理
def update
begin
(省略)
rescue ActiveRecord::StaleObjectError
render plain:"競合しちゃってるよ"
end
end
次にupdateメソッド内に例外処理を記述します。
「ActiveRecord::StaleObjectError」はまさしく競合が起きた時のエラーで、rescue文でその際にエラー文を表示してあげます。
【Rails】renderメソッドで出来ることまとめ
試してみる
実際に試してみましょう。
まずlocalhost:3000/foodsにアクセス。
すると料理一覧のページで、先ほど追加した唐揚げが(見えないけど)あるので「Edit」をクリック。
これを2つのタブで同時に行ってあげて同じ編集画面を開きます。
ブログ主
エラーメッセージが表示されればおけ!
これで終了です。
くるみ
(ちなみに素のエラー画面はこんな感じ。)
まとめ
以上、lock_versionを使って排他制御(楽観ロック)を行う方法でした。
参考になれば幸いです!では⸝⸝- ̫ -⸝⸝