Thursday, September 8, 2011

Navigation methods in Rails

I may not always feel like coding, but most of the time if I sit down to code I get warmed up and quickly forget I didn't feel like coding. Today was one of those days. I didn't feel like coding, but I took a look at my to-do list and figured "Hey, I can at least knock out some of this easy CSS stuff, right?" I try to live by a code of "Rails every day".

With that said, here's a little problem I've got.

Here's how I currently have a Back link written in my Menu partial:

<%= link_to 'Back', characters_path %>

The problem is, I render this partial on every page, and so the Back link shouldn't always point to characters_path. I thought an easy solution to this would be to write the code like so:

<%= link_to 'Back', :back %>

This returns the user to the last page viewed, which is closer to what I want, but then I run into some irritating navigational issues. Say you were looking at a Character's Items, and created a new Item, then hit the Back button once it was created. Since I'm rendering the form in the Items view page, it simply takes the user back to the previous view, showing a form with blank fields. So here's my question:

Is there an easier way to handle this? A better method than :back? Should I simply make two versions of the menu partial, each with a separate back link? I don't think I'll need more than two. The menu partial rendered when viewing a Character will want to point back to characters_path, whereas every other page (Items, Skills, and so forth) will need to point back to the Character view page. Maybe I'm overthinking this, considering it's just an extra partial, but I'm thinking there might be a better way to handle this. Imagine if I wanted five different menu partials, or twenty? I think it's better to think of more abstract solutions that can be applied to multiple scenarios.


  1. There are a lot of ways you could handle this. I'm not sure if you are calling this partial in your layout or in specific views. If in a view, below is one way.

    Suppose your menu partial was in /shared/_menu.html.erb (or haml, if that's what you use). If you render your partial passing whatever instance variable is present on the page it might be something like...

    = render 'shared/menu', :obj => @skill
    = render 'shared/menu', :obj => @item
    = render 'shared/menu', :obj => @character

    Thus, the partial will have the "obj" variable available to it representing whichever object was passed in.

    The contents of the partial could be something like this...

    = link_to "Back", obj.respond_to?('character') ? character_path(obj.character) : characters_path

    This is saying...If the instance variable passed in responds to "character", use the character_path. If not, use the characters_path. So, if you have passed in @skill, it will respond to "character" (i.e. @skill.character). However, if you passed in @character, it will not respond to "character" (i.e. @character.respond_to?('character') will return false).

    This is using a ternary operator, which some people like and some people hate. I like them in simple situations, but they can get ugly real quick. A ternary tests the truthiness of a statement to the left of the "?". If it evaluates to true, it returns the value of the expression to the right of the "?". If false, it returns the value to the right of the colon.

    This code could easily be rewritten as...

    if obj.respond_to?('character')
    = link_to "Back", character_path(obj.character)
    = link_to "Back", characters_path

    If nothing else, respond_to? is a great method to get comfortable with. It takes you along the path of testing how objects act rather than what they are.

  2. I've seen a lot of people get around this by omitting back links and ensuring that links to the main site divisions are present on every page, usually through a common navigation partial.

    If you want greater flexibility, you might update a session variable as the user navigates around so that the back link always makes sense.

  3. @Baldwin this method looks pretty good. So am I right in assuming if / unless statements are also ternary operators? I'm using some of them already, and I'm with you in liking them for simple things. I think I'll try this out, mostly to get comfortable with respond_to? as you suggest.

  4. @Matt yeah, I think I may just end up axing it after messing around with the respond_to? method Baldwin mentioned. I already link to Items, Skills, Feats, etc, for each Character if that page has been created, so a Back button kind of seems redundant at this point. I may just replace it with a default Home that will return to the Characters index.

  5. @Tyler - AFAIK the ternary operator is the name of that specific operational syntax (a ? b : c). It is like If/Unless statements in that it is a mechanism for conditional execution of code though.

  6. Easiest solution: why do they need a back link? the browser handles this perfectly well enough.

  7. @Eli yeah, that's the solution I've arrived at, though (somehow) it had slipped my mind that browsers have back buttons.