Import dữ liệu từ các định dạng csv và xlsx, export dữ liệu ra định dạng pdf và xls trong Rails

Giới thiệu vấn đề

  • Đối với các hệ thống web lớn, lưu trữ nhiều loại dữ liệu thì việc import một tập tin với lượng dữ liệu lớn lên hệ thống là một nhu cầu tất yếu. Thường các tập tin cần import thường ở định dạng csv, office (word, excel, ...). Trong Rails, để xử lý vấn đề import dữ liệu nó đã cung cấp sẵn công cụ import dữ liệu từ file csv mà lập trình viên có thể dễ dàng sử dụng
  • Ngoài ra, ở các hệ thống có nhu cầu quản lý, tổng kết và trích xuất dữ liệu thì hệ thống cần cung cấp tính năng export dữ liệu ra các định dạng phổ biến

Ở bài viết này, mình xin trình bày phương pháp import dữ liệu và export dữ liệu ra một số định dạng quen thuộc như pdf, xls, xlsx

Dự án demo

Để minh họa cho các kĩ thuật mà mình nhắc đến ở trên, ta sẽ tạo một project Rails để áp dụng
Tạo project

rails new demo_export_import

Tạo một số model với đầy đủ các thành phần controller, views, model với câu lệnh

rails generate scaffold User name:string email:string

Sau đây, mình xin vào phần chính của bài viết. Giao diện của sản phẩm như sau

Import dữ liệu từ tập tin csv, xls, xlsx

Để import dữ liệu từ các tập tin trên, mình sử dụng gem "roo" và để tăng hiệu suất import dữ liệu vào database sử dụng gem "activerocord-import"

gem "roo"
gem "activerecord-import"

Định nghĩa route và tạo thẻ input nhận file cần import

<%= form_tag import_users_path, multipart: true do %>
  <%= file_field_tag :file %>
  <%= submit_tag "Import", class: "btn btn-success" %>
<% end %>

Trong model User, định nghĩa các phương thức dưới đây để thực hiện import dữ liệu

def import_file file
 # Mở file được xử lý 
  spreadsheet = open_spreadsheet(file)
  # Gán header (tên các cột) là hàng 1 trong file
  header = spreadsheet.row(1)
  # Khởi tạo một mảng rỗng để chứa các bản ghi
  rows = []
  # Đọc bản ghi dữ liệu từ dòng thứ 2 đến dòng cuối và gán vào mảng trên
  (2..spreadsheet.last_row).each do |i|
    rows << spreadsheet.row(i)
  end
  # Sử dụng phương thức import mà thư viện activerecord-import cung cấp để tạo dữ liệu đạt hiệu suất cao
  import! header, rows
end

# Phương thức mở tập tin để import tùy vào từng loại file
def open_spreadsheet(file)
  case File.extname(file.original_filename)
      when ".csv" then Roo::CSV.new(file.path)
      when ".xls" then Roo::Excel.new(file.path)
      when ".xlsx" then Roo::Excelx.new(file.path)
      else raise "Unknown file type: #{file.original_filename}"
  end
end

Trong controller, tạo phương thức import để xử lý

def import
  if params[:file].present?
    User.import_file params[:file]
    redirect_to root_url
    flash[:success] = "Data imported"
  else
    redirect_to root_url
    flash[:error] = "Data not imported"
  end
end

Export dữ liệu ra định dạng pdf

Để export dữ liệu ra định dạng pdf, ta thêm 2 gem sau vào Gemfile và chạy bundle để cài đặt chúng

gem "wicked_pdf"
gem "wkhtmltopdf-binary"

Tạo template chung cho giao diện của tập tin pdf được tạo ra bằng cách tạo các tập tin pdf.jspdf.scss

