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

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?


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
| improve this answer | |
  • \$\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 send item_id into the method. \$\endgroup\$ – James Chevalier Jun 13 '14 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")

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.

| improve this answer | |

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}"

and in app/views/shared/_new_button.html.haml you write

  = 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).

| improve this answer | |
  • \$\begingroup\$ Nice. I do agree content_tag can get a little noisy. \$\endgroup\$ – Henrik N Jun 27 '14 at 18:01

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.