Problem generating pdfs with acts_as_flying_saucer - ruby-on-rails

I'm using the rails plugin acts_as_flying_saucer to generate a pdf, but the pdf doesn't work: i can save it ok, but when i try to open it i get this error message:
Unable to open document
File type SOR File (text/plain) is not supported
Here's my set up. I'm using rails 2.3.4 and the latest version of the acts_as_flying_saucer plugin. I have this route:
map.all_help '/help/all.:format', :controller => "help", :action => "all"
which goes to this controller action:
class HelpController < ApplicationController
acts_as_flying_saucer
def all
respond_to do |format|
format.html {
render :action => "all", :layout => "help_pdf"
}
format.js
format.pdf {
render_pdf :template => 'help/all.html.erb',
:layout => "help_pdf",
:send_file => { :filename => "my-filename.pdf", :type => "application/pdf"}
}
end
end
end
and i also have this mime type defined:
Mime::Type.register "application/pdf", :pdf
When i go to the page, /help/all.pdf, it generates a 0 byte pdf (which i can save ok) and i get the error message from the top of this post when i try to open it.
Going to the standard web page version of the page (/help/all) works fine. I thought maybe my java vm wasn't set up but it seems to be fine:
max-laptop:millionaire[subjects]$ java -version
java version "1.6.0_20"
Java(TM) SE Runtime Environment (build 1.6.0_20-b02)
Java HotSpot(TM) Client VM (build 16.3-b01, mixed mode, sharing)
I'm out of ideas at this point...grateful for any advice! max
EDIT: Just noticed that a bunch of error output is in my mongrel-running tab:
ERROR: 'Premature end of file.'
Exception in thread "main" org.xhtmlrenderer.util.XRRuntimeException: Can't load the XML resource (using TRaX transformer). org.xml.sax.SAXParseException: Premature end of file.
at org.xhtmlrenderer.resource.XMLResource$XMLResourceBuilder.createXMLResource(XMLResource.java:191)
at org.xhtmlrenderer.resource.XMLResource.load(XMLResource.java:71)
at org.xhtmlrenderer.swing.NaiveUserAgent.getXMLResource(NaiveUserAgent.java:205)
at org.xhtmlrenderer.pdf.ITextRenderer.loadDocument(ITextRenderer.java:102)
at org.xhtmlrenderer.pdf.ITextRenderer.setDocument(ITextRenderer.java:106)
at Xhtml2Pdf.main(Xhtml2Pdf.java:19)
Caused by: javax.xml.transform.TransformerException: org.xml.sax.SAXParseException: Premature end of file.
at com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl.transform(TransformerImpl.java:719)
at com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl.transform(TransformerImpl.java:313)
at org.xhtmlrenderer.resource.XMLResource$XMLResourceBuilder.createXMLResource(XMLResource.java:189)
... 5 more
Caused by: org.xml.sax.SAXParseException: Premature end of file.
at com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.parse(AbstractSAXParser.java:1231)
at com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl.transformIdentity(TransformerImpl.java:636)
at com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl.transform(TransformerImpl.java:707)
... 7 more
I'm wondering now if there's something wrong with the page i'm trying to convert to pdf. I ran the html version of the page past the w3c validator and the page validates ok. So now i'm stumped again...
EDIT 2: i think i'm getting closer now: looking in my log, i see
Rendering help/all.html.erb
html file: /tmp/ff4c8ff01d544500ea4bfea43e6108c1.html
Sending X-Sendfile header /tmp/ff4c8ff01d544500ea4bfea43e6108c1.pdf
So, i'd expect /tmp/ff4c8ff01d544500ea4bfea43e6108c1.html to have the html version of my page in it, but it's just a four line empty text file. No wonder flying saucer can't convert it. I don't know why it's saving an empty html file though.
EDIT 3: here's the html i'm currently trying to convert
<!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" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>Help for Millionaire For Schools</title>
</head>
<body class="pdf">
<div id="bgeffect"></div>
<div id="pageWrapper" class="mainHelpPages">
<div id="helpContent">
<div id="helpPane">
<div class="inner">
<p>Welcome to my pdf</p>
</div>
</div>
</div>
</div>
</body>
</html>

