Supposedly, ActionController::Base.helpers acts like a proxy for accessing helpers outside views. However many of the methods defined there rely on controller variables and I'm unable to succesfully call:
ActionController::Base.helpers.image_path("my_image.png")
>> TypeError Exception: can't convert nil into String
Digging at the source I see compute_asset_host method is trying to access config.asset_host but config is nil.
How can I successfully call image_path from outside views?
Use view_context to access those helper methods that are available in the view.
You can call image_path like this from the controller.
view_context.image_path "my_image.png"
For Rails 3 please checkout the much cleaner solution here How can I use image_path inside Rails 3 Controller
This solution, posted by Mason Jones, works for me.
In your application controller:
def self.tag_helper
TagHelper.instance
end
class TagHelper
include Singleton
include ActionView::Helpers::TagHelper
include ActionView::Helpers::AssetTagHelper
end
Then you can do the following kind of thing, or whatever else you need.
active_scaffold :mything do |config|
config.columns = [:name, :number, :active, :description]
config.update.link.label = tag_helper.image_tag('document_edit.png', :width => "30")
config.delete.link.label = tag_helper.image_tag('document_delete.png', :width => "30")
config.show.link.label = tag_helper.image_tag('document.png', :width => "30")
list.sorting = {:name => 'ASC'}
end
You're creating a Singelton instance of TagHelper in your ApplicationController. This gives you the helpers wherever you need them. He explains it in his post.
Also, I use this to extend my models (to create a more flexible image_tag helper that returns a default image if there's no image present -- e.g. person.small_image is an instance variable of the person model, which uses tag_helper). To do that I've put the same code in a Monkey Patch initializer that extends ActiveRecord::Base. I then call ActiveRecord::Base.tag_helper from within my models. This is a bit messy, but I'm new to rails. There's probably a cleaner way.
Hope that helps.
Related
I've been researching the 'recommended' way to use Rails view helpers (e.g. link_to, content_tag) in a plain ruby class, such as a presenter. It seems there's very little information on this front and I wanted to get an idea of what the Stack community thought.
So, the options we have are.. (note I'm using Rails 4, and am less concerned about older versions)
Include the required modules manually
This is probably the cleanest way, since only the helpers needed are included. However I have found this method to not work in some cases, as the usual view context provided in plain Rails helpers is configured for the current request. url_for wouldn't know about the current request for example, so the host might not match.
class MyPresenter
include ActionView::Helpers::UrlHelper
include ActionView::Helpers::CaptureHelper
def wrapped_link
content_tag :div, link_to('My link', root_url)
end
end
Use ActionController::Base.helpers
Since Rails 3, ActionController::Base has included a helpers method to access the current view context. I believe the view context provided by this method is configured as it would be in a rails helper, but I might be wrong. There's not really any documentation about this which seems worrying, but it does work quite well in practice.
class MyPresenter
def wrapped_link
h.content_tag :div, h.link_to('My link', h.root_url)
end
protected
def h
ActionController::Base.helpers
end
end
I believe this view context can also be mixed in with include, but the rails view helpers have hundreds of methods and it feels dirty to include them all indiscriminately.
Inject the view context when calling the presenter
Finally, we could just pass the view context to the class when it's initialized (or alternatively in a render method)
class MyPresenter
attr_accessor :context
alias_method :h, :context
def initialize(context)
#context = context
end
def wrapped_link
h.content_tag :div, h.link_to('My link', h.root_url)
end
end
class MyController < ApplicationController
def show
# WARNING - `view_context` in a controller creates an object
#presenter = MyPresenter.new(view_context)
end
end
Personally I tend to lean towards the latter two options, but with no definitive answer from the Rails team (that I've been able to find) I felt a bit unsure. Who better to ask than Stack!
I would go with the mix of the second and third option, something like:
class MyPresenter
def initialize(helpers)
#h = helpers
end
def wrapped_link
h.content_tag :div, h.link_to('My link', h.root_url)
end
private
attr_reader :h
end
Your second option require all your unit tests to be stubbed as ActionController::Base.helpers which maybe isn't a good option and your third option you're using a huge context to access just some methods.
I would really make that dependent on what kind of methods you use. If it's just the basics like content_tag etc. I would go for the ActionController::Base.helpers way. It is also possible to call some helpers directly, e.g. for paths inside models I almost always use something along the lines of Rails.application.routes.url_helpers.comment_path.
For controller-specific stuff the third option might be useful, but personally the "pure" way seems nicer. Draper has an interesting approach too: They save the view_context for the current request and then delegate the calls to h-helpers to it: https://github.com/drapergem/draper/blob/master/lib/draper/view_context.rb
It really is just a matter of preference. I would never include all helpers at once, as you already said. But the second option is quite nice if you want to build the presentation layer yourself without using a gem like Draper or Cells.
I'm trying to implement Decorators using the learnings from "Rails 4 Patterns" Code School course, but I'm running into trouble as I need a view helper in the Decorator class.
I want my view to have:
<%= #model_decorator.previous %>
Then in the decorator:
def previous
if object.prev_item.nil?
"Previous"
else
link_to("Previous", object)
end
end
The course suggests you make a call to the decorator within your view helper in the view file itself, but that's no good if the logic could output one result with a helper and one without. (i.e. need the output to be a link or not).
I've tried using helpers.link_to but it errors out as not providing the correct information for the url_for option. I've confirmed link_to("Previous", object) works within the view itself.
For Rails 4
include ActionView::Helpers::UrlHelper
link_to("Previous", Rails.application.routes.url_helpers.send("#{object.class.name.underscore}s_path".to_sym, object))
As for me it`s better to make a decorator for it:
class LinkDecorator
include ActionView::Helpers::UrlHelper
def initialize(label, object)
#label = label
#object = object
end
def show
link_to(label, url_helpers.send("#{object.class.name.underscore}s_path".to_sym, object))
end
def index
link_to(label, url_helpers.send("#{object.class.name}s_path".to_sym))
end
...
private
attr_reader :label, :object
def url_helpers
Rails.application.routes.url_helpers
end
end
Example usage:
LinkDecorator.new(object.name, object).show
If I understand your problem correctly, you essentially want links in a plain old ruby object.
My solution would be this:
include ActionView::Helpers::UrlHelper
link_to("Previous", Rails.application.routes.url_helpers.objects_path(object))
# assuming the object is always of one class
If the object is of a different class, than it would be possible to use the .send method to send the correct message to app ie.:
include ActionView::Helpers::UrlHelper
link_to("Previous", Rails.application.routes.url_helpers.send("#{object.class}s_path".downcase.to_sym, object))
# I'd create a function out of that line to make it a bit neater
It sounds like the error thrown by url_for comes from missing the routes and there's a few ways to include those. My solution kinda avoids that problem by using Rails.application.routes.url_helpers. Hope this helps!
I'm using Rabl to generate XML output in a rake task:
xml = Rabl.render #listings, 'feeds/listings', :format => :xml
# do stuff with xml
However, I need to use multiple helper methods in the rabl view file referenced, and I keep getting a NoMethodError as I expected from the answer to this question.
I tried using extends and include in the class used by the rake task but I still get the same error on the helper methods:
require "#{Rails.root}/app/helpers/feeds_helper.rb"
class SerializeData
extends FeedsHelper
def perform
xml = Rabl.render #listings, 'feeds/listings', :format => :xml
# do stuff with xml
end
end
My question is: is there any way to use helper methods in rabl view files generated in this way? (or at least in a way that I can still render them as a string in a rake task?) The helper methods are used many, many times to correctly format various data per fixed requirements, so it would be very difficult to remove them entirely.
I ended up with a monkey-patchy solution.
I noticed that the NoMethodFound error came from an instance of the Rabl::Engine class, so I included the needed routing and helper methods in that class and was then able to access them:
require "#{Rails.root}/app/helpers/feeds_helper.rb"
...
class Rabl::Engine
include Rails.application.routes.url_helpers
include FeedsHelper
end
Also note that the URL host needs to be set if using url in addition to path helpers (e.g. root_url and root_path):
Rails.application.routes.default_url_options[:host] = "www.example.com"
I would definitely prefer a non-monkey-patch solution or at least one where helpers could be included as needed depending on the controller of the action rendered. I'll wait to accept this to see if anyone can come up with such an answer.
You can pass in a scope object with the scope parameter. So if you have access to an object with the helper included, like when in the view context, then you can pass that
eg:
<%= Rabl::Renderer.json(object_to_render, 'api/v1/object/show', view_path: 'app/views', scope: self).html_safe%>
So outside of the view context you'd need to pass in a custom object with the helpers included to make this clean.
eg
class RakeScope
include FeedHelper
end
Rabl::Renderer.json(object_to_render, 'api/v1/object/show', view_path: 'app/views', scope: RakeScope.new() )
I've not tried the second option but the first works great.
While not quite the same problem, I had a similar problem accessing helpers from RSpec specs. I created a helper function that creates a scope that you can use to add whatever helpers you need. The following gave me access to the path and url helper methods and something similar should work for Rake.
#spec/support/rabl_helper.rb
def render_rabl(object, options={})
options = {
format: 'json',
view_path: 'app/views',
file: example.example_group.top_level_description,
scope: RablScope.new
}.merge(options)
result = Rabl.render(object, options.delete(:file), options)
options[:format] == 'json' ? JSON.parse(result) : result
end
class RablScope
include Rails.application.routes.url_helpers
end
I am trying to render html in my model (for Mandrill inline code) but i cant seem to get the url_helpers to render correctly
questions.haml
%a{:href => email_question_url(question)}
model.rb
view = ActionView::Base.new(ActionController::Base.view_paths, #email_vars)
view.extend ApplicationHelper
questions_html = view.render(:partial => 'transactional_mailer/questions_html')
I ideally want to run:
view.include Rails.application.routes.url_helpers
But that bombs out with undefined method include for actioniew::base
Any suggestions on how i could approach this?
I had to open the instance class and include the instance methods within it via:
view = ActionView::Base.new(ActionController::Base.view_paths,{})
class << view
include Rails.application.routes.url_helpers
end
questions_html = view.render(:partial => 'transactional_mailer/questions_html')
In my case, where I needed to test (via RSpec) a class that required a reference to the view instance inside of it, I did the following:
let(:view) do
ActionView::Base.new(ActionController::Base.view_paths, {}).tap do |view_instance|
view_instance.instance_eval { self.class.include Rails.application.routes.url_helpers }
end
end
It worked for me. It doesn't look nice but it works.
I try to access URL helper from inside a Module class. Here's my code :
module Station
class Plugins
##plugins = [] unless defined?(##plugins) && ##plugins.class == Array
class << self
def all
return ##plugins.sort_by { |p| p[:weight] }
end
def register(plugin = {})
raise "plugin must be a Hash (ie.: `register(:foo => 'bar')`)" unless plugin.class == Hash
raise "plugin must contain :name (ie.: `register(:name => 'my_plugin')`)" unless plugin[:name].present?
plugin[:weight] = 1 unless plugin[:weight].present?
plugin[:href] = eval("#{plugin[:name].downcase.pluralize}_url") unless plugin[:href].present?
##plugins.push(plugin) unless ##plugins.include?(plugin)
end
end
# include default plugins:
Station::Plugins.register(:name => "Pages", :weight => -1)
end
end
When I run my server, I got this error back:
undefined local variable or method `pages_url' for Station::Plugins:Class
I read a lot about "how to call url helper from a Class", but none of the solutions I found worked for me.
Firstly, what you're not making clear is if the url helper you're trying to access is from the parent application the engine is added to, or if it's from another engine this engine has included.
If from parent application, then all you need is:
main_app.pages_url
So you'll need to edit your code accordingly. Note that the "main_app" part is not the name of the parent application but literally the words "main_app".
If you're trying to access a url helper of an engine that you included in this engine, then you need to access it like you would to access any engine from the parent application. I.e.:
Your gemspec file should include:
s.add_dependency('my_engine', path: "~/path/to/my_engine")
routes.rb should include:
mount MyEngine::Engine => "/my_engine", as: "any_name_for_my_engine"
and then access it in your code using:
any_name_for_my_engine.pages_url
Hope this helps.
EDIT:
Change your engine's application.rb file to look as shown below, so that you can inherit all the parent application's ApplicationController variables and routes:
class Station::ApplicationController < ApplicationController
end
You might want to read the Rails Guide on Engines for a more detailed explanation on how to make these work together. Ask again if you're still having trouble.
What worked for me was to include the helpers into the specific class:
include ENGINE_NAME::Engine.routes.url_helpers
include Rails.application.routes.url_helpers
This worked for me, found it while reading ActionPack's code.
Contains all the mounted helpers across different engines and the main_app helper for the application. You can include this in your classes if you want to access routes for other engines.
include ActionDispatch::Routing::RouteSet::MountedHelpers