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 type là Submit, 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à index và create, 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.
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 %>

