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


Được đăng vào ngày 24/11/2016 | 10 bình luận
Rails – Tùy chỉnh giỏ hàng
5 (100%) 1 vote

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.







Bình luận (10)

  1. Anh

    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 là controller enrollment, many to many association

      def create
    
        item = Item.find(params[:item_id])
        @enrollment = @current_user.add_to_list(item)
    
        respond_to do |format|
          if @enrollment.save
            flash[:success] = "Item was successfully added"
            format.html { redirect_to @enrollment.user # action: 'show', id: user.id
               }
          else
            format.html {render :index}
    
          end
        end
      end
    
      def remove_a_item
        item = Item.find(params[:item_id])
        @enrollment = @current_user.remove_from_list(item)
        
        respond_to do |format|
          flash[:danger] = "Item was successfully removed"
          format.html { redirect_to @enrollment.user }
        end
      end
    
    1. Phở Code Admin

      current_item.destroy chạy nhưng current_item.quantity -= 1 không chạy là đúng mà bạn.
      Chỗ đó bạn làm if…else, một cái chạy thì cái kia phải không chạy chứ 🙂

  2. Anh

    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ỉ

    https://instag-clone.s3.amazonaws.com/uploads/pet/image/50/Capture.PNG
    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

    1. Phở Code Admin

      Ý bạn là update đối tượng Item thông qua đối tượng Cart phải không?
      Nếu vậy thì bạn chỉ cần lấy đối tượng Item đó là được:

      item = Cart.items.find_by_id(...)
      item.quantity = item.quantity + 1
      
      1. Anh

        ý 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
        https://instag-clone.s3.amazonaws.com/uploads/pet/image/51/Untitled_picture.png

        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 show của User luôn

        1. Phở Code Admin

          À mình hiểu ý bạn rồi, hiện tại của mình chỉ có một chức năng duy nhất là xóa sạch cái cart chứ không thêm/bớt từng sản phẩm được.

          Có điều mình cũng chưa hiểu cái Many to Many thì có liên quan gì? Làm kiểu bình thường là làm như thế nào?

          Mình nghĩ ở đây chỉ có 2 thao tác cần làm là thay đổi quantity và xóa sản phẩm thôi. Mấy cái này thì bạn chỉ cần tạo phương thức action mới, định nghĩa routing cho nó rồi gọi AJAX hay HTTP gì cũng được.

  3. Anh

    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

    1. Phở Code Admin

      Ok, mình làm ví dụ cho bạn này, bạn xem có giải quyết được không nhé:
      Đầu tiên vào file app/views/carts/_cart.html.erb thêm dòng này:

      <%= button_to 'Update cart', edit_cart_path(current_cart), :method => :get %>
      

      Dòng này nó tạo nút bấm dẫn đến trang /carts/<id>/edit?.

      Rails nó tạo sẵn cho mình cái trang /carts/<id>/edit? dựng từ file app/views/carts/edit.html.erb nên mình tận dụng luôn, đầu tiên bạn vào file đó sửa lại như này:

      <h2>Your cart</h2>
      <tabel>
      	<% @cart.line_items.each do |line_item| %>		
      		<%= form_for(line_item) do |f| %>					
      			<tr>
      				<td>
      					<%= f.text_field :quantity, :size => '1' %> x <%= f.label line_item.product.title %>
      				</td>
      			</tr>
      			<tr>
      				<td>
      					<%= f.submit %>
      				</td>
      			</tr>
      		<% end %>		
      	<% end %>
      	<tr class="total_line">
              <td colspan="2">Total</td>
              <td class="total_cell"><%= number_to_currency(@cart.total_price) %></td>
          </tr>
      </table>
      

      Form này mình tạo chơi thôi, ở đây mình thay đổi số lượng sản phẩm được, sau đó bấm nút submit thì nó sẽ gửi đến phương thức update trong lớp LineItemsController, kèm theo tham số là idquantity, nên mình sửa lại để nó cập nhật trong phương thức này:

      class LineItemController < ApplicationController
          .
          .
          .
          # PATCH/PUT /line_items/1
          # PATCH/PUT /line_items/1.json
          def update	
      	@newQty = params[:line_item][:quantity]
      	@cart = current_cart
      	@line_item = @cart.line_items.find_by_id(params[:id])
      	@line_item.quantity = @newQty
      	@line_item.save
      	
              respond_to do |format|
                format.html { redirect_to '/' }
              end
          end
          .
          .
          .
      end
      

Trả lời


Lưu ý: bọc code trong cặp thẻ [code language="x"][/code] để highlight code.


Ví dụ:


[code language="cpp"]


    std::cout << "Hello world";


[/code]



Các ngôn ngữ được hỗ trợ gồm: actionscript3, bash, clojure, coldfusion, cpp, csharp, css, delphi, diff, erlang, fsharp, go, groovy, html, java, javafx, javascript, latex, matlab, objc, perl, php, powershell, python, r, ruby, scala, sql, text, vb, xml.

Thư điện tử của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *