いるもの

概要

このHowToでは、顧客一覧をPDFに出力する例が説明されている。

  1. 顧客一覧をXMLで出力するアクションを作成。
  2. ここで生成されるXMLを利用して、iReportで帳票のテンプレートを作成する。
  3. iReportで保存される「.jasper」ファイルをRailsディレクトリに移動
  4. Wikiに書いてあるとおりにクラスを作ったりする。
  5. 帳票出力用のアクションを作成
  6. 5で作成したアクションにURLからアクセスするとPDFが出力される。

テスト用のRailsの環境を作成

> gem install sqlite3-ruby ・・・sqlite3がなかったのでインストール
> rails jaspertest
> cd jaspertest
> ruby script/generate model CustomerType
> ruby script/generate model Customer

SQLite3をインストール

SQLite Download Pageから以下の2つのファイルをダウンロード
* sqlite-3_5_8.zip
* sqlitedll-3_5_8.zip
これらのファイルをダウンロードし、解凍したらPATHの通っているところにコピーする。
その後以下のコマンドを実行してRubyからsqlite3を使えるようにする。

> gem install sqlite3-ruby

テーブルを作成

db/migrate/001_create_customer_types.rbを以下のように編集

class CreateCustomerTypes < ActiveRecord::Migration
  def self.up
    create_table :customer_types do |t|
      t.integer :id
      t.string :code, :limit => 3
      t.string :name, :limit => 10
      t.timestamps
    end
  end

  def self.down
    drop_table :customer_types
  end
end

db/migrate/002_create_customers.rbを以下のように編集

class CreateCustomers < ActiveRecord::Migration
  def self.up
    create_table :customers do |t|
      t.integer :id
      t.string :name, :null => false
      t.string :address
      t.integer :customer_type_id
      t.timestamps
    end
  end

  def self.down
    drop_table :customers
  end
end

migrateを実行してテーブルを作成

> rake db:migrate

テスト用のデータをフィクスチャのファイルに入力

test/fixtures/customer_types.yml

type1:
   id: 1
   code: '001'
   name: '支払先'
type2:
   id: 2
   code: '002'
   name: '請求先'
type3:
   id: 3
   code: '003'
   name: '支払・請求'

test/fixtures/customers.yml

custmer_1:
   id: 1
   name: '株式会社 ABC'
   address: '東京都渋谷区'
   customer_type_id: 1
   
custmer_2:
   id: 2
   name: '株式会社 CDE'
   address: '東京都渋谷区'
   customer_type_id: 1
   
custmer_3:
   id: 3
   name: '株式会社 EFG'
   address: '東京都渋谷区'
   customer_type_id: 3
   
custmer_4:
   id: 4
   name: 'XYZ 株式会社'
   address: '東京都渋谷区'
   customer_type_id: 2

テーブルにフィクスチャのデータをロードする

> rake db:fixtures:load

CustomerとCustomerTypeの関連を設定

app/models/customer.rbを以下のようにする

class Customer < ActiveRecord::Base
   belongs_to :customer_type
end

顧客一覧XMLを取得するアクションを作成

> ruby script/generate controller Accounting

app/controllers/accounting_controller.rbに以下のメソッドを追加

   def customer_list
      @customers = Customer.find(:all)
   end

app/views/accounting/customer_list.rxmlを以下のように作成

xml.instruct!
xml.customer_list_result do
    xml.invoice_customers do
        @customers.each do |customer|
            xml << customer.to_xml(:dasherize=>false,
               :skip_instruct=>true,
               :only=>[:id,:name,:address],
               :root=>"customer",
               :include => :customer_type) # belongs_toで指定したリレーションもXMLに
        end
    end
end

WEBrickサーバを起動し

> ruby script/server

次のようにブラウザに表示されたらOK
このXMLは保存して(customer_list.xmlとしておく)、次のiReportのテンプレートして使う。
最終的にはこのXMLの内容がPDFとして出力される。

<?xml version="1.0" encoding="UTF-8"?>
<customer_list_result>
  <invoice_customers>
<customer>
  <address>&#26481;&#20140;&#37117;&#28171;&#35895;&#21306;</address>
  <id type="integer">1</id>
  <name>&#26666;&#24335;&#20250;&#31038;&#12288;ABC</name>
  <customer_type>
    <id type="integer">1</id>
    <name>&#25903;&#25173;&#20808;</name>
  </customer_type>
