I'm using Rails 4.2, sidekiq, rubyzip, axlsx and axlsx-rails
What I need to do:
I need to export file in a mail, but as a link and not as attachment.
I want an email send with a url to the download for it
Problem:
I don't know how to do it with this gem, in the git documentation there's nothing about it and every url I use don't work. I need to know how to create a download link for it and not as attachment to the mail
Code:
controller:
def export
export_args = {account_id: current_account.id}
ExportReportJob.perform_later(export_args)
redirect_to action: :index
end
job:
def perform(export_args)
begin
export_failed = false
account = Admin::Account.find_by(id: export_args[:account_id])
account.connect_to_target_db
#accounts = Admin::Account.active_accounts
file_name = "#{Time.now.strftime("%d_%m_%Y_at_%I_%M%p")}_export.xlsx"
dir_path = "#{Rails.root}/public/exports"
FileUtils.mkdir_p(dir_path) unless File.directory?(dir_path)
file_location = "#{absolute_path}/#{file_name}"
Admin::Mailer.export(account.pi, #accounts, file_location).deliver
rescue => e
export_failed = true
Airbrake.notify(e, environment_name: Rails.env, error_message: "export account failed #{e.message}")
ensure
ActiveRecord::Base.clear_active_connections!
end
end
template:
wb = xlsx_package.workbook
wb.add_worksheet(name: "Accounts") do |sheet|
sheet.add_row ["Account id", "Account name", "Organization"]
accounts.each do |account|
sheet.add_row [account.id, accoount.name, account.organization_id]
end
end
xlsx_package.to_stream.read
mailer:
def export(member, accounts, file_location)
#member = member
#title = "Export"
#header = subject = "Your Export is Ready"
#link = file_location
xlsx = render_to_string(layout: false, handlers: [:xlsx], template: "export.xlsx.axlsx", locals: {accounts: accounts})
# attachment = Base64.encode64(xlsx)
# attachments["export_#{DateTime.now.strftime("%d_%m_%Y_%HH%MM")}.xlsx"] = {mime_type: Mime::XLSX, content: attachment, encoding: 'base64'}
# ^ this is not what I want, I want to get the url for it and insert to #link
mail(:to => member.email, :subject => subject) do |format|
format.html
end
end
mail template:
= render partial: 'header'
= h_tag 'The export you requested has been completed'
= button 'Download the file', #link, { download: true }
= p_tag '(the file download will expire after <b>2</b> days)'
= render partial: 'footer'
In my Rails app I have a Job to create PDF files with WickedPDF. This worked fine until I migrated to Rails 5.2, now I get this error:
ActionView::Template::Error (Can't resolve image into URL: undefined method `polymorphic_url' for #<ActionView::Base:0x0000000004854590>):
The code where this happens is this line in the view:
image_tag(image)
This view is rendered from the Job, which I added in full below. The error happens when executing the last method in the class: private def render_image_pdf(view, pdf_name, image), where it is rendering a PDF.
After rolling back to Rails 5.1, everything works fine again, so I'm pretty sure it has something to do with an added/changed feature in Rails 5.2. So what should I change to make this working again?
The PDF Job:
class SendInvoiceAsAttachmentJob < ApplicationJob
require 'open-uri'
queue_as :default
def perform(target, invoice, send_invoice, to, cc, subject, body1, body2, body3)
view = ActionView::Base.new(ActionController::Base.view_paths, {})
view.extend(ApplicationHelper)
view.extend(Rails.application.routes.url_helpers)
# Create invoice as PDF
pdf = Rails.root.join('tmp', "tmp_invoice.pdf")
File.open(pdf, 'wb') do |file|
file << render_invoice_pdf(invoice, view, "tmp_invoice.pdf")
end
# Add (signed) timesheet as PDF
projectuser = Projectuser.where(project_id: invoice.project_id, user_id: invoice.user_id).order(:updated_at).last
if projectuser.blank? or invoice.fixed_price? or invoice.project.service?
pdf_name = "#{invoice.set_pdf_filename}.pdf"
pdf_to_email = pdf
else
if invoice.timesheet and invoice.timesheet.signed_copy?(projectuser)
# Download signed copy
timesheet_copy = TimesheetCopy.where(projectuser_id: projectuser.id, timesheet_id: invoice.timesheet_id).first
timesheet_file = Rails.root.join('tmp', timesheet_copy.attachment_file_name)
timesheet_name = timesheet_copy.attachment_file_name
if timesheet_copy.attachment.url.index("http").blank?
download = open("https:#{timesheet_copy.attachment.url}")
else
download = open(timesheet_copy.attachment.url)
end
IO.copy_stream(download, timesheet_file)
# Push into a PDF when image
if timesheet_copy.attachment_file_name.index(".pdf") == nil
timesheet_tmp = Rails.root.join('tmp', "tmp_timesheet.pdf")
File.open(timesheet_tmp, 'wb') do |file|
file << render_image_pdf(view, "tmp_timesheet.pdf", timesheet_file)
end
timesheet_file = timesheet_tmp
File.delete Rails.root.join('tmp', timesheet_copy.attachment_file_name)
end
else
# Create timesheet PDF
timesheet = Timesheet.find(invoice.timesheet_id)
timesheet_builder = TimesheetBuilder.new(timesheet.month, timesheet.year)
timesheet_name = "Timesheet #{timesheet.user.full_name} #{I18n.t("date.month_names")[timesheet.month]} #{timesheet.year}"
timesheet_file = Rails.root.join('tmp', timesheet_name)
File.open(timesheet_file, 'wb') do |file|
file << render_timesheet_pdf(timesheet, view, timesheet_name, projectuser, timesheet_builder)
end
end
# Combine the 2 PDF's
combined_pdf = CombinePDF.new
combined_pdf << CombinePDF.load(pdf, allow_optional_content: true)
combined_pdf << CombinePDF.load(timesheet_file, allow_optional_content: true)
pdf_name = "#{invoice.set_pdf_filename}.pdf"
combined_pdf.save Rails.root.join('tmp', pdf_name)
pdf_to_email = Rails.root.join('tmp', pdf_name)
File.delete(timesheet_file)
end
# Send email
if target == "basecone"
company = Company.find(invoice.company_id)
UserMailer.send_pdf_to_basecone(company.basecone_email, pdf_to_email, pdf_name).deliver
else
UserMailer.invoice_email(invoice, send_invoice, to, cc, subject, body1, body2, body3, pdf_to_email, pdf_name).deliver
end
File.delete(pdf_to_email)
end
private def render_invoice_pdf(invoice, view, pdf_name)
WickedPdf.new.pdf_from_string(
view.render(
pdf: pdf_name,
template: 'admin/invoices/show_as_attachment.pdf.haml',
locals: { invoice: invoice, copy_invoice: nil },
print_media_type: true,
orientation: 'Portrait',
page_size: 'A4'
)
)
end
private def render_timesheet_pdf(timesheet, view, pdf_name, projectuser, timesheet_builder)
WickedPdf.new.pdf_from_string(
view.render(
pdf: pdf_name,
template: 'timesheets/show_as_attachment.pdf.haml',
locals: { timesheet: timesheet, timesheet_builder: timesheet_builder, projectuser: projectuser },
print_media_type: true,
orientation: 'Portrait',
page_size: 'A4'
)
)
end
private def render_image_pdf(view, pdf_name, image)
WickedPdf.new.pdf_from_string(
view.render(
pdf: pdf_name,
template: 'admin/invoices/image_timesheet_as_attachment.pdf.haml',
locals: { image: image },
print_media_type: true,
orientation: 'Portrait',
page_size: 'A4'
)
)
end
end
Use wicked_pdf_image_tag Instead of image_tag
<%=wicked_pdf_image_tag image%>
Instead of manually constructing the ActionView setup (and thus needing to add all the right helpers, which is where you're going wrong -- I think some moved around), you can use the newish ActionController::Renderer API to perform a view render within the context of an arbitrary controller.
# Very untested; please treat this is syntactically-valid pseudocode...
WickedPdf.new.pdf_from_string(
ApplicationController.render(
template: 'admin/invoices/show_as_attachment.pdf.haml',
assigns: {
pdf: pdf_name,
print_media_type: true,
orientation: 'Portrait',
page_size: 'A4',
},
locals: { invoice: invoice, copy_invoice: nil },
)
)
I am trying to download my dynamic content as a pdf file. The pdf is being generated but with no content in it, not even a simple text(when I test with that).
As I need a lot of data which I need to calculate and send parameters without refreshing page so I am doing it through an Ajax request.
$("#download_pdf").click(function() {
report_type = $("#report_type").val();
start_date = $("#start_date").val();
end_date = $("#end_date").val();
date_type = $("#date_type").val();
$.ajax({
data: { report_type: report_type, start_date: start_date, end_date: end_date, date_type: date_type },
url: '/reports/generate_pdf.pdf',
success: function(data) {
var blob=new Blob([data]);
var link=document.createElement('a');
link.href=window.URL.createObjectURL(blob);
link.download="Report_"+new Date()+".pdf";
link.click();
console.log("pdf printed");
}
});
});
Here is my ruby code in the controller:
def generate_pdf
#results = get_report_result_by_datetype(params[:report_type], params[:start_date], params[:end_date], params[:date_type])
#type = params[:report_type].to_i
#start_date = params[:start_date]
#end_date = params[:end_date]
respond_to do |format|
format.html
format.pdf do
render pdf: "report",
layout: 'pdf_layout',
template: 'reports/generate_pdf.html.erb',
encoding: 'UTF8',
print_media_type: true,
disposition: 'attachment',
page_size: 'letter',
orientation: 'landscape',
lowquality: 'false',
debug: true
end
end
end
There is no issue in the data which I am fetching.
Note: The most strange thing I noticed is that when the calculated array is small the generated pdf has one page and if it is big the generated pdf shows multiple pages.
I would really appreciate if someone points where I am going wrong.
Thanks in Advance!!
Although I don't know how to fix your wicked_pdf code, I can recommend the following using the gem prawn (which is not rails-specific and removes some of the Rails magic from the equation):
format.pdf do
pdf_path = Rails.root.join("tmp", "my_file_name.pdf")
template_path = Rails.root.join("app", "views", "reports", "generate_pdf.html.erb")
this = binding
Prawn::Document.generate(pdf_path) do
text Erb.new(template_path).result(this)
end
render file: pdf_path
end
Haven't actually tested it so apologies if it doesn't work.
I am generating xlsx files with axlsx_rails gem.
After collecting user input I am posting data to /basic_report_post.xlsx
Controller action looks like
def basic_report_post
#config = params[:config]
#data = params[:data]
#filename = "#{Rails.root}/public/test.xlsx"
respond_to do |format|
format.xlsx {
render xlsx: 'basic_report_post'
}
end
end
View file for this action basic_report_post.xlsx.axlsx
wb = xlsx_package.workbook
wb.add_worksheet(name: 'Data1') do |s|
# Drawing columns
end
xlsx_package.serialize #filename
My problem is that I am getting response data(in post success action) that is raw .xlsx file.
But I need somehow respond #filename (format json/html) to download it after.
It is possible to use the axlsx_rails template renderer to create a string and save it to file:
def basic_report_post
#config = params[:config]
#data = params[:data]
#filename = "#{Rails.root}/public/test.xlsx"
File.open(#filename, 'w') do |f|
f.write render_to_string(handlers: [:axlsx], formats: [:xlsx], template: 'path/to/template')
end
render json: {name: #filename}
end
Then you can use the template to serve the file directly if need be.
After some experiments with respond_to I move .xlsx generation logic to view helper.
So I have included BasicReportHelper in controller.
basic_report_helper.rb
module BasicReportsHelper
def generate_basic_report(filename)
p = Axlsx::Package.new
wb = p.workbook
wb.add_worksheet(:name => "Basic Worksheet") do |sheet|
# Drawing here
end
p.serialize filename
end
end
Changed post call to /basic_report_post.json and changed action to
def basic_report_post
#config = params[:config]
#data = params[:data]
#filename = "#{Rails.root}/public/test.xlsx"
generate_basic_report(#filename)
respond_to do |format|
format.json {
render json: {name: #filename}
}
end
end
I have a helper class that renders a partial to store the content in the database.
It works fine but when the view contains a url provided by the url helper
<%= link_to "Show project", projects_url, class: "button" %>
it throws the following exception
undefined local variable or method projects_url for #
The code to render in my helper named NotificationRender is
def render(options)
viewer = ActionView::Base.new()
viewer.view_paths = ActionController::Base.view_paths
viewer.extend ApplicationHelper
viewer.render options
end
I include this helper in a class Notification
class Notification < ActiveRecord::Base
include NotificationRender
.....
def self.create_for(user, event, params)
template = "notifications/_email_project"
list_params = {:template => template,:locals => params}
notification = Notification.new
notification.message = notification.render(list_params) #here I render the partial
notification.subject = I18n.t "subject.#{event}"
notification.to = user.email
notification.save
end
end