購物車實作思路

購物車實作思路:

[TOC]

1.建立將商品加入到購物車的action

(1)在商品頁面新建“加入購物車”按鈕
(2)設定product的routes

resources :products do
member do
post :add_to_cart
end
end

(3)在products_controller中加入add_to_cart的action

def add_to_cart
@product = Product.find(params[:id])
flash[:notice] = "已將商品加入到購物車"
redirect_to :back
end

功能:上述步驟完成后,在商品詳情頁面,點擊“加入購物車”會提示“商品已加入購物車”,但還沒有指定商品加入到了哪臺購物車。

2.建立商品product和購物車cart之間的關系

(1)由于一種商品可以加入到不同的購物車中,一個購物車也可以加入不同種商品,因此商品和購物車之間是多對多關系,這時候我們需要引入第三方model(購物欄)來建立商品product和購物車cart之間的關系
(2)新建購物車的model cart,和購物欄的model cart_item
終端執行:
rails g model cart
rails g model cart_item
打開cart_item的migration文件,在其中記錄商品product_id,購物車cart_id和購物欄自身數量quantity默認為1(商品對應購物欄的數量就是購物車中該商品的數量,通過修改購物欄自身數量來修改購物車中同一種商品的數量,進而計算購物車總價)
(3)建立購物車cart,購物欄cart_item,商品product之間的關系
在cart.rb中加入:

has_many :cart_items
has_many :products through: :cart_items, source: :product

注意上述代碼中products是購物車已加入的商品,后面的source: :product中的product是所有商品,如果怕搞混了,可以將前面的products修改為其他任意合法的變量,比如goods
在cart_item.rb中加入:

belongs_to :cart
belongs_to :product

這樣就可以通過購物車cart就可以通過購物欄cart_item找到對應的商品product
這里我們不在product.rb中加入對稱的關系,因為我們不需要通過商品good來找對應的購物車cart。我們采用rails中基于cookie的session機制來分配給用戶購物車,并追蹤用戶的購物車,以防購物車錯亂。
(4)為當前在操作的用戶分配購物車
在全局的application_controller中加入代碼

helper_method :current_cart
def current_cart
@current_cart ||= find_cart
end

也加入代碼:

private

def find_cart
cart = Cart.find_by(id: session[:cart_id])
if cart.blank?
cart = Cart.create
end
session[:cart_id] = cart.id
return cart
end

之所以要寫在application_controller中是因為我們希望無論進入哪個頁面購物車的信息都保留,比如current_cart這個方法會在購物車,訂單頁面都會用到,確保用戶的購物車不會錯亂
如果用戶有購物車,就通過session檢測用戶用了哪臺購物車,以確保自己的商品不會加入到別人的購物車中
如果用戶的購物車丟失,就重新為用戶分配一個購物車,重新登記購物車的信息
由于接下來我們要計算購物車中有多少種商品,計算購物車商品總價,都需要捕捉到用戶的購物車,就需要調用current_cart方法,但在視圖view中想要controller中的方法,需要聲明該controller方法為helper_method,因此這里我們將current_cart聲明為helper_method

(5)前面已經建立了購物車cart,購物欄cart_item,商品product之間的關系,這里通過新建購物欄cart_item,存入對應的商品product的信息,以及指定商品的購物欄數量,來將商品product存入到購物車的購物欄中,通過獲得是哪臺購物車,就可以知道商品被加入到哪個購物車了
在cart.rb中加入代碼:

def add_product_to_cart(product)
ci = cart_items.build #在購物車中新建購物欄
ci.product = product #記錄當前點擊的商品信息到購物欄中
ci.quantity = 1           #默認購物欄的數量是1(用來指代商品加入購物車后默認的數量為1,以便后面計算購物車商品總價)
ci.save
end

上述代碼中:

ci = cart_items.build #在購物車中新建購物欄

由于build和new互為別名方法(參考資料篇:build和new),因此也可以寫成:

ci = cart_items.new #在購物車中新建購物欄

但不能用

ci = CartItem.new

另外根據build和new互為別名方法,我猜測:

    ci = cart_items.build
    可以等價寫成
    ci = CartItem.new
    ci.cart = current_cart