Didn't you solve this problem a year ago by setting x_sendfile to false?

I solved this, it turned out to be really simple (and dumb). The problem was this: i was pointing to the layout i wanted to use in the normal railsy way,
:layout => "help_pdf",
But, it looks like you have to spell out the full file name: when i changed it to this, it worked:
:layout => "help_pdf.html.erb",
ARRRGGHHH!!!!!! Ah well at least it's working now. Thanks a lot John for having a look.
max

Related

Rails not rendering public/index.html file; blank page in browser

I have a problem with my Rails + React app when I deploy it to Heroku. The React client is inside a client/ directory of the Rails app. Due to using react-router, the Rails server needs to know to render the index.html from the React build. When I deploy the client on Heroku, a script copies the content from client/build/. to the Rails app's public/ dir.
Now here is the problem: when my route detects a path like example.com/about it tries to render public/index.html. Here is the method:
def fallback_index_html
render file: "public/index.html"
end
However, the contents from this file are not sent to the browser. I get a blank page. I have added a puts "hit fallback_index_html" in the method and confirmed that this method is being hit. I have also opened the file in puts each line to confirm the file has the required html (this is what appeared in the logs from that puts and what SHOULD be sent to the browser):
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no">
<meta name="theme-color" content="#000000">
<link rel="manifest" href="/manifest.json">
<link rel="shortcut icon" href="/favicon.ico">
<title>Simple Bubble</title>
<link href="/static/css/main.65027555.css" rel="stylesheet">
</head>
<body><noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script type="text/javascript" src="/static/js/main.21a8553c.js"></script>
</body>
</html>
The most recent fix I tried was going into config/environments/production.rb and changing config.public_file_server.enabled to true. This did not help.
I'm using Rails API, so my ApplicationController inherits from ActionController::API instead of ActionController::Base.
From Rails API docs it says:
The default API Controller stack includes all renderers, which means you can use render :json and brothers freely in your controllers. Keep in mind that templates are not going to be rendered, so you need to ensure your controller is calling either render or redirect_to in all actions, otherwise it will return 204 No Content.
Thus Rails API only cannot render HTML! The following allowed me to render the html without including everything from ActionController::Base.
class ApplicationController < ActionController::API
include ActionController::MimeResponds
def fallback_index_html
respond_to do |format|
format.html { render body: Rails.root.join('public/index.html').read }
end
end
end
The reason I am including ActionController::MimeResponds is to have access to the respond_to method.
My Rails application now renders index.html from my public directory when a subdirectory is hit and my React client / react-router takes over from there.

Capybara RSpec with CSS and JS?