</customer>
<customer>
  <address>&#26481;&#20140;&#37117;&#28171;&#35895;&#21306;</address>
  <id type="integer">2</id>
  <name>&#26666;&#24335;&#20250;&#31038;&#12288;CDE</name>
  <customer_type>
    <id type="integer">1</id>
    <name>&#25903;&#25173;&#20808;</name>
  </customer_type>
</customer>
<customer>
  <address>&#26481;&#20140;&#37117;&#28171;&#35895;&#21306;</address>
  <id type="integer">3</id>
  <name>&#26666;&#24335;&#20250;&#31038;&#12288;EFG</name>
  <customer_type>
    <id type="integer">3</id>
    <name>&#25903;&#25173;&#12539;&#35531;&#27714;</name>
  </customer_type>
</customer>
<customer>
  <address>&#26481;&#20140;&#37117;&#28171;&#35895;&#21306;</address>
  <id type="integer">4</id>
  <name>XYZ&#12288;&#26666;&#24335;&#20250;&#31038;</name>
  <customer_type>
    <id type="integer">2</id>
    <name>&#35531;&#27714;&#20808;</name>
  </customer_type>
</customer>
  </invoice_customers>
</customer_list_result>

iReportで帳票のテンプレートを作成する

  1. iReportをダウンロードし解凍してできたディレクトリ内のiReport.exeを実行
  2. メニューの[データ]>[接続/データソース]を選択しウィザードを起動
  3. [新規]>[XLMファイルデータソース]を選択し次へ
  4. 以下のように設定
    • 名前:customer_list_xml
    • XMLファイル:前の工程で作成した「customer_list.xml」の場所を指定
    • 「レポート作成時にレポートXPath表現を使用」にチェック
  5. 「保存」を押して完了する
  6. メニューの[ファイル]>[レポートウィザード]を選択
  7. 以下のテンプレートを使用してくださいに「なし」、接続データソースに「No connection or datasource」を指定し次へ(ステップ4まで特に設定しないので次へ押していく)
  8. ステップ4で「テーブルレイアウト」を選択し「gray_landscapeT.xml」を選択し次へ、最後に完了を押して終了。
  9. [gray_landscape]が作成されてるので、名前をつけて保存で「custrep.jrxml」として保存しておく。
  10. メニューの[データ]>[レポートクエリ]を選択、「レポートクエリ」タブのクエリ言語に「xpath2」を指定(右側にXMLの構造が表示される)
  11. 階層を展開していき、「customer」(たくさんあるがどれでもOK)を右クリックし「Set record node(generate xpath)」を選択。左に「/customer_list_result/invoice_customers/customer」と設定される。
  12. さらに「customer」を展開し、「address, id, name」と項目が見えたらこれを一ずつ下の「フィールド、フィールドタイプ」となっている部分へドラッグ&ドロップしていく(ここではaddress,nameをドラッグ)
  13. customer_typeのリレーションの「name」だけが必要なので、これ(customer_type以下のname)を同様にドラッグ、ただし、ドラッグしたらname2となってしまい分かりにくいので、ダブルクリックし「name2」を「customer_type_name」に変更しておく。
  14. レイアウトを整える。日本語のラベルや、テキストボックスはPDF文字エンコーディング、フォントに以下のうちいずれかを選択しておく必要がある。
    • フォント
      • HeiseiKakuGo-W5
      • HeiseiMin-W3
    • PDFエンコーディング
      • UniJIS-UCS2-H (Japanese)
      • UniJIS-UCS2-V (Japanese)
      • UniJIS-UCS2-HW-H (Japanese)
      • UniJIS-UCS2-HW-V (Japanese)
  15. メニューの「ビルド」>「実行(有効な接続)」を実行し帳票の出力イメージを確認
  16. このとき、iReport.exeのある場所に「custrep.jasper」ファイルが出力される(このファイルを後で使う)

RailsからJasperReportsを利用するための設定

  1. jaspertest/app以下に次のディレクトリを作成、ライブラリや作成したファイルを置く
    • app/jasper
    • app/jasper/bin
    • app/jasper/lib
    • app/reports

