Crafting Rails 4 Applications - capter 1-2 - Writing the Renderer
↓のつづきです。
1.2 Rendererを書く
まずはじめに、render()メソッドと許容されるいくつかのオプションについて話をしましょう。rendererとは何かということを正式に説明するわけではありませんが...
rendererは、render()メソッドの振る舞いをカスタムするフックでしかありません。(訳不安) 独自のrendererをRailsに追加することはとてもシンプルにできます。では、:json rendererのサンプルコードを見ていきましょう。
rails/actionpack/lib/action_controller/metal/renderers.rb
add :json do |json, options| json - json.to_json(options) unless json.kind_of?(String) if options[:callback].present? self.content_type ||= Mime::JS "#{options[:callback]}(#{JSON})" else self.content_type ||= Mime::JSON json end end
アプリ内で:json使ってレンダリングしたいときは以下の様にするといい。
render json: @post
↑のコードは:json rendererとして定義されたブロックを呼びます。ブロック内のローカル変数jsonは@postオブジェクトを指し、他のプションはrender()に渡され、options変数の中で使用できます。この場合、メソッドは他のオプションなしに呼ばれる為、空のハッシュになります。
次のセクションでは、:pdf rendererを追加して、pdfドキュメントを与えられたテンプレートから作成し、適切なヘッダー情報とともにクライアントに返してあげる様にします。:pdfオプションには、送られたファイルの名前が入ります。
render pdf: 'contents', template: 'path/to/template'
Railsはテンプレートのレンダリング方法やクライアントへのファイルの送り方を知っていますが、PDFファイルの扱い方は知りません。ここでPrawnが活きてきます。
Prawnを使ってみよう
以下の様にpdf_renderer.gemspecに追記すれば、プラグインの依存関係に入ります。
pdf_renderer/1_prawn/pdf_renderer.gemspec
s.add_dependency 'prawn', '0.12.0'
次にbundlerを使ってインストールして、irb(Interactive Ruby)を使ってテストしましょう。
$ bundle install $ irb
irbの中で、サンプルPDFを作りましょう。
require 'prawn' pdf = Prwan::Document.new pdf.text('A PDF in four lines of code') pdf.render_file('sample.pdf')
irbを閉ると、irbを開始したディレクトリにPDFファイルが作成されています。PrawnはPDFsを作成する独自にシンタックスを提供します。これはとてもフレキシブルなAPIですが、HTMLからPDFsにしたものを、HTMLに戻すことはできません。
p7
コードを書く前にテストを書きましょう・ダミーアプリtest/dummyを使います。controllerを作成し、リクエストスタックをテストします。controllerをHomeContrllerと名づけて、以下のコンテンツを追加します。
pdf_renderer/1_prawn/dummy/app/controllers/home_controller.rb
class HomeController < ApplicationController def index respond_to do |format| format.html format.pdf { render pdf: 'contents' } end end end
次にPDF Viewを作成します。
pdf_renderer/1_prawn/test/dummy/app/views/home/index.pdf.erb
This template is rendered with Prawn.
次にindex actionをrouteに追加します。
pdf_renderer/1_prawn/test/dummy/config/routes.rb
Dummy::Application.routes.drawn do get '/home', to: 'hoge#index', as: :home end
最後にintegrationテストを記載して、hoge.pdfにアクセスした際にPDFが返ることを確認します。
require 'test_helper' class PdfDeliveryTest < ActionDispatch::IntegrationTest test 'pdf request sends ta pdf as file' do get home_path(format: :pdf) assert_match 'PDF', response.body assert_email 'binary', headers['Content-Transfer-Encoding'] assert_equal 'attachment; filename=|'contents.pdf'|', headers['Content-Dispostion'] assert_equal 'application/pdf', header['Content-Type'] end end
テストは、添付ファイルとして送信され、バイナリにエンコードされたPDFファイルと宣言するレスポンスヘッダーに使われます。レスポンスヘッダーにあファイル名も含まれます。PDFのbodyに関してはエンコードされている為多くのテストは出来ませんが、少なくともPDFのbodyにPrawnで追加されたPDFという文字列があることが宣言されています。rake testをして、テストが失敗することを確認してみましょう。
1) Failure test_pdf_request_sends_a_pdf_a_file(PdfDeliveryTest): Expected /PDF/ to match "This template is rendered with Prawn. \n".
テストは予想通り失敗します。Railsに、render()に:pdfオプションを操作する方法を教えていないので、PDFにラッピングされることなく単純にテンプレートがレンダリングされます。lib/pdf_renderer.rbの中の数行でrendererが実装されることでテストが通るようになります。
pdf_renderer/1_prawn/lib/pdf_renderer.rb
require 'prawn' ActionController::Renderers.add :pdf do |filename, options| pdf = Prawn::Document.new pdf.text render_to_string(options) send_data(pdf.render, filename: "#{filename}.pdf"), disposition: "attachment") end
このブロック内で、PDFドキュメントが新規作成され、テキストが追加され、Railsで使用可能なsend_date()メソッドを使い、添付ファイルとしてPDFを送信します。テストを再度走らせると、今度は通ります。test/dummyに移動し、rails serverでサーバーを起動させます。そして http://localhost:3000/home.pdfにアクセスしてみます。
テストは通りますが、まだ説明すべきことがあります。はじめに、application/pdfにContent-Typeをセットしてないことを確認しましょう。では、どうやってRailsはレスポンスにセットされるContent-Typeを知るのでしょうか?
Content-Typeは、Railsが登録されたフォーマットやMIMEタイプのセットを持つことで、正しくセットされます。
rails/actionpack/lib/action_dispatch/http/mime_types.rb
Mime::Type.register "text/html", :html, %w( applicaiton/xhtml+xml ), %w (xhtml) Mime::Type.register "text/plain", :text, [], %w(txt) Mime::Type.register "text/javascript", :js, %w(application/javascript application/x=javascript) Mime::Type.register "text/css", :css Mime::Type.register "text/caledar", :ics Mime::Type.register "text/csv", :csv Mime::Type.register "image/png", :png, [], %w(png) Mime::Type.register "image/jpeg", :jpeg, [], %w(jpg jpeg jpe pjpeg) Mime::Type.register "image/gif", :gif, [], %w(gif) Mime::Type.register "image/bmp", :bmp, [], %w(bmp) Mime::Type.register "image/tiff", :tiff, [], %w(tif tiff) Mime::Type.register "application/xml", :xml, %w(text/xml application/x-xml) Mime::Type.register "application/xml", :rss Mime::Type.register "application/atom+xml", :atom Mime::Type.register "application/x-yaml", :yaml, %w( text/yaml ) Mime::Type.register "multipart/form-data", :multipart_form Mime::Type.register "application/x-www-form-urlencoded", :url_encoded_form Mime::Type.register "application/json", :json, %w(text/x-json application/jsonrequest) Mime::Type.register "application/pdf", :pdf, [], %w(pdf) Mime::Type.register "application/zip", :zip, [], %w(zip)
PDFフォーマットがそれぞれのcontent typeで定義されています。/home.pdfにリクエストしたとき、RailsはHomeController#indexで定義されたformat.pdfブロックとマッチすると確認されたURLからpdfフォーマットを呼び出します。そして、renderをコールするブロックを呼び出す前に正しいcontent typeをセットします。
rendererの実装に戻りましょう。send_data()はパブリックなRailsのメソッドであり、最初のバージョンのRailsから利用できました。しかし、render_to-string()メソッドについては聞いたことないかもしれません。より理解を深めるには、Railsのレンダリングプロセス全体を見ていくことがいいでしょう。
Work In Progress
1-1. Crafting Rails 4 Applications - capter 1-1 - Satomi's Daily Notes