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 %>