色々試してて、多少は形になったのでメモしときます。

まず、RubyでExcelを作らないといけません。色々方法はありますが、というか素直な方法はJRuby+POIなんじゃないかとも思いますが、今回は「spreadsheet」を使います。

spreadsheet ライブラリとは

spreadsheetは普通にgemでインストールできます。

1
sudo gem install spreadsheet

spreadsheet自体には、 .to_blob に当たるインスタンスメソッドがありません(ようです)が、書き込み先にFile IOを指定できます。なので、いったんTempfileに書き込んで、 read して send_data する方法で出来るんじゃないかと思いました。

emvironment.rbで require 'spreadsheet' すると、一緒にtempfile.rbも読み込まれる模様です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
class ExcelExportController < ApplicationController
  def download
    birds = Bird.find(:all)
    legend = ["名前", "色", "鳴き声"]
 
    book = Spreadsheet::Workbook.new
    sheet1 = book.create_worksheet(:name => '鳥類')
 
    sheet1.row(0).concat legend
    sheet1.row(0).default_format = Spreadsheet::Format.new(
                                     :color => :red,
                                     :weight => :bold,
                                     :horizontal_align => :center
                                   )
    sheet1.row(0).height = 12
 
    rownum = 1
    birds.each do |bird|
      sheet1.row(rownum).concat [bird.name, bird.color, bird.twitter]
      sheet1.row(rownum).height = 12
      rownum += 1
    end
 
    # 横幅を自動調整
    legend.size.times do |i|
      sheet1.column(i).width = sheet1.column(i).max_by(&:size).size * 0.85
    end
 
    tmpfile = Tempfile.new ["excel_tmp", ".xls"]
    book.write tmpfile
 
    tmpfile.open # reopen
 
    send_data(
      tmpfile.read,
      :disposition => 'attachment',
      :type => 'application/octet-stream',
      :filename => "excel-#{Time.now.strftime('%y%m%d%H%M%S')}-#{'%03d' % rand(999)}.xls"
    )
 
    tmpfile.close(true)
  end
end

こんな感じでしょうか。ちなみに、動作検証してません。。特に :type の指定とか怪しいと思う。

色々と読み替えてお使い下さい。

参考になるサイト

tips: Tempfile

マニュアルのどこにも書いてませんが、newの引数basenameに配列を渡すと、[元となるファイル名, 拡張子]みたいなTempfileを作れます。

1
t = Tempfile.new(["temp", ".xls"])

ソースはこんな感じでした。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Tempfile < DelegateClass(File)
  #...
 
  def make_tmpname(basename, n)
    case basename
    when Array
      prefix, suffix = *basename
    else
      prefix, suffix = basename, ''
    end
 
    t = Time.now.strftime("%Y%m%d")
    path = "#{prefix}#{t}-#{$$}-#{rand(0x100000000).to_s(36)}-#{n}#{suffix}"
  end
  private :make_tmpname
  #...
end