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

3/5 - (77 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

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.

14 Comments
Inline Feedbacks
View all comments
Anh
Anh
7 năm trước

chào bạn, cho mình hỏi làm sao edit được thông tin trong giỏ hàng

Anh
Anh
7 năm trước

Ý mình là edit số lượng của line item ấy, chứ vào cart nó thành edit cart rồi

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

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.

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

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

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

Cảm ơn cậu rất nhiều, t rất thích các bài viết về rails của c.

Thong
Thong
7 năm trước

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 ạ 🙂

bee
bee
3 năm trước

chào anh, em làm theo từ đầu khóa học tới chỗ này thì hiện lỗi :

ActiveModel::UnknownAttributeError in LineItemsController#create
unknown attribute ‘cart_id’ for LineItem.

@cart = current_cart
product = Product.find(params[:product_id])
@line_item = @cart.line_items.build(:product => product) (dòng này bị note đỏ )

respond_to do |format|
if @line_item.save

anh có thể chỉ em cách khắc phục được không?