Trong bài trước chúng ta đã xây dựng tính năng giỏ hàng, tuy nhiên chưa xử lý được thông minh lắm, giả sử bạn bấm nút ‘Add to Cart’ của một sản phẩm nhiều lần, thì khi hiển thị trên trang /carts/<id>,
danh sách các sản phẩm được hiển thị ra hết trên từng dòng, có 100 sản phẩm thì có 100 dòng, cho dù chúng cùng là một thứ, bây giờ chúng ta sẽ thiết kế để in số lượng sản phẩm ngay bên cạnh tên sản phẩm luôn, chứ không cần phải in trên nhiều dòng như thế.
Để làm việc này thì chúng ta cần thêm một cột/trường mới để lưu số lượng sản phẩm trong model/bảng LineItem
. Tuy nhiên việc thay thế cấu trúc các bảng trong CSDL như thêm, xóa, sửa cột sẽ gây ra nhiều rắc rối (đặc biệt là các cột khóa ngoại, khóa chính…v.v), chúng ta có thể “bảo” Rails thay thế giùm chúng ta bằng tính năng Migration của Rails.
Mỗi khi chạy migration, Rails sẽ tạo ra một phiên bản CSDL mới dựa vào các migration mà chúng ta đã tạo ra và cập nhật lại.
Migration trong Rails là các file .rb
có tên tương tự như <YYYYMMDDHHMMSS>_create_products.rb,
bên trong là phần định nghĩa một lớp có tên như CreateProducts
được kế thừa từ lớp ActiveRecord::Migration,
và một phương thức có tên là change
dùng để thêm/xóa/sửa cột trong bảng.
Phần YYYYMMDDHHSS
là thời gian file này được tạo ra, Rails sẽ dựa vào chuỗi này để biết phần nào được cập nhật trước.
Ví dụ chúng ta thực hiện việc thêm trường quantity
dùng để lưu số lượng sản phẩm trong bảng line_items
bằng cách chạy lệnh rails generate migration
như sau:
C:\Projects\Rails\depot>rails generate migration add_quantity_to_line_items quantity:integer invoke active_record create db/migrate/20161123043154_add_quantity_to_line_items.rb
Chúng ta tạo một migration có tên add_quantity_to_line_items,
Rails sẽ tạo một file có tên dạng như 20161123043154_add_quantity_to_line_items.rb
trong thư mục db/migrate,
bên trong là định nghĩa lớp AddQuantityToLineItems.
Nếu lớp migration của bạn có tên dạng như Add<X>To<Y> hoặc Remove<X>From<Y> thì Rails sẽ tự động hiểu là tạo thêm trường X vào bảng Y hoặc xóa trường X trong bảng Y, và tự động thêm các đoạn code thích hợp trong phương thức change.
Ở đây Rails sẽ hiểu là thêm một trường có tên là quantity
vào bảng line_items,
và tự biết là trường này có kiểu dữ liệu là INTEGER
do chúng ta truyền vào trong lệnh generate migration
ở trên. Nội dung của file 20161123043154_add_quantity_to_line_items.rb
hiện tại như sau:
class AddQuantityToLineItems < ActiveRecord::Migration def change add_column :line_items, :quantity, :integer end end
Bên trong phương thức change
chỉ có một dòng duy nhất là lời gọi phương thức add_column,
bạn có thể tự biết đây là phương thức thêm một cột mới trong bảng, thêm số đầu tiên là tên bảng, thâm số thứ 2 là tên cột, tham số thứ 3 là kiểu dữ liệu, các kiểu dữ liệu khác có thể đưa vào là :string, :text, :float, :decimal, :datetime, :timestamp, :time, :date, :binary, :boolean
. Ngoài ra còn có một tham số thứ 4 nữa mà chúng ta có thể đưa vào nếu muốn, đây là tham số tùy chọn, chúng ta sửa lại để cột này nhận giá trị mặc định là 1 như sau:
class AddQuantityToLineItems < ActiveRecord::Migration def change add_column :line_items, :quantity, :integer, :default => 1 end end
Các tham số tùy chọn khác có thể đưa vào là :limit
(giới hạn) và :null
(cột có được phép để NULL
hay không), khi truyền nhiều tham số tùy chọn thì chúng ta truyền vào kiểu bảng băm ví dụ như: { :limit => 50, :null => false, :default => 1 }.
Ngoài ra còn có một số phương thức khác như:
create_table(name, options):
tạo bảng với tên làname
drop_table(name):
xóa bảng với tên làname
change_table(name, options):
thay đổi cấu trúc bảngname
rename_table(oldname, newname):
đổi tên bảngoldname
thànhnewname
rename_column(table, oldname, newname):
đổi tên cộtoldname
thànhnewname
trong bảngtable
change_column(table, column, options):
thay đổi cộtcolumn
trong bảngtable
remove_column(table, column, type, options):
xóa cộtcolumn
khỏi bảngtable
Để thực hiện thay đổi CSDL thì chúng ta chạy lệnh rake db:migrate
là được
C:\Projects\Rails\depot>rake db:migrate == 20161123043154 AddQuantityToLineItems: migrating =================================== -- add_column(:line_items, :quantity, :integer, {:default=>1}) -> 0.0205s == 20161123043154 AddQuantityToLineItems: migrated (0.0216s) ==========================
Bây giờ chúng ta cần chỉnh sửa lại cách model tạo giỏ hàng cho đúng với CSDL mới.
Đầu tiên chúng ta sửa lại file cart.rb
trong thư mục app/models
như sau.
class Cart < ActiveRecord::Base has_many :line_items, :dependent => :destroy def add_product(product_id) current_item = line_items.find_by_product_id(product_id) if current_item current_item.quantity += 1 else current_item = line_items.build(:product_id => product_id) end current_item end end
Chúng ta định nghĩa phương thức add_product
nhận vào id của sản phẩm để tạo hoặc chỉnh sửa đối tượng LineItems.
Đầu tiên chúng ta tìm xem trong dách sách các đối tượng LineItems
(trong biến line_items)
có đối tượng nào có product_id
trùng với product_id
trong tham số hay không bằng cách dùng phương thức find_by_product_id,
phương thức này mặc định không có trong lớp ActiveRecord::Base,
nhưng chúng ta vẫn có thể sử dụng được, đó là nhờ vào tính năng Dynamic Finders (đọc thêm ở đây) trong lớp này, theo đó thì trong model/bảng của bạn có thuộc tính nào thì bạn có thể gọi một phương thức có tên find_by_<tên cột>,
và truyền vào tham số là một giá trị có cùng kiểu dữ liệu với cột đó, ở đây chúng ta truyền vào một số nguyên là id của Product.
Sau đó nếu tìm thấy đối tượng LineItems
nào thì chúng ta tăng giá trị của thuộc tính quantity
lên 1, nếu không thì chúng ta tạo một đối tượng LineItems
mới. Cuối cùng chúng ta trả về đối tượng LineItems
đó.
Tiếp theo chúng ta sửa lại phương thức create
của lớp controller trong file line_items_controller.rb
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.add_product(product.id) 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
Đối tượng LineItems
sẽ được tạo bằng phương thức add_product
đã được định nghĩa ở trên.
Cuối cùng chúng ta sửa lại View để hiển thị số lượng như sau:
<%= notice %> <h2>Your cart</h2> <ul> <% @cart.line_items.each do |a| %> <li><%= a.quantity %> x <%= a.product.title %></li> <% end %> </ul> <%= link_to 'Edit', edit_cart_path(@cart) %> | <%= link_to 'Back', carts_path %>
Vậy là xong, chúng ta có thể chạy được rồi.