<!DOCTYPE html>
<html>
  <head>
    <meta charset='utf-8' />
    <%= wicked_pdf_stylesheet_link_tag 'pdf' %>
    <%= wicked_pdf_javascript_include_tag 'jquery', 'users', 'pdf' %>
  </head>
  # Đánh số trang khi export file
  <body onload="number_pages()">
    <%= yield %>
  </body>
</html>

Đánh số trang khi export file

function number_pages() {
    var vars={};
    var x=document.location.search.substring(1).split('&');
    for(var i in x) {var z=x[i].split('=',2);vars[z[0]] = decodeURIComponent(z[1]);}
    var x=['frompage','topage','page','webpage','section','subsection','subsubsection'];
    for(var i in x) {
        var y = document.getElementsByClassName(x[i]);
        for(var j=0; j<y.length; ++j) y[j].textContent = vars[x[i]];
    }
}

Tạo nút bấm để thực hiện export dữ liệu ra tập tin pdf

<%= link_to export_users_path(format: :pdf)do %>
  <i class="fa fa-paperclip" aria-hidden="true"></i> PDF Attachment
<% end %>

Phương thức export ra pdf file với các tùy chọn mà gem "wkhtmltopdf-binary" cung cấp

def export
  @users = User.order created_at: :desc # Xác định nội dung file
  respond_to do |format|
    format.pdf do
      render pdf: 'users', # Xác định view truyền nội dung file
        layout: 'pdf.html', # Xác định layout mà file sẽ nhận
        template: 'users/export', # Xác định layout mà file sẽ nhận
        footer: {right: '[page]'}, # Thêm số trang ở bên phải phần footer
        margin: { :top => 15, :bottom => 21, :left => 12, :right => 12 }, # Căn lề cho file
        orientation: 'Landscape', page_size: 'A4' # Xác định cỡ giấy và chiều hiển thị bảng
    end
  end
end

Tạo tập tin users.pdf.html để tạo nội dung cho tập tin (đổ dữ liệu vào file)

<div class="container">
  <h1 align="center">List Users</h1>

  <table class='table table-bordered'>
    <thead>
    <tr>
      <th>Name</th>
      <th>Email</th>
      <th>Create at</th>
    </tr>
    </thead>

    <tbody>
    <% @users.each do |user| %>
      <tr>
        <td><%= user.name %></td>
        <td><%= user.email %></td>
        <td><%= l user.created_at, format: :time %></td>
      </tr>
    <% end %>
    </tbody>
  </table>
</div>

Export dữ liệu ra định dạng xls, xlsx

Để export dữ liệu ra định dạng excel, ta thêm 2 gem sau vào Gemfile và chạy bundle để cài đặt chúng

gem "axlsx"
gem "axlsx_rails"

Tạo nút bấm để thực hiện export dữ liệu ra tập tin xls, xlsx

<%= link_to export_users_path(format: :xlsx)do %>
  <i class="fa fa-file-excel-o" aria-hidden="true"></i> Download as Excel
<% end %>

Tạo tập tin export.xlsx.axlsx để tạo nội dung cho tập tin (đổ dữ liệu vào file)

# Tạo một file workbook mới
wb = xlsx_package.workbook
# Tạo worksheet có tên là Users
wb.add_worksheet(name: "Users") do |sheet|
  # Thêm nội dung hàng tiêu để cho file
  sheet.add_row %w(name email)
  # Tạo các bản ghi dữ liệu cho file
  @users.each do |user|
    sheet.add_row [user.name, user.email]
  end
end

Phương thức export ra xlsx file

def export
  @users = User.order created_at: :desc # Xác định nội dung file
  respond_to do |format|
    format.xlsx
  end
end

Sản phẩm khi chạy sẽ cho kết quả như sau

Bài viết có tham khảo một số bài sau

https://viblo.asia/p/importing-records-from-csv-and-excel-in-rails-5-ByEZk0QqlQ0
https://github.com/roo-rb/roo
https://github.com/mileszs/wicked_pdf
https://github.com/randym/axlsx