但是model不能直接引用controller中的方法,因此這里我不知道怎么在model中告知cart_item,它對應的購物車cart是那一臺,具體參見:[helper方法,model方法,controller方法的運用](/Users/xyy/Documents/知識專題/ruby on rails/全棧營學習/學習總結/helper方法,model方法,controller方法的運用.md)

(6)將添加商品到購物車的方法加入product_controller中,這樣在商品頁面通過點擊“加入購物車”就可以將商品加入到購物車中,并且記錄了的是加入到了當前購物車
在product_controller中加入代碼:

def add_to_cart
@product = Product.find(params[:id])
+ current_cart.add_product_to_cart(@product)
flash[:notice] = "已將商品加入到購物車"
redirect_to :back
end

這里我們也可以不用cart model中建立的add_product_to_cart方法作,而是把其中的代碼放到controller中,然后刪除掉add_product_to_cart方法,需要略作修改:

def add_to_cart
@product = Product.find(params[:id])
- current_cart.add_product_to_cart(@product)
+ ci = current_cart.cart_items.build
+    ci.product = @product
+    ci.quantity =1
+    ci.save
    flash[:notice] = "已將商品加入到購物車"
redirect_to :back
end

這樣也是可行的,不過為了維護方便建議還是將代碼包裝成model方法,然后在controller中調用
(7)購物車圖標顯示當前購物車中有多少種商品
在導航欄視圖中加入購物車圖標,并顯示商品一共有多少種(這時候還沒有做判斷,即便是同一種商品,在多次點擊加入到購物車也會分別作為不同件商品處理)

<%= link_to "#" do %>
購物車 <i class="fa fa-shopping-cart"> </i> (<%= current_cart.products.count %>)
<% end %>

3.實作購物車詳情頁

(1)新建購物車的控制器carts_controller
終端執行:
rails g controller carts
這里在不在carts_controller中寫index action 都可以,因為我們要撈取的資料不是購物車,而是cart_item中的資料,所以沒有通過carts_controller的index action來操作model,進而操作資料庫,就可以不用寫index action.
(2)設定購物車詳情頁的路由
修改routes,在其中加入代碼:

+ resources :carts

(3)新建購物車詳情頁視圖carts/index.html.erb
在carts/index.html.erb中加入關鍵代碼:

<div class="row">
  <div class="col-md-12">

    <h2> 購物車 </h2>

    <table class="table table-bordered">
      <thead>
        <tr>
          <th colspan="2">商品資訊</th>
          <th>單價</th>
          <th>數量</th>
        </tr>
      </thead>
      <tbody>

        <% current_cart.cart_items.each do |cart_item| %>
          <tr>
            <td>
              <%= link_to product_path(cart_item.product) do %>
                <% if cart_item.product.image.present? %>
                  <%= image_tag(cart_item.product.image.thumb.url, class: "thumbnail") %>
                <% else %>
                  <%= image_tag("http://placehold.it/200x200&text=No Pic", class: "thumbnail") %>
                <% end %>
              <% end %>
            </td>
            <td>
              <%= link_to(cart_item.product.title, product_path(cart_item.product)) %>
            </td>
            <td>
              <%= cart_item.product.price %>
            </td>
            <td>
              <%= cart_item.quantity %>
            </td>
          </tr>
        <% end %>
      </tbody>
    </table>

    <br>
    <div class="total clearfix">
      <span class="pull-right">
         <span> 總計 xxx RMB  </span>
      </span>
    </div>

    <hr>
    <div class="checkout clearfix">
      <%= link_to("確認結賬", "#", method: :post, class: "btn btn-lg btn-danger pull-right") %>
    </div>
  </div>
</div>

如果商品有商品圖片,則顯示對應的商品圖片,如果沒有則顯示默認的預設圖
點擊商品圖可以跳轉到商品詳情頁
顯示商品的價格,和商品數量

(4)修改購物圖標對應的路徑,從而可以通過點擊購物車圖標進入購物車詳情頁

- <%= link_to "#" do %>
+ <%= link_to carts_path do %>
購物車 <i class="fa fa-shopping-cart"> </i> (<%= current_cart.products.count %>)
<% end %>

4.計算購物車中的商品總價

(1)修改購物車詳情頁carts/index.html.erb中的代碼

