saving html from ruby on rails output to a variable - ruby-on-rails

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

Related

PDF from ERB template with PDFKit and 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.

How to replace erb with liquid?

I'd like to use liquid in my Rails app. I've installed the gem. In order to use in all templates, I've created a library (lib/liquid_view.rb:):
class LiquidView
def self.call(template)
"LiquidView.new(self).render(#{template.source.inspect}, local_assigns)"
end
def initialize(view)
#view = view
end
def render(template, local_assigns = {})
#view.controller.headers["Content-Type"] ||= 'text/html; charset=utf-8'
assigns = #view.assigns
if #view.content_for?(:layout)
assigns["content_for_layout"] = #view.content_for(:layout)
end
assigns.merge!(local_assigns.stringify_keys)
controller = #view.controller
filters = if controller.respond_to?(:liquid_filters, true)
controller.send(:liquid_filters)
elsif controller.respond_to?(:master_helper_module)
[controller.master_helper_module]
else
[controller._helpers]
end
liquid = Liquid::Template.parse(template)
liquid.render(assigns, :filters => filters, :registers => {:action_view => #view, :controller => #view.controller})
end
def compilable?
false
end
end
And added the following initialiser (config/initializers/liquid_template_handler.rb:):
require 'liquid_view'
ActionView::Template.register_template_handler :liquid, LiquidView
PS: I've followed these instructions.
Now, if rename a template file with liquid my_template.html.liquid the <%= stylesheet_link_tag 'mycss' %> stopped working, but more importantly the {{user.first_name}} variable did not print. In my controller I have #user = current_user
What am I missing?
My intention is to completely override erb with liquid in some templates, so ideally it should work like erb (in a sense that I can pass variables from the controller and simply render it in the template without using Liquid::Template.parse(#page.template) which by the way, I don't understand how it works on a file-based template.
PS: I'm also using [this] gem (https://github.com/yoolk/themes_on_rails) for separate templates. I'm not sure it does any impact on it.
PPS: I've seen this but doesn't apply as its a older version of Rails and I'm not using prepend.
PPPS: I'm using Ruby 2.2.2 and Rails 4.2
I hope this not the problem you are thinking it is . You can check the way as it was said here Github Description
Did you create a Drop to access #user?
https://github.com/Shopify/liquid/wiki/Introduction-to-Drops
Liquid is a safe template system, so we can interpret on the backend templates that are created by the user. To access anything non trivial (number, string, hashes or arrays) you need a Drop, which is a controlled interface to define what the templates can access.
This is by design and for security reasons.

Using rails methods with Haml::Engine

I want to have a rake task that reads a HAML file and creates a static html file out of it. The reason for this is that I want to dynamically localize my error pages in a manner described here http://devcorner.mynewsdesk.com/2010/01/13/rails-i18n-and-404500-error-pages/
Here is the method for writing the error pages.
def write_error_page(status, locale = nil)
dest_filename = [status.to_s, locale, "html"].compact.join(".")
File.open(File.join(Rails.root, "public", dest_filename), "w") do |file|
path = File.join(Rails.root, "app", "views", "errors", "#{status}.haml")
file.print Haml::Engine.new(File.read(path)).render
end
end
The problem is that Haml::Engine does not have rails methods available. So when a try to read the haml file, I get an error for every rails method in the file (I want to use methods
like image_tag, form_for and obviously I18n.translate).
I noticed a similar issue that had been solved here: Rails HAML engine rendering
However, when I try the solution mentioned in the link above, I get the following error: "undefined local variable or method `config' for #".
How could I get the rails methods to work in the Haml::Engine so that I could read the HAML file? I also tried switching to ERB, but noticed that it leads to the same problem, which somebody else has at least partially resolved here render erb from database into view problem please help! But this solution didn't help me either.
I'm also open to other solutions than using Haml::Engine. I looked into capture_haml helper but don't see how that would help me either.
You need to make :environment a dependency of your rake task:
task :some_task => :environment do
# stuff here
end
This will load Rails. It sounds like it's not been loaded.
I just now realized that I don't need Haml::Engine in this situation, because I'm in a rails environment so I can just call render. Silly me.
However, it's not completely trivial to call render from a rake task, because we are not in a controller or a view (and so rails purists even say that you should never do so, I think, but in this case it seems like the easiest way), so I post the code I used here (I used the approach mentioned here: http://wholemeal.co.nz/blog/2011/04/05/rendering-from-a-model-in-rails-3/).
def write_error_page(status, locale = nil)
dest_filename = [status.to_s, locale, "html"].compact.join(".")
File.open(File.join(Rails.root, "public", dest_filename), "w") do |file|
path = File.join("app", "views", "errors", "#{status}.haml")
file.print ActionView::Base.new(Rails.configuration.paths.app.views.first).render(:file => path)
end
end
I had some problems with this approach too. For instance, form_for still didn't work properly (I want to have a feedback form on the error page) so I simply created the form with plain HTML (which you can luckily inject straight into .haml files). But the one thing from rails I needed to get to work in the .haml template - method I18n.translate - works like charm.
I did this solution based on yours, once yours wasn't working for me, because of the views path, and because i needed to include some functions from my application helper. I think you can resolve the form_for problem including this: ActionView::Base.send :include, ActionView::Helpers::FormHelper
I change the format to html, because it was what i needed...
def to_html
ActionView::Base.send :include, ActionView::Helpers::ApplicationHelper
File.open(File.join(Rails.root, "public", 'test.html'), "w") do |file|
file.print ActionView::Base.new(Rails.configuration.paths["app/views"].first).render(
:partial => 'partial_folder/partial',
:format => :html,
:locals => { :model => self}
)
end
end

Ruby w/ Sinatra: what is the equivalent of a .js.erb from rails?

.js.erb's are nice, because you can use them to replace parts of a page without having to leave the current page, which gives a cleaner and unchopped up feel to the site / app.
Is there a way to use them in sinatra? or an equivalent?
Just add .js to the end of the symbol you're passing erb(). A la (to call mypage.js.erb):
erb "mypage.js".to_sym
Dirty, but it works.
Based on your description, I'm guessing that your desire is to have portions of a page editable and replaced via AJAX. If this is wrong, please clarify.
I do this in my Sinatra apps by including (my own) AJAXFetch jQuery library and writing code as shown below. This lets me use the partial both when rendering the page initially as well as when editing via AJAX, for maximum DRYness. The AJAXFetch library handles all AJAX fetch/swap through markup alone, without needing to write custom JS on the pages that use it.
helpers/partials.rb
require 'sinatra/base'
module Sinatra
module PartialPartials
ENV_PATHS = %w[ REQUEST_PATH PATH_INFO REQUEST_URI ]
def spoof_request( uri, headers=nil )
new_env = env.dup
ENV_PATHS.each{ |k| new_env[k] = uri.to_s }
new_env.merge!(headers) if headers
call( new_env ).last.join
end
def partial( page, variables={} )
haml page, {layout:false}, variables
end
end
helpers PartialPartials
end
routes/bug.rb
get '/bug/:bug_id' do
if #bug = Bug[params[:bug_id]]
# ...
haml :bug
end
end
# Generate routes for each known partial
partials = %w[ bugdescription bughistory bugtitle fixer
pain project relatedbugs status tags version votes ]
partials.each do |part|
[ part, "#{part}_edit" ].each do |name|
get "/partial/#{name}/:bug_id" do
id = params[:bug_id]
login_required
halt 404, "(no bug ##{id})" unless #bug = Bug[id]
partial :"_#{name}"
end
end
end
post "/update_bug/:partial" do
id = params[:bug_id]
unless params['cancel']=='cancel'
# (update the bug based on fields)
#bug.save
end
spoof_request "/partial/#{params[:partial]}/#{id}", 'REQUEST_METHOD'=>'GET'
end
views/bug.haml
#main
#bug.section
= partial :_bugtitle
.section-body
= partial :_bugdescription
<!-- many more partials used -->
views/_bugtitle.haml
%h1.ajaxfetch-andswap.editable(href="/partial/bugtitle_edit/#{#bug.pk}")= title
views/_bugtitle_edit.haml
%form.ajaxfetch-andswap(method='post' action='/update_bug/bugtitle')
%input(type="hidden" name="bug_id" value="#{#bug.id}")
%h1
%input(type="text" name="name" value="#{h #bug.name}")
%span.edit-buttons
%button(type="submit") update
%button(type="submit" name="cancel" value="cancel") cancel
sinatra really isn't meant to be a full stack framework. Its supposed to get you on the road very quickly. You could use an erb separately and then load into your sinatra code.

Rails: Easiest way to provide file downloads?

G'day guys, currently have almost finished writing a rails application that allows a CSV download of the database. This is generated when the index is first viewed.
Is there an easy way to insert a link to a helper that returns a CSV document? That is to say is it easy to insert links to helpers? This would make a lot of problems I've been having much easier
If you sticked to the general conventions, then you registered a mime-type for csv and return the csv file content via your #index action. So your link helper would be like this:
link_to 'export as csv', posts_path(:format => :csv)
If, in exchange, your file is generated WHEN index is first view but NOT BY Rails, you may want to avoid standart render and call send_data or send_file instead (check the api for them).
# in your controller:
def index
# your suff here
#csv_path = find_or_generate_csv_file
send_data #csv_path, :type=>"text/csv", :disposition=>'attachment'
end
protected
def find_or_generate_csv_file
#your file generation logic
end

Resources