Rails – Tùy chỉnh giỏ hàng

3.9/5 - (15 votes)

Chúng ta sẽ thêm nút xóa giỏ hàng, hiển thị tổng tiền cho giỏ hàng.

Thêm nút xóa giỏ hàng

Đầu tiên chúng ta sửa lại file View show.html.erb trong thư mục app/views/carts như sau:

<%= notice %>
<h2>Your cart</h2>
<ul>
    <% @cart.line_items.each do |a| do %>
        <li><%= a.quantity %> x <%= a.product.title %></li>
    <% end %>
</ul>

<%= button_to 'Empty cart', 
              @cart, 
              :method => :delete,
              data: {:confirm => 'Are you sure?'} %>

Chúng ta gọi phương thức button_to để tạo một nút bấm với nhãn là ‘Empty cart’, URL chúng ta lấy từ biến @cart, nếu muốn bạn vẫn có thể dùng các phương thức như cart_path(), phương thức gửi lên là DELETE, tham số :data với thuộc tính :confirm sẽ tạo ra một hộp thoại như hàm alert() trong Javascript nếu bạn còn nhớ.

Ngoài ra chúng ta cũng đã xóa 2 nút EditBack do Rails tự tạo nếu bạn còn nhớ:

<%= link_to 'Edit', edit_cart_path(@cart) %> |
<%= link_to 'Back', carts_path %>

Nút ‘Empty cart’ sẽ gọi đến phương thức destroy trong lớp controller CartsController, trong trang /cart Rails cũng đã tạo cho chúng ta một nút như vậy với nhãn là ‘Destroy’ (nếu muốn bạn có thể vào xem thử), và mặc định phương thức destroy trong lớp Controller sẽ xóa giỏ hàng với id được truyền từ tham số, tuy nhiên ở đây chúng ta muốn phương thức này chỉ xóa giỏ hàng nào đang được lưu trong session của trình duyệt thôi, do đó chúng ta sửa lại phương thức destroy này như sau:

class CartsController < ApplicationController 
    .
    .
    .
    # DELETE /carts/1
    # DELETE /carts/1.json
    def destroy 
        @cart = current_cart
        @cart.destroy
        session[:cart_id] = nil
 
        respond_to do |format|
            format.html { redirect_to carts_url, notice: 'Cart was successfully destroyed.' }
            format.json { head :no_content }
        end
    end
    .
    .
    .
end

Chúng ta lấy model Cart hiện tại bằng phương thức current_cart đã được định nghĩa trong lớp ApplicationController, sau đó gọi hàm destroy để xóa, cuối cùng gán giá trị trong sessionnil.

Hiển thị tổng số tiền trong giỏ hàng

Chúng ta sửa lại file View show.html.erb một lần nữa như sau:

<%= notice %>
<h2>Your cart</h2>
<table>
    <% @cart.line_items.each do |item| %>
        <tr>
            <td><%= item.quantity %> x </td>
            <td><%= item.product.title %></td>
            <td class="item_price"><%= number_to_currency(item.total_price) %></td>
        </tr>
    <% end %>
    
    <tr class="total_line">
        <td colspan="2">Total</td>
        <td class="total_cell"><%= number_to_currency(@cart.total_price) %></td>
    </tr>
</table>

<%= button_to 'Empty cart', 
              @cart, 
              :method => :delete,
              data: {:confirm => 'Are you sure?'} %>

Ở đây thay vì dùng các thẻ <ul>, <li> để hiển thị danh sách thì chúng ta sẽ dùng bảng. Giá trị của tổng số tiền trên từng LineItem và tổng số tiền của Cart sẽ lấy từ phương thức total_price của lớp model tương ứng.

Các thẻ trên cũng dùng một số lớp CSS riêng, chúng ta định nghĩa các lớp CSS mới như sau:

.
.
.
#store .cart_title {
    font: 120% bold;
}

#store .item_price, #store .total_line {
    text-align: right;
}

#store .total_line .total_cell {
    font-weight: bold;
    border-top: 1px solid #595;
}

Và bây giờ chúng ta sẽ định nghĩa phương thức total_price cho từng model, đầu tiên chúng ta định nghĩa cho lớp LineItem như sau:

class LineItem < ActiveRecord::Base
    belongs_to :product
    belongs_to :cart
 
    def total_price
        product.price * quantity
    end
end

Rất đơn giản, số tiền bằng tổng số sản phẩm nhân với đơn giá sản phẩm.

Tiếp theo là lớp Cart:

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)
        if current_item 
            current_item.quantity += 1
        else
            current_item = line_items.build(:product_id => product_id)
        end
        current_item
    end
 
    def total_price 
        line_items.to_a.sum { |item| item.total_price }
    end
end

