PDF from ERB template with PDFKit and Rails - ruby-on-rails

In my Rails application I'd like to have a special route to download a custom PDF.
This PDF should be generated via PDFKit from an ERB template in my application. Instead of describing what I'd like to achieve, I better paste some non-executable but commented code:
class MyController < ApplicationController
def download_my_list_as_pdf
# The template uses the instance variables below
#user_id = params[:user_id]
#items = ['first_item', 'second_item']
# This line describes what I'd like to do but behaves not like I want ;)
# Render the ERB template and save the rendered html in a variable
# I'd also use another layout
rendered_html = render :download_my_list_as_pdf
kit = PDFKit.new(rendered_html, page_size: 'A4')
kit.to_pdf
pdf_file_path = "#{Rails.root}/public/my_list.pdf"
kit.to_file(pdf_file_path)
send_file pdf_file_path, type: 'application/pdf'
# This is the message I'd like to show at the end
# But using 'render' more than once is not allowed
render plain: 'Download complete'
end
end
I wasn't able to find an answer to this problem yet, any help would be greatly appreciated!

render_to_string(*args, &block)
Raw rendering of a template to a string.
It is similar to render, except that it does not set the response_body
and it should be guaranteed to always return a string.
render does not return a string it sets the response_body of the response.
class MyController < ApplicationController
def download_my_list_as_pdf
# The template uses the instance variables below
#user_id = params[:user_id]
#items = ['first_item', 'second_item']
# This line describes what I'd like to do but behaves not like I want ;)
# Render the ERB template and save the rendered html in a variable
# I'd also use another layout
rendered_html = render_string(:download_my_list_as_pdf)
kit = PDFKit.new(rendered_html, page_size: 'A4')
kit.to_pdf
pdf_file_path = "#{Rails.root}/public/my_list.pdf"
kit.to_file(pdf_file_path)
send_file pdf_file_path, type: 'application/pdf'
end
end
However if you are sending a file you cannot send text or html as well. This is not a limitation of Rails but rather how HTTP works. One request - one response.
Usually javascript is used to create notifications surrounding file downloads. But consider first if its really needed as its pretty annoying to users as the browser usually tells you that you downloaded a file anyways.

Related

Generate HTML using grape API in rails

I have a requirement where I need to generate/spit out HTML markup from one of my APIs. I am using grape API but cannot find a way to throw out HTML markup. I can specify content-type as text/html and create a HTML markup but is there a better way to achieve this like rendering a template similar to below:
render template:'my_template' locals: {:data => data}
and 'my_template' (HTML) can take care of how the page looks like ? render is an undefined method in GrapeAPI so not sure what other stuff I can use ?
I think it's quite a bad idea to use an API only framework to render HTML...
Nevertheless, you should be able to use the :txt content-type to simply render out your string like you described.
You could use ERB for that, as it is part of the standard-library and pretty easy to use:
require "erb"
class Template
attr_reader :name, :data
def initialize(name, data)
#name = name
#data = data
end
def build
raw = File.read("templates/#{name}.erb")
ERB.new(raw).result(binding)
end
end
as far as I read, grape automatically uses the to_s method of an entity to render :txt, so you could implement something like this in your model:
def to_s
Template.new(self.class.to_s.downcase, self)
end
it might also be possible to register a html content type and write some kind of formatter that does this kind of stuff.

Rails 3 render plaintext page using view

I have a Linux configuration file stored in my database in Rails, and I'd like to be able to download the configuration through a web request
My goal on the Linux side is to curl/wget the webpage, compare it to the current configuration, and then hup the server. Easy enough to do in a script.
In normal circumstances on Rails, you could do
render :text => #config_file
However, I need to do some formatting of the data first to apply the static headers, etc. This isn't a one-liner, so I need to be able to render a view.
I have the following set in my controller, but I still get a minimal set of HTML tags in the document
render(:content_type => 'text/plain', :layout => false);
I've done something similar in .Net before, so it printed out a text file with \n interpreted. How do I get that in Rails?
Normally, this is done with
# config/initializers/mime_types.rb
# ...
# Mime::Type.register "text/plain", :plaintext
# No changes needed as rails comes preconfigured with the text/plain mime type
# app/controllers/my_controller.rb
class MyController < ApplicationController
def my_action
respond_to do |format|
format.text
end
end
end
and a view file
# app/views/my_controller/my_action.text.erb
...
About the minimal HTML you find in the DOM: Are you seeing this from within some kind of in-browser inspector, like the ones included in google chrome or safari? If so then don't worry, this is added by the browser in order to display your text/plain document inline. If you look at the source of the delivered document (ctrl-u) no HTML should show up.

How does one use instance methods in a static method? I'm trying to async'ly create a document

