Logan Logan - 3 months ago 24
Ruby Question

nil can't be coerced into BigDecimal - Ruby On Rails

I've seen other questions similar to this one, but none that are exactly the same (I'm still somewhat of a beginner). If there is another thread that is the same just let me know! Thanks!!

UPDATE:
I forgot to add this code. Which is a migration file to add the quantity.

class AddQuantityToProductItems < ActiveRecord::Migration[5.0]
def change
add_column :product_items, :quantity, :integer, default: 1
end
end


I keep getting the error "nil can't be coerced into BigDecimal" from the code below (ProductItem Model:)

class ProductItem < ApplicationRecord
belongs_to :product
belongs_to :cart

def total_price
product.price * quantity
end
end


Here is the Cart Model:

class Cart < ApplicationRecord
has_many :product_items, dependent: :destroy

def add_product(product_id)
current_item = product_items.find_by(product_id: product_id)
if current_item
current_item.quantity += 1
else
current_item = product_items.build(product_id: product_id)
end
current_item
end

def total_price
product_items.to_a.sum{|item| item.total_price}
end
end


Product Model:

class Product < ApplicationRecord
before_destroy :ensure_not_product_item
has_many :product_items
validates :title, :description, presence: true
validates :price, numericality: {greater_than_or_equal_to: 0.01}
validates :title, uniqueness: true
has_attached_file :image, styles: { medium: "300x300>", thumb: "100x100>" }
validates_attachment_content_type :image, content_type: /\Aimage\/.*\Z/

def ensure_not_product_item
if product_items.empty?
return true
else
errors.add(:base, 'Product Items')
return false
end
end
end


ProductItems Controller:

class ProductItemsController < ApplicationController

include CurrentCart
before_action :set_cart, only: [:create]
before_action :set_product_item, only: [:show, :destroy]

def create
product = Product.find(params[:product_id])
@product_item = @cart.add_product(product.id)
if @product_item.save
redirect_to shop_url, notice: 'Your Product was added to the cart!'
else
render :new
end
end

private

def set_product_item
@product_item = ProductItem.find(params[:id])
end

def product_item_params
params.require(:product_item).permit(:product_id)
end

end


Code for Product Items and Products from schema.rb

create_table "product_items", force: :cascade do |t|
t.integer "product_id"
t.integer "cart_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "quantity"
t.index ["cart_id"], name: "index_product_items_on_cart_id"
t.index ["product_id"], name: "index_product_items_on_product_id"
end

create_table "products", force: :cascade do |t|
t.string "title"
t.string "image"
t.text "description"
t.decimal "price"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "image_file_name"
t.string "image_content_type"
t.integer "image_file_size"
t.datetime "image_updated_at"
end


If any other files are needed, I'll gladly post them. I'm sure it's something really simple, I just can't seem to figure it out. Maybe I'm trying too hard to find the problem. Lol.
Thanks for taking the time to help!

Answer

From the sounds of it, you are not initialising the quantity attribute when you create a new product item.

You could either initialise the quantity attribute in the by adding an after_initialize block in ProductItem

class ProductItem < ApplicationRecord
  after_initialize do
    self.quantity ||= 1
  end
end

Or, you could set the quantity attribute in the Cart#add_product method when you create a product item:

class Cart < ApplicationRecord
  def add_product(product_id)
    current_item = product_items.find_by(product_id: product_id)
    if current_item
      current_item.quantity += 1
    else
      # initialize the quantity value when creating a new item
      current_item = product_items.build(product_id: product_id, quantity: 1)
    end
    current_item
  end
end

Yet another method would be to set a default value in the ProductItem#total_price method:

class ProductItem < ApplicationRecord
  def total_price
    product.price * (quantity || 1)
  end  
end

Personally, I would go with after_initialize method as it is the most consistent option in that the quantity always has a value.