I have an uploader (internal use only) that will upload an HTML document to a binary column of a table in my client-facing website. The client facing site has an index that allows the user to view the page as a normal website (using send_data h_t.html_code, :type => "html", :disposition => "inline"). I also want to give the user the ability to download a PDF of the page. For that I'm using wicked_pdf.
The entire problem seems to stem from the fact that the data is stored in the database. As strange as it sounds, it is vital to business operations that I get formatting exact. The issue is I can't see any image, and the stylesheets/style tags don't have any effect.
What I've tried-
Gsub-
def show
html = HtmlTranscript.find(params[:id])
html_code = html.html_code.gsub('<img src="/images/bwTranscriptLogo.gif" alt="Logo">','<%= wicked_pdf_image_tag "bwTranscriptLogo.gif" %>')
html_code = html_code.gsub('<link rel="StyleSheet" href="" type="text/css">','<%= wicked_pdf_stylesheet_link_tag "transcripts.css" %>')
transcript = WickedPdf.new.pdf_from_string(html_code)
respond_to do |format|
format.html do
send_data transcript, :type => "pdf", :disposition => "attachment"
end
##### i never could get this part figured out, so if you have a fix for this...
# format.pdf do
# render :pdf => "transcript_for_#{#html.created_at}", :template => "html_transcripts/show.html.erb", :layout => false
# end
end
end
Using a template-
#Controller (above, modified)
html = HtmlTranscript.find(params[:id])
#html_code = html.html_code.gsub('<img src="/images/bwTranscriptLogo.gif" alt="Logo">','<%= wicked_pdf_image_tag "bwTranscriptLogo.gif" %>')
#html_code = #html_code.gsub('<link rel="StyleSheet" href="" type="text/css">','<%= wicked_pdf_stylesheet_link_tag "transcripts.css" %>')
transcript = WickedPdf.new.pdf_from_string(render_to_string(:template => "html_transcripts/show.html.erb", :layout => false))
#view
<!-- tried with stylesheet & image link tags, with wicked_pdf stylesheet & image link tags, with html style & img tags, etc -->
<%= raw(#html_code) %>
And both will generate a transcript- but neither will have style OR image.
Creating an initializer-
module WickedPdfHelper
def wicked_pdf_stylesheet_link_tag(*sources)
sources.collect { |source|
"<style type='text/css'>#{Rails.application.assets.find_asset("#{source}.css")}</style>"
}.join("\n").gsub(/url\(['"](.+)['"]\)(.+)/,%[url("#{wicked_pdf_image_location("\\1")}")\\2]).html_safe
end
def wicked_pdf_image_tag(img, options={})
image_tag wicked_pdf_image_location(img), options
end
def wicked_pdf_image_location(img)
"file://#{Rails.root.join('app', 'assets', 'images', img)}"
end
def wicked_pdf_javascript_src_tag(source)
"<script type='text/javascript'>#{Rails.application.assets.find_asset("#{source}.js").body}</script>"
end
def wicked_pdf_javascript_include_tag(*sources)
sources.collect{ |source| wicked_pdf_javascript_src_tag(source) }.join("\n").html_safe
end
end
did absolutely nothing, and I have no idea what to try next.
As a side note, the code to view the HTML version of the transcript is as follows:
def transcript_data
h_t = HtmlTranscript.find(params[:id])
send_data h_t.html_code, :type => "html", :disposition => "inline"
end
It requires no view, as the html data is stored in the database, but I get image, style, etc. Everything works with the HTML version- just not the PDF.
I'm on ruby 1.8.7 with rails 3.0.20.
Solved-
As it turns out, there was more than one issue at hand.
1- Installation of wkhtmltopdf for Ubuntu via $apt-get install does not quite do the trick for what I wanted...
see http://rubykitchen.in/blog/2013/03/17/pdf-generation-with-rails
(there may have also been an issue with having not previously run sudo apt-get install openssl build-essential xorg libssl-dev libxrender-dev, as when I did, it installed a number of components I did not previously have.)
2- The HTML files I had uploaded contained image & style code that was breaking the formatting. I fixed it with this...
def rm_by_line(which = 0, line1 = 0, line2 = 0)
h_t = HtmlTranscript.find(which)
line_by_line = h_t.html_code.split('
')
for i in line1..line2
line_by_line[i] = ''
end
line_by_line = line_by_line.join('
').strip
return line_by_line
end
Then, all I had to do was pass which lines I wanted to remove.
(I had to split the parens with a carriage return because '\n' didn't function properly when calling 'raw' on the returned string.)
3- wicked_pdf_stylesheet_link_tag and wicked_pdf_image_tag were undefined. I had to inline the style formatting I wanted into a layout I created (turns out wicked_pdf_stylesheet_link_tag used asset pipeline wich my ruby/rails did not implement, which also means I had to get rid of the javascript helpers) and created a helper for wicked_pdf_image_tag, making a switch in the layout for which image tag (image_tag or wicked_pdf_image_tag) to be used.
4- I needed both a .html.erb & a .pdf.erb for my templates, so I made both.
5- Got rid of WickedPdf.new.pdf_from_string in favor of linking to either html or pdf by using :format => 'html' or :format => 'pdf' in the link_to tag.
Related
I've got a controller "tech" that has an action to email and invoice, from there we use Delayed::Job.enqueue to shove the actual email action into a background process which will be handled via a worker dyno on Heroku.
This is all working fine.
The trouble that I found is that my generated PDF invoice lives over on the Heroku Web Dyno file system and the Worker has no idea where this is.
I will do upload the PDF during the generation process, it takes too damn long.
So I need to create the invoice over on the worker dyno when it goes to execute the mailer action to send the message.
I found this blog with some detailed instructions on creating the pdf from a string: http://viget.com/extend/how-to-create-pdfs-in-rails
But it's not working at all for me, here is the code:
html = render_to_string(:action =>":show", :layout => "invoice.html")
#pdf = WickedPdf.new.pdf_from_string(html)
And the error:
"last_error"=>"undefined method `response_body=' for #<MailSenderJob:0x007fdf7e70a638>
I know this is from the docs:
WickedPdf.new.pdf_from_string(
render_to_string('templates/pdf.html.erb', :layout => 'pdfs/layout_pdf'),
:footer => {
:content => render_to_string(:layout => 'pdfs/layout_pdf')
}
)
And that code has never worked for me at all.
What I'm getting over and over is the response_body= error. It's like it's not getting a response at all.
At the top of my file I'm doing:
include ActionController::Rendering
Because this is the module that has the render_to_string method inside it.
Any help at all - please keep in mind in your response that I'm running this code on a Heroku WORKER dyno - so if there's any dependency that I need to manually include that is naturally included on the web server, please let me know.
I ended up having to do some weird stuff with this to finally get it working.
html = File.read(Rails.root.join('app','views','technician','invoice.html.erb'))
html = ERB.new(html).result(binding)
html = html.gsub!(/\0/,'') # There is a null byte in the rendered html, so we'll strip it out (this is kind of a hack)
# Render the PDF - we're on a worker dyno and have no access to the pdf we rendered already on the web dyno
#pdf = WickedPdf.new.pdf_from_string(
html,
:handlers => [:erb],
:footer => {
:center => "Copyright 2014"
},
:disable_smart_shrinking => true,
)
#pdf = #pdf.gsub!(/\0/,'') # Again with the null bytes!
Using Partials.
I know what you mean, it gets a little funky when you're rendering PDFs in the background job as opposed to a Controller action.
I thought I would share my implementation as a comparison and for others to get another example from.
notification_mailer.rb
def export
header_html = render_to_string( partial: 'exports/header.pdf.erb',
locals: { report_title: 'Emissions Export' } )
body_html = render_to_string( partial: "exports/show.pdf.erb" )
footer_html = render_to_string( partial: 'exports/footer.pdf.erb' )
#pdf = WickedPdf.new.pdf_from_string(
body_html,
orientation: 'Landscape',
margin: { bottom: 20, top: 30 },
header: { content: header_html },
footer: { content: footer_html } )
# Attach to email as attachment.
attachments["Emissions Export.pdf"] = #pdf
# Send email. Attachment assigned above will automatically be included.
mail( { subject: 'Emissions Export PDF', to: 'elon#musk.com' } )
end
I'm working on migrating a rather big project to Rails 3.
Here's my controller action:
def recent
#account = Account.find(session[:account_id])
render :layout => false
end
Here's my recent.json.erb file
formatted_account =
{
:code = 1,
:id = #account.id,
:prefix = 2
}
formatted_account.to_json()
I'm using jQuery.getJSON to get this data, when I get the response, this is what I get:
[{"code":1,"id":"1 "prefix":2}]
Instead of
[{code:1, id:1, prefix:2}]
I had to use safe_html in some other pieces of code to solve escaping issues like this but in this case I can't figure out how to solve without getting rid of the .json.rb file and rendering a json object in a proper way.
html_safe did the trick:
formatted_account =
{
:code = 1,
:id = #account.id,
:prefix = 2
}
formatted_account.to_json.html_safe
If you want to build up json from a template you'll need to use some kind of builder as ERB won't really cut it.
JBuilder comes commented out in a fresh Rails 3.2 Gemfile. Haven't used it myself but it seems well thought out with a clean DSL. There's also a list of links at the bottom of the README on the JBuilder github page.
RABL is another tool for building JSON, as well as supporting multiple other formats.
JBuilder
RABL
I have the following requirement. I have a 'school' drop down and as the last options I have add new school, so if the user selects that option I want to load the new_school form as a partial via ajax.
I'm on
gem 'rails', '3.2.9'
gem 'coffee-rails', '~> 3.2.1'
Jquery via gem 'jquery-rails'
Earlier with rails < 3 and prototype I used to do it with
Ajax.Updater (aka Rails link_to_remote :update => 'some_div')
and with rails > 3 + JQuery I'm familiar with *.js.erb, and having something like
$("#school_form").html("<%= escape_javascript(render(:partial => "form"))%>");
But I'm new to coffeescript and I have no idea on how to do this with coffeescript, can someone help me :), (because I believe you shouldn't have to do a server request for this)
So far I have done following to catch the select_tag change event
$ ->
$('#school_name_select').change ->
unless $(this).val()
$('school_name').html([I want to have the _new_school_form partial here])
Use a hidden div.
In general, you don't want to bother trying to mix JS and HTML. The escaping can be complicated, error-prone, and flat out dangerous due to the possibility of cross-site scripting attacks.
Simply render your form partial in a div that's not displayed by default. In ERB:
<div id="school_name_form" style="display: none;">
<%= render 'form' %>
</div>
In your CoffeeScript:
$ ->
$('#school_name_select').change ->
if $(this).val()
$('#school_name_form').slideUp()
else
$('#school_name_form').slideDown()
I recommend using a small, tasteful transition like slide or fade. It gives your app a more polished feel.
No AJAX is required. This pattern is so common that I have an application-wide style defined as follows.
.not-displayed {
display: none;
}
Then using HAML (if you're into that), the HTML template becomes simply:
#school_name_form.not-displayed
= render 'form'
You can try to render the form partial inside hidden div (not too correct from semantic point of view), or put the form html as data attribute of any relevant element, something like
f.select school_name, ... , data: {form: escape_javascript(render(:partial => "form"))}
And the Coffeescript
$ ->
$('#school_name_select').change ->
unless $(this).val()
$('school_name').html($('#school_name_select').data('form'))
I wanna override html code when working with active_admin gem in Rails; because the nav-bar and many elements in these gem's views are different with my views (other pages). I hope that has a way to change html code without changing css manually! Thanks
It is not very easy , activeadmin use DSL for building html (called "Arbre")
You have to monkey patch every page class, also , it may prevent customizing of css too.
For example to move sidebar to left, create initializer with next patch.
class ActiveAdmin::Views::Pages::Base < Arbre::HTML::Document
def build_page_content
build_flash_messages
div :id => "active_admin_content", :class => (skip_sidebar? ? "without_sidebar" : "with_sidebar") do
build_sidebar unless skip_sidebar?
build_main_content_wrapper
end
end
end
default method was
def build_page_content
build_flash_messages
div :id => "active_admin_content", :class => (skip_sidebar? ? "without_sidebar" : "with_sidebar") do
build_main_content_wrapper
build_sidebar unless skip_sidebar?
end
end
The full list of classes used for rendering can be found here , so some of them you need to patch.
https://github.com/gregbell/active_admin/tree/master/lib/active_admin/views
Be ready for a big piece of work.
UPD. Gem for changing activeadmin sidebar position
https://github.com/Fivell/active_admin_sidebar
To implement Gravatar in my Rails3 application, I'm using the gravatar_image_tag gem in a helper, but I'm having issues when mixing 2 config options:
If the user doesn't have a gravatar attached to his email a default image is rendered; but I want it to reference an external file (e.g., http://www.iconfinder.com/ajax/download/png/?id=43350&s=128 instead of :identicon or others)
I also want the image to be resized on the fly to, let's say 50px.
Independently, both options work as expected, but when I put them together:
def gravatar_for(user, options = { :default => 'http://www.iconfinder.com/ajax/download/png/?id=43350&s=128', :size => 50 })
gravatar_image_tag(user.email.downcase, :alt => user.full_name,
:class => 'gravatar',
:gravatar => options)
end
the size option is not applied, and the gravatar gets rendered in it's full size (128px in this case).
What am I doing wrong, or how can I achieve this combination?
Gravatar will not resize your default image for you. I assume that it just 302s to the ulr gave as a default if it does not find an gravatar for the email you gave it. It looks like the 's' parameter in the iconfinder url is for the size you are trying to grab but that icon does not have a size of 50px available only 128, 256, and 512
Example:
http://www.iconfinder.com/ajax/download/png/?id=43350&s=256
If you wanted a 50px and 80px versions of the icon I would save it to your applications public/image directory as default_gravatar_50.png and default_gravatar_80.png respectively and change your method like so.
end
def gravatar_for(user, options = {})
options = { :size => 50 }.merge(options)
options[:default] = image_tag("default_gravatar_#{options[:size]}.png
gravatar_image_tag(user.email.downcase,
:alt => user.full_name,
:class => 'gravatar',
:gravatar => options)
end
Or if you find an icon on icon finder that is the size(s) you like change the setting of the default option like so.
options[:default] = "http://www.iconfinder.com/ajax/download/png/?id=43350&s=#{options[:size]}"
Iconfinder here. You don't want to link to the download script. Instead just grab the URL to the image it self so you wan't get a lot of header information.