トランザクションブロックとは
「プログラミングRuby 第2版 言語編」に、トランザクションブロックというものがのっていました。
- 「プログラミングRuby 第2版 言語編」(p. 46)
ブロックは、何からのトランザクション制御の下で実行する必要がある一連のコードを定義するときにも使うことができます。
ふむふむ。こういう使い方はしていましたが、「トランザクションブロック」という言い方もあるんですね。
トランザクションブロックの簡単な例
本で紹介されていた例を引用します。
class File def File.open_and_process(*args) f = File.open(*args) yield f f.close end end File.open_and_process("testfile", "r") do |file| while line = file.gets puts line end end
このように書くと、「File.open_and_process」によって開いたファイルは、ブロック内の処理が実行された後、勝手にclose されることが保証されます。(厳密にはbegen〜ensureなどを使うことになりますが)
このブロック内の処理に加えて、ファイルのclose 処理までを行うことから、トランザクションブロックという呼び方がされているのでしょう。
関連する複数の処理を一つの処理単位にまとめて管理する処理方式。複数の作業を連結した処理単位を「トランザクション」という。
トランザクションブロックの他の例
ここで紹介しているfreeze.rb を使うと、以下のように書いた際に、そのブロック内では、時間がとまる(Time.now)が一定の時間を返すようになります。
Time.freeze_time do assert @user.save assert_equal Time.now, @user.reload.updated_at end
この、freeze_time の実装は次のようになっています。
def freeze_time(frozen_time = 946702800) self.instance_eval do frozen_now = (frozen_time) alias :original_now :now alias :now :frozen_now end if block_given? begin yield ensure unfreeze_time end end end
これによって、Time.freeze_time の終了後には、Time.now が通常に動作することが保証されます。
他にもブロック使うと便利だという例
トランザクションブロックとは呼べないですがブロックの使用例をもう2つ。エラー処理等で、正常だったらブロック内の処理を行い、エラーになったら一定の動作を行うというもの。
- Jpmobile を使った携帯アプリケーションの例
with_ident 内では、個体識別番号がとれた時の処理を書く。とれなかったら、エラーメッセージを出す。引数を指定しない場合は、デフォルトのビュー(この場合はlogin.rhtml)が呼ばれる。
def login with_ident(:redirect_to => {:action => 'index'}) do |ident| if user = User.authenticate(ident) session[:user_id] = user.id redirect_to :controller => 'user', :action => 'index', :id => user else flash[:error] = 'ユーザー登録されていません' redirect_to :action => 'index' end end end def with_ident(action_with_errors = {}) if ident = request.mobile.ident yield(ident) else msg = <<-EOS 個体識別番号を確認できませんでした。個体識別番号を送信する設定に変更して下さい。 EOS if action_with_errors[:redirect_to] flash[:error] = msg redirect_to action_with_errors[:redirect_to] else flash.now[:error] = msg render action_with_errors[:render] if action_with_errors[:render] end end end
- エラー表示用のページを用意して、エラー時にはそこに飛ばすという例
毎回、begin〜rescue書かなくていい。
def show error_handler('指定したユーザーは存在しません', :rescue => ActiveRecord::RecordNotFound) do @user = User.find(params[:id]) end end def error_handler(*args) options = args.last.is_a?(Hash) ? args.pop : {} @message = args.first || '無効な操作が行われました' if block_given? begin yield rescue options[:rescue] || StandardError render :template => 'common/error' end else render :template => 'common/error' end end