I've got this code in my ApplicationHelper
file:
def new_button
case controller_name
when 'cars'
"<li class='has-form'><a class='button' href='#{new_car_path}'>New Car</a></li>".html_safe
when 'trucks'
"<li class='has-form'><a class='button' href='#{new_truck_path}'>New Truck</a></li>".html_safe
when 'mopeds'
"<li class='has-form'><a class='button' href='#{new_moped_path}'>New Moped</a></li>".html_safe
else
nil
end
end
So I can just put <%= new_button %>
in the view, to display the appropriate button based on the controller_name
being accessed.
I have about 10 different controllers to select from (and I'm sure that collection will grow), so the code is getting a bit lengthy.
Is there a better way to accomplish this?
3 Answers 3
You can use the name of your current controller to dynamically generate your buttons. Controllers names are plural by convention in Rails, so you will want to get the singular version of your controller name.
singular = controller_name.singularize # get the controller name & make it singular
You can generate path to the "new" action by using send
to call a dynamic method name. If you are not using Rails resources, you can use url_for(controller: controller_name, action: "new")
to accomplish the same thing.
path = send("new_#{singular}_path") # generate the URL for that item's new action
Similarly, you can dynamically generate the text for your buttons.
title = "New #{singular.titleize}" # generate the text for the link
And finally, put it all together to generate your HTML:
"<li class='has-form'><a class='button' href='#{path}'>#{title}</a></li>".html_safe # return
-
\$\begingroup\$ I also have an edit button with similar syntax & I found that the link could be generated with
path = send("edit_#{singular}_path", item_id)
if I senditem_id
into the method. \$\endgroup\$James Chevalier– James Chevalier2014年06月13日 16:00:02 +00:00Commented Jun 13, 2014 at 16:00
I haven't run this code, but maybe something along these lines:
def new_button
content_tag(:li, class: "has-form") do
text = "New #{controller_name.singularize.titleize}"
path = { controller: controller_name, action: "new" }
link_to(text, path, class: "button")
end
end
Using content_tag
and link_to
is arguably cleaner, and they handle escaping automatically.
Letting the router generate the URL for you from a hash is preferable to doing string manipulation.
The text generation is perhaps inelegant, but simple. If you're using I18n elsewhere in this app I'd consider using that:
model_slug = controller_name.singularize
text = t("new_x", x: t("activerecord.models.#{model_slug}"))
But it's overkill if you're not using it for anything else.
For links, I prefer to use link_to
as it is really clear to read. However, once more view code is concerned, instead of going all content_tag
or typing in strings and using interpolation, which could lead to hard to find errors, and definitely hard to read code, I tend to prefer to use partials.
So your code would become something like
def new_button
single_item = controller_name.singular
render 'shared/new_button', title: "New #{single_item.capitalize}"
end
and in app/views/shared/_new_button.html.haml
you write
%li.has-form
= link_to title, {action: :new}, class: 'button'
Notice for the path I only need to specify the action (which is in this case always the same), since rails will automatically fill in the current controller if it is missing.
Notice how simple it would now be to make the title dependent on translations, or give it as the only parameter to new_button
as it is (in the communication to the client/user) the only thing that matters (or could change easily).
-
\$\begingroup\$ Nice. I do agree content_tag can get a little noisy. \$\endgroup\$Henrik N– Henrik N2014年06月27日 18:01:51 +00:00Commented Jun 27, 2014 at 18:01