Converting a string into a controller method call - ruby-on-rails

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.

Related

Unpermitted parameter error when adding request parameter while using Administrate

I'm using Administrate v0.11.0 with search_term textbox,
it works totally fine,
and now I want to add a request parameter my_search_condition_flag which is a boolean flag value that affects search condition.
In my index action of controller,
I added the following line, so that requests with this parameter pass the Strong Parameters validation.
params.permit(:search, :my_search_condition_flag)
The rest of the code in index action is simply copied from ApplicationController.rb of Administrate.
When I make a HTTP request with request parameter my_search_condition_flag=1 ,
my index action is processed just fine,
but HTTP response returns following error:
ActionController::UnpermittedParameters in Admin::MyPage#index
Showing /usr/local/bundle/gems/administrate-0.11.0/app/views/administrate/application/_search.html.erb where line #19 raised:
found unpermitted parameter: :my_search_condition_flag
which is raised from rendering method of search_term textbox inside index.html.erb
<% if show_search_bar %>
<%= render(
"search",
search_term: search_term,
resource_name: display_resource_name(page.resource_name)
) %>
<% end %>
I've already tried the following to my Dashboard class, introduced here:
# -- Overwrite the method to add one more to the permit list
def permitted_attributes
super + [:my_search_condition_flag] # -- Adding our now removed field to thepermitted list
end
How can I tell Administrate to permit a parameter which I want to add?
Do I have to use request body instead? (which I don't want)
You were on the right track there. The exception originates at /app/views/administrate/application/_search.html.erb:19, as you mention. If you look there, you'll see it uses the method clear_search_params, which also uses strong_parameters to allow/deny query params. You can override this with a helper of your own. For example:
module Admin
module ApplicationHelper
def clear_search_params
params.except(:search, :page, :my_required_condition_flag).permit(
:per_page, resource_name => %i[order direction]
)
end
end
end
If you do this, you'll get a new, related error. This time from /app/helpers/administrate/application_helper.rb:48. The method there is called sanitized_order_params, and can be overriden similarly:
module Admin
module ApplicationHelper
# ...
def sanitized_order_params(page, current_field_name)
collection_names = page.item_includes + [current_field_name]
association_params = collection_names.map do |assoc_name|
{ assoc_name => %i[order direction page per_page] }
end
params.permit(:search, :my_required_condition_flag, :id, :page, :per_page, association_params)
end
end
end
And with that, you should be clear of errors.
Admittedly, this is not very nice fix. Ideally Administrate should be providing some better way to override this list of allowed search params. Fancy submitting a PR? ;-)

Using form_tag my instance variables are not saving when I try to search an API

I'm trying to create a simple Rails app that will retrieve API data. My original intention were not to save the search to the database, which is why I used form_tag. My search works when I run in the console, but when I call #results or #first_match, it give me nil. Many thanks in advance.
Actor Controller Method
def index
#results = API::Topic.search([:actor])
#first_match = #results.values.first
end
Actor Form
= form_tag 'actors/show', method: :get do
= text_field_tag "Actor"
= submit_tag "Show me"
Routes
RailsApp::Application.routes.draw do
resources :actors
end
Update
Since I am routing in the show method in the form. I was able to retrieve the variables in my show method in the controller. I don't know if this is the best way though.
def show
#results = API::Topic.search([:actor])
#first_match = #results.values.first
end
I think, you need to use API::Topic.search(params[:actor])

Rails partial locals in helper

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) %>

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.

Automatically append parameters to *_url or *_path methods (Rails)

I have a particular set of views relating to one of my controllers, whereby I want any call to *_path or *_url to append a set of parameters.
Is there some magic method I can override that will let me do this? I have no idea where in the Rails code the *_path or *_url methods are even handled.
Edit for clarity: I'm looking for a way to do this such that I don't have to modify every link in every view where this needs to occur. I don't want every coder who touches this set of views to have to remember to append a parameter to every link they add to the page. The same parameter should be appended automatically. I consider a change to the *_url or *_path call a failure. Similarly, having to override every *_url or *_path call is considered a failure since a new method would have to be added/removed whenever a new link is added/removed.
You can do this by overriding url_for since all the routing methods call it.
module ApplicationHelper
def url_for(options = {})
options.reverse_merge!(#extra_url_for_options) if #extra_url_for_options
super
end
end
Now all you need to do is use a before_filter to set #extra_url_for_options to a hash to force all urls.
class MyController < ApplicationController
before_filter do { #extra_url_for_options = { :format => 'html' } }
end
Note that this will force all links to use the extra options.
Thanks to Samuel's answer, I was able to create a final working solution via a new helper, which I've included below.
module ExampleHelper
def url_for(options={})
options = case options
when String
uri = Addressable::URI.new
uri.query_values = #hash_of_additional_params
options + (options.index('?').nil? ? '?' : '&') + uri.query
when Hash
options.reverse_merge(#hash_of_additional_params)
else
options
end
super
end
end
You can try to use the with_options method. In your view you can do something like
<% with_options :my_param => "my_value" do |append| -%>
<%= append.users_path(1) %>
<% end %>
Assuming you have the users_path of course. my_param=value will be appended to the url
You could make a helper method:
def my_path(p)
"#{p}_path all the parameters I want to append"
end
and in the view use
<%= eval(my_path(whatever)) %>
Eval with give you dynamic scope, so every variable available in your view can be used in the helper. If your parameters are constant you can get rid of eval calls.

Resources