title: Rails 3 - Transactions and Locking
body: <p>I am new to Rails and have a system that needs to process transactions.  A user can enter a transaction to which one more users are tied.  These users owe some amount of money to the person making the transaction.  For example, Bill might buy lunch for 4 friends and the bill is $125.  They decide to split the bill 5 ways, so each owes $25.  Bill would enter a total of $125 and enter each friend (including himself) as owing $25 to the transaction.  <strong>I have code in my controller and in my model to accomplish this goal, but I don't really know if I am using transactions and locking correctly.  Also, is this the entended way to have this information in the controller?</strong>  I am using a transaction since all of these actions must occur together or fail (atomicity) and I need locking in case multiple users try to submit at the same time (isolation).   Maybe I should let the db on the backend handle locking?  Does it do that already - say, MySQL?  Thanks.</p>
  <h1>trans_controller.rb</h1>
  <pre><code>class TransController &lt; ApplicationController
      # POST trans/
      def create
          @title = &quot;Create Transaction&quot;
          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
  </code></pre>
  <p>tran.rb</p>
  <pre><code>class Tran &lt; ActiveRecord::Base
      has_many :transaction_users, :dependent =&gt; :destroy, :class_name =&gt; 'TransactionUser'
      belongs_to :submitting_user, :class_name =&gt; 'User'
      belongs_to :buying_user, :class_name =&gt; 'User'
      
      accepts_nested_attributes_for :transaction_users, :allow_destroy =&gt; true
  
      validates :description, :presence =&gt; true,
                              :length =&gt; {:maximum =&gt; 100 }
      #validates :total,      :presence =&gt; true
      validates_numericality_of :total, :greater_than =&gt; 0
      
      validates :submitting_user_id,      :presence =&gt; true               
      validates :buying_user_id,          :presence =&gt; 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 =&gt; -1 * self.total, :user_id =&gt; self.buying_user_id, :tran =&gt; self)
          #if buying_tran_user.valid?
          #   raise &quot;Error&quot;
          #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, &quot;A transcation must have transaction users.&quot; 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, &quot;The transcation items do not sum to the total of the transaction.&quot; 
          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, &quot;The participant #{key.user.full_name} has been listed more than once.&quot;
              end
          
              user_array &lt;&lt; key.user.email
          end
      end 
  end
  </code></pre>