I have a scenario where I would like to have the name of the view that will be rendering while I'm in the layout file. I can find solutions to find which layout will be wrapping the current view from the view, but not the other way around. How can I find which view is rendering?
In Rails 3.0.3, I was able to see the name of the controller and action using controller_name and action_name. But those are not publicly documented (at least the action name) so I wouldn't depend on it long-term.
It might be better to monkey patch template render. In an initializer:
module ActionView::Rendering
alias_method :_render_template_original, :_render_template
def _render_template(template, layout = nil, options = {})
#last_template = template
_render_template_original(template, layout, options)
end
end
Then use #last_template in your layout.
The following solution works in Rails 3.1. Place this code in an initializer.
(The rails 3.0.3 answer is not working any more in Rails 3.1)
This enables an #active_template variable for every controller. This is an instance of an ActionViewTemplate class.
The method active_template_virtual_path method returns the template as a name in the following form "controller/action"
class ActionController::Base
attr_accessor :active_template
def active_template_virtual_path
self.active_template.virtual_path if self.active_template
end
end
class ActionView::TemplateRenderer
alias_method :_render_template_original, :render_template
def render_template(template, layout_name = nil, locals = {})
#view.controller.active_template = template if #view.controller
result = _render_template_original( template, layout_name, locals)
#view.controller.active_template = nil if #view.controller
return result
end
end
I like the following approach, as you can reduce code size a good bit in a lot of cases. Include this in your application_controller.rb:
before_filter :instantiate_controller_and_action_names
caches_action :instantiate_controller_and_action_names
def instantiate_controller_and_action_names
#current_action = action_name
#current_controller = controller_name
end
Then, if your views correspond to an action, you just do:
dosomething if #current_action == 'new'
Related
I have sub-classed the default form builder to add some additional methods. Sample code is below.
module ApplicationHelper
class AppFormBuilder < ActionView::Helpers::FormBuilder
def coordinates_field(method, options = {})
options[:readonly] = 'true'
....
#template.text_field(#object_name, method, objectify_options(options))
end
end
end
This works well, but to use it I have to change the view code for every form that uses the coordinates_field method, i.e.,
<%= form_for #object, :builder => ApplicationHelper::AppFormBuilder do |f| %>
...
<% end %>
It sounds like it is theoretically possible to change the default form builder globally (config.action_view.default_form_builder), but I can't seem to get this to work. This is what I have tried in /config/application.rb:
module Web
class Application < Rails::Application
...
config.action_view.default_form_builder = "ApplicationHelper::AppFormBuilder"
end
end
Which results in error undefined method 'new' for "ApplicationHelper::AppFormBuilder":String when I hit a view that has a form.
If I instead try this
config.action_view.default_form_builder = ApplicationHelper::AppFormBuilder
I get the error *config.action_view.default_form_builder = ApplicationHelper::AppFormBuilder* when the application starts.
Can anyone provide guidance on how to get this to work?
As mentioned in the official docs, in Rails 5 the correct way is to specify it in a controller. To make it application wide, just set in your ApplicationController:
class ApplicationController < ActionController::Base
default_form_builder AppFormBuilder
end
This makes much more sense. FormHelpers are part of the view layer and not the application config.
ApplicationHelper::AppFormBuilder is not required yet at the time application.rb loads. You can try to put this in a separate initializer file (create one in config\initializers):
module Web
class Application
ActionView::Base.default_form_builder = AppFormBuilder
end
end
I like Max's answer, but (rails n00b here so YMMV) I believe this is equivalent and cleaner, directly in config/application.rb:
config.after_initialize do
ActionView::Base.default_form_builder = MyCustomHelper::MyCustomFormBuilder
end
obviously you replace the names MyCustomHelper and MyCustomFormBuilder.
took me about 48hrs to figure this out, thanks to all that well-structured rails documentation.
You should be able to set this from an initializer as you would for other config options. Create a default_form_builder.rb file under config/initializers/. The syntax should simpler than in #Max's answer.
Rails.application.config.action_view.default_form_builder = AppFormBuilder
I suggest you do not include this in a helper. Add it as separate class inside the /lib directory. You may or may not need to prefix the with the module it's contained within.
Finally, you can set this globally from config/application.rb, but you would have to pass it as a string since the class may not be loaded when rails starts up.
config.action_view.default_form_builder = 'AppFormBuilder'
Rails 5+ 👌
class ApplicationController < ActionController::Base
default_form_builder AppFormBuilder
end
Rails 4 🏖
class ApplicationController < ActionController::Base
ActionView::Base.default_form_builder = AppFormBuilder
end
... or if you prefer an initializer, I'd recommend setting it as a string instead, that way you will not have to restart Rails every time you change something in your form builder.. 😴 (since the class may not be loaded when rails starts up. https://stackoverflow.com/a/27992240/2037928)
module Web
class Application
ActionView::Base.default_form_builder = "AppFormBuilder"
end
end
EDIT:
I got many responses with different approaches for solving the problem, thanks a lot!
Sadly, none of them worked until now.
To easily understand and reproduce the failure, I created a small Rails repo on GitHub with a Rspec suite.
One of the specs is passing (where the presenter is initialized in the view).
One of the specs is failing (where the presenter is initialized in the controller).
How make them both pass ?
ORIGINAL QUESTION BELOW:
This is my Presenter:
class UserPresenter
def initialize(user, vc)
#user = user
#vc = vc
end
def linkify()
#
# HERE IS THE PROBLEM
#
vc.link_to("foo") do
yield
end
end
end
This is my Controller:
I initialize my Presenter in the controller, passing the view context of the controller with the presented model.
class UserController
def show
#user = User.find(#.....
#presenter = UserPresenter.new(#user, view_context)
end
end
In my Slim template, I call my Presenter to put the content in a link:
=#presenter.linkify do
p "123"
My problem is, I can't pass the block from the view to my linkify method.
In the with comment marked above code, the passed block is the whole view content, instead of the p 123.
When I initialize my Presenter in the view via: #presenter = UserPresenter.new(#user, self), it works as expected.
How I can make the linkify method uses the provided block, without initializing the presenter in the view ?
Because if you are going to use the yield command, you mustn't specify the &block, since now you are effectively receiving a block as a parameter using normal parameter syntax.
class UserPresenter
def initialize(user, vc)
#user = user
#vc = vc
end
def linkify() # <-- Remove &block
vc.link_to("foo") do
yield
end
end
end
# ...
# Somewhere else, assuming you have access to #presenter which is an instance of
# UserPresenter
# ...
def show
#presenter.linkify do
# ...
# do my view stuff here
# ...
end
end
show()
# Now, if your "View" is nothing but a block that needs to get passed in
# then you'd do this...
def show(&block)
#presenter.linkify do
block.call()
end
end
# This would be used this way:
show(lambda {
# ...
# View stuff here
# ..
})
As specified in lacrosse's answer. The wrong view context is the root of this cause. I tried to make a work around for your situation. And, this is how ended up doing it:
I created a helper method in ApplicationHelper:
module ApplicationHelper
def link(presenter)
presenter.linkify(self) do
yield
end
end
end
changed linkify() to:
def linkify(vc)
vc.link_to("foo") do
yield
end
end
which means, no need to have vc in presenter's class constructer, or you can update the vc in link method defined in the helper(your choice).
views are now looks something like this:
presenter_from_view.html.slim:
-#presenter = UserPresenter.new(#user, self)
=link #presenter do
p 123
presenter_from_controller.html.slim:
=link #presenter do
p 123
I agree, maybe this is not how you wanted your solution to be done. But, I couldn't get any cleaner work around for this. However, here you don't have to worry about passing self in views wherever you use link #presenter do..(which may become too much for writing code when you use linkify in multiple views I guess).
P.S.: Your all specs are passing now. And, if you need the modified code then I can push it to your repository in a separate branch. Let me know.
From Slim's documentation on Helpers and Capturing:
module Helpers
def headline(&block)
if defined?(::Rails)
# In Rails we have to use capture!
"<h1>#{capture(&block)}</h1>"
else
# If we are using Slim without a framework (Plain Tilt),
# this works directly.
"<h1>#{yield}</h1>"
end
end
end
Can you try using capture as follows?
def linkify(&block)
result = capture(&block)
vc.link_to("foo") do
result
end
end
The wrong view context is causing this issue. Just change UserPresenter#initialize to not accept view context, initialize presenter in the controller and pass the correct view context from the view instead, like so:
= #presenter.linkify(self) do
p "123"
What error are you getting? Just looking at the code...
In this method
def linkify()
#
# HERE IS THE PROBLEM
#
vc.link_to("foo") do
yield
end
end
where is vc defined?
I think you mean #vc which is the instance variable you're initializing.
Also as a side note... the empty () in linkify() are redundant in a ruby method with no variables. You can eliminate them.
Also you may want to take a look at the cells gem. As you're basically mirroring this behavior in your presenters and IMO cells is a cleaner way of accomplishing this in rails.
I think I figured out WHY it's not working. When you pass in the view_context in the controller, it's rendering the view_context once when you pass it in to the presenter, and then you again when you actually render the view.
def initialize(user, vc)
#user = user
#vc = vc
end
# When called from the view:
#presenter = UserPresenter.new(#user, self)
# You're passing the view directly in "as is" and renders as expected.
# However, when you pass it in from the controller:
#presenter = UserPresent.new(#user, view_context)
# you're essentially rendering the view_context in the controller, and then again
# once it renders at the end of your action. That's why you're getting this:
# "<p>123</p><p>123</p>"
You'll either need to send self in from the view each time you call linkify, or you can use an application helper method that will always have the view context.
In my Rails 3 application I use Ajax to get a formatted HTML:
$.get("/my/load_page?page=5", function(data) {
alert(data);
});
class MyController < ApplicationController
def load_page
render :js => get_page(params[:page].to_i)
end
end
get_page uses the content_tag method and should be available also in app/views/my/index.html.erb.
Since get_page uses many other methods, I encapsulated all the functionality in:
# lib/page_renderer.rb
module PageRenderer
...
def get_page
...
end
...
end
and included it like that:
# config/environment.rb
require 'page_renderer'
# app/controllers/my_controller.rb
class MyController < ApplicationController
include PageRenderer
helper_method :get_page
end
But, since the content_tag method isn't available in app/controllers/my_controller.rb, I got the following error:
undefined method `content_tag' for #<LoungeController:0x21486f0>
So, I tried to add:
module PageRenderer
include ActionView::Helpers::TagHelper
...
end
but then I got:
undefined method `output_buffer=' for #<LoungeController:0x21bded0>
What am I doing wrong ?
How would you solve this ?
To answer the proposed question, ActionView::Context defines the output_buffer method, to resolve the error simply include the relevant module:
module PageRenderer
include ActionView::Helpers::TagHelper
include ActionView::Context
...
end
Helpers are really view code and aren't supposed to be used in controllers, which explains why it's so hard to make it happen.
Another (IMHO, better) way to do this would be to build a view or partial with the HTML that you want wrapped around the params[:page].to_i. Then, in your controller, you can use render_to_string to populate :js in the main render at the end of the load_page method. Then you can get rid of all that other stuff and it'll be quite a bit cleaner.
Incidentally, helper_method does the opposite of what you're trying to do - it makes a controller method available in the views.
If you don't want to include all of the cruft from those two included modules, another option is to call content_tag via ActionController::Base.helpers. Here's some code I recently used to achieve this, also utilizing safe_join:
helpers = ActionController::Base.helpers
code_reactions = user_code_reactions.group_by(&:code_reaction).inject([]) do |arr, (code_reaction, code_reactions_array)|
arr << helpers.content_tag(:div, nil, class: "code_reaction_container") do
helpers.safe_join([
helpers.content_tag(:i, nil, class: "#{ code_reaction.fa_style } fa-#{ code_reaction.fa_icon }"),
helpers.content_tag(:div, "Hello", class: "default_reaction_description"),
])
end
end
I'm writing a plugin that adds a method to controllers and declares it as a helper method. If it were done statically (rather than through the plugin), it would look something like this:
# in RAILS_ROOT/app/controllers/stuffed_animals_controller.rb
class StuffedAnimalsController < ActionController::Base
private
def bear
'Teddy Bear'
end
helper_method :bear
end
# in RAILS_ROOT/app/views/stuffed_animals/index.html.erb:
<%= bear -%>
It works just fine. I want to test that :some_helper_method is actually a helper method, though. I tried this:
def test_declared_bear_as_helper_method
assert StuffedAnimalsController.helper_methods.include?(:bear)
end
Unfortunately, ActionController::Base does not have a :helper_methods class method. Anyone know where I can get the list of things a class exposes via :helper_method?
Got it!
def test_declared_bear_as_helper_method
helper = Object.new
helper.extend StuffedAnimalsController.master_helper_module
assert helper.respond_to?(:bear)
end
So I'm new to Rails and I'm trying to figure out what the canonical way to add custom form elements is. Currently the way I'm doing it is super grotty.
module ActionView
module Helpers
module FormOptionsHelper
def some_new_field(object, method, options = {}, html_options = {})
#code code
end
end
class FormBuilder
def contract_year_select(method, options = {}, html_options = {})
#template.some_new_field(#object_name, method, objectify_options(options), #default_options.merge(html_options))
end
end
end
end
I have seen this however.
class Forms::ApplicationFormBuilder < ActionView::Helpers::FormBuilder
Forms::ApplicationHelper.instance_methods.each do |selector|
src = <<-end_src
def #{selector}(method, options = {})
#template.send(#{selector.inspect}, #object_name, method, objectify_options(options))
end
end_src
class_eval src, __FILE__, __LINE__
end
private
def objectify_options(options)
#default_options.merge(options.merge(:object => #object))
end
end
(from here)
Extending FormBuilder seems like a better solution than duck punching it. Is there some other way to do this that doesn't require directly copying parts of the FormBuilder class into my custom one?
Two parter here:
First, as someone new to rails, I would recommend using the Formtastic gem.
V is the abused and neglected part of MVC in rails.
Formtastic is very well tested and has support for Rails 3
https://github.com/justinfrench/formtastic
With Formtastic you can then create custom elements extending it's brilliant class and get a whole host of other benefits beyond simple field display.
Second, extending the class is definitely acceptable. I prefer the Module method as it's easier to look at. Like something along the lines of:
module Forms::ApplicationFormBuilder
class WhateverYourExtending
def some_special_method(stuff)
# all kinds of special stuff here
end
end
end
But I barely know what I am talking about here... This stuff is right on the edge of my understanding.
Just saw you talking about forms and I love Formtastic!