Two objects at the same time not one add to my shopping cart. Here is the controller and models :
class PublicController < ApplicationController
before_filter :find_or_create_cart, :only => [:add_to_cart, :show_cart]
def list
#products = Product.sorted.paginate(:per_page => 5 , :page => params[:page])
end
def add_to_cart
product = Product.find(params[:id])
#cart.add_product(product)
redirect_to(:action => 'show_cart')
end
def show_cart
end
private
def find_or_create_cart
#cart=session[:cart]||=Cart.new
end
end
AND the model :
class Cart
attr_reader :items
attr_reader :total_price
def initialize
#items = []
#total_price = 0.0
end
def add_product(product)
#items << LineItem.new_based_on(product)
#total_price += product.price
end
end
Model which come from a join table :
class LineItem < ActiveRecord::Base
belongs_to :order
belongs_to :product
def self.new_based_on(product)
line_item = self.new
line_item.product = product
line_item.quantity = 1
line_item.price = product.price
return line_item
end
end
It is a small application which is supposed to do online shopping, but when I hit the button add to cart this will add two objects (same object) to my shopping cart. Hope someone can help me.
I'll write another answer to address your code, but I felt this might help you.
We've developed our own cart system for one of our apps (you can see at http://firststopcosmeticshop.co.uk) we developed based on Ryan Bates' session models Railscast
How It Works
When using sessions to store cart values, you're basically storing a session var with id's from added cart items. Because you want to keep the session as thin as possible, you'll literally just have an array constructed of product id's:
session[:cart][:products] = [] # -> to init
session[:cart][:products] << product.id # -> builds array like [1,5,6,1,6,4,1,5]
This will allow you to then process the cart as you desire (by reading / manipulating the session[:cart][:products] array). If you test the app I referenced, it should help you see it in action
I mentioned your code is inefficient because you're relying on 3 data sources: LineItem, Cart and PublicController. What you really need is CartController and CartSession for a session-based model. This will allow you to call LineItem from your CartController, making it lean
Code
Our session based cart works like this:
#app/models/cart_session.rb
class CartSession
#Initalize Cart Session
def initialize(session)
#session = session
#session[:cart] ||= {}
end
#Cart Count
def cart_count
if (#session[:cart][:products] && #session[:cart][:products] != {})
#session[:cart][:products].count
else
0
end
end
#Cart Contents
def cart_contents
products = #session[:cart][:products]
if (products && products != {})
#Determine Quantities
quantities = Hash[products.uniq.map {|i| [i, products.count(i)]}]
#Get products from DB
products_array = Product.find(products.uniq)
#Create Qty Array
products_new = {}
products_array.each{
|a| products_new[a] = {"qty" => quantities[a.id.to_s]}
}
#Output appended
return products_new
end
end
#Qty & Price Count
def subtotal
products = cart_contents
#Get subtotal of the cart items
subtotal = 0
unless products.blank?
products.each do |a|
subtotal += (a[0]["price"].to_f * a[1]["qty"].to_f)
end
end
return subtotal
end
end
#app/controllers/cart_controller.rb
class CartController < ApplicationController
include ApplicationHelper
#Index
def index
#items = cart_session.cart_contents
#shipping = Shipping.all
end
#Add
def add
session[:cart] ||={}
products = session[:cart][:products]
#If exists, add new, else create new variable
if (products && products != {})
session[:cart][:products] << params[:id]
else
session[:cart][:products] = Array(params[:id])
end
#Handle the request
respond_to do |format|
format.json { render json: cart_session.build_json }
format.html { redirect_to cart_index_path }
end
end
#Delete
def delete
session[:cart] ||={}
products = session[:cart][:products]
id = params[:id]
all = params[:all]
#Is ID present?
unless id.blank?
unless all.blank?
products.delete(params['id'])
else
products.delete_at(products.index(id) || products.length)
end
else
products.delete
end
#Handle the request
respond_to do |format|
format.json { render json: cart_session.build_json }
format.html { redirect_to cart_index_path }
end
end
end
#config/routes.rb
get 'cart' => 'cart#index', :as => 'cart_index'
post 'cart/add/:id' => 'cart#add', :as => 'cart_add'
delete 'cart/remove(/:id(/:all))' => 'cart#delete', :as => 'cart_delete'
This basically allows you to keep a list of people's cart items in a simple session array. The array then gives you the ability to determine the quantity, product type, brand etc, all from the array. It's the most efficient way, and is what I'd recommend for your app
To address your question directly, I believe you need to do the following:
Routes
#config/routes.rb
resources :cart, only: :index do
post :add # -> allows you to add an item
end
Views
#app/views/cart/index.html.erb
<% for product in #products do %>
<%= product.name %>
<%= product.price %>
<% end %>
Controller
class CartController < ApplicationController
respond_to :html, :json, :js
before_action :cart_init, :only => [:add, :index]
#index should replace show_cart
def index
unless #cart.nil?
#products = Product.find #cart #-> will allow you to find multiple ID's
end
respond_with #products
end
#KISS (Keep It Simple)
def add
#cart << params[:id]
respond_with #cart
end
private
def cart_init
#cart = session[:cart][:products] ||= Cart.new #-> keep products in its own key. Allows you to add [:cart][:shipping] etc
end
end
Model
class Cart
attr_reader :items
attr_reader :total
def initialize
#items = session[:cart][:products] ||= []
#total = 0.0
end
end
Related
I'm working on a E Commerce app and I can´t figure out how I can update the product stock quantity when the user adds product to the cart.
I followed a tutorial for most of this app since I'm a total newbie. But the tutorial doesn't go into how to update the product stock quantity when the user adds product to the cart.
so far I've added this method to the cart.rbmodel
def upd_quantity
if cart.purchased_at
product.decrement!(quantity: params[:stock_quantity])
end
end
But I´m really stuck and don't know what I'm doing
It would be great if someone could take a look at this and advise me how to implement this function to the app.
here is a link to the github repo https://github.com/DadiHall/brainstore
this what I have at the moment.
cart_item.rb model
class CartItem
attr_reader :product_id, :quantity
def initialize product_id, quantity = 1
#product_id = product_id
#quantity = quantity
end
def increment
#quantity = #quantity + 1
end
def product
Product.find product_id
end
def total_price
product.price * quantity
end
def upd_quantity
if cart.purchased_at
product.decrement!(quantity: params[:stock_quantity])
end
end
end
cart.rbmodel
class Cart
attr_reader :items
def self.build_from_hash hash
items = if hash ["cart"] then
hash["cart"] ["items"].map do |item_data|
CartItem.new item_data["product_id"], item_data["quantity"]
end
else
[]
end
new items
end
def initialize items = []
#items = items
end
def add_item product_id
item = #items.find { |item| item.product_id == product_id }
if item
item.increment
else
#items << CartItem.new(product_id)
end
end
def empty?
#items.empty?
end
def count
#items.length
end
def serialize
items = #items.map do |item|
{
"product_id" => item.product_id,
"quantity" => item.quantity
}
end
{
"items" => items
}
end
def total_price
#items.inject(0) { |sum, item| sum + item.total_price }
end
end
product.rb model
class Product < ActiveRecord::Base
mount_uploader :image, ImageUploader
validates_presence_of :name, :price, :stock_quantity
validates_numericality_of :price, :stock_quantity
belongs_to :designer
belongs_to :category
belongs_to :page
def self.search(query)
where("name LIKE ? OR description LIKE ?", "%#{query}%", "%#{query}%")
end
end
`cart_controller.rb`
class CartsController < ApplicationController
before_filter :initialize_cart
def add
#cart.add_item params[:id]
session["cart"] = #cart.serialize
product = Product.find params[:id]
redirect_to :back, notice: "Added #{product.name} to cart."
end
def show
end
def checkout
#order_form = OrderForm.new user: User.new
#client_token = Braintree::ClientToken.generate
end
def remove
cart = session['cart']
item = cart['items'].find { |item| item['product_id'] == params[:id] }
if item
cart['items'].delete item
end
redirect_to cart_path
end
end
Does adding this line:
product.update_columns(stock_quantity: product.stock_quantity - 1)
To your add action in the CartsController do the trick?
def add
#cart.add_item params[:id]
session["cart"] = #cart.serialize
product = Product.find params[:id]
product.update_columns(stock_quantity: product.quantity - 1)
redirect_to :back, notice: "Added #{product.name} to cart."
end
Try this for removing the product from cart:
def remove
cart = session['cart']
item = cart['items'].find { |item| item['product_id'] == params[:id] }
product = Product.find(item['product_id'])
product.update_columns(stock_quantity: product.stock_quantity + 1)
if item
cart['items'].delete item
end
redirect_to cart_path
end
Note, this will only work if you are adding and removing 1 item/product at a time. I suggest renaming 'item' to 'product' in your remove action for consistency.
I have an App with Demos and Ratings. The basic idea is that each user can rate demos, and they can update the ratings. However, I'm having a lot of errors with several things:
Updating the rating for the demo
Creating a form to update the rating
Demo Controller
def show
#demo = Demo.find(params[:id])
#user = User.find(#demo.user_id)
#topic = Subject.find(#demo.subject_id)
if repeat_reviewer?(current_user)
#rating = Rating.create(reviewer: current_user, reviewed: #demo)
else
#rating = Rating.where(reviewer: current_user, reviewed: #demo)
end
end
....
def repeat_reviewer?(user)
#demo = Demo.find(params[:id])
return false unless user == User.where(reviewed: #demo)
end
def update_average_rating
#value = 0
self.ratings.each do |r|
#rating += #rating.value
end
#total = self.ratings.size
update_attributes(rating_average: #value.to_f / #total.to_f)
end
Ratings Controller
before_action :require_user, only: [:create, :update]
after_action :update_demo_rating
def create
#rating = Rating.create(rating_params)
if #rating.save
redirect_back_or
end
end
def update
#rating = Rating.where(reviewer: current_user)
if #rating.update_attributes(rating_params)
redirect_back_or
end
end
private
def rating_params
params.require(:rating).permit(:value, :reviewer_id, :reviewed_id)
end
def update_demo_rating
#rating = Rating.find(params[:id])
#demo = #rating.reviewed
#demo.update_average_rating
end
Demos Show View
<%= form_for #rating, url: ratings_path(#rating), method: :put do |f| %>
<%= options_for_select([[5,5],[3,3],[1,1]], 1) %>
<%= f.submit %>
I have errors with generating the form, with updating the correct attributes, and also find the rating associated with the demo and user.
i've been having issues trying to build a Cart + Cart/OrderItem + Order combination simple enough yet effective. I've looked around online but i couldn't find anything that fit, so i tried something but .. i'm a bit blocked atm, i don't see how to continue with this. The problem is that i don't know how to get the items in the order and start the cart from scratch (btw it's kind of messy). Also a nice simple tutorial on this would be appreciated as well. Do note i already went through agile web's book example, but for some reason i didn't follow it, it didn't seem to be what i was looking for.
controllers - cart + order
class CartsController < ApplicationController before_filter :initialize_cart
def add
#cart.add_item params[:id]
session["cart"] = #cart.serialize
product = Product.find(params[:id])
redirect_to :back, notice: "Added #{product.name} to cart." end
def show end
def checkout
#order = Order.new user: User.new end end
class OrdersController < ApplicationController
def index
#orders = Order.order('created_at desc').page(params[:page])
end
def show
end
def new
#order = Order.new
end
def create
#order = Order.new(order_params)
respond_to do |format|
if #order.save
format.html { redirect_to root_path, notice:
'Thank you for your order' }
format.json { render action: 'show', status: :created,
location: #order }
else
format.html { render action: 'new' }
format.json { render json: #order.errors,
status: :unprocessable_entity }
end
end
end
private
def set_order
#order = Order.find(params[:id])
end
def order_params
params.require(:order).permit(:pay_type, :user_id)
end
end
now the models
class Order < ActiveRecord::Base
belongs_to :user
PAYMENT_TYPES = [ "Check", "Credit card", "Purchase order" ]
validates :pay_type, inclusion: PAYMENT_TYPES
end
class CartItem
attr_reader :product_id, :quantity
def initialize product_id, quantity = 1
#product_id = product_id
#quantity = quantity
end
def product
Product.find product_id
end
def total_price
product.price * quantity
end
end
class Cart
attr_reader :items
def self.build_from_hash hash
items = if hash["cart"] then
hash["cart"]["items"].map do |item_data|
CartItem.new item_data["product_id"], item_data["quantity"]
end
else
[]
end
new items
end
def initialize items = []
#items = items
end
def add_item product_id
item = #items.find { |item| item.product_id == product_id }
if item
item+=1
else
#items << CartItem.new(product_id)
end
end
def empty?
#items.empty?
end
def count
#items.length
end
def serialize
items = #items.map do |item|
{
"product_id" => item.product_id,
"quantity" => item.quantity
}
end
{
"items" => items
}
end
def total_price
#items.inject(0) { |sum, item| sum + item.total_price }
end
end
Thank you.
I have been searching the net for last few hours but cannot find how to call one of method in a controller that I have created in another controller which will be used then to populate my html page in my view.
The method I am trying to call is this one:
class CataloguesController < ApplicationController
def index
#catalogues = Catalogue.all
respond_to do |format|
format.html # index.html.erb
format.json { render json: #catalogues }
end
and i am trying to call it in the following method:
def index
session[:cart].catalogues = Catalogue.all
end
Rails in not having and is returning the following error when i run it in my html page.
undefined method `catalogues=' for {:id=>5}:Hash
Any help will be much appreciated...
EDIT
cart_controller
class CartController < ApplicationController
before_filter do
session[:cart] = session[:cart] || {} # set an empty cart if no cart exist
end
def index
session[:cart].catalogues = Catalogue.all
end
def success
end
def add
id = params[:id]
amount = session[:cart][id] || 0 # gets cart amount or 0 if no amount was found
session[:cart][id] = amount + 1 # set new amount
#catalogue = Catalogue.find(params[:id])
session[:cart] = {:id => #catalogue.id }
redirect_to action: "index"
# #catalogue = Catalogue.find(params[:id])
# session[:cart] = cart
# cart = {:id => #catalogue.id, :catalogue => #catalogue.name,category => #catalogue.category, :code => #catalogue.code , :colour=> #catalogue.colour, :description => #catalogue.description, :image => #catalogue.image , :unitprice => #catalogue.unitprice, :unitquantity => #catalogue.unitquantity, :unitweight => #catalogue.unitweight }
# session[:cart] = cart
# render 'cart/add'
end
def remove
end
def clear
end
def checkout
end
end
I'm using Instant-Rails 2.0 and following the Depot example project of Agile Web Development with Rails 3rd edition.
My question is: When a customer makes an order, with the cart and the order form, I need the update the column quantity of products table.
An example: If I have 10 books (the value "10" is stored in products table with the specific id of the product) and the customer wants 2 books, after the order I want that my project updates the quantity value of available books, decrement it to 8 books.
I tried to add that in store_controller.rb, in the add_to_cart method:
def add_to_cart
product = Product.find(params[:id])
#quantity = Product.find(params[:quantity])
#cart = find_cart
#current_item = #cart.add_product(product)
#removed = Product.remove_q(#quantity)
respond_to do |format|
format.js if request.xhr?
format.html {redirect_to_index}
end
rescue ActiveRecord::RecordNotFound
logger.error("Product not found #{params[:id]}")
redirect_to_index("invalid product!")
end
Where remove_q is a method of product.rb model:
def self.remove_q(quantity)
#quantity = quantity - 1
end
RoR gives me the error "product not found" in the console when I click in the "add to cart" button. What am I doing wrong?
UPDATE: Thanks to ipsum for answer. The solution is to decrement the quantities of products after successful order. This is the method save_order of store_controller.rb:
def save_order
#cart = find_cart
#order = Order.new(params[:order])
#order.add_line_items_from_cart(#cart)
#recipient = 'email#notify.com'
#subject = 'Order'
email = Emailer.create_confirm(#order, #recipient, #subject)
email.set_content_type("text/html")
#cliente = sent
if #order.save
Emailer.deliver(email)
return if request.xhr?
session[:cart] = nil
redirect_to_index("Thank you")
else
render :action => 'checkout'
end
end
Please note that Emailer is a model for notification via email after successful order, the cart is made from many line_items that are the products customer adds to cart. How can I decrement the quantities of products in cart after successful order? How can I extract products from cart?
There is the model cart.rb:
class Cart
attr_reader :items
def initialize
#items = []
end
def add_product(product)
current_item = #items.find {|item| item.product == product}
if current_item
current_item.increment_quantity
else
current_item = CartItem.new(product)
#items << current_item
end
current_item
end
def total_price
#items.sum { |item| item.price}
end
def total_items
#items.sum { |item| item.quantity }
end
end
and the model line_item.rb:
class LineItem < ActiveRecord::Base
belongs_to :order
belongs_to :product
def self.from_cart_item(cart_item)
li = self.new
li.product = cart_item.product
li.quantity = cart_item.quantity
li.total_price = cart_item.price
li
end
end
You try to find a product through the quantity.
but "find" expects a primary key
Instead of:
#quantity = Product.find(params[:quantity])
try this:
#quantity = product.quantity
UPDATE:
def add_to_cart
product = Product.find(params[:id])
#cart = find_cart
#current_item = #cart.add_product(product)
product.decrement!(:quantity, params[:quantity])
respond_to do |format|
format.js if request.xhr?
format.html {redirect_to_index}
end
rescue ActiveRecord::RecordNotFound
logger.error("Product not found #{params[:id]}")
redirect_to_index("invalid product!")
end