ITコンサルの日常

ITコンサル会社に勤務する普通のITエンジニアの日常です。

indexアクションをCSVやExcel(JRuby + POI)に対応してみる

scaffoldで作った場合、デフォルトではHTMLとXMLに対応していますが、これをCSVとかExcelに対応してみようというものです。

まず事前準備として、.csvや.xlsといった拡張子に反応するよう、config/environment.rbに登録します。

...
  # Activate observers that should always be running
  # config.active_record.observers = :cacher, :garbage_collector
end

Mime::Type.register "text/csv", :csv
Mime::Type.register "application/vnd.ms-excel", :xls

次にscaffoldで作られたindexアクションをごりごりと変えていきます。

require 'java'
require 'stringio'
require 'lib/poi-3.1-FINAL-20080629.jar'

class PeopleController < ApplicationController
  class RubyOutputStream < java.io.OutputStream
    def set_io(io)
      @io = io
    end

    def write(b)
      if b.is_a?(Fixnum)
        if b < 0
          b = b + 256
        end
        @io.print(b.chr)
      else
        (0...b.length).each do |index|
          write(b[index])
        end
      end
    end

    def close
      @io.close
    end
  end

  # GET /people
  # GET /people.xml
  def index
    @people = Person.find(:all)

    respond_to do |format|
      format.html # index.html.erb
      format.xml  { render :xml => @people }
      format.csv  {
        result = ""
        @people.each do |person|
          result += "#{person.id},#{person.name}\n"
        end
        render :text => result
      } 
      format.xls {
        io = StringIO.new

        rio = RubyOutputStream.new
        rio.set_io(io)

        wb = org.apache.poi.hssf.usermodel.HSSFWorkbook.new
        sheet = wb.createSheet("People")
        rowHeader = sheet.createRow(0)
        rowHeader.createCell(0).setCellValue("ID")
        rowHeader.createCell(1).setCellValue("NAME")

        i = 1
        @people.each do |person|
          row = sheet.createRow(i)
          row.createCell(0).setCellValue(person.id)
          row.createCell(1).setCellValue(person.name)

          i += 1
        end
        wb.write(rio)

        render :text => io.string

        io.close
      }
    end
  end
...
以下(showアクションなど)同じ

CSVの方は特別なことは何もなくて、idとnameをカンマでつないでrenderしているだけです。


Excelの方は、JRuby + Apache POIを使ってます。
昨日作ったRubyのIOをラップするRubyOutputStreamを使って、
ここではExcelブックの内容を一旦StringIOに蓄積します。
次にStringIOからstringを取り出してrenderしてあげます。
こうすることで、一旦ファイルに保存することなく、Excelをダウンロードさせることが可能になります。


JRuby on Railsって、CRubyがJRubyに変わっただけじゃんとか思ってましたが、
Javaのライブラリを活用できることで、Railsのパワーをさらに増すことが出来るので、
結構面白いな思いました。


ちなみにですが、ru_excelとかいうRubyのライブラリを使えば、
CRubyでもCOMに依存せずに(つまりWindowsじゃなくても)Excelを扱えるようです。