Commit c2d0a2b6 by Hoang Phuc Do

Refactor stock feature

parents 59977d5a 57588ebc
...@@ -2,51 +2,52 @@ class CartsController < ApplicationController ...@@ -2,51 +2,52 @@ class CartsController < ApplicationController
include CartsHelper include CartsHelper
before_action :set_cart before_action :set_cart
before_action :set_item, only: [:add_line_item, :update_line_item, :remove_line_item] before_action :set_product_item,
before_action -> { render_warning_notice('Please specify quantity') }, only: [:add_product_item, :update_product_item, :remove_product_item]
if: :quantity_equal_zero?, only: :add_line_item before_action :build_empty_quantity_notice, if: :quantity_equal_zero?,
before_action -> { render_warning_notice('Product is out of stock') }, only: :add_product_item
unless: :product_is_out_of_stock?,
only: [:add_line_item, :update_line_item]
rescue_from ActiveRecord::RecordNotFound, with: :invalid_cart rescue_from ActiveRecord::RecordNotFound, with: :invalid_cart
# GET /cart # GET /cart
def index def index
@line_items = @cart.get_all_items @product_items = @cart.product_items
end end
# POST /cart/add/1 # POST /cart/add/1
def add_line_item def add_product_item
@cart.add(*cart_params.to_h.values) if @cart.add_product_item(params[:product_id], params[:product_quantity])
respond_to do |format|
respond_to do |format| format.js do
notice_msg = { msg: "#{@item.title} was sucessfully added to your cart", @notice = { msg: "#{@product_item.title} was sucessfully added to your cart",
type: :success } type: :success }
format.html { redirect_to cart_index_url, notice: notice_msg[:msg] } end
format.js { @notice = { msg: notice_msg[:msg], type: notice_msg[:type] } } end
else
render_warning_notice('Product is out of stock')
end end
end end
# PUT/PATCH /cart/update/1 # PUT/PATCH /cart/update/1
def update_line_item def update_product_item
@cart.update(*cart_params.to_h.values)
respond_to do |format| respond_to do |format|
notice_msg = { msg: "#{@item.title} was sucessfully updated", if @cart.update_product_item(params[:product_id],
type: :success } params[:product_quantity])
format.html { redirect_to cart_index_url, notice: "#{@product_item.title} was sucessfully updated" }
format.html { redirect_to cart_index_url, notice: notice_msg[:msg] } else
format.js { @notice = { msg: notice_msg[:msg], type: notice_msg[:type] }} format.html { redirect_to cart_index_url, notice: "Can not update #{@product_item.title}" }
end
end end
end end
# DELETE /cart/1 # DELETE /cart/1
def remove_line_item def remove_product_item
@cart.remove_item(params[:id])
respond_to do |format| respond_to do |format|
notice_msg = "#{@item.title} was sucessfully removed from your cart" if @cart.remove_product_item(params[:product_id])
format.html { redirect_to cart_index_url, notice: notice_msg } format.html { redirect_to cart_index_url, notice: "#{@product_item.title} was sucessfully removed from your cart" }
else
format.html { redirect_to cart_index_url, notice: "#{@product_item.title} can not remove from your cart" }
end
end end
end end
...@@ -55,36 +56,27 @@ class CartsController < ApplicationController ...@@ -55,36 +56,27 @@ class CartsController < ApplicationController
destroy_cart_session destroy_cart_session
respond_to do |format| respond_to do |format|
format.html { redirect_to root_url } format.html { redirect_to root_url, notice: 'Your cart is empty' }
end end
end end
private private
def cart_params
params.permit(:id, :product_quantity)
end
def invalid_cart def invalid_cart
destroy_cart_session destroy_cart_session
redirect_to root_url redirect_to root_url
end end
def set_item def set_product_item
@item = Product.find(params[:id]) @product_item = Product.find(params[:product_id])
end end
def quantity_equal_zero? def quantity_equal_zero?
params[:product_quantity].to_i.zero? params[:product_quantity].to_i.zero?
end end
def product_is_out_of_stock? def build_empty_quantity_notice
if action_name == 'add_line_item' render_warning_notice('Please specify quantity')
input_quantity = @cart.get_item_quantity(*cart_params.to_h.values)
else
input_quantity = params[:product_quantity]
end
Product.find(params[:id]).is_in_stock?(input_quantity)
end end
def render_warning_notice(notice) def render_warning_notice(notice)
......
...@@ -3,7 +3,7 @@ class OrdersController < ApplicationController ...@@ -3,7 +3,7 @@ class OrdersController < ApplicationController
before_action :authenticate_user!, only: :new before_action :authenticate_user!, only: :new
before_action :set_cart, only: [:new, :create] before_action :set_cart, only: [:new, :create]
before_action :set_line_items, only: :new before_action :set_order_items, only: :new
# GET /orders/new # GET /orders/new
def new def new
...@@ -13,27 +13,30 @@ class OrdersController < ApplicationController ...@@ -13,27 +13,30 @@ class OrdersController < ApplicationController
# POST /orders # POST /orders
def create def create
@order = current_user.orders.create! @order = Order.create(user: current_user)
create_line_items_for_order(@order) save_order_items(@order)
respond_to do |format| respond_to do |format|
destroy_cart_session if !@order.blank?
OrderMailer.send_order_detail_to_user(current_user, @order).deliver_later destroy_cart_session
format.html { redirect_to root_url, notice: 'Your order is successfully created' } OrderMailer.send_order_detail_to_user(current_user, @order).deliver_later
format.html { redirect_to root_url, notice: 'Your order is successfully created' }
else
format.html { redirect_to root_url, notice: 'Your order can not be created' }
end
end end
rescue ActiveRecord::RecordNotSaved
redirect_to root_url, notice: 'Your order can not be created'
end end
def create_line_items_for_order(order) def save_order_items(order)
@cart.get_all_items.each do |_, attrs| @cart.product_items.values.each do |attrs|
order.line_items.create(product: attrs[:product], quantity: attrs[:quantity]) order.product_items.create(product: attrs[:product],
quantity: attrs[:quantity])
end end
end end
private private
def set_line_items def set_order_items
@line_items = @cart.get_all_items @order_items = @cart.product_items
end end
end end
\ No newline at end of file
module CartsHelper module CartsHelper
def set_cart def set_cart
session[:cart] ||= {} session[:cart] ||= {}
@cart = Cart.new(session[:cart]) @cart = Cart.new(Product, session[:cart])
end end
def destroy_cart_session def destroy_cart_session
......
class Order < ApplicationRecord class Order < ApplicationRecord
has_many :line_items, dependent: :destroy has_many :product_items, dependent: :destroy
belongs_to :user belongs_to :user
def total_price def total_price
line_items.to_a.sum { |item| item.product.price * item.quantity } product_items.to_a.sum { |item| item.product.price * item.quantity }
end end
end end
\ No newline at end of file
...@@ -7,7 +7,7 @@ class Product < ApplicationRecord ...@@ -7,7 +7,7 @@ class Product < ApplicationRecord
validates :category_id, presence: true validates :category_id, presence: true
validates :user_id, presence: true validates :user_id, presence: true
def is_in_stock?(required_quantity) def in_stock?(required_quantity)
quantity >= required_quantity.to_i quantity >= required_quantity.to_i
end end
......
class LineItem < ApplicationRecord class ProductItem < ApplicationRecord
belongs_to :product, optional: true belongs_to :product, optional: true
belongs_to :order, optional: true belongs_to :order, optional: true
after_create :update_product_quantity after_create :update_product_quantity
......
class Cart class Cart
def initialize(cart_session = {}) def initialize(product, cart_session = {})
@cart = cart_session @cart = cart_session
@product = product
end end
def add(id, quantity) def product_items
return if quantity.to_i.zero? product_items = {}
if @cart.key?(id.to_s) @cart.each do |id, attrs|
@cart[id.to_s][:quantity.to_s] += quantity.to_i product_items[id] = { product: @product.find(id),
quantity: attrs[:quantity.to_s].to_i }
end
product_items
end
def add_product_item(product_id, quantity)
real_quantity = product_quantity(product_id, quantity)
return false unless product_is_in_stock?(product_id, real_quantity)
if product_item_exist?(product_id)
@cart[product_id.to_s][:quantity.to_s] += real_quantity.to_i
else else
@cart[id.to_s] = { quantity: quantity.to_i } @cart[product_id.to_s] = { quantity: real_quantity.to_i }
end end
end end
def update(id, quantity) def update_product_item(product_id, quantity)
return unless @cart.key?(id) return false unless can_update_product_item?(product_id, quantity)
if quantity.to_i.zero? if quantity.to_i.zero?
remove_item(id) remove_product_item(product_id)
else else
@cart[id][:quantity.to_s] = quantity.to_i @cart[product_id][:quantity.to_s] = quantity.to_i
end end
end end
def get_item_quantity(id, new_quantity = 0) def remove_product_item(product_id)
return new_quantity.to_i if @cart.empty? || !@cart.key?(id.to_s) @cart.tap { |cart| cart.delete(product_id) } if product_item_exist?(product_id)
old_quantity = @cart[id.to_s][:quantity] || @cart[id.to_s][:quantity.to_s] end
def product_quantity(product_id, new_quantity = 0)
return new_quantity.to_i if @cart.empty? || !product_item_exist?(product_id)
old_quantity = @cart[product_id.to_s][:quantity] ||
@cart[product_id.to_s][:quantity.to_s]
new_quantity.to_i + old_quantity new_quantity.to_i + old_quantity
end end
def get_all_items def can_update_product_item?(product_id, quantity)
line_items = {} product_is_in_stock?(product_id, quantity) && product_item_exist?(product_id)
@cart.each do |id, attrs| end
line_items[id] = { product: Product.find(id),
quantity: attrs[:quantity.to_s].to_i } def product_is_in_stock?(product_id, quantity)
end @product.find(product_id).in_stock?(quantity)
line_items
end end
def remove_item(id) def product_item_exist?(product_id)
@cart.tap { |cart| cart.delete(id) } if @cart.key?(id) @cart.key?(product_id.to_s)
end end
def cal_total_price def cal_total_price
get_all_items.sum do |_, attrs| product_items.sum do |_product_id, attrs|
attrs[:product].price * attrs[:quantity] attrs[:product].price * attrs[:quantity]
end end
end end
def cal_total_items def cal_total_items
return 0 if @cart.nil? return 0 if @cart.nil?
@cart.sum { |_, attrs| attrs[:quantity] || attrs[:quantity.to_s] } @cart.sum { |_product_id, attrs| attrs[:quantity] || attrs[:quantity.to_s] }
end end
end end
\ No newline at end of file
...@@ -10,8 +10,8 @@ ...@@ -10,8 +10,8 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<% @line_items.each do |id, line_item| %> <% @product_items.each do |id, line_item| %>
<%= render partial: 'line_item', locals: { id: id, line_item: line_item } %> <%= render partial: 'product_item', locals: { id: id, product_item: line_item } %>
<% end %> <% end %>
</tbody> </tbody>
<tfoot> <tfoot>
......
<tr> <tr>
<td class="product-action-td"> <td class="product-action-td">
<%= link_to fa_icon('times'), cart_remove_line_item_path(id), method: :delete, data: { confirm: 'Are you sure?' } %> <%= link_to fa_icon('times'), cart_remove_product_item_path(id), method: :delete, data: { confirm: 'Are you sure?' } %>
</td> </td>
<td class="product-image-td"></td> <td class="product-image-td"></td>
<td class="product-name-td"> <td class="product-name-td">
<h2 class="product-name"> <h2 class="product-name">
<%= link_to line_item[:product].title, product_url(line_item[:product]) %> <%= link_to product_item[:product].title, product_item_url(product_item[:product]) %>
</h2> </h2>
</td> </td>
<td><%= number_to_currency(line_item[:product].price) %></td> <td><%= number_to_currency(product_item[:product].price) %></td>
<td> <td>
<%= form_tag cart_update_line_item_path(id), method: :put do %> <%= form_tag cart_update_product_item_path(id), method: :put do %>
<div class="qty-holder"> <div class="qty-holder">
<%= number_field_tag :product_quantity, line_item[:quantity], id: nil, class: 'qty-input' %> <%= number_field_tag :product_quantity, product_item[:quantity], id: nil, class: 'qty-input' %>
</div> </div>
<% end %> <% end %>
</td> </td>
<td><%= number_to_currency(line_item[:product].price * line_item[:quantity]) %></td> <td><%= number_to_currency(product_item[:product].price * product_item[:quantity]) %></td>
</tr> </tr>
\ No newline at end of file
...@@ -75,7 +75,7 @@ ...@@ -75,7 +75,7 @@
<th align="center" bgcolor="#EEEEEE" style="border: 1px solid #CCCCCC;">Price</th> <th align="center" bgcolor="#EEEEEE" style="border: 1px solid #CCCCCC;">Price</th>
<th align="center" bgcolor="#EEEEEE" style="border: 1px solid #CCCCCC;">Total</th> <th align="center" bgcolor="#EEEEEE" style="border: 1px solid #CCCCCC;">Total</th>
</tr> </tr>
<% @order.line_items.each do |item| %> <% @order.product_items.each do |item| %>
<tr valign="top"> <tr valign="top">
<td align="left" style="border: 1px solid #CCCCCC;"> <td align="left" style="border: 1px solid #CCCCCC;">
<%= item.product.id %> <%= item.product.id %>
......
...@@ -28,8 +28,8 @@ ...@@ -28,8 +28,8 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<% @line_items.each do |id, line_item| %> <% @order_items.each do |id, product_item| %>
<%= render partial: 'line_items/line_item', locals: {id: id, line_item: line_item} %> <%= render partial: 'product_items/product_item', locals: {id: id, product_item: product_item} %>
<% end %> <% end %>
</tbody> </tbody>
</table> </table>
......
...@@ -2,10 +2,10 @@ ...@@ -2,10 +2,10 @@
<td class="product-image-td"></td> <td class="product-image-td"></td>
<td class="product-name-td"> <td class="product-name-td">
<h2 class="product-name"> <h2 class="product-name">
<%= link_to line_item[:product].title, product_url(line_item[:product]) %> <%= link_to product_item[:product].title, product_url(product_item[:product]) %>
</h2> </h2>
</td> </td>
<td><%= number_to_currency(line_item[:product].price) %></td> <td><%= number_to_currency(product_item[:product].price) %></td>
<td><%= line_item[:quantity]%></td> <td><%= product_item[:quantity]%></td>
<td><%= number_to_currency(line_item[:product].price * line_item[:quantity]) %></td> <td><%= number_to_currency(product_item[:product].price * product_item[:quantity]) %></td>
</tr> </tr>
\ No newline at end of file
...@@ -26,7 +26,7 @@ ...@@ -26,7 +26,7 @@
</p> </p>
</div> </div>
<div class="product-actions"> <div class="product-actions">
<%= form_tag cart_add_line_item_path(product.id), remote: true do %> <%= form_tag cart_add_product_item_path(product.id), remote: true do %>
<div class="product-detail-qty"> <div class="product-detail-qty">
<%= number_field_tag :product_quantity, 1, min: 0, class: 'vertical-spinner' %> <%= number_field_tag :product_quantity, 1, min: 0, class: 'vertical-spinner' %>
</div> </div>
......
...@@ -4,12 +4,15 @@ Rails.application.routes.draw do ...@@ -4,12 +4,15 @@ Rails.application.routes.draw do
resources :categories resources :categories
resources :products resources :products
resources :orders, only: [:new, :create, :show] resources :orders, only: [:new, :create, :show]
resources :line_items resources :product_items
scope 'cart' do scope 'cart' do
get '/', to: 'carts#index', as: 'cart_index' get '/', to: 'carts#index', as: 'cart_index'
post '/add/:id', to: 'carts#add_line_item', as: 'cart_add_line_item' post '/add/:product_id', to: 'carts#add_product_item',
put '/update/:id', to: 'carts#update_line_item', as: 'cart_update_line_item' as: 'cart_add_product_item'
delete '/remove/:id', to: 'carts#remove_line_item', as: 'cart_remove_line_item' put '/update/:product_id', to: 'carts#update_product_item',
as: 'cart_update_product_item'
delete '/remove/:product_id', to: 'carts#remove_product_item',
as: 'cart_remove_product_item'
delete '/remove', to: 'carts#destroy', as: 'cart_destroy' delete '/remove', to: 'carts#destroy', as: 'cart_destroy'
end end
devise_for :users devise_for :users
......
class CreateLineItems < ActiveRecord::Migration[5.1] class CreateProductItems < ActiveRecord::Migration[5.1]
def change def change
create_table :line_items do |t| create_table :product_items do |t|
t.integer :quantity, default: 1 t.integer :quantity, default: 1
t.references :product, index: true t.references :product, index: true
t.references :order, index: true t.references :order, index: true
......
...@@ -17,21 +17,21 @@ ActiveRecord::Schema.define(version: 20170615035827) do ...@@ -17,21 +17,21 @@ ActiveRecord::Schema.define(version: 20170615035827) do
t.text "description" t.text "description"
end end
create_table "line_items", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8" do |t| create_table "orders", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8" do |t|
t.integer "quantity", default: 1 t.bigint "user_id"
t.bigint "product_id"
t.bigint "order_id"
t.datetime "created_at", null: false t.datetime "created_at", null: false
t.datetime "updated_at", null: false t.datetime "updated_at", null: false
t.index ["order_id"], name: "index_line_items_on_order_id" t.index ["user_id"], name: "index_orders_on_user_id"
t.index ["product_id"], name: "index_line_items_on_product_id"
end end
create_table "orders", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8" do |t| create_table "product_items", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8" do |t|
t.bigint "user_id" t.integer "quantity", default: 1
t.bigint "product_id"
t.bigint "order_id"
t.datetime "created_at", null: false t.datetime "created_at", null: false
t.datetime "updated_at", null: false t.datetime "updated_at", null: false
t.index ["user_id"], name: "index_orders_on_user_id" t.index ["order_id"], name: "index_product_items_on_order_id"
t.index ["product_id"], name: "index_product_items_on_product_id"
end end
create_table "products", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8" do |t| create_table "products", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8" do |t|
......
class Cart
def abc
'hi'
end
end
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment