1

I'm working on a class that generates a PDF using Prawn gem. I have some similar methods. All of them start with the same line. Here is the code:

module PDFGenerator
  class MatchTeamInfo
    include Prawn::View

    def initialize(match)
      @match = match
      @output = Prawn::Document.new page_layout: :landscape
      defaults
      header
      footer
    end

    def render
      @output.render
    end

    def defaults
      @output.instance_exec do
        font_size 16
        text 'hola'
      end
    end

    def header
      @output.instance_exec do
        bounding_box [bounds.left, bounds.top], :width  => bounds.width do
          text "Fulbo", align: :center, size: 32
          stroke_horizontal_rule
          move_down(5)
        end
      end
    end

    def footer
      @output.instance_exec do
        bounding_box [bounds.left, bounds.bottom + 25], :width  => bounds.width do
          stroke_horizontal_rule
          move_down(5)
          text "Tu sitio favorito!", align: :center
        end
      end
    end
  end
end

Is there a way to avoid @output.instance_exec in every method and use something like blocks? I tried it, but I can't get it work. Can I do something like this?

def apply
  @output.instance_exec do
    yield
  end
end

How am I supposed to define the code blocks?

sawa
  • 165,429
  • 45
  • 277
  • 381

2 Answers2

2

First of all, you need to make all helper methods to return lambda instance:

def defaults
  lambda do
    font_size 16
    text 'hola'
  end
end

Now you might pass lambdas returned by your helpers to instance_exec. To acknowledge it about “this is code block rather than regular param,” lambda is to be prefixed with ampersand:

def apply
  #                     ⇓ NB! codeblock is passed!
  @output.instance_exec &defaults
end

If you want to pass a codeblock to apply, you should re-pass it to instance_exec. Unfortunately I know no way to re-pass it using yield keyword, but here is a trick: Proc.new called without parameters inside a method that was called with a codeblock given, is instantiated with this codeblock, so here you go:

def apply
  raise 'Codeblock expected' unless block_given?
  @output.instance_exec &Proc.new
end
Aleksei Matiushkin
  • 119,336
  • 10
  • 100
  • 160
2

You can define a method document that returns a Prawn::Document instance.

Prawn::View will then delegated the method calls to that document. Here's an example:

module PDFGenerator
  class MatchTeamInfo
    include Prawn::View

    def initialize(match)
      @match = match
      defaults
      header
      footer
    end

    def document
      @document ||= Prawn::Document.new page_layout: :landscape
    end

    def defaults
      font_size 16
      text 'hola'
    end

    def header
      bounding_box [bounds.left, bounds.top], :width  => bounds.width do
        text "Fulbo", align: :center, size: 32
        stroke_horizontal_rule
        move_down(5)
      end
    end

    def footer
      bounding_box [bounds.left, bounds.bottom + 25], :width  => bounds.width do
        stroke_horizontal_rule
        move_down(5)
        text "Tu sitio favorito!", align: :center
      end
    end
  end
end

Example usage:

pdf = PDFGenerator::MatchTeamInfo.new(nil)
pdf.save_as('team_info.pdf')

Output: (converted to PNG)

team_info.pdf

Stefan
  • 109,145
  • 14
  • 143
  • 218
  • Thanks alot! I've marked as accepted the answer by @mudasobwa because he has responded exactly to my question. However, your suggestion is very nice and the code results more clear. I guess this is the 'correct' way to do it with Prawn. So, I will do it in that way. Again, thanks and cheers! – luuchorocha Sep 24 '15 at 17:39
  • @Lucho-Rocha you're welcome. Don't forget to upvote helpful answers once you have enough reputation ;-) – Stefan Sep 24 '15 at 18:40
  • @Stefan I have enough reputation to upvote helpful answers, so here we go :) – Aleksei Matiushkin Sep 25 '15 at 02:21
  • @mudasobwa Of course, when I have enough reputation, I will upvote your answers :) – luuchorocha Sep 25 '15 at 04:51