Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails nested transactions

I'm trying to make rails application, and I got stacked with this issue. I have nested transactions and I though they had been working. But, I found out that they don't work properly.

Here's UserService

    class UserService
    def self.reduce_balance!(user, delta, attachmentable)
      ActiveRecord::Base.transaction do
        user.balance.amount -= delta
        balance_decrease = BalanceDecrease.new(user: user, attachmentable: attachmentable, balance_before: user.balance.amount_was, amount: delta)
        user.balance.save!
        balance_decrease.save!
      end
      return true
    end
    end

There's GameService

    class GameService
      def self.take_spot(user, spot)
        if UserService.have_enough_money? user, game.spot_price
          begin
            ActiveRecord::Base.transaction do
              spot.free = false
              spot.user = user
              spot.save!
              UserService.reduce_balance!(user, game.spot_price, spot)
              return res = {success: true, json: spot, status: 200}
            end
          rescue => e
            return res = {json: {error: 1, error_message: "Something gone wrong"}, status: 500}
          end
        else
          return res = {json: {error: 1, error_message: "You don't have enough money"}, status: 400}
        end
      end
    end

And I have my controller which is

    def update
      @res = GameService.take_spot current_user, @spot
      return render json: @res[:json], status: @res[:status] unless @res[:success]
    end

And somehow 1 of 1700 queries gone wrong: spot had been bought and balance didn't reduce. This is picture of json

The json file(there's picture of it below):

{
"id": 1759,
"type": "BalanceDecrease",
"balance_before": "950.00",
"amount": "10.00",
"spot": {
"id": 1965,
"number": 7,
"free": false,
"game": {
"id": 19,
"name": "M249 | Блокировка",
"img_url": "(img url)",
"stattrak": false,
"spot_price": "10.00",
"state": "ended",
"created_at": "2015-11-26T23:20:28.081+03:00",
"updated_at": "2015-11-26T23:20:59.406+03:00"
},
"created_at": "2015-11-26T23:20:28.087+03:00",
"updated_at": "2015-11-26T23:20:57.270+03:00"
}
},
{
"id": 1758,
"type": "BalanceDecrease",
"balance_before": "950.00",
"amount": "10.00",
"spot": {
"id": 1964,
"number": 6,
"free": false,
"game": {
"id": 19,
"name": "M249 | Блокировка",
"img_url": "(img url)",
"stattrak": false,
"spot_price": "10.00",
"state": "ended",
"created_at": "2015-11-26T23:20:28.081+03:00",
"updated_at": "2015-11-26T23:20:59.406+03:00"
},
"created_at": "2015-11-26T23:20:28.087+03:00",
"updated_at": "2015-11-26T23:20:57.282+03:00"
}
}

I have 10$ DigitalOcean droplet. Could it be the problem?

like image 536
Gleb Vishnevsky Avatar asked Dec 15 '22 10:12

Gleb Vishnevsky


2 Answers

ActiveRecord::Rollback does not propagate outside of the containing transaction block and so the parent transaction does not receive the exception nested inside the child.

To ensure a rollback is received by the parent transaction you must add the requires_new: true option to the child transaction.

Check out docs for more details.

So applying it to your case, you should have the user_service.rb as follows:

class UserService
  def self.reduce_balance!(user, delta, attachmentable)
    ActiveRecord::Base.transaction(requires_new: true) do
      # code omitted
    end
  end
end
like image 73
Andrey Deineko Avatar answered Jan 01 '23 12:01

Andrey Deineko


Your issues stems from the way you use transactions.

Whenever you nest them you have to use requires_new: true

ActiveRecord::Base.transaction do 
  ActiveRecord::Base.transaction(requires_new: true) do 

  end
end

Its explained well in the doc.

Btw, it doesnt hurt to always use requires_new: true.


Sidenote, you seem to use service objects and chain them, maybe this would interest you.

like image 22
apneadiving Avatar answered Jan 01 '23 13:01

apneadiving