The Capturing an Order chapter in Agile Wed Development with Rails uses the following code:
# orders_controller.rb
def create
#order = Order.new(params[:order])
#order.add_line_items_from_cart(current_cart)
if #order.save
Cart.destroy(session[:cart_id])
session[:cart_id] = nil
redirect_to store_url
else
#cart = current_cart
render 'new'
end
end
# order.rb
def add_line_items_from_cart(cart)
cart.line_items.each do |item|
item.cart_id = nil
line_items << item
end
end
How does the cart retain its line items when there's a validation error? The add_line_items_from_cart runs before we know whether the order is valid or not. It associates the line items with the order, then sets the item.cart_id to nil:
item.cart_id = nil
self.line_items << item # self is an instance of `Order`.
When I submit an empty form then view the cart, all the line items are still there. How is this possible? What have I missed?
The cart is only destroyed when the order is saved. So I guess it will never lose the items. What happens is when you create the order it probably destroy the current cart and the it creates a new one.
Related
I am doing cart for cafe, everything works fine except increasing quantity. When I add a food to cart...it just dublicating the same food...instead of increase the quantity. I want to make the solution that will check...If the food already in cart...instead of adding it will just increase the quantity. Below is my controller that adds food to cart. Any help will be appreciated
def add_to_cart
add_to_session(params[:cafe], params[:food])
redirect_to about_cafe_path(Cafe.find(params[:cafe]))
end
def show_cart
#cart = session[:cart]
end
def clear_cart
session[:cart] = {}
redirect_to show_cart_path
end
def remove
destroy_food(params[:cafe], params[:food])
redirect_to :back
end
private
def add_to_session(cafe_id, food_id)
if session[:cart][food_id].present?
session[:cart][cafe_id][food_id].quantity += 1
else
session[:cart][cafe_id].push food_id
end
end
def destroy_food(cafe_id, food_id)
session[:cart][cafe_id].delete food_id
end
you should check if session[:cart][cafe_id][food_id].present? instead of if session[:cart][food_id].present?
I'm trying to call some ActiveRecord methods in OrdersController#new action.
I've tested the code in Rails Console and it works the way I intended.
But in the controller action it does not produce the same
order has_many cart_items
cart has_many cart_items
cart_items belong_to order cart_items
belong_to cart
cart_items belong_to product
product has_many cart_items
def new
#order.new
#order.cart_items = current_cart.cart_items
#order.save
current_cart.cart_items.destroy_all
end
Now current_cart is an application_controller method that checks if the current user has a shopping cart. If it does it pulls that cart from the database and if the user does not then it creates a new cart for the user. What I am trying to do here is when the user finalizes their order I'm trying to transfer the cart_items from current_cart to orders then clear the shopping cart.
When I do this in rails console, it gives me what I want. Order with cart_items that were in current_cart, and after I run destroy_all on the cart I have an empty active record association array.
When I test this in my controller both Order and Cart return an empty active association array.
What is going on here?
#application controller method of finding current_users cart
def current_cart
# if user is logged in
if current_user
#user = current_user
# checking user to see if account is confirmed and verified
if #user.confirmed_at != nil
# checking if user already has cart in cart database
if Cart.find_by(users_id: #user.id) != nil
# find a row in the database where users_id: equal to #user.id
# where clause does not work here
cart = Cart.find_by(users_id: #user.id)
session[:cart_id] = cart.id
cart.save
#establish Cart session cart for user
Cart.find(session[:cart_id])
else
# create a new Cart Object for user.assign current_user's id to cart object
cart = Cart.new
cart.users_id = #user.id
# save it to get cart id assign session[:cart_id] == cart.id
cart.save
session[:cart_id] = cart.id
end
end
end
end
class CartItemsController < ApplicationController
before_action :set_cart_item, only: [:show, :edit, :update, :destroy]
# scope for most_recent and subtotal
# find out if rails sorts on update column cuz this is annoying.
def create
# grabbing cart from application controller current_cart method
#cart = current_cart
# session[:cart_id] = #cart.id
# individual product items get added to cart item and added to cart and saved
#cart_item = #cart.cart_items.build(cart_item_params)
#cart.save
end
def update
#cart = current_cart
# finding cart_items by cart_id
#cart_item = #cart.cart_items.find(params[:id])
# #cart_items.order(:id)
#cart_item.update_attributes(cart_item_params)
#cart_items = #cart.cart_items.order(:id)
# redirect 'cart_show_path'
#cart.save
end
def destroy
#cart = current_cart
#cart_item = #cart.cart_items.find(params[:id])
#cart_item.destroy
#cart_items = #cart.cart_items
#cart.save
end
private
def set_cart_item
#cart_item = CartItem.find(params[:id])
end
def cart_item_params
params.require(:cart_item).permit(:cart_id, :product_id, :unit_price, :quantity, :total_price)
end
end
When you say you are transferring the cart_items from current_cart you are passing on the objects, you are not creating new cart_items (which means the database ids are same) and when you do current_cart.cart_items.destroy_all it is deleting them from the database. See ActiveRecord::Relation#destroy_all
For you use case, its enough if you just do
def new
#order.new
#order.cart_items = current_cart.cart_items
#order.save
current_cart.cart_items = []
end
Alright, figured it out.
I was using #order.save which was not processing the error message!!!!!!!!
After #order.save! it gave me the validation error in another model.
I commented that out and it worked.
I iterated current.cart_items and assigned the cart_id to nil and order_id to #order.id essentially clearing the cart and "transferring" the items over.
I couldn't figure out a way using destroy_all though. I think this is impossible like #Sri said.
Thanks alot!
So the end code was this:
#order = Order.create!(users_id: current_cart.users_id)
current_cart.cart_items.each do |item|
item.order_id = #order.id
item.save!
end
if #order.save
current_cart.cart_items.each do |item|
item.cart_id = nil
item.save!
end
I am getting this error when trying to add a product to a cart:
ActiveRecord::RecordNotFound in LineItemsController#create
Couldn't find Cart with 'id'=2
So something is wrong with the create action, but I'm not sure what it is... Create action:(the gap between #product and quantity isn't there in my actual code, can't get it formatted right.)
def create
#product = Product.find(params[:product_id])
#line_item = LineItem.create!(:cart => current_cart, :product => #product,
:quantity=> 1, :unit_price => #product.price)
flash[:notice] = "Added #{#product.name} to cart."
redirect_to cart_url(current_cart)
end
current_cart method from application controller:
def current_cart
session[:cart_id] ||= Cart.create!.id
#current_cart ||= Cart.find(session[:cart_id])
end
Thanks for the help.
Your code is failing in the ApplicationController#current_cart method. Do you already have a cart ID in the session? If so, the code will try to find a Cart record with that ID and if it's been deleted, it'll fail with the above message.
So, firstly, figure out what you have in your session. Secondly, figure out how to create a new cart without supplying your application stale or invalid data.
Hope this helps.
I was reading a book for agile web development and I found this code
application controller
private
def current_cart
Cart.find(session[:cart_id])
rescue ActiveRecord::RecordNotFound
cart = Cart.create
session[:cart_id] = cart.id
cart
end
line items controller
def create
#cart = current_cart
product = Product.find(params[:product_id])
#line_item = #cart.line_items.build(:product => product)
respond_to do |format|
if #line_item.save
format.html { redirect_to(#line_item.cart,
:notice => 'Line item was successfully created.') }
I don't understand the following two lines
#line_item = #cart.line_items.build(:product => product)
format.html { redirect_to(#line_item.cart,
:notice => 'Line item was successfully created.') }
#cart holds the value of cart_id then what #cart.line_items point and what is the use of build method here?
Also what #line_item.cart means here? which action will be called?
Okay, let's start with #cart. As phoet says, it is not a simple integer. It is set by Cart.find(session[:cart_id]), so it is an instance of the Cart model based off a row in the database.
The Cart model presumably inherits from ActiveRecord::Base which has all the logic for has_many (and other) associations (relations) between models. So, I would expect to see at least
def Cart < ActiveRecord::Base
has_many :line_items
end
in the Cart model.
So, when you call #cart.line_items you get something that is conceptually a list of that cart's line items through the association set up by the call to has_many. Calling build on that results in a new (as of yet, unpersisted) instance of the LineItem model that has its cart_id value set to #cart.id.
I hope that clears things up a bit. Rails associations are tricky business.
Your assumption is not correct. #cart holds a Cart object from the current_cart method. It's either new, or the one held in the session.
So #cart.line_items works with the has_many relation of Cart and line_items.build will setup a new LineItem to be saved to the database on #line_tem.save.
I'm trying to integrate eCommerce functionality into my rails app, and am having trouble creating a new order. I start with a cart, which has_many orders, which has_many transactions. The first field in my order database is cart_id. I need to be able to access information in the cart (such as total_price) from the view/order/new.html.erb.
Where would be the best place to build this relation, and how? I can find the cart through the session id, but I don't know how to build the relationship. I was thinking in the order model, in the new action, somthing like so?
def new
#order = Order.new
current_cart.#order.build
Defined in my application controller is the function current_cart
def current_cart
Cart.find(session[:cart_id])
rescue ActiveRecord::RecordNotFound
cart = Cart.create
session[:cart_id] = cart.id
cart
end
UPDATE
Here is my new and create function, and where I need the value
def new
#order = Order.new
respond_to do |format|
format.html # new.html.erb
format.json { render json: #order }
end
end
# POST /orders
# POST /orders.json
def create
#order = Order.new(params[:order])
# THIS IS WHERE I HAVE TRIED TO BUILD THE RELATIONSHIP
# I have tried current_cart.orders.build, #order.cart = current_cart, and
# #order = current_cart.build_order(params[:order])
#order.ip_address = request.remote_ip
if #order.save
if #order.purchase
render :action => "success"
else
render :action => "failure"
end
else
render :action => 'new'
end
end
and this is where I need to access the cart in the model
def price_in_cents
(cart.total_price*100).round
end
And I always get an exception caught for an undefined function, either the build function, or the total price function
In the Order model you have the cart_id, so define the relation there:
belongs_to :cart
You can also define the relation in the Cart model, additionally:
has_many :orders
After that you can simply add new orders to your current basket:
#order = Order.new
#order.cart = current_cart
EDIT:
Maybe there is an other Problem with the current_cart method.
Try:
#order.cart_id = session[:cart_id]
I made a video about this: http://www.ror-e.com/info/videos/6
I actually separate the cart from the order. So basically The cart has_many cart_item and the order has_many order_items. I'd love to help out more. Feel free to contact me directly. I'd love to discuss the pro's & con's of different approaches.
In your create action:
#order = current_cart.build_order(order_params)
and add strong params:
private
def order_params
params.require(:order).permit(:first_name, :last_name, :card_type, :card_expires_on)
end