Ở đây số tiền sẽ bằng tổng số tiền của các đối tượng LineItem cộng lại. Phương thức to_a sẽ chuyển một đối tượng danh sách sang kiểu mảng, sau đó chúng ta gọi phương thức sum của mảng đó.

Bạn có thể xem tổng tiền và có thể xóa giỏ hàng được rồi:

capture

Ngoài ra nếu trước khi làm chức năng session mà bạn đã từng tạo nhiều giỏ hàng thì trong trang /carts (liệt kê danh sách giỏ hàng), trang này gọi đến phương thức index trong lớp CartsController, chúng ta sẽ thấy có nhiều dòng giỏ hàng, đây là trang hiển thị mặc định của Rails mà đến bây giờ chúng ta vẫn chưa chỉnh sửa gì trong này.

capture

Trang này có hiển thị nút ‘Destroy’ để bạn xóa giỏ hàng, tuy nhiên sau khi làm chức năng chỉ xóa giỏ hàng hiện tại ở trên thì bạn chỉ có thể xóa một giỏ hàng trong danh sách đó thôi, mặc dù nếu muốn bạn vẫn có thể bấm nút xóa trên các giỏ hàng khác và Rails vẫn thông báo xóa thành công, nhưng khi load lại trang thì bạn vẫn thấy các giỏ hàng đó tồn tại. Chúng ta sửa lại để hàm này chỉ hiển thị giỏ hàng trong session của trình duyệt như sau:

class CartsController < ApplicationController
    .
    .
    .
    # GET /carts
    # GET /carts.json
    def index  
        @carts = []
        if session[:cart_id] != nil 
            @carts << Cart.find_by_id(session[:cart_id])
        end
    end
    .
    .
    .
end

Vậy là xong, bây giờ nếu bạn mở 2 trình duyệt hoặc mở 2 máy khác nhau, và mỗi trình duyệt đều có giỏ hàng riêng, và cùng trỏ đến trang /carts thì bạn chỉ thấy giỏ hàng của từng trình duyệt đó chứ không thấy giỏ hàng của trình duyệt khác.

0 0 votes
Article Rating
Subscribe
Thông báo cho tôi qua email khi
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

10 Comments
Inline Feedbacks
View all comments
Anh
Anh
8 năm trước

Cậu ơi giúp tớ mục này với User model// def add_to_list(item) current_item = enrollments.find_by(item_id: item.id) if current_item current_item.quantity += 1 else current_item = enrollments.build(item_id: item.id) end current_item end def remove_from_list(item) current_item = enrollments.find_by(item_id: item.id) if current_item &amp;&amp; current_item.quantity &gt; 2 current_item.quantity -= 1 else current_item.destroy end current_item end Phần add_to_list nó chạy rất ok nhưng khi mình muốn sử dụng phần xóa 1 items đi thì nó lại không chạy .destroy thì chạy nhưng current_item.quantity -= 1 thì không. Rõ ràng là code có chạy 1 phần nhưng mục giảm số lượng thì lại không chạy đc :(( Còn đây… Đọc thêm »

Anh
Anh
8 năm trước

cảm ơn cậu, còn 1 cái cực kì quan trọng nữa
many to many association mà qua through: :cart ý
Làm sao để edit thông tin ở đó vậy nhỉ
User >> CART << Item
t muốn tạo 1 cái form để update
User.find(58).CART.first.quantity
Làm cách nào để tớ update or Put cái quantity của Item add vào trong giỏ hàng đc nhỉ

comment image
sr cậu nếu t giải thích hơi khó hiểu.
Túm lại là update trong Many to Many association

Anh
Anh
8 năm trước
Reply to  Phở Code

ý tớ là giống như là update item ý, sách này nó chỉ dạy mình cách add thêm vào thôi chứ không chỉ cách chọn item bất kì giống như các trang TMĐT khác, tớ thử làm 2 cách là 1. tạo edit cho phần item trong cart 2. get @quantity = params[:quantity] Đều k được Đành phải dùng cách cuối là 2 lựa chọn add more 1 và 10 Cậu nghĩ ra cách nào mà update được dựa trên Many to Many association thì chỉ tớ nhé, làm kiểu bình thường không ăn thua Tớ chèn item vào mục… Đọc thêm »

Anh
Anh
8 năm trước

Có cậu ạ ví dụ nhé
K có association: Tạo user mới thì là User.create
User has many item and (Item belongs_to a User) @item = current_user.items.build

Còn với mục has many, :through
User has many items :through :line_item
Item has many users :through :line_item
line_item belongs_to ..
Model LineItem gồm có user_id. item_id, quantity

Thì tớ không biết để update quantity của LineItem bằng form dùng như thế nào

Anh
Anh
8 năm trước

T làm được r, chắc t phải tiết kiệm đc 3 ngày nhờ cm này của cậu :)). Cần gì mình trao đổi thêm nhé.