Rails – Tạo nút thêm giỏ hàng


Được đăng vào ngày 22/11/2016 | 12 bình luận
Rails – Tạo nút thêm giỏ hàng
3.1 (61.92%) 52 votes

Trong các bài trước chúng ta đã xây dựng cơ chế session và tạo mối quan hệ giữa Product và Cart, bây giờ chúng ta sẽ xây dựng chức năng tạo giỏ hàng.

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

<% if notice %>
    <p id="notice"><%= notice %></p>
<% end %>
 
<h1>Product List</h1>
 
<% @products.each do |product| %>
    <div class="entry">
        <%= image_tag(product.image_url) %>
        <h3><%= product.title %></h3> 
        <%= sanitize(product.description) %>
        <div class="price_line">
            <span class="price"><%= number_to_currency(product.price) %></span> 
            <%= button_to 'Add to Cart', line_items_path(:product_id => product) %>
        </div>
    </div>
<% end %>

Phương thức button_to sẽ tạo một thẻ <form></form> với class là button_to, bên trong có một thẻ <input> với thuộc tính typeSubmit, nhãn là tham số do chúng ta truyền vào, ở đây là chuỗi ‘Add to Cart’, phương thức mặc định là POST, URL chuyển đến là tham số thứ 2, ở đây là chuỗi được tạo ra từ phương thức helper line_items_path(), phương thức này được Rails định nghĩa tự động cho từng controller và sẽ trả về URL của lớp controller tương ứng, ví dụ như line_items_path() trả về /line_item, cart_path() trả về /cart…v.v Phương thức này còn có thể nhận vào tham số và tự gắn vào đuôi như URL bình thường, ở đây chúng ta định nghĩa tham số :product_id và truyền vào giá trị của biến product, tức là id của sản phẩm, và URL cuối cùng sẽ có dạng như /line_item?product_id=1, /line_item?product_id=2…v.v

Và khi chúng ta click vào nút đó thì một trong số các phương thức routing của lớp LineItemsController sẽ được gọi:

class LineItemsController < ApplicationController
    before_action :set_line_item, only: [:show, :edit, :update, :destroy]

    # GET /line_items
    # GET /line_items.json
    def index
        @line_items = LineItem.all
    end

    # GET /line_items/1
    # GET /line_items/1.json
    def show 
    end

    # GET /line_items/new
    def new    
    end

    # GET /line_items/1/edit
    def edit
    end

    # POST /line_items
    # POST /line_items.json
    def create   
        @line_item = LineItem.new(line_item_params)

        respond_to do |format|
            if @line_item.save
                format.html { redirect_to @line_item, notice: 'Line item was successfully created.' }
                format.json { render :show, status: :created, location: @line_item }
            else
                format.html { render :new }
                format.json { render json: @line_item.errors, status: :unprocessable_entity }
            end
        end
    end

    # PATCH/PUT /line_items/1
    # PATCH/PUT /line_items/1.json
    def update
        respond_to do |format|
            if @line_item.update(line_item_params)
                format.html { redirect_to @line_item, notice: 'Line item was successfully updated.' }
                format.json { render :show, status: :ok, location: @line_item }
            else
                format.html { render :edit }
                format.json { render json: @line_item.errors, status: :unprocessable_entity }
            end
        end
    end

    # DELETE /line_items/1
    # DELETE /line_items/1.json
    def destroy
        @line_item.destroy
        respond_to do |format|
            format.html { redirect_to line_items_url, notice: 'Line item was successfully destroyed.' }
           format.json { head :no_content }
        end
    end

private
    # Use callbacks to share common setup or constraints between actions.
    def set_line_item
        @line_item = LineItem.find(params[:id])
    end

# Never trust parameters from the scary internet, only allow the white list through.
    def line_item_params
        params.require(:line_item).permit(:product_id, :cart_id)
    end
end

Có 2 phương thức có URL giống nhau (đều là /line_items) là indexcreate, tuy nhiên phương thức được gọi sẽ là create, bởi vì chỉ có create mới dùng phương thức POST. 

Tuy nhiên phương thức này đang chạy theo code mặc định của Rails, tức là tạo đối tượng LineItems được gửi lên từ form ở /line_items/new, bạn có thể trỏ vào dùng thử.

Chúng ta sửa lại phương thức create để lấy từ nút button do chúng ta định nghĩa ở trên như sau:

class LineItemsController < ApplicationControler
    .
    .
    .
    # POST /line_items
    # POST /line_items.json
    def create
        @cart = current_cart
        product = Product.find(params[:product_id])
        @line_item = @cart.line_items.build(:product => product)

        respond_to do |format|    
            if @line_item.save
                format.html { redirect_to(@line_item.cart, :notice => 'Line item was successfully created') }
                format.json { render :show, status: :created, location: @line_item }
            else
                format.html { render :new }
                format.json { render json: @line_item.errors, status: :unprocessable_entity }
            end
        end
    end
    .
    .
    .
end