- 總計 xxx RMB
+ <% sum= 0 %>
+ <% current_cart.cart_items.each do |cart_item| %>
+ <if cart_item.product.price.present? %>
+ <% sum += cart_item.product.price * cart_item.quantity %>
+ <% end %>
+ <% end %>
+ 總計<%= sum %> RMB

(2)上述計算總計的代碼寫在view中不易維護和閱讀,我們將其移動到carts_helper.rb中,就便于維護和閱讀了
在carts_helper中添加代碼:

def render_cart_total_price(cart)
    sum = 0
    cart.cart_items.each do |cart_item|
    if cart_item.product.price.present?
    sum += cart_item.product.price * cart_item.quantity
    end
    end
    sum
end

修改carts/index.html.erb中計算商品總價的代碼:

- 總計 xxx RMB
- <% sum= 0 %>
- <% current_cart.cart_items.each do |cart_item| %>
- <if cart_item.product.price.present? %>
- <% sum += cart_item.product.price * cart_item.quantity %>
- <% end %>
- <% end %>
+ 總計<%= render_cart_total_price(current_cart) %> RMB

(3)其實計算商品總價的任務不應該交給helper,而應該交給model,因此我們可以進一步重構代碼
修改carts_helper中的render_cart_total_price為:

def render_cart_total_price(cart)
cart.total_price
end

在cart.rb中加入代碼:

def total_price
sum = 0
cart_items.each do |cart_item|
if if cart_item.product.price.present? && cart_item.quantity.present?
sum += cart_item.product.price * cart_item.quantity
end
end
sum
end

5.一鍵清空購物車

目的是清除購物車中的商品,而不是刪除購物車,因此要新建清空功能的clean action,而不是使用購物車的destory action
(1)修改routes

- resources :carts
+ resources :carts do
+ collection do
+ delete :clean
+ end
+ end

(2)在carts_controller中建立清空購物車的clean action

def clean
current_cart.clean!
flash[:warning] = "已清空購物車"
redirect_to :back
end

(3)在cart.rb中加入clean!方法,把操作數據庫的任務交給model

def clean!
cart_items.destroy_all
end

你也可以使用:

def clean!
products.destroy_all
end

這里的products是購物車cart通過購物欄cart_item中保存的商品,已在文章的步驟1有所介紹
當然,你也可以不用clean!方法,直接在carts_controller中的clean action中寫成:

def clean
current_cart.cart_items.destroy_all
flash[:warning] = "已清空購物車"
redirect_to :back
end

不過讓model方法和controller方法分別執行各自的任務比較好
但不能寫成:

def clean!
products.destroy_all
end
def clean
current_cart.clean!
flash[:warning] = "已清空購物車"
redirect_to :back
end