app/jasper/bin
jasperreport_on_rails.tar.gzをダウンロードして解凍して出来るbinディレクトリ内のXmlJasperInterface.classをコピーして設置

app/jasper/lib
iReportを解凍したディレクトリ内のlibディレクトリ内の全てのJARファイルを置く
いるものの所で挙げた「iTextAsian.jar」もこのディレクトリに入れる

app/reports
前の工程で生成された「custrep.jasper」ファイルをを設置

  1. HowTowに載っているコードを同じように追加
  • app/controllers/application.rbの中に以下の内容をコピー&ペースト
class Document
  include Config
  def self.generate_report(xml_data, report_design, output_type, select_criteria)
    report_design << '.jasper' if !report_design.match(/\.jasper$/)
    interface_classpath=Dir.getwd+"/app/jasper/bin" 
    case CONFIG['host']
      when /mswin32/
        mode = "w+b" #windows requires binary mode
        Dir.foreach(Dir.getwd+"/app/jasper/lib") do |file|
          interface_classpath << ";#{Dir.getwd}/app/jasper/lib/"+file if (file != '.' and file != '..' and file.match(/.jar/))
        end
      else
        mode = "w+" 
        Dir.foreach(Dir.getwd+"/app/jasper/lib") do |file|
          interface_classpath << ":#{Dir.getwd}/app/jasper/lib/"+file if (file != '.' and file != '..' and file.match(/.jar/))
        end
    end
        result=nil
        IO.popen "java -Djava.awt.headless=true -cp \"#{interface_classpath}\" XmlJasperInterface -o#{output_type} -f#{Dir.getwd}/app/reports/#{report_design} -x#{select_criteria}", mode do |pipe|
            pipe.write xml_data
            pipe.close_write
            result = pipe.read
            pipe.close
        end
    return result
  end
end

このクラスはJavaのプロセスを起動しXmlJasperInterface.classを実行する。
このクラスを実行するときにcustomre_listアクションで生成されたXMLのデータを引き渡し、
PDFへ変換してもらう。

  • app/helpersに「send_doc_helper.rb」ファイルを作成し、次の内容をコピー&ペースト
module SendDocHelper
  protected
  def cache_hack
    if request.env['HTTP_USER_AGENT'] =~ /msie/i
      headers['Pragma'] = ''
      headers['Cache-Control'] = ''
    else
      headers['Pragma'] = 'no-cache'
      headers['Cache-Control'] = 'no-cache, must-revalidate'
    end
  end

  def send_doc(xml, xml_start_path, report, filename, output_type = 'pdf')
    case output_type
    when 'rtf'
      extension = 'rtf'
      mime_type = 'application/rtf'
      jasper_type = 'rtf'
    else # pdf
      extension = 'pdf'
      mime_type = 'application/pdf'
      jasper_type = 'pdf'
    end

    cache_hack
    send_data Document.generate_report(xml, report, jasper_type, xml_start_path),
        :filename => "#{filename}.#{extension}", :type => mime_type, :disposition => 'inline'
  end
end

このクラスのsend_docメソッドでは、一つ前に作成したDocumentクラスを呼びだし、
取得したデータ(PDFかRTF形式のデータ)をレスポンスとして返す処理が書かれている。

  • app/controllers/accounting_controller.rbに以下の宣言を追加(2009/02/20 追記)
class AccountingController < ApplicationController
  # ↓この2行を追加
  helper :send_doc
  include SendDocHelper
  • app/controllers/accounting_controller.rbに以下のメソッドを追加
   def customer_report
      @customers=Customer.find(:all)
      send_doc(
            render_to_string(:template => 'accounting/customer_list', :layout => false),
            '/customer_list_result/invoice_customers/customer', 
            'custrep',
            'CustomerReport', 
            'pdf')
   end

このメソッドで指定している内容

    • :template => 'accounting/customer_list': app/views/accounting/customer_list.rxml
    • /customer_list_result/invoice_customers/customer: iReportで使われるXPath(なんだろ)
    • custrep: app/jasper内にある.jasperファイルの名前
    • CustomerReport: ダウンロードしたときのファイル名
    • pdf: PDFで出力するように指定

以上で設定完了。