Получение PDF из WickedPDF для вложения через Carrierwave

В Rails3 я использую WickedPDF gem для рендеринга формата PDF одной из моих моделей. Это работает нормально: /invoices/123 возвращает HTML, /invoices/123.pdf загружает PDF.

В моей модели Invoice я использую гем state_machine для отслеживания статуса Invoice. Когда счет переходит из состояния «без выставления счета» в состояние «выставлен счет», я хотел бы получить копию счета в формате PDF и прикрепить ее к модели счета с помощью CarrierWave.

У меня три части работают отдельно: контроллер создает представление PDF, модель отслеживает состояние и запускает обратный вызов при правильном переходе, а CarrierWave настроен правильно. Тем не менее, у меня чертовски много времени, чтобы заставить их хорошо играть вместе.

Если бы я просто хотел получить HTML-версию счета, я мог бы вызвать render_to_string из модели. Но render_to_string, кажется, задыхается при получении двоичного файла PDF. Если я смогу вернуть поток данных, довольно легко записать эти данные во временный файл и прикрепить его к загрузчику, но я не могу понять, как получить поток данных.

Есть предположения? Код ниже:

Контролер счетов

def show
  @invoice = @agg_class.find(params[:id])

  respond_to do |format|
    format.pdf do
      render_pdf
    end
    format.html # show.html.erb
    format.json { render json: @aggregation }
  end
end

...

def render_pdf(options = {})
  options[:pdf] = pdf_filename
  options[:layout] = 'pdf.html'
  options[:page_size] = 'Letter'
  options[:wkhtmltopdf] = '/usr/local/bin/wkhtmltopdf'
  options[:margin] = {
    :top      => '0.5in',
    :bottom   => '1in',
    :left     => '0in',
    :right    => '0in'
  }
  options[:footer] = {
    :html => {
      :template   => 'aggregations/footer.pdf.haml',
      :layout     => false,
    }
  }
  options[:header] = {
    :html => {
      :template   => 'aggregations/header.pdf.haml',
      :layout     => false,
    }
  }
  render options
end

Накладная.рб

def create_pdf_copy

   # This does not work.    
   pdf_file = ApplicationController.new.render_to_string(
    :action => 'aggregations/show',
    :format => :pdf,
    :locals => { 
      :invoice => self
    }
  )

  # This part works fine if the above works.
  unless pdf_file.blank?
    self.uploads.clear
    self.uploads.create(:fileinfo => File.new(pdf_file), :job_id => self.job.id)
  end

end

ОБНОВЛЕНИЕ Решение найдено.

def create_pdf_copy

    wicked = WickedPdf.new

    # Make a PDF in memory
    pdf_file = wicked.pdf_from_string( 
        ActionController::Base.new().render_to_string(
            :template   => 'aggregations/show.pdf.haml', 
            :layout     => 'layouts/pdf.html.haml',
            :locals     => { 
                :aggregation => self
            } 
        ),
        :pdf => "#{self.type}-#{self}",
        :layout => 'pdf.html',
        :page_size => 'Letter',
        :wkhtmltopdf => '/usr/local/bin/wkhtmltopdf',
        :margin => {
            :top      => '0.5in',
            :bottom   => '1in',
            :left     => '0in',
            :right    => '0in'
        },
        :footer => {
            :content => ActionController::Base.new().render_to_string({
                :template => 'aggregations/footer.pdf.haml', 
                :layout => false
            })
        },
        :header => {
            :content => ActionController::Base.new().render_to_string({
                :template => 'aggregations/header.pdf.haml', 
                :layout => false
            })
        }
    )

    # Write it to tempfile
    tempfile = Tempfile.new(['invoice', '.pdf'], Rails.root.join('tmp'))
    tempfile.binmode
    tempfile.write pdf_file
    tempfile.close

    # Attach that tempfile to the invoice
    unless pdf_file.blank?
        self.uploads.clear
        self.uploads.create(:fileinfo => File.open(tempfile.path), :job_id => self.job.id)
        tempfile.unlink
    end

end

person lascarides    schedule 07.02.2013    source источник
comment
Хорошо, у меня все работает. Шаг 1 заключался в том, чтобы обойти тот факт, что WickedPDF игнорирует глобальные параметры конфигурации, когда он не работает в контексте контроллера. Шаг 2: используйте Tempfile для сохранения вывода PDF в виде временного файла в двоичном режиме, затем прикрепите его к Carrierwave. Отредактирован исходный пост, чтобы отразить решение.   -  person lascarides    schedule 08.02.2013
comment
Не могли бы вы дать мне более подробную информацию о шаге 1? У меня была старая версия wkhtmltopdf локально, и я отлично экспортировал с помощью render_to_string. На сервере у меня есть wkhtmltopdf 0.11.0, и render_to_string работает, но PDF не читается.. Обновлен до 0.11.0 локально, и render_to_string задыхается.. Кажется, это та же проблема, что и у вас.. К вашему сведению: я использую file = StringIO( render_to_string( options ) ), поэтому я могу пропустить Tempfile. Это со скрепкой, но вы можете попробовать, если вам нравится эта идея.   -  person Tim Baas    schedule 08.02.2013
comment
Рад, что вы решили это. Вы должны записать свое решение в качестве ответа, а затем принять свой ответ. Нет ничего плохого в том, чтобы ответить на свой вопрос!   -  person sockmonk    schedule 24.05.2013
comment
Вы должны опубликовать свое решение как ответ и установить его как выбранный ответ.   -  person ldnunes    schedule 29.05.2013


Ответы (2)


Решение:

def create_pdf_copy

    wicked = WickedPdf.new

    # Make a PDF in memory
    pdf_file = wicked.pdf_from_string( 
        ActionController::Base.new().render_to_string(
            :template   => 'aggregations/show.pdf.haml', 
            :layout     => 'layouts/pdf.html.haml',
            :locals     => { 
                :aggregation => self
            } 
        ),
        :pdf => "#{self.type}-#{self}",
        :layout => 'pdf.html',
        :page_size => 'Letter',
        :wkhtmltopdf => '/usr/local/bin/wkhtmltopdf',
        :margin => {
            :top      => '0.5in',
            :bottom   => '1in',
            :left     => '0in',
            :right    => '0in'
        },
        :footer => {
            :content => ActionController::Base.new().render_to_string({
                :template => 'aggregations/footer.pdf.haml', 
                :layout => false
            })
        },
        :header => {
            :content => ActionController::Base.new().render_to_string({
                :template => 'aggregations/header.pdf.haml', 
                :layout => false
            })
        }
    )

    # Write it to tempfile
    tempfile = Tempfile.new(['invoice', '.pdf'], Rails.root.join('tmp'))
    tempfile.binmode
    tempfile.write pdf_file
    tempfile.close

    # Attach that tempfile to the invoice
    unless pdf_file.blank?
        self.uploads.clear
        self.uploads.create(:fileinfo => File.open(tempfile.path), :job_id => self.job.id)
        tempfile.unlink
    end

end
person jamesharker    schedule 15.07.2013

Есть лучший способ: PDF в Rails без контроллеров .

person Rafał Cieślak    schedule 20.03.2014