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.



