読者です 読者をやめる 読者になる 読者になる

Rails Webook

自社のECを開発している会社で働いています。Rails情報やサービスを成長させる方法を書いていきます

RailsでPDFKit + wkhtmltopdfを使ってPDFを作成する

Rails gem PDF Generation

PDFKit + wkhtmltopdf は、RubyもしくはRailsでHTMLをPDFに変換するGemとツールです。
これらを使うことで、HTMLをPDFに変換する形でPDFファイルを作成できます。
wkhtmltopdfが、HTMLをPDFに変換し、PDFKitはそのラッパーという構成になっています。

メリットとして、HTMLを作成するようにPDFファイルを作成することができるので比較的簡単にPDFファイルを作成できます。
デメリットとして、wkhtmltopdf の問題で現在は忠実にHTMLをPDFに変換できないことが問題であります。

今回は、RailsでPDFKit + wkhtmltopdfを使って注文票のPDFを作成する手順を説明します。

f:id:nipe880324:20140907190213p:plain:w480


他にもPDFを作成するGemがあり、「Ruby/RailsのPDF作成Gemまとめ 」でまとめています。

対象読者

  • Rails + PDFKit + wkhtmltopdfを使ったPDFの作成方法を知りたい
  • 一通りRailsの基礎について分かっている

動作確認

  • Mac OS X 10.9.4
  • Ruby 2.1.2
  • Rails 4.1
  • PDFKit 0.6.2
  • wkhtmltopdf 0.1.2 (Gemfile.lock)
  • wkhtmltopdf 0.8.3 ($ wkhtmltopdf -V)

目次

  1. 準備(注文詳細画面の作成)
  2. PDFKitの導入
  3. 結論

1. 準備(注文詳細画面の作成)

まず、注文書のPDFを作成するために簡単な注文詳細画面を作成しましょう。
作成する画面は次の画面です。
f:id:nipe880324:20140906204302p:plain:w480

手順はこちらを参照してください。



2. PDFKitの導入

インストール

毎度ながらGemfileに以下を追加します。

# Gemfile
...
# For PDF generation
gem 'pdfkit'       # wkhtmtopdfのラッパー
gem 'wkhtmltopdf'  # HTMLをPDFに変換するツール

Gemをインストールします。

$ bundle install

ターミナルからwkhtmltopdfが使えるようになっていると思います。
PDFKitでは、このツールでHTMLからPDFへ変換することでPDFを作成します。
このツールは、HTMLだけでなく、URLを指定してもPDFファイルを作成できます。次のコマンドを実行してください。
PDFファイルが作成されます。

$ wkhtmltopdf http://yahoo.co.jp yahoo.pdf

wkhtmltopdfにより作成されたPDFです。とっても簡単にPDFファイルを作成できますね。

f:id:nipe880324:20140907190251p:plain:w480

このように、PDFKitを使ってwkhtmltopdfを使うことによりPDFを作成していきます。

PDF文書の作成

URLのフォーマットが.pdfのときにPDF出力処理を呼びます。
まずは、PDF表示用のリンクをViewに追加します。

# app/views/orders/show.html.erb
...
# 一番下の行に追加
<%= link_to "PDFで表示",
              order_path(@order, format: "pdf"),
              :class => 'btn btn-primary' %>

そして、Controllerのshowアクションにrespond_toでPDF文書を作成するロジックを追加します。

# app/controllers/orders_controller.rb
  ...
  # GET /orders/1
  # GET /orders/1.json
  def show
    respond_to do |format|
      format.html # show.html.erb
      format.pdf do
        # 詳細画面のHTMLを取得
        html = render_to_string template: "orders/show"

        # PDFKitを作成
        pdf = PDFKit.new(html, encoding: "UTF-8")

        # 画面にPDFを表示する
        # to_pdfメソッドでPDFファイルに変換する
        # 他には、to_fileメソッドでPDFファイルを作成できる
        # disposition: "inline" によりPDFはダウンロードではなく画面に表示される
        send_data pdf.to_pdf,
          filename:    "#{@order.id}.pdf",
          type:        "application/pdf",
          disposition: "inline"
      end
    end
  end
  ...

PDFKitの設定

PDFKitの設定をします。
config/initializers/pdfkit.rbを新たに作成し、設定を記載します。

# config/initializers/pdfkit.rb
PDFKit.configure do |config|
  config.wkhtmltopdf = `which wkhtmltopdf`.to_s.strip
  config.default_options = {
    encoding:                "UTF-8",  # エンコーディング
    page_size:               "A4",     # ページのサイズ
    margin_top:              "0.25in", # 余白の設定
    margin_right:            "1in",
    margin_bottom:           "0.25in",
    margin_left:             "1in",
    disable_smart_shrinking: false
  }
end

では、サーバーを再行動して、注文詳細画面の "PDFで表示"ボタン を押してみましょう。
Template is missingというエラーが発生します。
エラー内容をよく見ると、:formats=>[:pdf]と記載されており、app/views/orders/show.pdf.xxxというファイルが存在しないからエラーが発生しているという意味です。

