How to rollback all recent transactions in Rails

Viewed 932

I'm writing my first Ruby on Rails application, and I'm having trouble managing record associations overall. In this question, specifically, I'd like to ask the community for suggestions on how to rollback recent saves to the database, based on the code below.

def create
    @aula = Aula.create!(:name => aula_params[:name], :block_duration => aula_params[:block_duration], :rest_time => aula_params[:rest_time], :user_id => @current_user.id)

    if @aula.valid?
      JSON.parse(aula_params[:blocks]).each do |id, b|
        @block = @aula.blocks.create!(:name => b["name"], :sequence_number => id)

        if @block.valid?
          b["workouts"].each do |id, w|
            @block.block_workouts.create!(:workout_id => w["id"], :duration => w["duration"], :sequence_number => id, :repetitions_number => w["repetitions_number"])
          end
        else
          render :json => { message: "Não foi possível criar essa aula", status: 500 }
        end
      end

      render :json => { message: "Aula criada com sucesso", status: 200 }
    else
      render :json => { message: "Não foi possível criar essa aula", status: 500 }
    end
  end

This piece of code basically creates an "aula", that consists of multiple "blocks", that in turn are composed by multiple "workouts".

But, the problem is: suppose that the creation of a specific workout fails. In this case, the "aula" that was just created (and so the blocks and its related workouts) should be deleted. But I can't find a way of doing this in a concise, elegant manner.

2 Answers

It's called transactions, if one thing fail on creation, all records will rollback https://api.rubyonrails.org/classes/ActiveRecord/Transactions/ClassMethods.html

Transactions are protective blocks where SQL statements are only permanent if they can all succeed as one atomic action.

So in your case, you need to wrap your code into Rails transaction, something like this:

Aula.transaction do
  @aula = Aula.create!(...)

end

remember to use create! with exclamation mark to raise an error in case of failure

First thing, as you are creating objects of multiple classes in that block so I would suggest using ActiveRecord::Base.transaction over Aula.transaction to not be specific to that single class.

In the following code snippet, use only one aula = @current_user... line on the basis of your associations.

def create
  ActiveRecord::Base.transaction do
    # Use only any one of below lines
    aula = @current_user.aulas.create!(aula_params) # if user has_many aulas
    aula = @current_user.create_aula!(aula_params) # if user has_one Paula

    JSON.parse(params[:aula][:blocks]).each do |id, b|
      block = aula.blocks.create!(name: b['name'], sequence_number: id)

      block.save!
      b['workouts'].each do |s_id, w|
        block.block_workouts.create!(workout_id: w['id'],
                                     duration: w['duration'],
                                     sequence_number: s_id,
                                     repetitions_number: w['repetitions_number'])
      end
    end

    render json: { message: 'Aula criada com sucesso', status: 200 }
  end
rescue ActiveRecord::RecordInvalid
  render json: { message: 'Não foi possível criar essa aula', status: 500 }
end

def aula_params
  params.require(:aula).permit(:name, :block_duration, :rest_time)
end

I think this should do the trick.

Related