Rails partial locals in helper - ruby-on-rails

Is there a way in RoR to access current partial locals in helper. I want something like
<% render partial: 'foo', locals: { :foo: 'bar' } %>
then to be accessed in lets say ApplicationHelper:
def my_helper_method
...
my_var = ...local_assigns[:foo] # should assign 'bar'
...
end
Other way to describe the problem would be: How do I pass all the locals passed to a partial to my helper method implicitly? If I do it explicitly, there are a lot of boilerplate code, which just pass partial arguments to to a helper method, and I have so many of them.
Is it possible?

Helpers have no knowledge of local variables inside partials. Unless you explicitly pass them a parameter, you can't do what you are proposing. What you can do is take an object-oriented approach using presenters, and avoid using helpers all together.
Either make your own, as outlined in the Railscasts episodes, or use a gem like Draper. Personally, I am in favour of the "roll your own" approach because it's very simple.
Some pseudo-code to get the idea across:
class FooPresenter
def initialize(object, template)
#object, #template = object, template
end
def amazing_foo
#template.content_tag :div, class: 'foo' do
"#{#object.name}: Wow! this is incredible!"
end
end
end
module FooHelper
def present_foo(object)
presenter = FooPresenter.new(object, self)
yield presenter if block_given?
presenter
end
end
Just instantiate that from your view.
= present_foo(foo) do
= amazing_foo
Yay, no need to pass params.
Helpers are just modules floating around in the namespace, and frankly, much of the time they encourage bad coding practices. Presenters offer a clear OOP way of handling complex view logic. Give it a try.

Usually you would pass the parameter into the method from the view, so change your method to be:
def my_helper_method(input_param)
...
my_var = ...foo # should assign 'bar'
...
end
and call this as any other method in the view passing foo as the input_param.

you need to send param to my_helper_method
def my_helper_method(foo)
...
my_var = foo
...
end
in partial
<%= my_helper_method(foo) %>

Related

Semi-global Rails partial

Is there a better way to achieve what I'm going for?
I have a partial in the /views/shared/ folder that has all the fields that are in a form being used to send an email.
A helper method with default options to render said partial (render partial: 'shared/email_fields' locals: locals where locals is a hash of default variables).
A helper method for every form sending an email that calls the above helper method and passes in either a FormBuilder object or a string containing the beginning of the name html attribute.
The problem I'm having: Most of the email forms differ slightly which results in me having to add additional options to the locals hash and I feel like the global partial is becoming bloated. Is there some way of using a global partial in this way such that the partial doesn't become super bloated?
I've thought of having each form completely separate but that's bad for upkeep and DRY. I've thought of passing in the name of a partial to be rendered inside the global partial but some of these forms need the same options and are rendered from different controllers and I wouldn't want to put a bunch of partials that aren't global in the /views/shared/ folder. Right now, I'm just sticking with the bloated global partial.
Any help would be appreciated!
Here's how I do it. This is going to sound weird, but bear with me.
So, I have basically two forms in my applications. For a form that submits via javascript, it looks like this:
#views/shared/_remote_form.html.haml
= form_tag #presenter.form_path,
remote: true,
id: #presenter.form_id,
class: #presenter.form_classes,
data: #presenter.form_data,
method: #presenter.form_method do
.well
= #presenter.form_inner
.form-controls-container
.form-controls-wrapper
= #presenter.form_controls
As you can see, I use presenters. The presenters are instantiated in the relevant controller as a controller variable, so that the presenter is available to the partial. Something like:
class FooController < ApplicationController
def new
#presenter = NewFooFormPresenter.new(self)
render partial: 'shared/remote_form'
end
...
end
You can see that I'm passing in the controller so that the presenter is able to render various parts of the form.
All FormPresenters inherit from FormPresenterBase that has stubbed methods for each of the methods called in the form. Something like this:
class FormPresenterBase
def initialize(controller)
#controller = controller
end
def form_path
root_path
end
def form_id
'bogus-form-id'
end
def form_classes
'something-bogus'
end
def form_inner; end
def form_controls; end
...
end
That let's me bootstrap the form without throwing a bunch of errors all the time. Naturally, that stubbed form won't really work, but that's okay because each FormPresenter will override the stubbed methods with real values. So, something like:
class NewFooFormPresenter < FormPresenterBase
def form_path
new_for_form_path
end
def form_id
'new-foo-form'
end
def form_classes
'something-not-bogus'
end
# The form fields could be unique to this form. Or, I might have a set of common
# fields that I use across multiple forms. I just decide which partial has the
# correct set of fields and render it here.
def form_inner
render partial: 'new_inner_fields'
end
# The controls are also rendered from partials. Here, I want to have an okay
# button and a cancel button. So, I just call the correct partial that
# renders those. I call html_safe on the resultant string so that it renders
# correctly.
def form_controls
[:okay, :cancel].each_with_object("") do |control_sym, to_return|
render partial: "shared/form_widgets/#{control_sym.to_s}_button"
end.html_safe
end
...
end
Of course, I can get tricky with my FormPresenters. If there are families that share common methods, I can either use further inheritance or module inclusion to keep everything DRY.
So, once I have all my basic form widgets (field combinations, controls, etc.) configured as partials, I can just mix and match in my presenter to my heart's delight. And (at least for forms), I basically never have to write another partial for the rest of my life. Whenever I need a new variant, I just spin up a new FormPresenter and customize it to give me the form I desire.
Actually, there's a little bit more to it than all of that, but hopefully this gives you a sense of another way to skin the cat.
An approach is to have a separate partial for each form. Take all of the items the forms have in common and put them in a partial. You can then reference the "common items" partial within your individual form partials. Depending on how your forms are structured, you may have several "common items" partials, but that is okay. The goal is to keep the code organized and DRY.

