kjs3
kjs3

Reputation: 6178

Rails transaction not rolling back

Why wouldn't this transaction rollback if even one create! fails? It's in a controller if that makes any difference.

def process_photos(photos)
  ActiveRecord::Base.transaction do
    begin
      photos.each do |photo|
        Photo.create!(creator_user: @user, buyer: @buyer, url: photo['url'])
      end
    rescue
      raise ActiveRecord::Rollback
    end
  end
end

I'm explicitly sending an array with some bad records. The good ones are getting created and the bad ones aren't but I need the whole thing to rollback if even one fails.

Here's my rspec test (passing a json array of photos) and it's incrementing.

expect { post :create, json }.not_to change(Photo, :count)

Upvotes: 13

Views: 7315

Answers (3)

Andrei Erdoss
Andrei Erdoss

Reputation: 1643

This works as well, without the need to propagate the ActiveRecord::Rollback exception.

def process_photos(photos)
  begin
    ActiveRecord::Base.transaction do
      photos.each do |photo|
        Photo.create!(creator_user: @user, buyer: @buyer, url: photo['url'])
      end
    end
  rescue ActiveRecord::RecordInvalid
    puts 'Transaction failed'
  end
end

Upvotes: 0

kjs3
kjs3

Reputation: 6178

This was just a testing issue with rspec because you're already executing in a transaction in the test. It's the same nested transaction issue discussed here. In order to get the test to work you need to add…

requires_new: true

to the transaction call.

This fixed the test.

def process_photos(photos)
  ActiveRecord::Base.transaction(requires_new: true) do
    begin
      photos.each do |photo|
        Photo.create!(creator_user: @user, buyer: @buyer, url: photo['url'])
      end
    rescue
      raise ActiveRecord::Rollback
    end
  end
end

Important: Rollbacks will only work if your database engine supports them! For example MySQL with MyISAM doesn't support transactions, while MySQL with Inno DB does support them.

Upvotes: 18

Rahul
Rahul

Reputation: 1

You need to put begin outside the block like this

def process_photos(photos)
 begin
      ActiveRecord::Base.transaction do
          photos.each do |photo|
            Photo.create!(creator_user: @user, buyer: @buyer, url: photo['url'])
          end
        rescue
          raise ActiveRecord::Rollback
        end
      end
    end

Upvotes: -3

Related Questions