12/08/2018, 12:11

Cách sử dụng gem to_xls-rails trong rails

**1. Đặt vấn đề ** Có nhiều Gem trong rails để hỗ trợ việc export file excel như gem axlsx và axlsx_rails. Trong đấy có một cách đơn giản nhất và được dùng nhiều nhất là không cần dùng gem mà chỉ cần dùng file có định dạng .xls.erb. Ví dụ như file views/customers/show.xls.erb sau < ? xml ...

**1. Đặt vấn đề **

Có nhiều Gem trong rails để hỗ trợ việc export file excel như gem axlsx và axlsx_rails. Trong đấy có một cách đơn giản nhất và được dùng nhiều nhất là không cần dùng gem mà chỉ cần dùng file có định dạng .xls.erb. Ví dụ như file views/customers/show.xls.erb sau

<?xml version="1.0"?>
<Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet"
  xmlns:o="urn:schemas-microsoft-com:office:office"
  xmlns:x="urn:schemas-microsoft-com:office:excel"
  xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet"
  xmlns:html="http://www.w3.org/TR/REC-html40">
  <Worksheet ss:Name="Sheet1">
    <Table>
      <Row>
        <Cell><Data ss:Type="String">ID</Data></Cell>
        <Cell><Data ss:Type="String">First name</Data></Cell>
        <Cell><Data ss:Type="String">Last name</Data></Cell>
        <Cell><Data ss:Type="String">Age</Data></Cell>
        <Cell><Data ss:Type="String">Addess</Data></Cell>
        <Cell><Data ss:Type="String">Tel</Data></Cell>
        <Cell><Data ss:Type="String">Email</Data></Cell>
        <Cell><Data ss:Type="String">Birth day</Data></Cell>
        <Cell><Data ss:Type="String">Job</Data></Cell>
      </Row>
    <% @customers.each do |customer| %>
      <Row>
        <Cell><Data ss:Type="Number"><%= customer.id %></Data></Cell>
        <Cell><Data ss:Type="String"><%= customer.first_name %></Data></Cell>
        <Cell><Data ss:Type="String"><%= customer.last_name %></Data></Cell>
        <Cell><Data ss:Type="String"><%= customer.age %></Data></Cell>
        <Cell><Data ss:Type="Number"><%= customer.address %></Data></Cell>
        <Cell><Data ss:Type="Number"><%= customer.tel %></Data></Cell>
        <Cell><Data ss:Type="Number"><%= customer.email %></Data></Cell>
        <Cell><Data ss:Type="Number"><%= customer.birth_day %></Data></Cell>
        <Cell><Data ss:Type="Number"><%= customer.job %></Data></Cell>
      </Row>
    <% end %>
    </Table>
  </Worksheet>
</Workbook>

Nhưng liệu file bạn export ra theo cách này thì bạn không thể import file excel đươc vào mà phải chỉnh lại format về Use Microsoft Excel 97/2000/XP/2003 Format của file excel. Mình có một cách để giải quyết vấn đề này bằng 1 ứng dụng export ra file excel và import chính file excel mà hệ thống của mình xuất ra có sử dụng gem to_xls-rails. Sau đây mình xin trình bày về cách thức xây dựng ứng dụng

**2. Chức năng export ra file excel **

Trước hết bạn cần cài gem to_xls-rails

Gemfile
gem 'to_xls-rails'

Sau khi cài xong, bạn cần cấu hình trong file RAILS_ROOT/config/initializers/mime_types.rb

Mime::Type.register_alias "text/excel", :xls

Tại views/customers/show.html.erb bạn cần tạo 1 đường link để export file excel và 1 đường link về trang edit

<h1>Export file excel</h1>
<table style="margin: 0 auto">
  <tr>
    <th>First name: </th>
    <td><%= @customer.first_name %></td>
  </tr>
  <tr>
    <th>Last name: </th>
    <td><%= @customer.last_name %></td>
  </tr>
  <tr>
    <th>Age: </th>
    <td><%= @customer.age %></td>
  </tr>
  <tr>
    <th>Tel: </th>
    <td><%= @customer.tel %></td>
  </tr>
  <tr>
    <th>Address: </th>
    <td><%= @customer.address %></td>
  </tr>
  <tr>
    <th>Email: </th>
    <td><%= @customer.email %></td>
  </tr>
  <tr>
    <th>Birth day: </th>
    <td><%= @customer.birth_day %></td>
  </tr>
  <tr>
    <th>Job: </th>
    <td><%= @customer.job %></td>
  </tr>
  <tr>
    <%= link_to [@customer, format: :xls] do %>
      <%= button_tag "Export excel" %>
    <% end %>
    <%= link_to edit_customer_path(@customer) do %>
      <%= button_tag "Edit" %>
    <% end %>
  </tr>
