prawnpdf/prawn

View on GitHub
lib/prawn/view.rb

Summary

Maintainability
A
0 mins
Test Coverage
# frozen_string_literal: true

module Prawn
  # This mixin allows you to create modular Prawn code without the
  # need to create subclasses of {Prawn::Document}.
  #
  # ```ruby
  # class Greeter
  #   include Prawn::View
  #
  #   # Optional override: allows you to set document options or even use
  #   # a custom document class
  #   def document
  #     @document ||= Prawn::Document.new(page_size: 'A4')
  #   end
  #
  #   def initialize(name)
  #     @name = name
  #   end
  #
  #   def say_hello
  #     text "Hello, #{@name}!"
  #   end
  #
  #   def say_goodbye
  #     font("Courier") do
  #       text "Goodbye, #{@name}!"
  #     end
  #   end
  # end
  #
  # greeter = Greeter.new("Gregory")
  #
  # greeter.say_hello
  # greeter.say_goodbye
  #
  # greeter.save_as("greetings.pdf")
  # ```
  #
  # The short story about why you should use this mixin rather than creating
  # subclasses of `Prawn::Document` is that it helps prevent accidental
  # conflicts between your code and Prawn's code.
  #
  # Here's the slightly longer story...
  #
  # By using composition rather than inheritance under the hood, this mixin
  # allows you to keep your state separate from `Prawn::Document`'s state, and
  # also will prevent unexpected method name collisions due to late binding
  # effects.
  #
  # This mixin is mostly meant for extending Prawn's functionality with your own
  # additions, but you can also use it to replace or wrap existing Prawn
  # methods. Calling `super` will still work as expected, and alternatively you
  # can explicitly call `document.some_method` to delegate to Prawn where
  # needed.
  module View
    # @group Experimental API

    # Lazily instantiates a `Prawn::Document` object.
    #
    # You can also redefine this method in your own classes to use
    # a custom document class.
    #
    # @return [Prawn::Dcoument]
    def document
      @document ||= Prawn::Document.new
    end

    # Delegates all unhandled calls to object returned by {document} method.
    #
    # @param method_name [Symbol]
    # @param args [Array] Positional arguments.
    # @param kwargs [Hash] Keyword arguments.
    # @param block [Proc]
    def method_missing(method_name, *args, **kwargs, &block)
      return super unless document.respond_to?(method_name)

      document.public_send(method_name, *args, **kwargs, &block)
    end

    # Does this object respond to the specified message?
    #
    # @param method_name [Symbol]
    # @param _include_all [Boolean]
    # @return [Boolean]
    def respond_to_missing?(method_name, _include_all = false)
      document.respond_to?(method_name) || super
    end

    # Syntactic sugar that uses `instance_eval` under the hood to provide
    # a block-based DSL.
    #
    # @example
    #   greeter.update do
    #     say_hello
    #     say_goodbye
    #   end
    #
    # @yield
    # @return [void]
    def update(&block)
      instance_eval(&block)
    end

    # Syntatic sugar that calls `document.render_file` under the hood.
    #
    # @example
    #   greeter.save_as("greetings.pdf")
    #
    # @param filename [String]
    # @return [void]
    def save_as(filename)
      document.render_file(filename)
    end
  end
end