Exporting CSV, Excel and Docx

I. Exporting to CSV

Giả sử cần xuất dữ liệu cho đối tượng là "product". Không cần sử dụng bất kỳ gem nào cũng có thể dễ dàng export dữ liệu ra 1 file csv. Sử dụng thư viện CSV library để sinh ra dữ liệu CSV. Điều đơn giản cần làm là thêm dòng lệnh sau vào file config của app :

require File.expand_path("../boot", __FILE__)
require "csv"

Dữ liệu (comma-separated data) sẽ được sinh ra khi user vào /products.csv path. Để export được, trong controller của đối tương mà muốn xuất dữ liệu (/app/controllers/products_controller.rb) thêm 1 khối respond_to block. Ví dụ như đoạn mã sau :

class ProductsController < ApplicationController
  def index
    @products = Product.order(:name)
    respond_to do |format|
      format.html
      format.csv {render text: @products.to_csv}
    end
  end
end

Trong model của đối tượng thêm đoạn code sau để sinh ra dữ liệu (CSV data) :

def self.to_csv
  CSV.generate do |csv|
    csv << column_names
    all.each do |product|
      csv << product.attributes.values_at(*column_names)
    end
  end
end

Ở đây dữ liệu được sinh ra nhờ vào method to_csv là method kết hợp giữa method to_jsonto_xml. Nếu không muốn data sinh ra trong 1 dòng duy nhất thì có thể sử dụng send_data thay vì render :text trong controller. Khi load lại trang thì file CSV sẽ được download.

Nếu muốn chỉ download file CSV khi click vào link hoặc button thì có thể làm theo cách sau :

  • Định nghĩa 1 method để lấy dữ liệu bên trong model.rb
def download_keys keys
      CSV.generate do |csv|
        csv << column_names
        keys.each do |key|
          csv << product.attributes.values_at(*column_names)
        end
      end
end
  • Trong controller viết 1 method để gọi đến khi click link:
def index
      download_file params[:download] if params[:download]
  end

  private
  def download_file download_option
      filename = "product"
      send_data Product.all.download_keys(keys),
        type: "text/csv; charset=UTF-8; header=present",
        disposition: "attachment; filename=#{filename}"
  end
  • Ở phần view (ví dụ product/index.html.erb) thêm đường link để gọi method download trong controller :
<%= link_to "download CSV", products_path, class: "btn btn-success" %>

II. Exporting to Excel

  • Step1 : thêm gem vaò Gemfile :
gem "axlsx", "2.0.0"
  • Step2 : Viết hàm download (có thể tạo file .rb trong app/model/concerns), tham số truyền vào có thể là tên file mong muốn, ví dụ:
generate_excel_report file_name

Trong hàm, đầu tiên sẽ tạo 1 file trống bằng dòng lệnh sau:

package = Axlsx::Package.new
workbook = package.workbook
  • Tạo sheet cho file : Trong 1 file có thể gồm nhiều sheet, để tạo sheet cho file dùng lệnh sau :
workbook.add_worksheet(name: "sheet_1") do |sheet|
      ...
end
  • Trong vòng do ... end trên sẽ thêm các dòng lệnh cần để tạo nội dung cho sheet đó. Nếu muốn định style cho row + colum trong file excel thì sử dụng lệnh add_style, ví dụ như dòng lệnh sau :
color_title_table = workbook.styles.add_style(
      bg_color: Settings.color.bg_title_row, b: true,
      alignment: {horizontal: :left}, border: Axlsx::STYLE_THIN_BORDER)

bg_color: màu nền mong muốn cho từng cell cụ thể

`b:true` -> in đậm

`u: single` -> có gạch chân

`alignment:`

    `horizontal: :left`:căn lề trái/phải
    `border: Axlsx::STYLE_THIN_BORDER` -> kiểu và kích cỡ của border...
  • Ví dụ :
color_title_table = workbook.styles.add_style(bg_color: "F2DDDC", b: true, alignment: {horizontal: :left}, border: Axlsx::STYLE_THIN_BORDER)
  • Để đặt tên cho file dùng câu lệnh sau :
workbook.add_worksheet(name: "product_excel") do |sheet|
  /* Nội dung muốn in ra trong report */
end
  • Thêm row trong file excel, dùng sheet.add_row, ví dụ như dòng lệnh sau :
sheet.add_row "new row", style: bold_text

