今関わっているプロジェクトではレッスンの詳細をまとめたPDFに結構大きな需要があり、数年間運用されてきたのですが、このprawn-railsで書かれたテンプレートの当時を知っている者がいなくなり放置されて完全に秘伝のタレ化して機能追加したくても手が付けられなくなっていたのが大きな悩みのタネでした。
Thinreportsに移行して綺麗さっぱり書き直すことも検討したのですが、どうしても譲れない要件を満たすことが困難で泣く泣く断念しました(その際の機能比較はまた別の機会に)。
そこで、今回はファットなprawnのビューに多少なりとも秩序を取り戻していく過程をご紹介していこうと思います。
初期状態
ざっくり言うと以下の様な感じの数百行のコードでした。
- 大量のif式ネスト
- メソッド分割一切なし
- 木を見て森を見ないコメント(個々のコメントは役に立つが肝心の全体像が見えない)
- 特定の環境に依存したURLの処理が多数
- 書き方が古かったり、スタイルガイドに準拠していなかったり、記述に無駄があったりするコードが多数
- pdf.move_down 25のようなカーソル移動処理が縦横無尽に存在する
コンポーネントを分類する
まずは大きな処理の区分けを判別して分割します。if式まみれなことが不幸中の幸いになっており、if式の括りで何を記述しているかをおおまかに類推することが可能でした。
クラスを作ってぶち込む
ビューで使われていたprawnの書き方は「prawn_documentのブロックから受け取ったPrawn::Documentのオブジェクトをひたすら破壊的更新していく」という関数型言語ユーザーが聞いたら卒倒しそうな仕組みですが、幸いな事にPrawn::Documentのインスタンスメソッドさえ呼べれば処理が完結するので、RailsのActionViewのように書く場所によってヘルパーが呼べたり呼べなかったりすることがありません。
そこで、別のクラスを用意して処理を内側にに押し込むことができそうです。以下がその超簡略版になります。
class ReviewPDF
attr_reader :pdf
def initialize(model, pdf)
@model = model
@pdf = pdf
end
def title
@pdf.font_size 15
@pdf.formatted_text [
{ text: @model.user.fullname, color: "4AAED8" },
{ text: " さんの英会話" },
{ text: " (レッスン日: #{date.strftime("%Y/%m/%d")})", size: 9 }
]
@pdf.move_down 10
self
end
#以下省略...
private
def pdf_image_path(image)
if Rails.env.development?
#専用の処理を記述
else
image.url
end
end
end
prawn_document do |pdf|
pdf_model = ReviewPDF.new(model, pdf)
pdf_model
.title
.explanation
.phrase_block
.memo
.review
.skype_evaluation
end
インスタンス変数の@pdfにPrawn::Documentを放り込んでビュー側はReviewPDFのメソッドを呼び出すだけなので超シンプルになりました。書かれなかったメソッドの内側にあるコードは正直まだかなり臭いのですが、名前がついて隔離されたぶん一歩前進といったところです。
プライベートメソッドを拡充する
さらに、こうやってPDFに関する実装を隔離できたので、pdf_image_pathのような環境依存のコードをプライベートメソッドに切り出すことができます。ModelやHelperに本流でない処理が大量に書かれるのは気が引けたのでこちらの方が役割がはっきりしていい感じです。
まとめ
prawn-railsでビューテンプレートを書く場合は一つのファイルに全部まとめるとカオスなことになるので別途クラスを用意して役割ごとにメソッドを分けてあげると「まだマシ」になります。ここからさらに抽象化を進めたりテスト書いたりとあるべき姿はとても遠いのですがまずはその第一歩からというお話でした。