Rails. Save parent, before save children - ruby-on-rails

When I try create line_item, I get this error: ActiveRecord::RecordNotSaved (You cannot call create unless the parent is saved).
When I wrong? How fix it?
line_items controller
def create
#product = Product.find_by_id(params[:line_item][:product_id])
#cart = current_cart
#line_item = #cart.add_product(line_item_params)
end
cart model
has_many :line_items, dependent: :destroy
def add_product(line_item_args)
current_line_item.quantity += line_item_args[:quantity].to_i
current_line_item.save
if current_item
current_item.quantity += line_item.quantity.to_i
else
current_item = line_items.create!(line_item_args)
end
current_item
end
UPD
working helper method current_cart from application controller. problem was in it.
def current_cart
Cart.find(session[:cart_id])
rescue ActiveRecord::RecordNotFound
cart = Cart.create
session[:cart_id] = cart.id
cart
end

i don't know how you are working with the cart but i'm amoust sure current cart is not persited on de database, check if its true.

Related

Rails shopping Cart - how to destroy them in production without User to get a 404?

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

Add to Cart Function - No Method Error (undefined method)

Hi guys so i am attempting a "add to cart function". I keep receiving "cart undefined method or variable" error and i can't seem to figure out where i'm going wrong.
Here's my cart.rb model
class Cart < ActiveRecord::Base
has_many :tutors
def add_tutor(tutor_id)
tutor = Tutor.where('tutor_id = ?', tutor_id)
if tutor
Cart.tutors << tutor
end
save
end
end
Here's my carts_controller.rb
class CartsController < ApplicationController
def show
#cart = current_cart
end
def add_to_cart
current_cart.add_tutor(params[:tutor_id])
redirect_to tutors_path
end
end
methods defined in application_controller.rb
def current_cart
if session[:cart_id]
#current_cart ||= Cart.find(session[:cart_id])
end
if session[:cart_id].nil?
#current_cart = Cart.create!
session[:cart_id] = #current_cart.id
end
#current_cart
end
code in routes.rb for the button
post '/add_to_cart/:tutor_id' => 'carts#add_to_cart', :as => 'add_to_cart'
and the code for the button to add to cart
<%= button_to "Shortlist Tutor", add_to_cart_path(:tutor_id => :tutor_id), :method => :post %>
Whenever i try the "Shortlist Tutor" button, i receive a NoMethodError in CartsController#add_to_cart, undefined method `tutors' for Cart(id: integer):Class
and the highlighted error is on the cart.rb model file and the line being
Cart.tutors << tutor
Any help regarding this error is greatly appreciated. Thank you for your time and advice!
In Cart.tutors << tutor Cart is a class. To add tutors to Cart, Cart must be an object. In your case current_cart is an object but Cart is class. so replace Cart in cart.rb with self as shown below
class Cart < ActiveRecord::Base
has_many :tutors
def add_tutor(tutor_id)
tutor = Tutor.where('tutor_id = ?', tutor_id)
if tutor
self.tutors << tutor
end
save
end
end

Problems with Active Record Rails 4

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

NoMethodError in StoreController#display_cart

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.

Dynamic finders return a new object different from anyone in the has_many collection?

I'm trying to find a object from the has_many collection by dynamic-finders, and then update the attribute of the returned object. But I really don't know when the updated attribute value will be synchronized to the has_many collection. Because I found the returned object is a new object different from any of the objects in the has_many collection.
class Cart < ActiveRecord::Base
has_many :line_items, :dependent => destroy
def add_product(product_id)
current_item = line_items.find_by_product_id(product_id)
#here current_item is #<LineItem:0x007fb1992259c8>
#but the line_item in has_many collection line_items is #<LineItem:0x007fb19921ed58>
if current_item
current_item.quantity += 1
else
current_item = line_items.build(:product_id => product_id)
end
current_item
end
...
end
class LineItemsController < ApplicationController
...
def create
#cart = current_cart
product = Product.find(params[:product_id])
#line_item = #cart.add_product(product_id)
respond_to do |format|
if #line_item.save
format.js { #current_item = #line_item }
end
end
end
...
end
After updating the quantity of current_item, when render the Cart, the quantity of the line_item in the cart still be the value before updaing. But however, the next time when calling LineItemsController.create, the quantity of the line_item in the cart has been updated.
So is there any idea about when the quantity of the line_item in the cart been updated?
The simplest solution may be to call cart.line_items(true) after you've updated the individual line item. This forces Rails to reload the entire association from the database, which should include the changes to your line item quantity.
You could also use detect instead of find_by_product_id to fetch the line item directly from the array of line items. This would guarantee that you're using the same object, but it requires loading all the line items from the database first (which it sounds likes you may already be doing):
current_item = line_items.detect { |item| item.product_id == product_id }

Resources