Rails 3 - Транзакции и Блокировка - PullRequest
1 голос
/ 26 апреля 2011

Я новичок в Rails и имею систему, которая должна обрабатывать транзакции.Пользователь может ввести транзакцию, к которой привязан еще один пользователь.Эти пользователи должны определенную сумму лицу, совершившему транзакцию.Например, Билл может купить обед для 4 друзей, а счет составит 125 долларов.Они решают разделить счет на 5 способов, поэтому каждый должен 25 долларов.Билл вводил в общей сложности 125 долларов и вводил каждого друга (включая себя) как 25 долларов за транзакцию. У меня есть код в моем контроллере и в моей модели для достижения этой цели, но я действительно не знаю, правильно ли я использую транзакции и блокировку.Кроме того, является ли это предполагаемым способом хранения этой информации в контроллере? Я использую транзакцию, поскольку все эти действия должны выполняться вместе или завершаться неудачно (атомарность), и мне нужно заблокировать в случае, если несколько пользователей пытаются отправить одновременновремя (изоляция).Может быть, я должен позволить БД на ручку блокировки бэкэнда?Это уже делает это - скажем, MySQL?Спасибо.

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

1 Ответ

2 голосов
/ 26 апреля 2011

Я бы не стал делать блокировки вручную, поскольку 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
...