はじめに
Rails でアプリを開発していて、複数テーブルのデータを同時に作成する、という処理を実装していた。
その処理において、どちらかの処理が失敗した場合に双方の処理をなかったことにする transaction という概念があることを知ったので備忘録兼アウトプットとして、 transaction についておよび、実装した内容について書く。
transaction とは
「transaction」という言葉の定義を調べると
【処理、取り扱い、処置、業務、取引、売買、会報、紀要、議事録】 このように出てくる。
ITやプログラミングにおいては
「複数の処理を一つにまとめた分割不可能なもの」
と定義づけることが出来そうです。
こちらの記事がトランザクションの概念について非常にわかりやすいくまとめられており、参考にさせていただきました。
transaction メソッド使い方
- 複数の処理を一つのまとまった処理として扱う
- 一つでも処理に失敗した場合は、トランザクション全体として失敗となる
- 失敗した場合は、全ての処理がなかったこととなる
基本的な構文としてはこうなる
モデル.transaction do
# テーブルへのアクセス処理
# テーブルへのアクセス処理
end
# トランザクション処理が成功した場合の処理
rescue => e
# トランザクション処理が失敗した場合の処理
注意点としては「処理失敗時には例外を発生させるメソッドを使用する」こと。
トランザクションは、含まれる処理のいずれか1つでも失敗すると、トランザクション内のすべての処理をなかったことにするが、そのなかったことにする条件が「例外の発生」となる。
では実際に実装してみる。
実装
今回開発していたアプリにはユーザーのグループ化機能があった。
そこで、グループを新規作成するときにグループ作成をしたユーザーは自動的にグループに所属するユーザーとなる、という仕様を想定。
そこでグループ作成をした際に、思わぬ動作が起きてしまうことを避けるためにトランザクションの利用を検討。
groupsテーブルインスタンスを作成すると同時に、ログインユーザーのidをuser_idカラムとする group_usersテーブル(usersテーブルとgroupsテーブルの中間テーブル)が作成されるようにしたかった。
- groupsテーブルインスタンスの作成
- group_usersテーブルインスタンスの作成
この 1, 2 のうちどちらかの処理が何らかの理由により失敗した場合、双方の処理がなかったこととなるように groups コントローラの create アクションで transaction を活用する。
# createアクション部分を抜粋
# 変数current_userにはログインユーザーのインスタンスが格納
def create
@group = Group.new(group_params)
# トランザクションを適用(グループの作成と中間テーブルを同時作成)
# save! と create! と「!」がついている点に注意!
@group.transaction do
@group.save!
current_user.group_users.create!(group_id: @group.id, permission: true)
end
# トランザクション成功時の処理
flash[:success] = '新しいグループを作成しました'
redirect_to @group
rescue => e
# トランザクション失敗時の処理
flash.now[:danger] = 'グループ作成に失敗しました'
render :new
end
この transaction 内での処理には両方とも「!」がついており、テーブルデータ作成失敗時には例外が発生するメソッドを使っていることに注意!
仮に transaction を利用せずに以下ように実装した場合は、何らかの理由で group_usersテーブルの作成が失敗した場合に無人のグループが作成されてしまう懸念がある。
# transactionを利用しない場合
def create
@group = Group.new(group_params)
if @group.save
current_user.group_users.create(group_id: @group.id, permission: true)
flash[:success] = '新しいグループを作成しました'
redirect_to @group
else
flash.now[:danger] = 'グループ作成に失敗しました'
render :new:
end
end
最後に
記事を読んでいただきありがとうございます!
今回調べながら何とか実装してみたのですが、正直トランザクションについての理解はそこまで深くないです。誤りやより良い記述方法などがあれば、気軽にコメント等いただければ幸いです。
下記の記事、参考にさせていただきました。ありがとうございました。