Converting a string into a controller method call

I'm trying to create a generic breadcrumbs method in my application controller to assign the breadcrumbs based on the current controller. If I wanted the breadcrumbs for the index of 'Thing', I would need in the view:
<%= breadcrumb :things, things %>
And for edit or show:
<%= breadcrumb :thing, thing %>
Where things is a method in the things controller that returns all things, and thing is a method returning the relevant thing.Both are exposed, and I have in my application layout:
<%= breadcrumb crumb, crumb_resource %>
And in my application controller:
def crumb
return controller_name.singularize.to_sym if edit_or_show_action
controller_name.to_sym
end
def crumb_resource
resource = controller_name
resource = controller_name.singularize if edit_or_show_action
end
def edit_or_show_action
action_name == 'edit' || 'show'
end
This obviously returns a string for crumb_resource, rather than the call to the controller method. From what I can find I believe it has something to do with send, however
controller.send(resource)
obviously doesn't work. How can I convert the string that is returned into a controller method call?
If you're using Gretel, then I think what you might be looking for is this:
def crumb_resource
resource = controller_name
resource = controller_name.singularize if edit_or_show_action
self.instance_variable_get("##{resource}")
end
This is assuming you have stored the relevant resource into #resource_name during the edit/show/index action.
I accepted the answer given as I'm assuming it works for people using instance variables to access models in their view, however in the end this worked for me:
breadcrumb crumb, eval(crumb_resource)
where eval evaluates the string, basically reverse interpolation which sounds pretty cool.

Finding the current render MIME type from a Rails helper

I'm looking for a way to find which render type I am currently executing from a helper. Mostly to do something like this:
# some_helper.rb
def url_to_faq
if plain_text_render
...
else
# HTML
end
end
We've used a workaround override for render in a gem that we are using, but it's gross. Is there some official way to get at the renderer metadata, either in Rails 4 or Rails 5?
I think you can use presenter here.
In controller:
#link_presenter = LinkPresenter.new(format: request.format.symbol, view: view_context)
Link presenter class:
class LinkPresenter
def initialize(format:, view:)
#format = format
#view = view
end
def url_to_faq
if format == :html
...
else
...
end
end
end
then in the view:
#link_presenter.url_to_faq
By passing view_context to presenter you get access to view helpers. If not needed, then drop it.
Nice article about Presenters: Presenters in Rails by Nithin Bekal

How to make a model aware of its controller in Rails?

I am making a Rails application, and i would like to be able use a model object passed to a view to get the URL of some action on this object, like this, for example:
link_to object.public_send(attribute),
{ :controller => object.controller_path,
:action => :show,
:id => object.id }
What would be a good way to do this? Can it be done with a decorator like Draper? Are there some examples online?
Update. I have thought about this and decided that a decorator is not a good place to keep controller information. It is not decorator's responsibility. A decorator should only know to render formatted data with markup. For now i have created a module called Accessor where i try to mix models with controller and routing awareness. I still wonder if there is a better way to do.
If you don't mind having another instance variable on your view, you can implement this using a very simple class (no need for decorators).
class MyRouter
def initialize(controller, object)
#controller = controller
#object = object
end
def url_for(action_name)
controller.url_for(object, :action => action_name)
end
end
On your controllers:
class AController
def edit
#router = MyRouter.new(self, object)
render 'shared_view'
end
end
class BController
def edit
#router = MyRouter.new(self, object)
render 'shared_view'
end
end
And on your shared view:
<%= #router.url_for(:show) # Varies with the controller that rendered the view %>
Of course, this assumes that the controller you want as target is the same controller that renders the view, which might not be true. Still, using this pattern you can accommodate a more complex logic that suits your needs (having multiple Router classes, for instance), without having to change the view.
I've found a very interesting solution in Objects on Rails by Avdi Grimm: Exhibits for REST. In short, his idea is to apply multiple Ruby's SimpleDelegators as decorators with various functions.

Can't suppress output in nested block helper in rails 3

This one is sort of twisting my noodle.
I have something resembling this (in a rails 3 engine if that matters)
class Builder
def initialize
#foos = []
end
def foo(&block)
#foos << helper.capture(&block) #helper being a class that is including ActionView::Helpers
end
def to_html
#foos.join "\n"
end
end
module ApplicationHelper
def widget
b = Builder.new
yield b
b.to_html
end
end
#in a view somewhere
<%= widget do |b| %>
<% b.foo do %>
static content
<% end %>
<% end %>
Everything is working out great, but that nested static content is getting output twice -- once where I want it, and once where widget was called.
From what I have read, capture is supposed to deal with this exact problem. I am pretty sure the problem stems from how I am calling capture (from a dummy proxy class that includes ActionView::Helpers), but the problem is that b.foo call is calling a method on a class instance, not from the context of something that will be mixed into the template.
Is there any way to get around this problem? Or am I approaching this from the wrong direction. I am trying to model something fairly involved and am really happy with the api, just can't seem to get passed this problem.
If you modify the helper method to pass in self, which would be the current view instance, and then use this to capture, you might not have this issue. Substitute your use of helper for the provided view instance.

Resources