Kevin Custer
Kevin Custer

Reputation: 576

Ruby on Rails before_save is causing an update directly after creating a new record

I have added a "before_save" to my model to apply some logic to my model before saving. When I use this code, the record is created, then immediately updated (with the incorrect value). If I comment it out, there is no subsequent update when I create a new record.

Model

class Transaction < ApplicationRecord
belongs_to :account

attr_accessor :trx_type

before_save do
    if self.trx_type == "debit"
        self.amount = self.amount * -1
    end
end

end

Controller

class TransactionsController < ApplicationController
before_action :find_account
before_action :find_transaction, only: [:edit, :update, :show, :destroy]

# Index action to render all transactions
def index
    @transactions = @account.transactions

    respond_to do |format|
        format.html # index.html.erb
        format.xml  { render :xml => @transactions }
    end
end

# New action for creating transaction
def new
    @transaction = @account.transactions.build

    respond_to do |format|
      format.html # new.html.erb
      format.xml  { render :xml => @transaction }
    end
end

# Create action saves the trasaction into database
def create
    @transaction = @account.transactions.create(transaction_params)

    respond_to do |format|
        if @transaction.save
            format.html { redirect_to([@transaction.account, @transaction], :notice => 'Transaction was successfully created.') }
            format.xml  { render :xml => @transaction, :status => :created, :location => [@transaction.account, @transaction] }
        else
            format.html { render :action => "new" }
            format.xml  { render :xml => @transaction.errors, :status => :unprocessable_entity }
        end
    end
end

# Edit action retrieves the transaction and renders the edit page
def edit
end

  # Update action updates the transaction with the new information
def update
    respond_to do |format|
        if @transaction.update_attributes(transaction_params)
            format.html { redirect_to([@transaction.account, @transaction], :notice => 'Transaction was successfully updated.') }
            format.xml  { head :ok }
        else
            format.html { render :action => "edit" }
            format.xml  { render :xml => @transaction.errors, :status => :unprocessable_entity }
        end
    end
end

# The show action renders the individual transaction after retrieving the the id
def show
    respond_to do |format|
        format.html # show.html.erb
        format.xml  { render :xml => @transaction }
    end
end

# The destroy action removes the transaction permanently from the database
def destroy
    @transaction.destroy

    respond_to do |format|
        format.html { redirect_to(account_transactions_url) }
        format.xml  { head :ok }
    end
end

private

def transaction_params
    params.require(:transaction).permit(:trx_date, :description, :amount, :trx_type)
end

def find_account
    @account = current_user.accounts.find(params[:account_id])
end

def find_transaction
    @transaction = @account.transactions.find(params[:id])
end
end

Console Output

Started POST "/accounts/1/transactions" for 127.0.0.1 at 2018-03-20 13:59:37 -0400
Processing by TransactionsController#create as HTML
  Parameters: {"utf8"=>"✓", "authenticity_token"=>"kURRN8FaHmjrDU7y5cikBLREGZdMgHm4PsVUcOHxn7MAlqmi2zolA0LYOKQ46JkTzXl+Fkgj1O6SlBhVjdM5Qw==", "transaction"=>{"trx_type"=>"debit", "trx_date(1i)"=>"2018", "trx_date(2i)"=>"3", "trx_date(3i)"=>"20", "description"=>"Test 10", "amount"=>"132"}, "commit"=>"Create Transaction", "account_id"=>"1"}
  User Load (0.5ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = $1 ORDER BY "users"."id" ASC LIMIT $2  [["id", 1], ["LIMIT", 1]]
  Account Load (0.5ms)  SELECT  "accounts".* FROM "accounts" WHERE "accounts"."user_id" = $1 AND "accounts"."id" = $2 LIMIT $3  [["user_id", 1], ["id", 1], ["LIMIT", 1]]
   (0.2ms)  BEGIN
  SQL (0.6ms)  INSERT INTO "transactions" ("trx_date", "description", "amount", "account_id", "created_at", "updated_at") VALUES ($1, $2, $3, $4, $5, $6) RETURNING "id"  [["trx_date", "2018-03-20"], ["description", "Test 10"], ["amount", "-132.0"], ["account_id", 1], ["created_at", "2018-03-20 13:59:37.349781"], ["updated_at", "2018-03-20 13:59:37.349781"]]
   (3.5ms)  COMMIT
   (0.1ms)  BEGIN


  SQL (0.3ms)  UPDATE "transactions" SET "amount" = $1, "updated_at" = $2 WHERE "transactions"."id" = $3  [["amount", "132.0"], ["updated_at", "2018-03-20 13:59:37.355748"], ["id", 27]]
   (0.9ms)  COMMIT
Redirected to http://localhost:3000/accounts/1/transactions/27
Completed 302 Found in 16ms (ActiveRecord: 6.6ms)

I'm new with Rails and trying to understand what is happening with my code. I appreciate any help in advance.

Thanks!

Upvotes: 3

Views: 699

Answers (2)

lacostenycoder
lacostenycoder

Reputation: 11236

It seems like you need before_create because it's unlikely you would change the type of transaction right?

before_create do
  if self.trx_type == "debit"
    self.amount = self.amount * -1
  end
end

Update: Looks like you need in your controller change:

@transaction = @account.transactions.create(transaction_params)

to

@transaction = @account.transactions.build(transaction_params)

Upvotes: 1

Matt
Matt

Reputation: 337

There are two things here that are causing you some grief, but they're easy to address.

First, in the create action of your controller you're actually calling two methods that persist data to the database, so that's why you're seeing two saves in the console output.

The first line in the method is responsible for the first save:

@transaction = @account.transactions.create(transaction_params)

And this line here in your respond_to block is responsible for the second save:

if @transaction.save

Second, the reason the record has the correct amount in the first save and not in the second save is related to the logic in the before_save callback of your Transaction model. It's taking the amount and calling * -1 on it. Since the first save has already made the amount negative, the second save will flip it back to positive.

Upvotes: 4

Related Questions