f:id:nipe880324:20140907190331p:plain:w480


そのため、app/views/orders/show.pdf.erbを作成しましょう。
内容は、show.html.erbと一番したの4つのリンクを削除しただけで、後は全て同じです。

# app/views/orders/show.pdf.erb
<%- model_class = Order -%>
<div class="page-header">
  <h1><%=t '.title', :default => model_class.model_name.human.titleize %></h1>
</div>

<dl class="dl-horizontal">
  <dt><strong>注文番号:</strong></dt>
  <dd><%= @order.id %></dd>
  <dt><strong>注文日:</strong></dt>
  <dd><%= @order.purchased_at %></dd>
</dl>

<table class="table table-striped">
  <thead>
    <tr>
      <th>#</th>
      <th>品名</th>
      <th>単価</th>
      <th>数量</th>
      <th>値段</th>
    </tr>
  </thead>

  <tbody>
    <% @order.line_items.each_with_index do |line_item, idx| %>
      <tr>
        <td><%= idx + 1 %></td>
        <td><%= line_item.product_name %></td>
        <td><%= number_to_currency line_item.price %></td>
        <td><%= line_item.quantity %></td>
        <td><%= number_to_currency line_item.total_price %></td>
      </tr>
    <% end %>

    <tr class="warning">
      <td colspan="3" />
      <td>合計</td>
      <td><%= number_to_currency @order.total_price %></td>
    </tr>
  </tbody>
</table>

再度、PDFを表示してみましょう。今度は上手く表示されました。

f:id:nipe880324:20140907190349p:plain:w480


しかし、上手くスタイルが適用されていません。
これは、Controllerのrender_to_stringでHTMLコードしか取得しなかったため、CSSが適用されていないためです。
そのため、ControllerでCSSを適用しましょう。

CSSの適用

# app/controllers/orders_controller.rb
  ...
  # GET /orders/1
  # GET /orders/1.json
  def show
    respond_to do |format|
      format.html # show.html.erb
      format.pdf do
        # 詳細画面のHTMLを取得
        html = render_to_string template: "orders/show"

        # PDFKitを作成
        pdf = PDFKit.new(html, encoding: "UTF-8")

        ### 追加箇所 開始 ###
        # スタイルシートの設定
        pdf.stylesheets << "#{Rails.root}/app/assets/stylesheets/scaffolds.css.scss"
        pdf.stylesheets << "#{Rails.root}/app/assets/stylesheets/bootstrap.min.css"
        ### 追加箇所 終了 ###

        # 画面にPDFを表示する
        # to_pdfメソッドでPDFファイルに変換する
        # 他には、to_fileメソッドでPDFファイルを作成できる
        # disposition: "inline" によりPDFはダウンロードではなく画面に表示される
        send_data pdf.to_pdf,
          filename:    "#{@order.id}.pdf",
          type:        "application/pdf",
          disposition: "inline"
      end
    end
  end
  ...

.lessファイルは上手く読み込んでくれないようなので、既にGemでBootstrapをインストールしていますが、新たにapp/assets/stylesheets/bootstrap.min.cssを作成します。

内容は「Twitter Bootstrap 公式サイト」からダウンロードして、bootstrap.min.cssをコピーします。

では、PDFを再度表示しましょう。スタイルが適用されたPDFが表示されました :)

f:id:nipe880324:20140907190213p:plain:w480

Bootstrap Gemの削除

最後にGemと追加したbootstrap.min.cssで重複して不具合が発生するかもしれないので、Gemを削除しましょう。
まず、Bootstrapで追加したファイルを削除します。

$ rails d bootstrap:install

そして、GemfileからもBootstrap部分を削除します。

# Gemfile
...
# For Twitter-Bootstrap
# gem 'therubyracer' # javascript runtime。lessをコンパイルするために必要
# gem 'less-rails' # Railsでlessを使えるようにする。Bootstrapがlessで書かれているため
# gem 'twitter-bootstrap-rails' # Bootstrapの本体
...

bundleから削除します。

$ bundle install

では、PDFを確認し、スタイルが適用されていることを確認しましょう。

f:id:nipe880324:20140907190213p:plain:w480


RailsプロジェクトでBootstrapのJavascriptやアイコンなどを使いたい場合は、Bootstrapからダウンロードしたファイルのfontsjsフォルダの中身をRailsプロジェクトにコピーしてください


3. 結論

PDFKit + wkhtmltopdf を使うことで、HTMLをPDFに変換する形でPDFファイルを作成できます。
メリットとして、show.pdf.erb のように、HTMLを作成するようにPDFファイルを作成することができるので比較的簡単にPDFファイルを作成できます。
デメリットとして、wkhtmltopdf の問題で現在は忠実にHTMLをPDFに変換できないことが問題であります。

よくわからない箇所や間違いがありましたらコメントをください。

参考文献