Транзакции и блокировки в рельсы 3


Я новичок в рельсы и есть система, которая должна обрабатывать транзакции.

Пользователь может ввести проводку в которых один или более пользователей привязаны. Эти пользователи должны определенную сумму денег лицу, делая сделки. Например, законопроект может купить обед для 4-х друзей и законопроект составляет $125. Они решают разделить счет 5 способов, поэтому каждый должен $25. Законопроект предусматривает ввести в общей сложности $125 и заходят друг на друга (в том числе себя), как из-за $25 до сделки.

У меня есть код в моем контроллере и в моей модели, чтобы достичь этой цели, но я действительно не знаю, если я использую транзакции и правильно фиксировать. Кроме того, это способ, чтобы иметь эту информацию в контроллер?

Я использую транзакции, поскольку все эти действия должны произойти одновременно или плохо (атомарность) и мне нужен замок в случае, если несколько пользователей пытаются представить одновременно (изоляции).

Может быть, я должен позволить БД на сервере ручка замок? Это что уже - скажем, MySQL?

trans_controller.РБ

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

Тран.РБ

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


3727
5
задан 24 апреля 2011 в 10:04 Источник Поделиться
Комментарии
1 ответ

Я думаю, что это плохая практика, чтобы использовать транзакции в контроллерах.
Вы должны добавить метод на Тран модель (например, save_and_update_user_balances):

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?)
if(@tran.save_and_update_user_balances)
redirect_to trans_path, success: 'Transaction was successfully created.'
rescue
flash.now[:error] = @tran.errors.full_messages.to_sentence
render 'new'
end
end

В Тран.РБ:

def save_and_update_user_balances
transaction do
begin
save!
update_user_balances!
true
rescue
false
end
end
end

В update_user_balances! использовать update_attributes! и силы на провал (и отката), если происходит ошибка.

Кроме того, поскольку update_user_balances! вызывается внутри транзакции, вам не нужно создавать другой сделки, или замок. Это из-за способа и ActiveRecord работ:


Хотя метод класса transaction вызывается на некоторых активной записи класса, объекты внутри блока транзакции нужны не все экземпляры этого класса. Это потому, что транзакции в базе данных подключений, а не модель.

Поэтому метод должен быть:

def update_user_balances!
self.buying_user.update_attributes! current_balance: self.buying_user.current_balance - self.total

# Add an offsetting transaction_user for this record
# Note: You could improve this by using a relationship to create it, like this?
# transaction_users.create!(amount: -1 * self.total, user_id: buying_user_id)
TransactionUser.create!(:amount => -1 * self.total, :user_id => self.buying_user_id, :tran => self)

# Loop through each transaction user and update their balances.
transaction_users.each do |tu|
tu.user.update_attributes! current_balance: tu.user.current_balance + tu.amount
end
end

Дополнительная справка: Ruby на Rails документации по сделкам.

3
ответ дан 25 февраля 2014 в 10:02 Источник Поделиться