anndrew78
anndrew78

Reputation: 196

ruby on rails. stack level too deep error

stack level too deep error crashed my app. I have a deposit model with two rows: income and cashout. This two rows modified by after_create callbacks in income and cashout models. In cashout model I want to use a before_create callback for validate a balance of deposit (deposit balance must be greater or equal to 0 after creating cashout) and receiving an error: SystemStackError in CashoutsController#create.

ps: Income callback work fine.

Codes:

deposit.rb

class Deposit < ActiveRecord::Base
    belongs_to :user
    has_many :incomes
    has_many :cashouts
end

schema.rb

...

  create_table "deposits", force: true do |t|
    t.integer  "user_id"
    t.datetime "created_at"
    t.datetime "updated_at"
    t.decimal  "income",     precision: 12, scale: 2
    t.decimal  "cashout",    precision: 12, scale: 2, default: 0.0
  end

cashout.rb

class Cashout < ActiveRecord::Base
    belongs_to :deposit
    validates :deposit_id, :order_sum, presence: true
    validates :order_sum, numericality: true
    validates :order_sum, numericality: { greater_than_or_equal_to: 0}
    before_create :validate_order #before_save crashed too
    after_create :update_deposit_cashout # working fine

    private

    def validate_order
        @deposit = self.deposit
        @income = @deposit.income
        @cashout = self.order_sum
        if @income - @cashout >= 0
            self.save
        else
            !self.save
        end
    end

    def update_deposit_cashout
        @deposit = self.deposit
        @deposit.update_attributes(:cashout => @cashout + self.order_sum)
    end
end

cashouts_controller.rb

class CashoutsController < ApplicationController
  before_action :signed_in_user, only: [:create]
  
  def new
    @cashout = @deposit.cashouts.build
  end

    def create
    @deposit = current_user.deposit
    @cashout = @deposit.cashouts.build(cashout_params)
    @cashout.save 
    if @cashout.save
      flash[:success] = "Your order request has been sent!"
      redirect_to '/deposit'
    else
      flash[:error] = "Your order request hasn't been sent!"
      redirect_to '/deposit'
    end
  end

  def show
    @deposit = Deposit.find(params[:id])
    @deposit.cashout
  end


  private

    def cashout_params
      params.require(:cashout).permit(:order_sum)
    end
    
end

income.rb

class Income < ActiveRecord::Base
    belongs_to :deposit
    validates :deposit_id, :order_sum, presence: true
    validates :order_sum, numericality: true
    validates :order_sum, numericality: { greater_than_or_equal_to: 0}

    after_create :update_deposit_income

    private

    def update_deposit_income
        @deposit = self.deposit
        if @deposit.income == nil
            @income = 0
        else
            @income = @deposit.income
        end
        @deposit.update_attributes(:income => @income + self.order_sum)
    end
end

incomes_controller.rb

class IncomesController < ApplicationController
  before_action :signed_in_user, only: [:create]
  
  def new
    @income = @deposit.incomes.build
  end

    def create
    @user = current_user
    @deposit = @user.deposit
    @income = @deposit.incomes.build(income_params) 
      @income.save
    if @income.save
      flash[:success] = "Your order request has been sent!"
      redirect_to '/deposit'
    else
      flash[:error] = "Your order request hasn't been sent!"
      redirect_to '/deposit' if current_user.present?
    end
  end

  def show
    @deposit = Deposit.find(params[:id])
    @deposit.income
  end


  private

    def income_params
      params.require(:income).permit(:order_sum)
    end
    
end

Where is my mistake?

Upvotes: 3

Views: 2214

Answers (1)

Max Williams
Max Williams

Reputation: 32943

The stack level too deep error usually happens because your code is stuck in a loop: either a method calling itself, or two methods calling each other repeatedly.

In your case i think it's caused by the validate_order method. You are calling this in a before_save callback, and the method calls self.save, which will trigger the callback, which will call the method again, which will save again, which will trigger the callback etc etc.

Think about this for a moment - it doesn't make sense to save an object as part of it's "before_save" functionality does it?

What i suspect you want to do is to just validate the object, and if validation fails that will automatically block the save anyway. Something like this

validate :validate_order

def validate_order
  @deposit = self.deposit
  @income = @deposit.income
  @cashout = self.order_sum
  if @income - @cashout <= 0
    self.errors.add(:deposit, "is too small")
  end
end

This is just an example as i don't know what the actual logic of what you are testing is supposed to be.

Upvotes: 4

Related Questions