Muốn row có bao nhiêu cell (số cột của bảng) thì tạo bằng ấy cell tương ứng, ví dụ như muốn tạo row_title cho 1 bảng excel có 3 cột :

row_title = ["item1", "item2", "item3"]

add row_title trên vào bảng và định kiểu style cho nó, ta làm như sau :

sheet.add_row row_title, style: color_title_table, height: 50

Insert ảnh vào file excel:

def insert_img sheet, link_img, x, y, height, width
      path_img = get_local_link_file link_img
      if link_img.present? && File.exist?(path_img)
        img = File.expand_path path_img, __FILE__
        sheet.add_image image_src: img do |image|
          image.width, image.height = width, height if width && height
          image.start_at x, y
        end
      end
end
//sheet : sheet hiện tại cần chèn ảnh
//link_img : đường dẫn tới file ảnh
//x : tọa độ ngang, vị trí của ô cần chèn ảnh (int, tính từ 0)
//y : tọa độ dọc, vị trí của ô cần chèn ảnh (int, tính từ 0)
//height : chiều cao mong muốn cho ảnh (trung bình khoảng 50)
//width : độ rộng mong muốn cho ảnh (trung bình khoảng 40)

III. Exporting docx

Tương tự như xuất dữ liệu ra file excel

  • Bước 1 : Thêm gem vao Gemfile
gem "docxtor"
  • Bước 2 : viết 1 hàm xuất data
generate_word_report file_name

1 hàm cơ bản có thể viết như sau :

package = Docxtor.generate do
      //tạo 1 file docx
      table_of_contents "Contents"
      h 1, "heading1"

      p "text1", :b => true do
        //1 paragraph
        style 'p1'
        spacing :before => 80, :after => 240
        italic; u

        w "text2"
        br
        write "text3"
      end

      h 2 do
        w "heading2"
        line_break
        write "some text"
        br
        write "another text"
      end

      p "content", :style => 'p2', :i => true, :align => "center"
end
package.save("file_name.docx")
// save file với tên là tham số lúc truyền vào

Entry point

Docxtor.generate do
      ...
end

Paragraphs

  • Phần chính trong bản word này là đoạn. Có thể xây dựng đoạn bằng các cách sau :
    p "Hi there!", :bold => true

    //Tương đương với

    p do
      w "Hi there!"
      b
    end

    //OR

    p do
      write "Hi there!"
      bold
    end
  • Style được viết chung trong khối block sẽ được apply trong cả đoạn, có nhiều cách khác nhau để định nghĩa text và các option cho đoạn.
p "I'm a first string in this paragraph", :bold => true do
  write "I'm a second one."
  u
  w "And we all will be bold and underlined in the same time!"
end
  • Headings
h 1, "Chapter 1"
  h 2, "Where our hero goes for an adventure"
  • Footers & Headers

    Các thuộc tính cho footer và header :

    Chỉ định trang mà footer + header xuất hiện :

:odd - trang lẻ
    :even - trang chẵn
    only :first - trang đầu tiên

Căn lề :

:center - giữa
:left - trái
:right - phải

Nội dung của footer/header chứa từ khóa đặc biệt :pagenum, index của trang được chèn nội dung.

footer :pagenum, :align => :center
header "Proudly made by me", :pages => :odd
footer "2013", :pages => :first, :align => :right

Breaks:

Sang trang mới : page_break

Xuống dòng : br

`Styles` : tương tự như trong excel.

`Nén file export` : `require zip` trong phần định nghĩa `export`method
require "zip/zip"

Hàm nén :

def export_zip_file filename, file_destination, zipfile_name
      Zip::ZipFile.open(zipfile_name, Zip::ZipFile::CREATE) do |zipfile|
        zipfile.add filename, file_destination
      end
      FileUtils.rm_f file_destination
  end

Trong hàm xuất report, chèn method export_zip_file trên vào để nén file (sau khi .save). Ví dụ :

def report_word file_name
      package = Docxtor.generate do
        header ...
        p title, b: true
        ........... //nội dung file export
        package.save "#{file_name}.docx"
        export_zip_file "#{title}.docx", "#{file_name}.docx", "#{file_name}.zip"
      end
end

V. Nguồn tham khảo

1. https://github.com/docxtor/docxtor
2. http://ruby-doc.org/stdlib-1.9.3/libdoc/csv/rdoc/CSV.html
3. https://github.com/randym/axlsx/blob/master/examples/example.rb