Railsのシステムを作っていて、コントローラーからPDFKitに流してPDFを生成していたのですが、あるときそれをメールに添付する必要が出てきました。ActionMailerで二重に実装するのもアホらしいし、かといって普通のコントローラーに再アクセスさせるのも認証の加減で厄介になるしといろいろ考えた挙げ句、コードで駆動する、コントローラーのようなものを作ることで対応することにしました。
おことわり
これはRails 4.2で実践してみたものなので、他のバージョンでは書き方が違う可能性があります。
直接ERBでの問題点
もちろん、コントローラーと無関係にERBを呼ぶことでHTMLを生成することも可能です…が、それではコントローラーのビューと比べて機能的に足りないことだらけでした。
- ヘルパーが使えない
- 渡す値の取りまとめが煩雑になる
- アセットなどの位置が取れない
ということで、ある程度以上複雑なビューを組み立てるには、コントローラーと同じ枠組みに載せるしかなさそうです。
既存のものを参考にする
コントローラー以外で同様にビューから生成できるものとして、ActionMailerがあります。それを見ていると、AbstractController::Base
を基底クラスにして、各種のモジュールを加えてメーラーを構築していました。これに則れば、組み立てていけそうです。
実際に書いてみたコード
試行錯誤しつつ、足りないモジュールを足していったところ、以下のようなコードが出来上がりました。
class Renderrers::Base < AbstractController::Base
abstract!
include Rails.application.routes.url_helpers
include AbstractController::Rendering
include AbstractController::Logger
include AbstractController::Helpers
include AbstractController::AssetPaths
include AbstractController::Callbacks
include ActionView::Layouts
helper :application
class_attribute :template
# パス設定のコピー
%i|asset_host assets_dir|.each do |sym|
__send__(:"#{sym}=", ActionController::Base.public_send(sym))
end
# URL設定のコピー
class << self
delegate :default_url_options, to: :ApplicationMailer
end
def render
run_callbacks(:process_action) do
html = render_to_string(template, layout: 'pdf')
PDFKit.new(html).to_pdf
end
end
# ビューの処理に必要となる情報
def action_name
'print'
end
def controller_name
self.class.name.to_s.demodulize.underscore
end
end
引っかかった点としては、
- アセットの設定やURL設定は、他所から借りてくる必要がありました。
- コールバックを動作させるために、
run_callbacks
の中でrender_to_string
する必要がありました。 - ここでは省略していますが、
active_decorator
のような、ActionController
に追加のコードを組み込むgemを使っている場合、そのモンキーパッチ用コードも入れる必要があります。
なお、コントローラーと同じく1回render
するために1インスタンスが必要となりますので、都度new
する必要があります。