トランザクションブロックって便利

トランザクションブロックとは

「プログラミング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