If I call render a view from the controller, then asset_url('file.png') returns the entire url, aka http://www.example.com/assets/file.png. However, if I try to render the view from anything outside of the controller, such as a service or a custom method defined in lib/, then the asset_url('file.png') just simply returns /assets/file.png.
It seems that the full URL for asset_url is only accessible when the controller is rendering a view, and that's it. Is there anything I can set so that asset_url will always return the full path? Or do I need to go in and manually convert 200+ asset_url links in my app?
View:
#app/views/path/to/view.html.erb
asset_url('file.png')
Rendering the view from controller:
html = render_to_string template: '/path/to/view', layout: false, locals: {report: #report}
Results:
http://www.example.com/assets/file.png
Rendering the view from a service or custom method:
# 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
html = av.render template: '/path/to/view', layout: false, locals: {report: #report}
Results (missing domain):
/assets/file.png
Rather than calling asset_url within my views, I am now using a custom method called custom_asset_url, which checks to see if asset_url has the full URL in it. If not, it puts it in there and returns it.
def custom_asset_url(asset)
# This was defined because if you call asset_url outside of a controller, then the host doesn't
# render, therefore it just displays /assets/ instead of http://www.example.com/assets (which breaks PDF generation).
url = asset_url(asset)
if !url.include? "http"
if Rails.env.production?
url = "https://prod-url#{url}"
else
url = "http://dev-url:3000#{url}"
end
end
return url
end
Until I can figure out why asset_url is nil when called from outside of a controller, this is the workaround method.
Related
For whatever reason, I cannot get the full path to my asset URLs with asset_url when calling a view that uses asset_url from within a worker. If I render the view from the controller, then asset_url shows the full URL including http://
Here's what I'm loading in my worker:
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
cover_html = av.render(:template => "reports/cover_page.html.erb", encoding: "UTF-8",
When it gets to cover_page.html.erb, the asset_url called from within it doesn't load http://server/path/to/image.png like it should.
Not sure why you need view helpers in a worker. Seems not right to me (maybe there is a different way of achieving what you are doing or maybe your business requirement is unique)
Answering to your question, asset_url is part or ActionView::Helpers::AssetUrlHelper, so try including it,
av.class_eval do
include Rails.application.routes.url_helpers
include ActionView::Helpers::AssetUrlHelper
include ApplicationHelper
end
I try to convert rails views into pdf with the gem wicked_pdf.
But when I do a render_to_string like this
ActionController::Base.new.render_to_string(template: "templates/pdf_meteo.html.erb", locals: {communaute_meteo_id: id}, layout: 'pdf')
Methods like user_path don't work and return undefined method error... (note that the page work properly if I render it in html)
If someone can help me !
You can include helpers by using the following method
ac = ActionController::Base.new
ac.view_context_class.include(ActionView::Helpers, ApplicationHelper)
ac.render_to_string(template: "templates/pdf_meteo.html.erb", locals: {communaute_meteo_id: id}, layout: 'pdf')
Unfortunately, using render_to_string will not give you access to Rails URL helpers. One workaround is to include them directly in the locals that you pass into the PDF template using something like url: Rails.application.routes.url_helpers:
ActionController::Base.new.render_to_string(
template: "templates/pdf_meteo.html.erb",
locals: {url: Rails.application.routes.url_helpers, communaute_meteo_id: id}
layout: 'pdf'
)
And then inside of your PDF template you would call them with:
url.user_path
Keep in mind that by default the _path URL helpers will be relative, and not absolute paths. You can instead use the _url version of the helpers and set the host for them in a few different ways. You can configure them globally for your entire app:
# config/environments/development.rb
Rails.application.routes.default_url_options[:host] = 'www.mysite.com'
or set them individually on each helper inside of your PDF template:
url.user_url(host: 'www.mysite.com')
Hope that gets you what you need!
Simplest way is to use regular rails rendering flow - with view file your_action_name.pdf.erb, trick is in overriding formats for partials:
<%= render partial:"some_partial", formats:[:html] %>
Also you can run render_to_string in context of your controller to have helpers (because ActionController::Base knows nothing about your app)
I'm trying to pass a url string to a view from one of my controller methods, like so:
def index
#locals = {table_cols: $config['global_fields'],
table_title: $config['main_page']['table_title'],
ajax: url_for(action: index_data)}} # HERE
end
index_data is a function in my controller that returns some data in JSON format. However, when I navigate to the index page of my application, the browser simply displays the JSON output of index_data.
I'm assuming calling url_for within index is redirecting that page to index_data - is there a way to prevent this and just have url_for return the string, e.g. '/controller/index_data'?
Thanks
It's probably because you are calling the index_data action when you attempt to get a url to it, and calling it is tricking Rails into thinking you want to render from that action.
If you change your url_for to point to a symbol (or string) instead of the return value of the action, things will probably start working as you expect:
#locals = { ..., ajax: url_for(action: :index_data)}}
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.
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()