如何重构多次使用“instance_exec”的代码?

How do I refactor code that uses `instance_exec` several times?

我正在研究使用 Prawn gem 生成 PDF 的 class。我有一些类似的方法。它们都以同一行开头。这是代码:

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

有没有办法在每个方法中避免 @output.instance_exec 并使用类似块的东西?我试过了,但我无法让它工作。我可以做这样的事情吗?

def apply
  @output.instance_exec do
    yield
  end
end

我应该如何定义代码块?

首先,您需要将所有辅助方法制作成 return lambda 实例:

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

现在您可以将助手 return 编辑的 lambda 传递给 instance_exec。要承认“这是代码块而不是常规参数”,lambda 应以 & 符号为前缀:

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

如果您想将代码块传递给 apply,您应该将其重新传递给 instance_exec。不幸的是,我不知道如何使用 yield 关键字重新传递它,但这里有一个技巧: Proc.new 在使用给定代码块调用的方法中调用时没有参数,使用此代码块实例化,所以给你:

def apply
  raise 'Codeblock expected' unless block_given?
  @output.instance_exec &Proc.new
end

您可以定义一个 document 方法 returns 一个 Prawn::Document 实例。

Prawn::View 然后会将方法调用委托给该文档。这是一个例子:

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

用法示例:

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

输出:(转换为 PNG)