sam
sam

Reputation: 355

I am having this error: "You cannot call create unless the parent is saved",

I have seen this error, I understand the problem or so I hope I do, the problem being my order_items are saving before an Order Id has been created. The problem of being a nube is having a clue about the problem but not idea about how to implement the solution, your patience is appreciated.

The error I am getting.

ActiveRecord::RecordNotSaved (You cannot call create unless the parent is saved):

app/models/shopping_bag.rb:22:in add_item' app/controllers/order_items_controller.rb:10:increate'

My OrderItems controller

class OrderItemsController < ApplicationController

  def index

    @items = current_bag.order.items

  end

  def create
    current_bag.add_item(
      book_id: params[:book_id],
      quantity: params[:quantity]
    )

    redirect_to bag_path
  end

  def destroy

    current_bag.remove_item(id: params[:id])
    redirect_to bag_path

  end
end

My Orders controller

class OrdersController < ApplicationController

  before_action :authenticate_user!, except:[:index, :show]

  def index
    @order = Order.all
  end

  def new
    @order = current_bag.order
  end

  def create
    @order = current_bag.order
    if @order.update_attributes(order_params.merge(status: 'open'))
      session[:bag_token] = nil
      redirect_to root_path
    else
      render new
  end
end

private

    def order_params
      params.require(:order).permit(:sub_total, :status, :user_id)
    end

end

My shopping bag Model

class ShoppingBag

  delegate :sub_total, to: :order

  def initialize(token:)
    @token = token
  end

  def order
    @order ||= Order.find_or_create_by(token: @token, status: 'bag') do | order|
      order.sub_total = 0
    end
  end

  def items_count
    order.items.sum(:quantity)
  end

  def add_item(book_id:, quantity: 1)
    book = Book.find(book_id)

    order_item = order.items.find_or_create_by(
      book_id: book_id
    )

    order_item.price = book.price
    order_item.quantity = quantity

    ActiveRecord::Base.transaction do
      order_item.save
      update_sub_total!
    end

  end

  def remove_item(id:)
    ActiveRecord::Base.transaction do
      order.items.destroy(id)
      update_sub_total!
    end
  end

  private

  def update_sub_total!
    order.sub_total = order.items.sum('quantity * price')
    order.save
  end

end

Thank you, your time is appreciated.

Upvotes: 2

Views: 2735

Answers (2)

Saif Chaudhry
Saif Chaudhry

Reputation: 449

To use parent attributes in child when using nested_attributes you can use inverse_of. Here is the documentation which may help you understand why parents need to be created first.

UPDATED with example: This will create forums first and then posts.

class Forum < ActiveRecord::Base
  has_many :posts, :inverse_of => :forum
end

class Post < ActiveRecord::Base
  belongs_to :forum, :inverse_of => :posts
end

Upvotes: 1

katafrakt
katafrakt

Reputation: 2488

From docs about find_or_create_by:

This method always returns a record, but if creation was attempted and failed due to validation errors it won’t be persisted, you get what create returns in such situation.

Probably this is the situation - the record was not persisted in a database, but only created in memory. Looking at your code, I think you want to use a bang-version of the method (find_or_create_by!), which will raise an error in such situation.

Upvotes: 4

Related Questions