Daily Archives: 02/12/2016

Rails – Phân trang với will_paginate

Trong bài này chúng ta sẽ thực hiện chức năng phân trang.

Trong Rails không có hàm hay lớp nào có thể thực hiện chức năng phân trang được. Tuy nhiên do Rails là mã nguồn mở nên cũng có cộng đồng hỗ trợ rất lớn, rất nhiều thư viện được viết ra để hỗ trợ chúng ta. Các thư viện hỗ trợ phân trang cũng rất nhiều, ở đây chúng ta sẽ sử dụng thư viện will_paginate, có địa chỉ github tại https://github.com/mislav/will_paginate.

Chúng ta không cần phải tự tải về rồi biên dịch gì hết. Mặc định Rails có phần mềm quản lý các thư viện này rồi, đó là phần mềm Gem. Chúng ta sẽ tìm hiểu cách sử dụng gem sau.

Cài đặt will_paginate

Đầu tiên chúng ta khai báo thư viện này trong file Gemfile, file này không có phần mở rộng và nằm ở thư mục gốc của project:

source 'http://rubygems.org'


# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '4.2.5.1'
# Use sqlite3 as the database for Active Record
gem 'sqlite3'
# Use SCSS for stylesheets
gem 'sass-rails', '~> 5.0'
# Use Uglifier as compressor for JavaScript assets
gem 'uglifier', '>= 1.3.0'
# Use CoffeeScript for .coffee assets and views
gem 'coffee-rails', '~> 4.1.0'
# See https://github.com/rails/execjs#readme for more supported runtimes
# gem 'therubyracer', platforms: :ruby

# Use jquery as the JavaScript library
gem 'jquery-rails'
# Turbolinks makes following links in your web application faster. Read more: https://github.com/rails/turbolinks
gem 'turbolinks'
# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
gem 'jbuilder', '~> 2.0'
# bundle exec rake doc:rails generates the API under doc/api.
gem 'sdoc', '~> 0.4.0', group: :doc

# Use ActiveModel has_secure_password
# gem 'bcrypt', '~> 3.1.7'

# Use Unicorn as the app server
# gem 'unicorn'

# Use Capistrano for deployment
# gem 'capistrano-rails', group: :development

group :development, :test do
 # Call 'byebug' anywhere in the code to stop execution and get a debugger console
 gem 'byebug'
end

group :development do
 # Access an IRB console on exception pages or by using <%= console %> in views
 gem 'web-console', '~> 2.0'
end

# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]

gem 'will_paginate', '>= 3.0'

Phiên bản mới nhất được phát hành vào tháng 10/2016 là phiên bản 3.1.5. Chúng ta khai báo dòng gem 'will_paginate', '>= 3.0' cho biết chúng ta muốn dùng phiên bản 3.0 trở đi, nhưng không dùng 4.0.

Tiếp theo chúng ta cài thư viện này bằng lệnh bundle install:

C:\Projects\Rails\depot>bundle install
...
Installing will_paginate 3.1.5
...

Vậy là xong, bạn có thể sẽ cần phải khởi động lại server nếu có đang chạy.

Tạo dữ liệu mẫu

Chúng ta tạo 100 bản ghi Order để làm dữ liệu test. Thay vì tự ngồi gõ từng form đặt hàng thì chúng ta có thể chạy đoạn code có sẵn.

Chúng ta tạo một file có tên create_dummy_orders.rb nằm trong thư mục bin có nội dung như sau:

Order.transaction do
    (1..100).each do |i|
        Order.create(:name => "Customer #{i}", 
                     :address => "#{i} Street",
                     :email => "customer_#{i}@phocode.com",
                     :pay_type => "Bank Card")
    end
end

Đoạn code trên sẽ tạo 100 bản ghi Order. Để chạy đoạn code này thì chúng ta chạy lệnh rails runner bin/create_dummy_orders.rb:

C:\Projects\Rails\depot>rails runner bin/create_dummy_orders.rb

Lưu ý là đây không phải dữ liệu seed như chúng ta đã từng làm.

Bây giờ bạn có thể vào trang /orders và thấy danh sách 100 đơn hàng được hiển thị đầy đủ.

Phân trang

Chúng ta sẽ phân trang để cho hiển thị 10 đơn hàng trên một trang, đầu tiên chúng ta sửa lại phương thức index trong lớp OrdersController như sau:

class OrdersController < ApplicationController
    .
    .
    .
    # GET /orders
    # GET /orders.json
    def index   
        @orders = Order.paginate(:page => params[:page], :per_page => 10).order('created_at desc')
    end
    .
    .
    .
end

Thư viện will_paginate cho phép chúng ta sử dụng một phương thức có tên là paginate trên các đối tượng lưu trữ theo dạng danh sách. Phương thức này nhận vào tham số trang, ở đây là :page, tức là khi chúng ta xem các trang thì URL sẽ có dạng /orders?page=1, /orders?page=2… đây là tham số mặc định do thư viện này quy định. Phương thức này còn nhận vào tham số :per_page là số lượng “item” được hiển thị trên mỗi trang.

Ngoài ra chúng ta có thể sắp xếp các phần tử này bằng phương thức order, phương thức này nhận vào một chuỗi có dạng "<tên_trường> asc|desc", và các phần tử sẽ được sắp xếp dựa theo tên trường, ASC là sắp xếp từ A-Z, DESC thì ngược lại. Ở đây chúng ta sắp xếp theo trường created_at với thứ tự là DESC.

Cuối cùng trong file View app/views/orders/index.html.erb chúng ta sửa lại như sau:

<p id="notice"><%= notice %></p>

<h1>Listing Orders</h1>

<table>
    <thead>
        <tr>
            <th>Name</th>
            <th>Address</th>
            <th>Email</th>
            <th>Pay type</th>
            <th colspan="3"></th>
        </tr>
    </thead>

    <tbody>
        <% @orders.each do |order| %>
            <tr>
                <td><%= order.name %></td>
                <td><%= order.address %></td>
                <td><%= order.email %></td>
                <td><%= order.pay_type %></td>
                <td><%= link_to 'Show', order %></td>
                <td><%= link_to 'Edit', edit_order_path(order) %></td>
                <td><%= link_to 'Destroy', order, method: :delete, data: { confirm: 'Are you sure?' } %></td>
            </tr>
        <% end %>
    </tbody>
</table>

<br>

<%= link_to 'New Order', new_order_path %>

<p><%= will_paginate @orders %></p>

Chúng ta gọi hàm helper là will_paginate với tham số là @orders để tạo các thẻ <a> dẫn tới từng trang cụ thể.

Vậy là xong, bây giờ trang /orders sẽ hiển thị như thế này:

capture