Rails 3 - Transaksi dan Penguncian

Saya baru mengenal Rails dan memiliki sistem yang perlu memproses transaksi. Seorang pengguna dapat memasukkan transaksi yang terikat dengan satu pengguna lagi. Pengguna ini berhutang sejumlah uang kepada orang yang melakukan transaksi. Misalnya, Bill mungkin membeli makan siang untuk 4 orang temannya dan tagihannya adalah $125. Mereka memutuskan untuk membagi tagihannya menjadi 5 cara, sehingga masing-masing berhutang $25. Bill akan memasukkan total $125 dan memasukkan setiap teman (termasuk dirinya sendiri) sebagai utang $25 atas transaksi tersebut. Saya memiliki kode di pengontrol dan model saya untuk mencapai tujuan ini, namun saya tidak begitu tahu apakah saya menggunakan transaksi dan mengunci dengan benar. Selain itu, apakah ini cara yang dimaksudkan untuk memasukkan informasi ini ke dalam pengontrol? Saya menggunakan transaksi karena semua tindakan ini harus terjadi bersamaan atau gagal (atomisitas) dan saya perlu mengunci jika ada banyak pengguna yang mencoba mengirimkan di waktu yang sama (isolasi). Mungkin saya harus membiarkan db di pegangan backend terkunci? Apakah ia sudah melakukan hal itu - katakanlah, MySQL? Terima kasih.

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

trans.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

person skaz    schedule 26.04.2011    source sumber


Jawaban (1)


Saya akan menghindari melakukan kuncian secara manual karena mysql akan menangani penguncian tingkat baris yang diperlukan dengan benar di dalam transaksi. Penggunaan transaksi sudah benar dalam kasus ini. Apa yang saya hindari adalah membuat variabel lokal untuk melacak apakah transaksi selesai tanpa kesalahan:

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
person Wes    schedule 26.04.2011