這樣會提示報錯,找不到clean!方法(undefined method `clean!'))
想要正常運行代碼,需要略作修改:

def clean!(cart)
cart.cart_items.destroy_all
end
def clean
clean!(current_cart)
flash[:warning] = "已清空購物車"
redirect_to :back
end

注意:
這是因為current_cart.clean!方法用的是.方法,即從屬關系,因此它可以引用model中的方法,但是不能引用controller方法,因為controller中的方法不是從屬關系。
所以,我們不能用.來引用方法,而是包裝成一般的方法,通過傳入參數來調用controller方法
(4)在購物車詳情頁添加"清空購物車"的按鈕

<%= link_to("清空購物車",clean_carts_path,method: :post) %>

6.刪除購物車內的某一商品

由于購物車中的商品是存儲在購物欄cart_item中,因此我們需要建立cart_item的控制器,設定cart_item的路由,通過刪除商品對應的cart_item來完成刪除某一商品功能
(1)建立cart_item的控制器
終端執行:
rails g controller cart_items

(2)設定cart_items的路由(建立刪除購物車中某一商品按鈕對應的路徑)

resources :cart_items 

(3)在cart_items的控制器中建立destroy action

before_action :authenticate_user!
def destroy
@cart = current_cart
@cart_item = @cart.cart_items.find_by(product_id: params[:id])
@product = @cart_item.product
@cart_item.destroy
flash[:warning] = "已將'#{@product.title}'從購物車中刪除 "
redirect_to :back
end

(4)在購物車詳情頁carts/index中加入刪除商品的按鈕

<%= link_to cart_item_path(cart_item.product_id), method: :delete do %>
 <i class="fa fa-trash"></i>
 <% end %>

這里也可以使用cart_item作為路徑參數,不過需要將destroy action中的代碼修改為:

- @cart_item = @cart.cart_items.find_by(product_id: params[:id])
+ @cart_item = @cart.cart_items.find(params[:id])

補充:
關于使用cart_item自身id,還是cart_item對應的product_id來作為路徑參數,主要原因是routes路徑中的id和傳入的參數有關,具體內容參考我的這篇[購物車篇(5):刪除購物車中的某一件商品](/Users/xyy/Documents/知識專題/ruby on rails/全棧營學習/"購物網站"復習總結/購物車篇(5):刪除購物車中的某一件商品.md)

7.限制不能重復加入同種商品

前面已經建立了購物車cart和商品product之間的關系了,要想讓重復的商品不能再次加入,需要加一個判斷條件,即:檢查用戶的購物車中是否有該種商品,如果沒有則加入。
在products_contoller中的add_to_cart action中加入代碼:

def add_to_cart
@product = Product.find(params[:id])
+ if !current_cart.products.include?(@product)
current_cart.add_product_to_cart
- flash[:notice] = "成功加入購物車"
+ flash[:notice]="成功將 #{@product.title} 加入購物車""
else
+ flash[:warning] ="你的購物車內已有此物品"
+ end
redirect_to :back
end

8.用戶可以更改購物車中的商品數量

(1)在cart_items的controller中加入update action,讓用戶可以修改商品數量

def update
@cart = current_cart
@cart_item = @cart.cart_items.find_by(product_id: params[:id])
@cart_item.update(cart_item_params)
redirect_to carts_path
end

private

def cart_item_params
params.require(:cart_item).permit(:quantity)
end

(2)在購物車詳情頁carts/index加入更新商品數量的按鈕

- <%= cart_item.quantity %>
+ <%= form_for cart_item, url: cart_item_path(cart_item.product_id) do |f| %>
+ <%= f.select :quantity, [1,2,3,4,5] %>
+ <%= f.submit "更新", data: { disable_with: "Submiting..." } %>
+ <% end %>

你可以將數組[1,2,3,4,5]該成range形式,如(1..5)

9.庫存為 0 的商品不能購買

要考慮兩點,一是商品庫存為0,相應的商品頁面的"加入購物車"按鈕要被替換;二是由于很多用戶通過購物車購買會對同一種商品的數量有影響,因此要在cart_item的controller種加入條件判斷:如果用戶在購物車種設定的購買商品的數量大于當前商品的庫存,則不能更新購買數量
(1)在商品詳情頁添加條件判斷,如果商品庫存為0,則提示"商品已售完"

+ <% if @product.quantity.present? && @product.quantity > 0 %>
<%= link_to("加入購物車", add_to_cart_product_path(@product), method: :post,
                    class: "btn btn-lg btn-danger") %>
+ <% else %>
+ 商品已售完
+ <% end %>

(2)修改cart_items_controller中的update action,加入條件判斷

def update
@cart = current_cart
@cart_item = @cart.cart_item.find_by(product_id: params[:id])
+ if @cart_item.product.quantity.present? && @cart_item.product.quantity >=cart_item_params[:quantity].to_i
@cart_item.update(cart_item_params)
+ flash[:notice] = "成功變更數量"
+ else
+ flash[:warning] = "數量不足以加入購物車"
+ end

redirect_to carts_path
end

  private
  
  def cart_item_params
    params.require(:cart_item).permit(:quantity)
  end

這里的 cart_item_params[:quantity]是從cart_item資料庫中獲取對應的數字,但它是數組型的,因此我們需要用to_i將數字數組轉換成數字,才可以用比較運算符進行計算

10.在購物車新增數量時,不能更新超過原有庫存的數量

修改購物車詳情頁carts/index

<%= form_for cart_item, url: cart_item_path(cart_item.product_id) do |f| %>
-               <%= f.select :quantity, [1,2,3,4,5] %>
+               <%= f.select :quantity, 1..cart_item.product.quantity %>
                <%= f.submit "更新", data: { disable_with: "Submiting..." } %>
              <% end %>

其中1..cart_item.product.quantity也可以寫成(1..cart_item.product.quantity)

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容