Rails: render HTML from Nokogiri without template - ruby-on-rails

I'm trying to write a controller method (show) which renders HTML without using a template (to preview an email without styles, etc):
class EmailDownloadsController < ApplicationController
before_action :set_email_download, only: %i[create show]
def create
send_data(#html, filename: "#{#document.safe_title}.html")
end
def show
render #html.to_s
end
private
def set_email_download
#document = Document.find(params[:id])
#document.parse_email_vars
#html = Nokogiri::HTML(#document.email_campaign_version)
end
end
I can't figure out how to render it without referring to a view (which comes with its own <html> and <head> tags). Anyone have any experience here?
The above code returns this error:
Missing template <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" style="min-height: 100%; background-color: #f3f3f3;">
...

Ah, it's render inline:.
The render method can do without a view completely, if you're willing to use the :inline option to supply ERB as part of the method call. This is perfectly valid:
render inline: "<% products.each do |p| %><p><%= p.name %></p><% end %>"
Docs.

Related

Getting 'undefined method `view_paths=' for #<ActionView::LookupContext:0x00007fdb191d6cc0> Did you mean? view_paths' error using PDFKit in Rails

I'm using PDFKit (along with the Gem render_anywhere and wkhtmltopdf-binary) to create a button for creating PDF's from HTML in Rails. I've followed a couple tutorials that both use 'invoices' as an example. My app uses 'audits'. I thought I'd followed everything to the tee but getting an error on the ActionView::LookupContext object: undefined method 'view_paths=' for #<ActionView::LookupContext:0x00007fdb191d6cc0> Did you mean? view_path. It has something to do with the wrong path and using a service class in Rails but can't seem to find a solution. Here's my code to make the PDF:
The PDFKit Gem: https://github.com/pdfkit/pdfkit
The tutorial I'm using: https://code.tutsplus.com/tutorials/generating-pdfs-from-html-with-rails--cms-22918
The downloads_controller:
class DownloadsController < ApplicationController
def show
respond_to do |format|
format.pdf { send_audit_pdf }
end
end
private
def audit_pdf
audit = params[:incoming]
AuditPdf.new(audit)
end
def send_audit_pdf
send_file audit_pdf.to_pdf,
filename: audit_pdf.filename,
type: "application/pdf",
disposition: "inline"
end
end
The Download Model:
require "render_anywhere"
class Download
include RenderAnywhere
def initialize(audit)
#audit = audit
end
def to_pdf
kit = PDFKit.new(as_html, page_size: 'A4')
kit.to_file("#{Rails.root}/public/audit.pdf")
end
def filename
"audit #{audit.id}.pdf"
end
private
attr_reader :audit
def as_html
render template: "audits/pdf", layout: "audit_pdf", locals: { audit: audit }
end
end
My views/layout/audit_pdf.html.erb looks like this:
<!DOCTYPE html>
<html>
<head>
<title>Audit PDF</title>
<style>
<%= Rails.application.assets.find_asset('audit.pdf').to_s %>
</style>
</head>
<body>
<%= yield %>
</body>
</html>
Since you are using rails api only, you are facing this issue.
You need to change
def as_html
render template: "audits/pdf", layout: "audit_pdf", locals: { audit: audit }
end
to:
def pdf_html
ActionController::Base.new.render_to_string(template: 'audits/pdf.html.erb', layout: 'audit_pdf.erb')
end

How to use Rails caches_action with layout:false and dynamically change meta tags?

I am stuck at what I think is a very simple/common usecase in a Rails web application. I want to use "caches_action, layout:false" and display, from the layout, dynamic tags that will be set by the action (either from the view or the controller).
I could not find any standard rails way to do this as content_for does not work with caches_action, instance variables are not cached (?), and the metatags helper gems that I have tried (metamagic and meta-tags) do not support this usecase.
Is there any way to do this ?
Example
I am using caches_action, layout:false on a SandboxController#show method
#app/controllers/sandbox_controller.rb
class SandboxController < ApplicationController
caches_action :show, layout: false, expires_in: 1.minute
def show
#meta_title = "Best page ever"
do_some_expensive_operation
end
end
The view
#app/views/sandbox/show.html.erb
We are in show action.
The layout
#app/views/layouts/application.html.erb
<title><%= #meta_title %></title>
Debug: <%= #meta_title %> <br/>
<%= yield %>
Thanks !
I found a way to make it work, it's not as pretty as I would like it to be but it helps using caches_action and setting HTML meta tags from the view.
Also, for the record, it seems that this was forgotten and buried deep down in the pipeline, as I did not find any recent mentions of this problem, only that caches_action and content_for together are not expected to work.
Solution: I simply add a before_action to set the meta tags by using as less computation as possible.
#app/controllers/sandbox_controller.rb
class SandboxController < ApplicationController
caches_action :show, layout: false, expires_in: 1.minute
before_action :seo_show, only: :show
def seo_show
#meta_title = "Best page ever"
end
def show
do_some_expensive_operation
end
end
It's worth noting that it can be used in combination with metamagic gem too.
Layout:
#app/views/layouts/application.html.erb
<%= default_meta_tags && metamagic %>
<%= yield %>
And helper:
#app/helpers/application_helper.rb
module ApplicationHelper
def default_meta_tags
meta title: #meta_title || "Default meta-title of my website"
end
end
Hope this helps someone out there !

application.html.erb is still not rendering

I generated a Rails application, and am playing around with the internals. Previously my application.html.erb was rendering properly, but now it seems like Rails is totally ignoring it because it won't even generate an error.
There have been a bunch of questions on Stack Overflow regarding this problem. I've looked at what I think is all of them, but none have helped.
My routes:
Rails.application.routes.draw do
# static_pages from rails tutorial ch. 3
get 'static_pages/home'
get 'static_pages/help'
get 'static_pages/about'
end
Here is the views/layout/application.html.erb
<!DOCTYPE html>
<html>
<head>
<title>This Title is not showing up</title>
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %>
<%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
<%= csrf_meta_tags %>
</head>
<body>
<p> why isnt this showing up?? </p>
<%= yield %>
</body>
</html>
Here is the static_pages_controller:
class StaticPagesController < ApplicationController
layout 'application' #<- I know this shouldn't be necessary, but I thought i'd try
def initialize
#locals = {:page_title => 'Default'}
end
def about
#locals[:page_title] = 'About'
render #locals
end
def help
#locals[:page_title] = 'Help'
render #locals
end
def home
#locals[:page_title] = 'Home'
render #locals
end
end
Here is the Application Controller:
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
end
There are no other layouts. My Views folder has the following structure:
-Views
|-layouts
||-application.html.erb
|
|-static_pages
||-about.html.erb
||-home.html.erb
||-help.html.erb
I've tried purposefully generating an error in the application.html.erb, calling variables that don't exist and whatever other shenanigans. Rails is totally ignoring me and I'm feeling insecure.
All I wanted to do is to display the page name in the <title>, but I can't even get plaintext to render correctly. How can I get this to work so that I can properly fail at getting the controller variable in the title?
You should not override the controller initialize method. Doing this will break the base class behavior.
While, I believe, just calling the super from the initialize will fix your issue, the correct Rails way to initialize a controller for a specific action is to use a before filter instead.
Example:
class StaticPagesController < ApplicationController
layout 'application'
before_action :load_locals
def load_locals
#locals = {:page_title => 'Default'}
end
...
end

How to set title in controller?

View application.html.erb contains following content
<title><%= content_for?(:title) ? yield(:title) : t(:stocktaking_title) %></title>
How can I pass data to this view from the controller's method?
I mean use symbol :title. I'm don't know Ruby well.
class WelcomeController < ApplicationController
def download
view_context.content_for(:title, "My Awesome Title") # doesn't work :(
end
end
Rails 4.1.9, ruby 2.0.0 (2014-05-08) [universal.x86_64-darwin14]
The prependeded # character to a variable is what exposes the variable to the view scope. In your controller:
def show
#title = "My Title"
end
Will let any rendered template file access it using:
<%= #title %>
Clearly, you're looking for some sort of title processing logic. Perhaps you could try replacing the code in your application.html.erb file with something like:
<% if #title %>
<title><%= #title %></title>
<% elsif content_for?(:title) %>
<title><%= yield(:title) %></title>
<% else %>
<title><%= t(:stocktaking_title) %></title>
<% end %>
You could condense this into a ternary but the view wouldn't be very readable.
If you insist on using content_for inside of the controller, you can use the view_context method, but you can't seem to work with content_for directly like so:
view_context.content_for(:title, "My Awesome Title")
Instead, you'll need to implement your own content_for method to extend off of the view_context. I pulled it from this Gist, but here's the code:
class ApplicationController < ActionController::Base
...
# FORCE to implement content_for in controller
def view_context
super.tap do |view|
(#_content_for || {}).each do |name,content|
view.content_for name, content
end
end
end
def content_for(name, content) # no blocks allowed yet
#_content_for ||= {}
if #_content_for[name].respond_to?(:<<)
#_content_for[name] << content
else
#_content_for[name] = content
end
end
def content_for?(name)
#_content_for[name].present?
end
end
This has been tested and works.
Then just do content_for :title, "My Awesome Title" in your controller.
Seriously though, using #title will be way way easier and less "hacky." You could even do something cool like this:
<title><%= #title || content_for(:title) || t(:stocktaking_title) %></title>

AngularJS Callbacks on Rails

I have noticed that AngularJS requires the results in javascript from the back-end server.
The current server in the example returns angular.callbacks._0({"key": "value"}); with javascript headers. How to make the returns in the same format on Rails? Thanks!
AngularJS generates http://echo.jsontest.com/key/value?callback=angular.callbacks._0 link from that request by default
Here is my working example:
<!DOCTYPE html>
<html ng-app="Simple">
<body>
<script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js"></script>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular-resource.min.js"></script>
<div ng-controller="SimpleController">
{{some_item.key}}
</div>
<script>
angular.module('Simple', ['ngResource']);
function SimpleController($scope, $resource) {
$scope.simple = $resource('http://echo.jsontest.com/key/value',
{callback:'JSON_CALLBACK'},
{get:{method:'JSONP'}}
);
$scope.some_item = $scope.simple.get();
console.log($scope.some_item.key);
}
</script>
</body>
</html>
As long as you have a RESTful controller responding to JSON it will work fine.
class ArticlesController < ApplicationController
respond_to :json
def index
articles = Article.all
respond_with articles
end
def get
article = Article.find(params[:id])
respond_with article
end
# etc.
end
You can limit the attributes returned using respond_with article, include: {:id, :title, :description}

Resources