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
Related
In a rails 5 e-commerce site, Carts from a classic shopping cart model have not been destroyed because of a badly written code, so now the heroku database has more than 10 000 rows of empty carts. If I destroy them from the heroku console, next time a user who already came to the site but gave up in the middle of the buying process will try to reach the site, he will get a 404 error like this:
ActiveRecord::RecordNotFound (Couldn't find Cart with 'id'=305)
Because of the cookies, obviously. The heroku DB has exceeded its allocated storage capacity, so I need to destroy the empty carts. (And fix the code, but this is not the question here).
Is there a way to do this smoothly?
class CartsController < ApplicationController
def show
#cart = #current_cart
total = []
#cart.order_items.each do |item|
total << item.product.price_cents * item.quantity.to_i
end
#cart.amount_cents_cents = total.sum
end
def destroy
#cart = #current_cart
#cart.destroy
session[:cart_id] = nil
redirect_to root_path
end
end
class OrdersController < ApplicationController
before_action :authenticate_user!
def create
#order = Order.new
total = []
#current_cart.order_items.each do |item|
total << item.product.price_cents * item.quantity.to_i
end
#order.amount_cents_cents = total.sum
if #order.amount_cents_cents == 0
redirect_to root_path
else
#current_cart.order_items.each do |item|
#order.order_items << item
item.cart_id = nil
end
#user = current_user
#order.user_id = #user.id
#order.save
Cart.destroy(session[:cart_id])
session[:cart_id] = nil
redirect_to order_path(#order)
end
end
class ApplicationController < ActionController::Base
before_action :current_cart
def current_cart
if session[:cart_id]
cart = Cart.find(session[:cart_id])
if cart.present?
#current_cart = cart
else
session[:cart_id] = nil
end
end
if session[:cart_id] == nil
#current_cart = Cart.create
session[:cart_id] = #current_cart.id
end
end
instead of using find which throws an exception if it does not find a record you can use find_by_id which returns nil. And if you get nil you can make the cart empty and display a message to the user that your cart is empty.
also, you can use rescue block to rescue from the exception thrown by find
So I've built a system of products and a shopping cart in my rails app. The goal I have is to associate saved products from a cart with user model.
I'm struggling to find a solution on how could I save ids of the items from a cart from each current_user to the column from user's model?
So in my cart view page there is a list of all added products in a cart and I want to add a save button which will save those products by their ids.
As an example, if current_user ads three products in the cart with ids 1,2,3 and clicks on "Save" button in a cart, I want to be able to save those three ids by integers to the "addedproducts" column of the current_user.
This is part of my cart_controller:
def additems
id = params[:id]
if session[:cart] then
cart = session[:cart]
else
session[:cart] = {}
cart = session[:cart]
end
if cart[id] then
cart[id] = cart[id] + 1
else
cart[id] = 1
end
redirect_to :action => :index
end
and a part of my product controller(generated through scaffold):
class ItemsController < ApplicationController
before_action :set_item, only: [:show, :edit, :update, :destroy]
respond_to :html, :json, :js
def index
#products = Product.where(availability: true)
end
def show
end
def new
#product = Product.new
end
def edit
end
def create
#product = Item.new(item_params)
#product.save
respond_with(#product)
end
Is this possible to achieve?
(I'm using standard Devise setup if it's of any help)
How you want to save multiple items to a single column "addedproducts" ? I think the best solution is adding a foreign key to the cart. Making the cart belongs_to a user. And the user having many carts. Just the same way you have probably created a Cart model and an Item model too. With the proper relationship between the two... (If you do that you will have to add a new migration to the cart model with a reference to User.)
I am following the book Agile Web Development with Rails - A Pragmatic Guide. On Iteration C1, creating a cart, I am getting the following error:
NoMethodError in StoreController#display_cart
undefined method `items' for #<Hash:0xb5cf041c>
Extracted source (around line #15):
13 def display_cart
14 #cart = find_cart
15 #items = #cart.items
16 end
17
18 private
Here are the source files:
routes.rb
Rails.application.routes.draw do
get 'store' => 'store#index'
get 'add_item_to_cart' => 'store#add_to_cart'
get 'display_cart' => 'store#display_cart'
resources :products
store_controller.rb
class StoreController < ApplicationController
def index
#products = Product.salable_items
end
def add_to_cart
product = Product.find(params[:id])
#cart = find_cart
#cart.add_product(product)
redirect_to display_cart_path
end
def display_cart
#cart = find_cart
#items = #cart.items
end
private
def find_cart
session[:cart] ||= Cart.new
end
end
cart.rb
class Cart
attr_reader :items
attr_reader :total_price
def initialize
#items = []
#total_price = 0.0
end
def items
#items
end
def add_product(product)
#items << LineItem.for_product(product)
#total_price += product.price
end
end
line_item.rb
class LineItem < ActiveRecord::Base
belongs_to :product
def self.for_product(product)
item = self.new
item.quantity = 1
item.product = product
item.unit_price = product.price
item
end
end
I reach the action display_cart from the add_to_cart action of StoreController. Even though I have a def items in cart.rb, why am I getting a NoMethodError?
Assuming you have stored cart id in the session you can do following changes in your method
def find_cart
Cart.find_by_id(session[:cart_id]) ||= Cart.new
end
Try this to return your Cart instance:
def find_cart
Cart.find(session[:cart]
rescue ActiveRecord::RecordNotFound
cart = Cart.new
session[:cart] = cart.id
end
This is almost the same code as in your book. Semantically you could call that function current_cart.
By storing only the carts id and using the ActiveRecord find() method, rescuing with calling Cart.new , you will always return a cart instance.
Edit
if you do not implement Cart as a Model, you will (logically) never be able to instantiate an object of it.
This is a good article about handling sessions in Ruby on Rails. As you will see, the session itself is a Hash by 'nature', further down you will find some alternative solutions for storing sessions in your rails app.
The problem is in
def find_cart
session[:cart] ||= Cart.new
end
your session[:cart] is a Hash, and thus does not get Cart.new, and when you call #cart.items it gets called on Hash not on a Cart object
In your display_cart method: you're doing #cart = find_cart which may return a hash instead of an object as in the find_cart methhod you're doing session[:cart] ||= Cart.new which may return a Hash.
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.
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