In my controller, i have a method defined as:
def self.store_pdf(id)
...
end
in that method, I need to call render_to_string to render the correct file / layout:
render_to_string(
:action => "../view/current_version/show.pdf.erb",
:layout => false)
but because render_to_string is both an instance method and protected, I need to do the following:
me = self.new # self is the cortroller
me.send(:render_to_string,
:action => "../view/current_version/show.pdf.erb",
:layout => false)
but then there are dependencies such as the response object that render_to_string needs to work, as shown here: http://apidock.com/rails/ActionController/Base/render_to_string
So, I began adding them
me.send(:response=, ActionController::Response.new)
But, more and more of the global instance variables need to be defined, and I decided it was too much work just to try to get one static method to work.
The method needs to be static, so that delayed_job can run the method in the background at a later time.
Anyone have an idea as to how to pull this off?
You can read erb via ERB if you are not using any rails helper,If you are using any rails helper then include Rails helper.
you can refer using here or
require 'erb'
class PdfRender
#include ActionView::Helpers::OutputSafetyHelper
#include helper if any is present any
def self.render_pdf(id)
#set any instance variable if you are using in pdf
content = File.read('path/of/erb/template')
template = ERB.new(content)
# template content will give you text now you can render or generate pdf
template_content = template.result(binding)
end
end
Note:
replace h() with CGI.escapeHTML()

Render ERB Template in RABL Template

I have a scenario where I'd like to pass back a long message with my JSON. Instead of writing it out with string concatenation I'd rather put together an erb template that I can render into my JSON. Below is the code I'm currently trying:
object #invitation
node(:phone_message) do |invitation|
begin
old_formats = formats
self.formats = [:text] # hack so partials resolve with html not json format
view_renderer.render( self, {:template => "invitation_mailer/rsvp_sms", :object => #invitation})
ensure
self.formats = old_formats
end
end
Everything works as expected the first time this code is run, however, I run into problems the second time I run it because it says there is a missing instance variable (which I assume was generated and cached during the first run).
undefined method
_app_views_invitation_mailer_rsvp_sms_text_erb___2510743827238765954_2192068340
for # (ActionView::Template::Error)
Is there a better way to render erb templates into rabl?
You could try using ERB as standalone, and not going through the view renderer, like so:
object #invitation
node(:phone_message) do |invitation|
begin
template = ERB.new(File.read("path/to/template.erb"))
template.result(binding)
end
end
binding is a method on Object (through the Kernel module) and it returns the binding which holds the current context, which also includes instance variables (#invitation in this case)
Update:
Don't really know if this will help you get any further (and I also realised it's been more than a year since you posted this), but here's another way to render ERB templates in a standalone fashion:
view = ActionView::Base.new(ActionController::Base.view_paths, {})
class << view
include ApplicationHelper
include Rails.application.routes.url_helpers
end
Rails.application.routes.default_url_options = ActionMailer::Base.default_url_options
view.render(:file => "path/to/template.html.erb", :locals => {:local_var => 'content'})
When I have time I should actually try this with Rabl.

saving html from ruby on rails output to a variable

I have a ruby on rails website. The page is dynamically loaded and generated using ruby and rails. However, I'd like to also generate a static .html page to ease my server rather than calling the rails page every time.
In PHP I know how to capture the output buffer using ob_start() and ob_get_contents() to get the outputted text.
How do I capture the output from my rails page into a variable?
EDIT: The reason I want to do this is so that I can save my page as .html for use on other machines. So I generate the HTML using ruby and distribute to others in a format they can view.
You should use Rails caching to achieve this result. It achieves the ends you are looking for.
Alternatively, you can render_to_string and output the result using render:
#ticket_controller.rb
def TicketController < ApplicationController
def show_ticket
#ticket = Ticket.find(params[:id])
res = render_to_string :action => :show_ticket
#... cache result-- you may want to adjust this path based on your needs
#This is similar to what Rails caching does
#Finally, you should note that most Rails servers serve files from
# the /public directory without ever invoking Rails proper
File.open("#{RAILS_ROOT}/public/#{params[:action]}.html", 'w') {|f| f.write(res) }
# or .. File.open("#{RAILS_ROOT}/public/#{params[:controller]}/#{params[:action]}/#{params[:id]}.html", 'w') {|f| f.write(res) }
# or .. File.open("#{RAILS_ROOT}/snapshots/#{params[:controller]}/#{params[:action]}/#{params[:id]}.html", 'w') {|f| f.write(res) }
render :text => res
end
end
You probably want to look into caching rather then saving the output of your rails app directly. Check out:
http://guides.rubyonrails.org/caching_with_rails.html
http://api.rubyonrails.org/classes/ActionController/Caching/Pages.html
I ended up going with the following:
#page_data = render_to_string() # read the entire page's output to string
if (File.exist?('../cache.html'))
file = File.open('../cache.html','rb')
contents = file.read
else
contents = ''
end
if (#page_data!=contents) # if the page has changed
# save the output to an html version of the page
File.open('../cache.html','w') {|f| f.write(#page_data) }
end

Resources