</table>

Khi bạn click vào đường link export excel thì chương trình sẽ gọi đến hàm show trong customers_controller.html.rb. Nếu bạn xuất ra tất cả các danh sách customer thì bạn chỉ cần các câu lệnh đơn giản sau

    def index
      @customers = Customer.all
      respond_to do |format|
        format.html
        format.xls{send_data @customers.to_xls}
      end
    end

Nhưng ở đây chúng ta chỉ cần export ra 1 @customer thì sẽ cần viết phức tạp hơn 1 chút

   def show
     @customer = Customer.find params[:id]
     respond_to do |format|
      format.html
      format.xls do
        send_data(
          [Customer.new].to_xls(except: Customer::HEADERS_EXCEPT,
          prepend: [Customer::HEADERS, Customer.perform(@customer)]),
          type: "application/excel; charset=utf-8; header=present",
          filename: "#{@customer.full_name} #{Time.now.strftime("%Y/%m/%d %H:%M:%S")}.xls"
        )
      end
    end

prepend là giá trị bạn sẽ xuất ra type là kiểu dữ liệu mà bạn muốn export ra filename bao gồm tên đối tượng @customer và thời gian hiện tại Tại model customer.rb

class Customer < ActiveRecord::Base
  HEADERS = %w(id first_name last_name age address tel
    email birth_day job)
  HEADERS_EXCEPT = HEADERS + %w(created_at updated_at)

  def full_name
    "#{first_name} #{last_name}"
  end

  class << self
    def perform customer
      HEADERS.map{|header| customer.send(header)}
    end
  end

Trong đấy HEADERS để khai báo các trường của customer. Một nhược điểm của gem to_xls-rails là khi export ra file excel thì dòng thứ hai của file excel sẽ mặc định là tên của các trường và viết hoa chữ cái đầu tiên. Như vậy sẽ gây bất tiện cho người dùng, đặc biệt khi người dùng muốn xuất ra dòng đầu tiên là tiếng nhật (bạn có thể tham khảo chức năng này ở bài viết I18n với human attibutes trên viblo: https://viblo.asia/phan.thanh.giang/posts/zNPVMa6QGQOk). Chính vì vậy chúng ta phải thêm vào HEADERS_EXCEPT gồm tất cả các trường trong HEADER thêm vào 2 trường created_at và updated_at để loại đi việc lặp trường.

3. Chức năng import vào file excel

Trước tiên, bạn cần cài thêm hai gem là roo và roo-xls, hai gem này hỗ trợ việc đọc rất nhiều định dạng file khác như csv,ods, xlsx, xls, xml... Tại giao diện edit.html.erb, ta có form để import file excel

<%= form_for @customer, multipart: true do |f| %>
  <%= f.file_field :file, size: "70" %>
  <%= f.submit "import" %>
<% end %>

customers_controller.rb

  def edit
    @customer = Customer.find params["id"]
  end

  def update
    @customer = Customer.find params["id"]
    @customer = Customer.import params["customer"]["file"], @customer
    redirect_to customer_path
  end

Bạn sẽ cần truyền vào hàm import file excel và đối tượng bạn muốn import vào là @customer. Biến @customer sẽ được gán lại cho chính nó sau khi gọi đến hàm import trong model customer.rb

      class << self
        def import file, customer
          return unless (spreadsheet = open_spreadsheet file)
          header = spreadsheet.row 1
          row = Hash[[header, spreadsheet.row(2)].transpose]
          HEADERS.each do |field|
            customer.send "#{field}=", row[field]
          end
          customer.save
        end

        def open_spreadsheet file
          case File.extname File.basename file.path
          when ".xls" then Roo::Excel.new file.path
          when ".xlsx" then Roo::Excelx.new file.path
          end
        end
      end

header là hàng 1 của file excel

 ["id", "first_name", "last_name", "age", "address", "tel", "email", "birth_day", "job"]

row chính là tập hợp các giá trị tương ứng với mỗi cột trong file excel.

 {"id"=>1.0,
 "first_name"=>"nghia",
 "last_name"=>"doan dai",
 "age"=>114.0,
 "address"=>"Ha noi",
 "tel"=>"01694068234",
 "email"=>"nghiadoandai@gmail.com",
 "birth_day"=>Fri, 01 Feb 1991,
 "job"=>"IT"}

Câu lệnh customer.send "#{field}=", row[field] sẽ lấy từng giá trị trong mỗi cột để gán cho các trường trong @customer và lưu ngay tại model hoặc bạn có thể chuyển biến @customer lên controller để lưu

4.

0