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