Trong phần này chúng ta sẽ xây dựng chức năng đặt hàng đơn giản.
Chỉnh sửa model
Đầu tiên chúng ta định nghĩa một model có tên order
(đơn hàng) bao gồm các trường:
name
: tên người nhậnaddress
: địa chỉ giao hàngemail
: địa chỉ emailpay_type:
phương thức thanh toán
C:\Projects\Rails\depot>rails generate scaffold order name:string address:text email:string pay_type:string ...
Khi một khách hàng thêm sản phẩm vào giỏ hàng thì một đối tượng LineItem
sẽ được tạo ra, và chúng ta sẽ cần biết đối tượng LineItem
này nằm trong giỏ hàng nào, do đó chúng ta sẽ thêm trường order_id
vào model order
như sau:
C:\Projects\Rails\depot>rails generate migration add_order_id_to_line_item order_id:integer ...
Cuối cùng chúng ta cập nhật lại cấu trúc CSDL:
C:\Project\Rails\depot>rake db:migrate ...
Chỉnh sửa View
Chúng ta thêm nút ‘Checkout’ vào giao diện như sau:
<h2>Your cart</h2> <table> <%= render cart.line_items %> <tr class="total_line"> <td colspan="2">Total</td> <td class="total_cell"><%= number_to_currency(cart.total_price) %></td> </tr> </table> <%= button_to 'Checkout', new_order_path, :method => :get %><br> <%= button_to 'Empty cart', cart, :method => :delete, data: {:confirm => 'Are you sure?' } %>
Nút này sẽ gửi lên URL là '/orders/new'
có trong biến new_order_path
, phương thức gửi là GET
, chúng ta phải chỉ rõ phương thức gửi ra vì mặc định hàm button_to
gửi theo phương thức POST
.
URL '/orders/new'
được gọi tới phương thức action là new
trong lớp OrdersController
, phương thức này sẽ làm nhiệm vụ hiển thị form nhập thông tin đơn hàng, tuy nhiên chúng ta sửa lại để kiểm tra xem giỏ hàng hiện tại thật sự có hàng hay không thì mới hiển thị. Chúng ta sửa file orders_controller.rb
trong thư mục app/controllers
như sau:
class OrdersController < ApplicationController . . . # GET /orders/new def new @cart = current_cart if @cart.line_items.empty? redirect_to '/', :notice => 'Your cart is empty' return end @order = Order.new end . . . end
Nếu không có hàng trong giỏ hàng thì chúng ta cho quay lại trang '/'
, có tin nhắn thông báo là 'Your cart is empty'
.
Ngược lại thì file View mặc định do Rails tạo ra là new.html.erb
trong thư mục app/views/orders
sẽ được hiển thị, chúng ta sửa lại như sau:
<div class="depot_form"> <fieldset> <legend>Your information</legend> <%= render 'form' %> </fieldset> </div>
Ở đây chúng ta gọi hàm render
với tham số là ‘form’, hàm này sẽ hiển thị form nhập thông tin từ file partial là _form.html.erb
trong thư mục app/views/orders.
File này Rails cũng tự động tạo cho chúng ta rồi, nhưng chúng ta sửa lại như sau:
<%= form_for(@order) do |f| %> <% if @order.errors.any? %> <div id="error_explanation"> <h2> <%= pluralize(@order.errors.count, "error") %> prohibited this order from being saved: </h2> <ul> <% @order.errors.full_messages.each do |message| %> <li><%= message %></li> <% end %> </ul> </div> <% end %> <div class="field"> <%= f.label :name %><br> <%= f.text_field :name, :size => 40 %> </div> <div class="field"> <%= f.label :address %><br> <%= f.text_area :address, :rows => 3, :cols => 40 %> </div> <div class="field"> <%= f.label :email %><br> <%= f.text_field :email, :size => 40 %> </div> <div class="field"> <%= f.label :pay_type %><br> <%= f.select :pay_type, Order::PAYMENT_TYPES, :prompt => 'Select a payment method' %> </div> <div class="actions"> <%= f.submit 'Place order' %> </div> <% end %>
Hàm form_for
là một hàm helper giúp tạo form một cách nhanh chóng. Cú pháp có dạng:
<%= form_for <đối tượng> ... do |f| %> <%= f.label ... %> <%= f.text_area ... %> <% end %>
Hàm form_for
nhận vào một đối tượng, và tạo một đối tượng thuộc lớp ActionView::Helpers::FormaBuilder
, ở đây là 'f'
, chúng ta có thể gọi các phương thức của đối tượng này để tạo các thẻ trong <form>,
chẳng hạn như label
, select
, text_area
, submit
…v.v bạn có thể xem danh sách tại đây.
Ở phương thức f.select
dùng để tạo combobox, chúng ta cho danh sách các phần tử là hằng số PAYMENT_TYPES
trong lớp Order
(app/models/order.rb
),
do đó chúng ta định nghĩa thêm hằng số này như sau:
class Order < ActiveRecord::Base PAYMENT_TYPES = [ "Cash on delivery", "Ngân lượng", "Bảo Kim", "Bank Card" ] end
Cuối cùng chúng ta định nghĩa thêm một số lớp CSS như sau:
... /* Order form style */ .depot_form fieldset { background: #efe; } .depot_form legend { color: #dfd; background: #141; font-family: sans-serif; padding: 0.2em 1em; } .depot_form label { width: 5em; float: left; text-align: right; padding-top: 0.2em; margin-right: 0.1em; display: block; } .depot_form select, .depot_form textarea, .depot_forma input { margin-left: 0.5em; } .depot_form .submit { margin-left: 4em; } .depot_form div { margin: 0.5em 0; }
Đến đây giao diện sẽ như thế này:
Định nghĩa validation
Chúng ta muốn đơn hàng được gửi lên phải thỏa mãn một số điều kiện, đó là không có trường nào được để trống. Chúng ta sửa lại file app/models/order.rb
như sau:
class Order < ActiveRecord::Base PAYMENT_TYPES = [ "Cash on delivery", "Ngân lượng", "Bảo Kim", "Bank Card" ] validates :name, :address, :email, :pay_type, :presence => true validates :pay_type, :inclusion => PAYMENT_TYPES end
Định nghĩa mỗi quan hệ
Một đối tượng LineItem
sẽ thuộc về một đối tượng Order
. Chúng ta sửa lại file model line_item.rb
như sau:
class LineItem < ActiveRecord::Base belongs_to :order belongs_to :product belongs_to :cart def total_price product.price * quantity end end
Ngược lại, một đối tượng Order
sẽ có nhiều đối tượng LineItem
trong đó, ngoài ra khi một đối tượng Order
bị hủy thì các đối tượng LineItem
“thuộc về” Order đó cũng sẽ bị hủy theo. Chúng ta sửa file model order.rb
như sau:
class Order < ActiveRecord::Base has_many :line_items, :dependent => :destroy PAYMENT_TYPES = [ "Cash on delivery", "Ngân lượng", "Bảo Kim", "Bank Card" ] validates :name, :address, :email, :pay_type, :presence => true validates :pay_type, :inclusion => PAYMENT_TYPES end
Tạo đơn hàng
Khi người dùng điền đầy đủ thông tin và click nút ‘Place order’, nút này sẽ gửi các thông tin đó đến phương thức create
trong lớp OrdersController.
Chúng ta sửa lại một tí như sau:
class OrdersController < ApplicationController . . . # POST /orders # POST /orders.json def create @order = Order.new(order_params) @order.add_line_items_from_cart(current_cart) respond_to do |format| if @order.save Cart.destroy(session[:cart_id]) session[:cart_id] = nil format.html { redirect_to '/', notice: 'Thank you for your order' } format.json { render :show, status: :created, location: @order } else format.html { render :new } format.json { render json: @order.errors, status: :unprocessable_entity } end end end . . . end
Phương thức mặc định của Rails là tạo một đối tượng Order
với thông tin lấy từ các tham số do người dùng nhập vào. Tuy nhiên chưa có thông tin về các đối tượng LineItem,
chúng ta thêm vào bằng phương thức add_line_items_from_cart
, phương thức này sẽ được định nghĩa sau. Sau khi đã tạo đối tượng Order
xong và lưu vào giỏ hàng thì chúng ta xóa giỏ hàng trong session
đi.
Bây giờ chúng ta định nghĩa phương thức add_line_items_from_cart
như sau:
class Order < ActiveRecord::Base has_many :line_items, :dependent => :destroy PAYMENT_TYPES = [ "Cash on delivery", "Ngân lượng", "Bảo Kim", "Bank Card" ] validates :name, :address, :email, :pay_type, :presence => true validates :pay_type, :inclusion => PAYMENT_TYPES def add_line_items_from_cart(cart) cart.line_items.each do |item| item.cart_id = nil line_items << item end end end
Phương thức này nhận vào tham số là một đối tượng Cart,
chúng ta duyệt danh sách các đối tượng LineItem
có trong Cart
này, các đối tượng LineItem
sẽ được gán giá trị cho thuộc tính cart_id
là nil
để tránh báo lỗi khi xóa giỏ hàng, tiếp theo chúng ta đưa đối tượng LineItem
đó vào mảng line_items
.
Vậy là xong, bây giờ chúng ta có thể sử dụng chức năng đặt hàng được rồi.
Và khi đặt hàng xong thì chúng ta được trỏ về trang ‘/’ với thông báo đặt hàng thành công (Thanh you for your order).
Tuy nhiên nếu tiếp tục thêm hàng vào giỏ thì tin nhắn này vẫn tồn tại ở đó, chẳng qua là vì trình duyệt gửi lệnh AJAX để cập nhật giỏ hàng chứ không gửi lệnh HTTP để tải lại trang. Để ẩn dòng thông báo này thì chúng ta sửa lại file create.js.erb
như sau:
var content = "<%= escape_javascript(render(current_cart)) %>"; document.getElementById("cart").innerHTML = content; $("#current_item").css({'background-color':'#88ff88'}); $("#current_item").animate({backgroundColor:'#114411'}, 1000 ); var p_notice = document.getElementById("notice"); if(p_notice !== null) p_notice.remove();
Chỉ đơn giản là kiểm tra xem trang web có thẻ nào có id
là notice
hay không và xóa nó đi. Vậy là dòng thông báo sẽ biến mất.