Setting asset_host from within a worker - ruby-on-rails

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

Related

How to access full asset_url path in Ruby on Rails

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.

Include ActionController::Base outside of views directory

I can have a view file that contains only this:
root/app/views/layouts/application.html.erb
<%= link_to root_url %>
And of course it works. This is because
ActionController
is included in the view file somehow.
How does this work? Inside each view we don't write 'include ActionController' so how is it magically included?
Let's say I'm using an angular template:
root/app/assets/templates/angularview.html.erb
<%= link_to root_url %>
Everything works perfectly apart from the fact that the link_to isn't included in this view:
undefined method `link_to' for #<#<Class:0x000000020417b0>:0x0000000468f2c8>
How should I include ActionController into a file stored at root/app/assets/templates/angularview.html.erb?
What should I edit in my project to automatically make all files inside root/app/assets/templates include ActionController? Is this possible? I want them to behave like 'normal' views, and magically include everything a normal view includes.
It is not working as you have your angularview template in assets directory(as you have mentioned in your question: root/app/assets/templates/angularview.html.erb). You need to create it inside your application's app/views/layouts/ directory.
See these answers for more information:
https://stackoverflow.com/a/6951986/645886
https://stackoverflow.com/a/19849989/645886
UPDATE: However, if you must do that then you can create an initializer and put this code:
Rails.application.assets.context_class.class_eval do
include ActionView::Helpers
include MyAppHelper
include Rails.application.routes.url_helpers
end
Source: https://stackoverflow.com/a/14284279/645886

Is there a way to render an engine's 'super' view within the main app's overridden view?

I am developing an engine with a controller and views, but I want to allow for the views to be overridden (this is simple enough as Rails::Engine allows for this by automatically prepending app/views from the main app to the view path for the engine). However, I want the overridden view to be able to refer to the view from the engine - for example, I want to "wrap" the engine's view with custom stuff in my app:
# main_app/app/views/engine/template.haml
# ... custom stuff here
= render template: 'engine/template'
# ... custom stuff here
The problem is, I can't find a way to refer to the engine's view once I override it... is it possible?
Try rendering the file by providing the full path of the engine's template file.
# in view
<%= engine_view {|f| render file: f} %>
# in helper
def engine_view(&b)
yield eval("__FILE__.gsub(Rails.root.to_s, YourEngine::Engine.root.to_s)",b.binding)
end
tihom's approach is good, but the block feels unnecessarily hacky (and rubocop won't like it). I prefer something like:
## in helper
def render_engine_view
render file: caller_locations.first.path.gsub(Rails.root.to_s, YourEngine::Engine.root.to_s)
end
## in view
# ... custom stuff here
<%= render_engine_view %>
# ... custom stuff here

Ruby on Rails - using helpers in html.erb

I'm probably missing some things.
Say for example I have a helper function in app/helpers/foo_controller.rb and the code is as follows:
def sample_helper(count)
#implementaton...
end
and I want to use this helper in a webpage generated by rails and the code is as follows:
<%= sample_helper(user.id) %>
and if I try to run the webpage it will throw me an error saying that the method is not defined.
Thanks in advance!
You don't quite have the naming conventions right.
Name your helper file app/helpers/foo_helper.rb and in it you should have this:
module FooHelper
def sample_helper(count)
"#{count} items" # or whatever
end
end
And now, from any view rendered by FooController you should be able to use the sample_helper method.
Also, you should know that if you use the rails generators this structure is setup for you. All you need to do is add methods to the files that get generated. That way you don't need to guess the naming conventions.
For example, this command will make a controller file, controller test files, a helper file, and and an index view file, all ready for you to customize.
rails g controller foo index
Is your helper should be in a file called app/helpers/foo_helper.rb that contains a a module of the same name as the helper (camelized) Like:
module FooHelper
def sample_helper(cont)
# implementation
end
end
That's the way Rail auto loads helpers.

Rails static html template files in the asset pipeline and caching in development mode

I'm building a website using AngularJS and Rails. The HTML files that I'm using for templates are stored under /app/assets/templates and each time I update a route or change something inside of a nested partial inside of a template I need to "touch" the highest level file in the /app/assets/templates directory for the html file I'm changing.
So if I have a page "edit.html" which loads a partial "_form.html" then whenever I update a route or change something in _form.html I need to make sure that edit.html is touched.
This is annoying and very finicky. Is there any way to inform the asset pipeline/sprockets to avoid caching for the app/assets/templates directory?
The best solution I've found to this is not to use the asset pipeline for HTML template files.
Instead make a controller called TemplatesController and create only one action.
Then map all template URLs to that using a route such as:
get /templates/:path.html => 'templates#page', :constraints => { :path => /.+/ }
Then move all the template files into app/views/templates
Then inside the controller, setup the following:
caches_page :page
def page
#path = params[:path]
render :template => 'templates/' + #path, :layout => nil
end
This way all of your template files will be served from the controller and then will be cached into public/templates. To avoid cache problems, you can create a timestamp path into the template route so that your cached files are delivered with a version:
get '/templates/:timestamp/:path.html' => 'templates#page', :constraints => { :path => /.+/ }
This way you can have a new timestamp each time you upload the website and you can store the templates folder anywhere you like. You can even store the templates folder on S3 and have an assets URL for that. Then wherever your template files are addressed, you can use a custom asset method:
templateUrl : <%= custom_asset_template_url('some/file.html') %>
Where:
def custom_asset_template_url(path)
"http://custom-asset-server.website.com/templates/#{$some_global_timestamp}/#{path}"
end
Then just make the asset redirect to the Rails server if it's not found and it will be generated. Or all template files can be pre-generated once uploaded.
There's a much (much!) better way to deal with this.
<%= path_to_asset("template_name.html") %>
That will return a fully working file from the asset pipeline, which can use ERB, etc. It's undocumented, but it's a part of sprockets / the asset pipeline.
To my mind, several things are needed here:
The templates should be namespaced, so people templates go in the app/views/people/templates directory
The templates are entirely static, so no before filters should be called.
The templates should be cached, making them very fast.
Here's a possible solution using a Rails concern:
# Allows static content to be served from the templates
# directory of a controller
module HasTemplates
extend ActiveSupport::Concern
included do
# Prepend the filter
prepend_before_filter :template_filter, only: [:templates]
# Let's cache the action
caches_action :templates, :cache_path => Proc.new {|c| c.request.url }
end
# required to prevent route from baulking
def templates;end
# Catch all template requests and handle before any filters
def template_filter
render "/#{params[:controller]}/templates/#{params[:template]}", layout: 'blank'
rescue ActionView::MissingTemplate
not_found layout: 'blank'
false
end
end
Notice we are returning the template in a prepended filter. This allows us to return the static content without hitting any other filters.
You can then create a route, something like this:
resources :people do
collection do
get 'templates/:template' => 'people#templates', as: :templates
end
end
Your controller becomes simply:
class PeopleController < ApplicationController
include HasTemplates
end
Now any file placed in the /app/views/people/templates can be served at speed from a url.
Expanding on RandallB's answer a bit; this is mentioned explicitly in the documentation on the Asset Pipeline: http://guides.rubyonrails.org/asset_pipeline.html
Note that you have to append the extension .erb to your .coffee file to have this work. (e.g., application.js.coffee.erb)
You can try gem js_assets (https://github.com/kavkaz/js_assets).
This allows you to function asset_path in javascript code that emulates a similar method of sprockets.
Cleanest solution is to precompile your html assets using ejs and serve them as other javascript files.
No http query
minification
Possibility to pass js data to you templates to make them dynamic (ejs is a port from underscore template and basically
behave like erb for javascript)
It basically work like that :
#in you gemfile
gem 'ejs'
#in app/assets/javascripts/templates/my_template.jst.ejs
<p>my name is <%= name %> !<p>
#in your application.coffee
#= require_tree ./templates
JST['templates/my_template'](name: 'itkin')
=> '<p>my name is itkin !<p>'

Resources