In the system I wrote for my former company, several pages needed to be available in both HTML and PDF formats. As usual, there was a tradeoff to consider:
If we exported the HTML document directly to a PDF using wicked_pdf, then there was less code to write, but the resulting PDF wouldn’t look as good.
If we wrote the PDF code separately from the HTML using Prawn, then the PDFs looked nicer, but then we had twice as much code to maintain.
We went the latter route, and ended up with a lot of code that felt redundant:
# in HTML view - if @order.items.active.any? h3 Active items table tr th Item SKU th Item quantity - @order.items.active.each do |item| tr td = item.sku td = item.quantity # in Prawn PDF if @order.items.active.any? text Active items table [["Item SKU", "Item quantity"] + @order.items.active.map do |item| [ item.sku, item.quantity ] end end
Some of this duplication can be mitigated, though. We’ll do this using the Facade Pattern:
class ReportFacade def initialize(order) @order = order end def item_section_header "Active Items" end def show_items? items.any? end def items @order.items.active end end
This centralizes the logic for:
1. If the section should be shown at all: @order.items.active.any?
2. What items should be shown: @order.items.active
3. What the section header text should be: "Active Items"
Now, in our controller, we switch
def show @order = Order.find(params[:id]) end
To:
def show order = Order.find(params[:id]) @report_facade = ReportFacade.new(order) end
And access @report_facade
in our views.
# in HTML view - if @report_facade.show_items? h3 = item_section_header table tr th Item SKU th Item quantity - @report_facade.items.each do |item| tr td = item.sku td = item.quantity # in Prawn PDF if @report_facade.show_items? text item_section_header table [["Item SKU", "Item quantity"] + @report_facade.items.map do |item| [ item.sku, item.quantity ] end end
The advantage here is that it is harder for these two formats to get out of sync with one another. Changing the header in the ReportFacade
object will change it in both the HTML and the PDF versions.
The disadvantage is that it makes for even more code overall, and when trying to understand the HTML or PDF code, you have to constantly reference the ReportFacade
class.
It’s not so clear to me whether this is an overall win or not. What do you think? Is this worth doing?