rails (5.1.4)
rspec-rails (3.7.2)
capybara (2.16.1)
I'm trying to create a RSpec Rails 3.7 System spec as in https://relishapp.com/rspec/rspec-rails/v/3-7/docs/system-specs/system-spec .
Here my simple spec:
require 'rails_helper'
RSpec.describe "testing system", type: :system do
it "tests the spec" do
visit root_path
click_link 'Home'
save_and_open_page
end
The problem is that Capybara does render neither CSS content nor JS content after save_and_open_page call (in the browser) - just a plain HTML. The header inside this HTML-file contains some links
<link rel="stylesheet" media="all" href="/assets/application-ea5a1efcc44a908543519edabe00e74132151ebedeef3c1601921690d9162b5e.css" data-turbolinks-track="reload" />
<script src="/assets/application-ff63e43aef379fef744a00f21a8aadf96dc2ae8e612f8e7974b231f946569691.js" data-turbolinks-track="reload"></script>
but they reference some empty files.
Is there some way to fix it?
I tried some recipes, but still no luck. I tried to precompile the assets, to move "capybara.html" into the "public" folder, but no effect.
Modifying stylesheet_link_tag is not a good solution, a much better solution is to specify Capybara.asset_host which will add a <base> tag to any saved pages. Generally this would be set to something like
Capybara.asset_host = "http://localhost:3000/"
which would then load the JS/CSS assets from your dev server which would have access to the test mode compiled assets in the public subdirectory. Note: that none of this means the page will actually be functional since JS requests will still fail, DB records won't exist anymore, etc. Also, since it saves element attributes (not properties) a checkbox you just checked will probably not be checked in the saved page. However it will give you a generally styled page you can inspect the structure of. If all you're looking for is a current image of the page you should be using the save_screenshot/save_and_open_screenshot functionality provided by most of Capybaras drivers instead.
It has to do something with your assets.
Clear cache and run rake assets:clobber and rake assets:precompile
Still no luck, then check if Capybara is configured correctly.
Check app/views/layouts/application.html.erb has the correct Rails tags for stylesheets and javascripts. Something like this:
<!DOCTYPE html>
<html>
<head>
<title>My App</title>
<%= stylesheet_link_tag 'application', media: 'all' %>
<%= javascript_include_tag 'application' %>
On the command line, run:
rake assets:clobber
rake assets:precompile
Ensure that public/assets/ include:
.sprockets-manifest-<xyz>.json
application-<abc>.js
application-<def>.css
Open the .sprockets-manifest... file and you should see that there are application js and css files with filenames that match the actual public/assets/ files. This .sprockets-manifest file controls what actually gets included in the HTML head links and scripts when the Rails tags are replaced.
If this is still not working, ensure that the files are accessible by your user running the test (including the manifest). Occasionally lose the .sprockets-manifest file when copying files and in source control as it can appear to be hidden.
Finally, check your file log/test.log to see if there are any obvious errors being thrown during the tests.
I found a solution. Perhaps it's not the best one, but it works with me. If anybody find a better approach - let me know, please.
Run rake assets:precompile. I didn't even set RAILS_ENV=test.
Modify the stylesheet_link_tag method:
def stylesheet_link_tag2(*sources)
options = sources.extract_options!.stringify_keys
path_options = options.extract!('protocol').symbolize_keys
sources.uniq.map { |source|
tag_options = {
"rel" => "stylesheet",
"media" => "screen",
"href" => path_to_stylesheet(source, path_options)[1..-1]
}.merge!(options)
tag(:link, tag_options)
}.join("\n").html_safe
end
The idea is to turn the rendered link from this:
<link rel="stylesheet" media="all" href="/assets/application-ea5a1efcc44a908543519edabe00e74132151ebedeef3c1601921690d9162b5e.css" data-turbolinks-track="reload" />
to this:
<link rel="stylesheet" media="all" href="assets/application-ea5a1efcc44a908543519edabe00e74132151ebedeef3c1601921690d9162b5e.css" data-turbolinks-track="reload" />
eliminating the leading slash in the href attribute value (since we don't have a server running but just a saved HTML-page).
Replace the code inside the header in \app\views\layouts\application.html.erb to:
<% if Rails.env.test? %>
<%= stylesheet_link_tag2 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
<% else %>
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
<% end %>
Write a spec like this:
require 'rails_helper'
RSpec.describe "testing system", type: :system do
it "tests..." do
visit root_path
click_link 'Home'
save_and_open_page Rails.root.join( 'public', 'capybara.html' )
end
end
Add to .gitignore:
/public/capybara.html
Do the same thing with the JS-content.
UPDATE:
If you don't like modifying \app\views\layouts\application.html.erb you can do some monkey patching:
include ActionView::Helpers::AssetTagHelper
alias_method :old_stylesheet_link_tag, :stylesheet_link_tag
def stylesheet_link_tag2(*sources)
options = sources.extract_options!.stringify_keys
path_options = options.extract!('protocol').symbolize_keys
sources.uniq.map { |source|
tag_options = {
"rel" => "stylesheet",
"media" => "screen",
"href" => path_to_stylesheet(source, path_options)[1..-1]
}.merge!(options)
tag(:link, tag_options)
}.join("\n").html_safe
end
def stylesheet_link_tag(*sources)
if Rails.env.test?
stylesheet_link_tag2(*sources)
else
old_stylesheet_link_tag(*sources)
end
end
I usually put such code into app\helpers\application_helper.rb and add include ApplicationHelper into app\controllers\application_controller.rb
UPDATE 2
Setting Capybara.asset_host = "http://localhost:3000/" as #Thomas Walpole advised doesn't work. That's right - how can it work if http://localhost:3000/ is unavailable (AFTER the spec ran)? Of course - when I call save_and_open_page the HTML-file opens with a file://.... address - with no HTTP-server serving it. The attempts to set
Capybara.asset_host = "file://#{Rails.root}/public"
failed - looks like the base HTML-tag supports only http-adresses - not file://... ones. I checked it in Chrome and Firefox.
So my next code proposal is such:
include ActionView::Helpers::AssetTagHelper
alias_method :old_stylesheet_link_tag, :stylesheet_link_tag
def stylesheet_link_tag2(*sources)
options = sources.extract_options!.stringify_keys
path_options = options.extract!('protocol').symbolize_keys
sources.uniq.map { |source|
tag_options = {
"rel" => "stylesheet",
"media" => "screen",
"href" => "file://#{Rails.root}/public" + path_to_stylesheet(source, path_options)
}.merge!(options)
tag(:link, tag_options)
}.join("\n").html_safe
end
def stylesheet_link_tag(*sources)
if Rails.env.test?
stylesheet_link_tag2(*sources)
else
old_stylesheet_link_tag(*sources)
end
end
This eliminates the need to call
save_and_open_page Rails.root.join( 'public', 'capybara.html' )
instead you can simply call
save_and_open_page

Generating a PDF with Sidekiq & Wicked PDF: Undefined local variable or method for worker#

I've been trying to get PDF's generated via sidekiq and wicked_pdf in a rails 5.1 app but keep getting this error:
2017-11-01T02:20:33.339Z 1780 TID-ovys2t32c GeneratePdfWorker JID-b3e9487113db23d65b179b1c INFO: start
2017-11-01T02:20:33.369Z 1780 TID-ovys2t32c GeneratePdfWorker JID-b3e9487113db23d65b179b1c INFO: fail: 0.03 sec
2017-11-01T02:20:33.371Z 1780 TID-ovys2t32c WARN: {"class":"GeneratePdfWorker","args":[2,1],"retry":false,"queue":"default","jid":"b3e9487113db23d65b179b1c","created_at":1509502833.334234,"enqueued_at":1509502833.3345}
2017-11-01T02:20:33.380Z 1780 TID-ovys2t32c WARN: NameError: undefined local variable or method `quote' for #<GeneratePdfWorker:0x007fb6d5cea070>
Did you mean? #quote
2017-11-01T02:20:33.380Z 1780 TID-ovys2t32c WARN: /Users/stefanbullivant/quottes/app/workers/generate_pdf_worker.rb:18:in `perform'
I get this error even with no locals being passed in the av.render method. Any ideas on what's causing it are appreciated.
quotes_controller.rb calling the worker
def create_pdf
#quote = Quote.find(params[:id])
GeneratePdfWorker.perform_async(#quote.id, current_account.id)
redirect_to #quote
end
Generate_pdf_worker.rb
class GeneratePdfWorker
include Sidekiq::Worker
sidekiq_options retry: false
def perform(quote_id, account_id)
#quote = Quote.find(quote_id)
#account = Account.find(account_id)
# create an instance of ActionView, so we can use the render method outside of a controller
av = ActionView::Base.new()
av.view_paths = ActionController::Base.view_paths
# need these in case your view constructs any links or references any helper methods.
av.class_eval do
include Rails.application.routes.url_helpers
include ApplicationHelper
end
pdf = av.render pdf: "Quote ##{ #quote.id } for #{ #quote.customer_name }",
file: "#{ Rails.root }/tmp/pdfs/quote_#{#quote.id}_#{#quote.customer_name}.pdf",
template: 'quotes/create_pdf.html.erb',
layout: 'layouts/quotes_pdf.html.erb',
disposition: 'attachment',
disable_javascript: true,
enable_plugins: false,
locals: {
quote: #quote,
account: #account
}
# pdf_html = av.render :template => "quotes/create_pdf.html.erb",
# :layout => "layouts/quotes_pdf.html.erb",
# :locals => {
# quote: #quote,
# account: #account
# }
# use wicked_pdf gem to create PDF from the doc HTML
quote_pdf = WickedPdf.new.pdf_from_string(pdf, :page_size => 'A4')
# save PDF to disk
pdf_path = Rails.root.join('tmp', "quote.pdf")
File.open(pdf_path, 'wb') do |file|
file << quote_pdf
en
end
end
quotes_pdf.html.erb PDF Layout
<!DOCTYPE html>
<html>
<head>
<title>Quottes</title>
<meta charset="utf-8" />
<meta name="ROBOTS" content="NOODP" />
<style>
<%= wicked_pdf_stylesheet_link_tag 'quote_pdf' -%>
</style>
</head>
<body>
<div class="container">
<%= yield %>
</div>
</body>
</html>
create_pdf.html.erb PDF View (for the sake of getting things running first, just two lines using each local)
<%= account.brand_name %>
<%= quote.customer_name %>
Any advice on getting this running is much appreciated. I have played around with simply generating plain html text in the pdf view without passing any variables and still get this error so I'm confused as to what's causing it.
I sometimes forget about this as well - It seems like it's very insistent that line 18 (which I can only assume is render) is looking up the quote local and can't find it, and #quote is defined at that time. If that is all your code, then I would presume the changes are not being picked up.
My best suggestion (which I hope works) is you need to restart sidekiq!

Serving XHTML as application/xhtml+xml with Ruby on Rails

I'm trying to get my Rails app to serve XHTML content properly, with the correct content-type of application/xhtml+xml. Ideally with content negotiation so that IE users get a chance to use the site too.
Given that all the HTML generated by Rails is marked at XHTML 1.0 Transitional, I'm a bit surprised that there is no obvious option to make Rails serve markup as XHTML. I found this http://blog.codahale.com/2006/05/23/rails-plugin-xhtml_content_type/, but it seems to be for 1.1.2 and I can't get it working properly under 2.3.8.
Have I missed something here?
Ok, I've got something that works now. Thanks to #danivovich for starting me in the right place. The first thing I had to do was sort out the Mime types in mime_types.rb so that HTML wasn't aliased with XHTML:
module Mime
remove_const('HTML') # remove this so that we can re-register the types
end
Mime::Type.register "text/html", :html
Mime::Type.register "application/xhtml+xml", :xhtml
The I just added this to my application controller:
before_filter :negotiate_xhtml
after_filter :set_content_type
def negotiate_xhtml
#serving_polyglot = false
if params[:format].nil? or request.format == :html
#serving_polyglot = ((not request.accepts.include? :xhtml) or params[:format] == 'html')
request.format = :xhtml
end
end
def set_content_type
if #serving_polyglot
response.content_type = 'text/html'
end
end
This makes sure that XHTML is always servered as such, unless the client doesn't accept it, or HTML has been explicitly requested. HTML is always just XHTML served as a polyglot. The #serving_polyglot variable is available in the views where any switching is needed.
This is working for me under Chrome, Safari, Firefox, Opera and IE[6-8].
You can force the content type in any controller function or using an after filter. Either of these methods can set the content type via:
response.content_type = "application/xhtml+xml"
Add this to your application_controller.rb:
def correct_safari_and_ie_accept_headers
ajax_request_types = [ 'text/javascript', 'application/json', 'text/xml']
request.accepts.sort!{ |x, y| ajax_request_types.include?(y.to_s) ? 1 : -1 } if request.xhr?
end
This corrects the safari and ie accept headers so that it defaults to text/xml instead of text/html. It works for me. Tested both on IE and Safari. Other browsers default to text/xml anyways.
EDIT: I have set my DOCTYPE to <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> and not XHTML Transitional.

Problems Rendering View (ActionView::MissingTemplate ... Error) in Custom Plugin

I am trying to develop a plugin for Ruby on Rails and came across problems rendering my html view. My directory structure looks like so:
File Structure
---/vendor
|---/plugins
|---/todo
|---/lib
|---/app
|---/controllers
|---todos_controller.rb
|---/models
|---todos.rb
|---/views
|---index.html.erb
|---todo_lib.rb
|---/rails
|---init.rb
In /rails/init.rb
require 'todo_lib'
In /lib/app/todo_lib.rb
%w{ models controllers views }.each do |dir|
# Include the paths:
# /Users/Me/Sites/myRailsApp/vendor/plugins/todo/lib/app/models
# /Users/Me/Sites/myRailsApp/vendor/plugins/todo/lib/app/controllers
# /Users/Me/Sites/myRailsApp/vendor/plugins/todo/lib/app/views
path = File.expand_path(File.join(File.dirname(__FILE__), 'app', dir))
# We add the above path to be included when Rails boots up
$LOAD_PATH << path
ActiveSupport::Dependencies.load_paths << path
ActiveSupport::Dependencies.load_once_paths.delete(path)
end
In todo/lib/app/controllers/todos_controller.rb
class TodosController < ActionController::Base
def index
end
end
In todo/lib/app/views/index.html.erb
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"[url]http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd[/url]">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="content-type" content="text/html;charset=UTF-8" />
<title>Todos:</title>
</head>
<body>
<p style="color: green" id="flash_notice"><%= flash[:notice] %></p>
<h1>Listing Todos</h1>
</body>
</html>
In /myRailsApp/config/routes.rb
ActionController::Routing::Routes.draw do |map|
# The priority is based upon order of creation: first created -> highest priority.
map.resources :todos
...
The error I get is the following:
Template is missing
Missing template todos/index.erb in view path app/views
Can anyone give me a hand up and tell me what am I doing wrong here that is causing my index.html.erb file to not render? Much appreciated!
EDIT:
I have already tried the following without success:
In /todo/lib/app/controllers/todos_controller.rb
def index
respond_to do |format|
format.html # index.html.erb
end
end
EDIT:
hakunin solved this problem. Here's the solution.
He says that I'm building a Rails engine plugin (I had no idea I was doing this), and it requires a different directory structure, one that appears like so:
File Structure
---/vendor
|---/plugins
|---/todo
|---/lib
|---/app
|---/controllers
|---todos_controller.rb
|---/models
|---todos.rb
|---/views
|---/todos
|---index.html.erb
|---todo_lib.rb
|---/rails
|---init.rb
This required the following changes:
In todo/lib/todo_lib.rb
%w{ models controllers views }.each do |dir|
# Include the paths:
# /Users/Me/Sites/myRailsApp/vendor/plugins/todo/app/models
# /Users/Me/Sites/myRailsApp/vendor/plugins/todo/app/controllers
# /Users/Me/Sites/myRailsApp/vendor/plugins/todo/app/views
path = File.expand_path(File.join(File.dirname(__FILE__), '../app', dir))
# We add the above path to be included when Rails boots up
$LOAD_PATH << path
ActiveSupport::Dependencies.load_paths << path
ActiveSupport::Dependencies.load_once_paths.delete(path)
end
The change made above is in the line: path = File.expand_path(File.join(File.dirname(FILE), '../app', dir)). [Ignore the boldened 'FILE', this is an issue with the website].
Running script/server will render the index.html.erb page under todo/app/views/todos.
Looks like you want to build an "engine" plugin. Create "app" and "config" dirs in the root of your plugin dir (not under /lib). You can use app/views/ and app/controllers in your plugin as if it was a full featured Rails app. In config/routes.rb you should declare routes introduced by your engine.
See http://github.com/neerajdotname/admin_data for a decent example of what engine looks like.

Resources