Search code examples
ruby-on-railsbigdecimal

Ruby on Rails nil can't be coerced into BigDecimal


Why do I get nil can't be coerced into BigDecimal when I try to perform a calculation: here's the code:

model/drink.rb

class Drink < ActiveRecord::Base
  belongs_to :menu 
  before_save :total_amount 

def total_amount
    self.total_amount = self.price * self.quantity
end 

model/menu.rb

class Menu < ActiveRecord::Base
    has_many :drinks, :dependent => :destroy
    accepts_nested_attributes_for :drinks, :allow_destroy => true
    #Validations

end

*Drink is the (nested)child model and Menu the parent model When I attempt to create a new drink the browser display following error message nil can't be coerced into BigDecimal app/models/drink.rb:7:in 'total-amount' app/controllers/menus_controller.rb:47:in 'create' app/controllers/menus_controller.rb:46:in 'create'

app/db/migration

class CreateDrinks < ActiveRecord::Migration
  def change
    create_table :drinks do |t|
      t.string :name
      t.decimal :quantity,:precision => 8, :scale => 2
      t.decimal :price, :precision => 8, :scale => 2
      t.decimal :vat, :precision => 8, :scale => 2
      t.references :menu

      t.timestamps
    end
    add_index :drinks, :menu_id
  end
end

controllers/drinks_controller.rb

   class DrinksController < ApplicationController
      # GET /drinks
      # GET /drinks.json
      def index
        @drinks = Drink.all

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

      # GET /drinks/1
      # GET /drinks/1.json
      def show
        @drink = Drink.find(params[:id])

        respond_to do |format|
          format.html # show.html.erb
          format.json { render :json => @drink }
        end
      end

      # GET /drinks/new
      # GET /drinks/new.json
      def new
        @drink = Drink.new

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

      # GET /drinks/1/edit
      def edit
        @drink = Drink.find(params[:id])
      end

      # POST /drinks
      # POST /drinks.json
      def create
        @article = Drink.new(params[:drink])

        respond_to do |format|
          if @drink.save
            format.html { redirect_to @drink, :notice => 'Drink was successfully created.' }
            format.json { render :json => @drink, :status => :created, :location => @article }
          else
            format.html { render :action => "new" }
            format.json { render :json => @drink.errors, :status => :unprocessable_entity }
          end
        end
      end

      # PUT /drinks/1
      # PUT /drinks/1.json
      def update
        @drink = Drink.find(params[:id])

        respond_to do |format|
          if @drink.update_attributes(params[:drink])
            format.html { redirect_to @drink, :notice => 'Drink was successfully updated.' }
            format.json { head :ok }
          else
            format.html { render :action => "edit" }
            format.json { render :json => @drink.errors, :status => :unprocessable_entity }
          end
        end
      end

      # DELETE /drinks/1
      # DELETE /drinks/1.json
      def destroy
        @drink = Drink.find(params[:id])
        @drink.destroy

        respond_to do |format|
          format.html { redirect_to drinks_url }
          format.json { head :ok }
        end
      end 
    end 

Please can anyone tell me what's wrong with the code?


Solution

  • If you want nil to be evaluated as 0.0 then you can do something like this:

    def total_amount
        self.total_amount = self.price.to_s.to_d * self.quantity.to_s.to_d
    end 
    

    Or explicitly check for nil

    def total_amount
      if self.price && self.quantity
        self.total_amount = self.price * self.quantity
      else
        self.total_amount = "0.0".to_d
      end
    end 
    

    The problem is really that your record fields aren't set like you expect them to be. Do you need to use validations to make sure that the price and quantity fields are set?

    class Drink
      validates :price, :presence => true      # Don't forget add DB validations, too :)
      validates :quantity, :presence => true
    end
    

    That way you ensure that you don't get a nil value when calling #total_amount.