2011-04-26 92 views
1

我是Rails的新手,並且有一個需要處理事務的系統。用戶可以輸入另一個用戶綁定到的事務。這些用戶欠交易人的一定數量的金錢。例如,比爾可能爲4位朋友購買午餐,賬單爲125美元。他們決定以5種方式分割賬單,所以每個人都欠25美元。比爾將總共輸入125美元,並將每個朋友(包括他自己)輸入交易25美元。 我在我的控制器和我的模型中有代碼來完成這個目標,但我不知道我是否正在使用事務並正確鎖定。另外,這是在控制器中擁有這些信息的有效方式嗎?我正在使用一個事務,因爲所有這些操作必須一起發生或失敗(原子性),並且我需要鎖定以防多個用戶同時嘗試提交(隔離)。也許我應該讓後端的數據庫處理鎖定?它是否已經這樣做 - 比如說MySQL?謝謝。Rails 3 - 交易和鎖定

trans_controller.rb

class TransController < ApplicationController 
    # POST trans/ 
    def create 
     @title = "Create Transaction" 
     trans_successful = false 

     # Add the transaction from the client 
     @tran = Tran.new(params[:tran]) 

     # Update the current user 
     @tran.submitting_user_id = current_user.id 

     # Update the data to the database 
     # This call commits the transaction and transaction users 
     # It also calls a method to update the balances of each user since that isn't 
     # part of the regular commit (why isn't it?) 
     begin 
      @tran.transaction do 
       @tran.save! 
       @tran.update_user_balances 
       trans_successful = true 
      end 
     rescue 

     end 

     # Save the transaction 
     if trans_successful 
      flash[:success] = 'Transaction was successfully created.' 
      redirect_to trans_path 
     else 
      flash.now[:error] = @tran.errors.full_messages.to_sentence   
      render 'new' 
     end 
    end 

tran.rb

class Tran < ActiveRecord::Base 
    has_many :transaction_users, :dependent => :destroy, :class_name => 'TransactionUser' 
    belongs_to :submitting_user, :class_name => 'User' 
    belongs_to :buying_user, :class_name => 'User' 

    accepts_nested_attributes_for :transaction_users, :allow_destroy => true 

    validates :description, :presence => true, 
          :length => {:maximum => 100 } 
    #validates :total,  :presence => true 
    validates_numericality_of :total, :greater_than => 0 

    validates :submitting_user_id,  :presence => true    
    validates :buying_user_id,   :presence => true 

    #validates_associated :transaction_users 

    validate :has_transaction_users? 
    validate :has_correct_transaction_user_sum? 
    validate :has_no_repeat_users? 

    def update_user_balances 
     # Update the buying user in the transaction 
     self.buying_user.lock! 
     # Update the user's total, since they were in the transction 
     self.buying_user.update_attribute :current_balance, self.buying_user.current_balance - self.total 
     # Add an offsetting transaction_user for this record 
     buying_tran_user = TransactionUser.create!(:amount => -1 * self.total, :user_id => self.buying_user_id, :tran => self) 
     #if buying_tran_user.valid? 
     # raise "Error" 
     #end 

     # Loop through each transaction user and update their balances. Make sure to lock each record before doing the update. 
     self.transaction_users.each do |tu| 
      tu.user.lock! 
      tu.user.update_attribute :current_balance, tu.user.current_balance + tu.amount 
     end 
    end 

    def has_transaction_users? 
     errors.add :base, "A transcation must have transaction users." if self.transaction_users.blank? 
    end 

    def has_correct_transaction_user_sum? 
     sum_of_items = 0; 

     self.transaction_users.inspect 
     self.transaction_users.each do |key| 
      sum_of_items += key.amount if !key.amount.nil? 
     end 

     if sum_of_items != self.total 
      errors.add :base, "The transcation items do not sum to the total of the transaction." 
     end 
    end 

    def has_no_repeat_users? 
     user_array = [] 
     self.transaction_users.each do |key| 
      if(user_array.include? key.user.email) 
       errors.add :base, "The participant #{key.user.full_name} has been listed more than once." 
      end 

      user_array << key.user.email 
     end 
    end 
end 

回答

2

我會避免做人工鎖作爲MySQL將處理必要的行級的交易中正確鎖定。在這種情況下使用交易是正確的。我會避免的是創建一個局部變量來跟蹤交易是否完成沒有錯誤:

def create 
    @title = "Create Transaction" 

    # Add the transaction from the client 
    @tran = Tran.new(params[:tran]) 

    # Update the current user 
    @tran.submitting_user_id = current_user.id 

    # Update the data to the database 
    # This call commits the transaction and transaction users 
    # It also calls a method to update the balances of each user since that isn't 
    # part of the regular commit (why isn't it?) 
    begin 
     @tran.transaction do 
      @tran.save! 
      @tran.update_user_balances 
      trans_successful = true 
     end 
    rescue 
     flash.now[:error] = @tran.errors.full_messages.to_sentence   
     render 'new' 
    else 
     flash[:success] = 'Transaction was successfully created.' 
     redirect_to trans_path 
    end 
end