Search code examples
ruby-on-railsrubydocx

Docx gem: undefined method `close' for nil


I'm creating a services to generate or modify a docx file with some data from my database this is the code:

    require 'docx'
    require 'prawn'

    class DocumentGenerator
      TEMPLATE_PATH = Rails.root.join('app', 'assets', 'docs_templates', 'plantilla_word.docx')

      def initialize(dni)
        @dni = dni
      end

      def generate_docx
        byebug
        if File.exist?(TEMPLATE_PATH)
          doc = Docx::Document.open(TEMPLATE_PATH)
          doc.bookmarks.each_pair do |bookmark_name, bookmark_object|
            puts bookmark_name
          end
          byebug
          # doc.bookmarks['first_lastname'].replace(@dni.first_lastname)
          # doc.bookmarks['second_lastname'].replace(@dni.second_lastname)
          # doc.bookmarks['names'].replace(@dni.names)
          # doc.bookmarks['dni_number'].replace(@dni.dni_number)
          file_path = Rails.root.join('tmp', 'generated_doc.docx')
          doc.save(file_path)
          file_path
        else
          raise "El archivo de plantilla no se encuentra en la ruta: #{TEMPLATE_PATH}"
        end
      end

      def generate_pdf
        pdf = Prawn::Document.new
        pdf.text "First Lastname: #{@dni.first_lastname}"
        pdf.text "Second Lastname: #{@dni.second_lastname}"
        pdf.text "Names: #{@dni.names}"
        pdf.text "DNI Number: #{@dni.dni_number}"
        file_path = Rails.root.join('tmp', 'generated_pdf.pdf')
        pdf.render_file(file_path)
        file_path
      end
    end

The problem is when I try to open the file to replace the bookmarks with my data

    10:   def generate_docx
    11:     byebug
    => 12:     if File.exist?(TEMPLATE_PATH)
    13:       doc = Docx::Document.open(TEMPLATE_PATH)
    14:       doc.bookmarks.each_pair do |bookmark_name, bookmark_object|
    15:         puts bookmark_name
    16:       end
    (byebug) Docx::Document.open(TEMPLATE_PATH)
    *** NoMethodError Exception: undefined method `close' for nil

    nil

I already try move the file, use

Docx::Document.parse(File.read(TEMPLATE_PATH))

, but nothing appear solve the issue.


Solution

  • Your TEMPLATE_PATH constant contains a Pathname object because Rails.root.join returns a Pathname rather than just a String.

    Now, Docx::Document#initialize checks the provided argument and decides whether it constitutes a filename (if it is a String) or falls back to assume it's a IO-like object. With your Pathname object, this detection fails.

    As the Docx gem tries to use the Pathname object as an IO object and passes it to Zip::File.open_buffer, this method call fails with the following exception:

    RuntimeError (Zip::File.open_buffer expects a String or IO-like argument (responds to tell, seek, read, eof, close). Found: Pathname)
    

    Now, because Docx::Document#initialize has a trailing ensure statement which should close the opened document, this fails again as the document (i.e. @zip) was never opened and is thus nil, resulting in the exception you see there.

    To circumvent this issue, you should thus pass your TEMPLATE_PATH as a String by changing the assignment to:

    TEMPLATE_PATH = Rails.root.join('app', 'assets', 'docs_templates', 'plantilla_word.docx').to_s