Đầu tiên chúng ta tạo hoặc lấy một đối tượng Cart trong session của người dùng từ phương thức current_cart được định nghĩa trong lớp ApplicationController (application_controller.rb).

Sau đó chúng ta dùng id được truyền vào tham số :product_id để lấy đối tượng Product cụ thể, tham số :product_id được gửi vào biến params, đây là biến được tạo ra khi chúng ta bấm vào nút ‘Add to Cart’ đã định nghĩa ở trên.

Trong đối tượng @cart ở trên có đối tượng line_items, đây là một đối tượng lưu trữ các phần tử theo dạng danh sách thuộc lớp ActiveRecord::Associations::CollectionProxy, lớp này có một phương thức là build() dùng để tạo một phần tử cho danh sách của nó. Chúng ta gọi phương thức này để tạo một đối tượng LineItem mới, có tham số truyền vào là id của Product (:product_id) rồi lưu vào biến @line_item.

Tiếp theo là đoạn respond_to dùng để trả nội dung về cho người dùng, từ trước đến giờ chúng ta vẫn chưa tìm hiểu sâu về các đoạn respond_to cũng như đối tượng format, trong bài này chúng ta cũng sẽ chưa đi vào tìm hiểu làm gì, mình sẽ giải thích ở các bài sau, chỉ có một lưu ý là chúng ta gọi hàm redirect_to đến URL /cart/<id> dựa vào biến @line_item.cart, thay vì trả về /line_items.

Chúng ta có thể chạy thử được rồi, bấm vào một nút ‘Add to Cart’ để thêm một sản phầm vào giỏ hàng.

capture

Chúng ta có thể liệt kê danh sách các sản phẩm trong giỏ hàng ngay tại trang /cart/<id> đó luôn bằng cách sửa file show.html.erb trong thư mục app/views/carts như sau:

<p id="notice"><%= notice %></p>
<h2>Your cart</h2>
<ul>
    <% @cart.line_items.each do |a| %>
    <li><%= a.product.title %></li>
    <% end %>
</ul>
<%= link_to 'Edit', edit_cart_path(@cart) %> |
<%= link_to 'Back', carts_path %>

capture







Bình luận (12)

      1. Anh

        Cảm ơn bạn. Cho mình hỏi thêm là khi dùng button_to để post bài mới thì phải có create action của controller đó. Thế 1 page không thể có nhiều create action được phải không.

        Thêm nữa là làm sao để click button sẽ gọi là 1 action or method của controller khác.

        1. Phở Code Admin

          Nếu ý bạn là có nhiều action có tên là “create” thì đúng, ko thể có nhiều create action được

          Để một button gọi một action của controller bất kỳ thì bạn gán tham số thứ 2 của hàm button_to là URL của action đó, chẳng hạn bạn có controller là Dummy và action là index, thì bạn truyền vào URL của action này. Ở trên cái hàm line_items_path(…) trả về chuỗi ‘/line_items/1’ đó, mặc định thì Rails đã điều hướng cái URL này vào hàm create rồi.

          Vấn đề làm sao trỏ một URL vào một hàm nhất định thì mình chưa có nói trong các bài trước. Cách dễ nhất là khai báo mặc định trong file config/routes.rb, ví dụ có controller tên Dummyvà action tên index, thì bạn thêm dòng này vào:

          get ‘/ten-gi-cung-duoc’, to: ‘dummy#index’

          1. Anh

            Thế tức là page index của mình không thể vừa có 2 create action chẳng hạn
            Action 1: add to cart (create method)
            Actiion 2: like button (update true/false) cho items

            Thêm nữa là, nếu tớ muốn tạo 1 button để delete all post
            def xoa_het
            Post.all.destroy_all
            end
            Làm sao để insert button đó và thực hiện method xoa_het được

          2. Phở Code Admin

            Ý mình là bạn chỉ cần định nghĩa tên khác thôi, đừng để cùng tên đều là ‘create’ là được, create1, create2… chẳng hạn.

            Còn phương thức xoa_het thì đầu tiên bạn vào file config/routes.rb, giả sử controller của bạn là PostsController thì sau đó thêm dòng này vào:

            get '/xoa-het', to: 'posts#xoa_het'

            Rồi ở hàm button_to bạn truyền là:

            < %= button_to 'Xoa het', '/xoa-het' %>

            Vậy là xong! Cái /xoa-het thì tùy bạn, muốn đặt thế nào cũng được.

  1. Thong

    anh ơi cho em hỏi khi em truyền đường dẫn line_item_path(:product_id => product) vào trong button_to thì khi chạy nó báo lỗi missing require key id , bên line_items em đã viết lại method creat rồi ạ mà không được. mong anh giúp đỡ em là newbie ạ 🙂

    1. Phở Code Admin

      Bạn kiểm tra xem code có sai chỗ nào không, sai chính tả, thiếu dấu đóng ngoặc mở ngoặc…v.v
      Nếu không thấy thì bạn post code của view với controller lên đây mình xem.

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 *