Rails 404 pages

I have no idea why it has taken so long for me to figure out how to get 404 pages to be rendered in Rails. The problem I think was mostly due to my lack of know-how of how routes.rb works. What I wanted was very simple:

  • If a controller isn’t found then display a 404 page
  • If an action isn’t found then display a 404 page

I didn’t want to use just a static 404.html page either. So, here is the quick and simple solution:

  • Catch invalid controllers in routes.rb: at the end of routes.rb add the mapping line:
    map.error '*url', 
        :controller => 'errors', 
        :action => 'error_404'

    This uses uses the ErrorsController with its error_404 action which of course uses an error_404.haml template.

  • Catch invalid actions in application.rb: in application.rb add:
      def method_missing(*args)
        render :template => 'errors/error_404'
      end

    This makes missing controller methods (a.k.a. actions) render the errors_404 template too.

One event handler to rule them all

In my quest to simplify my JavaScript programming life I’ve added a new weapon to my arsenal: the Multiple Events Handler. (I have no idea what else to call it, so it’s MEH for now.)

The problem I’ve been trying to solve for a while was how to bind click events on items in a list or in a table without having to bind events to each item. For example, let’s say we had an ice cream flavor picker:

Pick Yer Flavor, Matey!

In this Web 1.0 way we have javascript:something for each item:

  <li><a href="javascript:alert('Arr... Rrrrrrasberry!');">Rrrrrrasberry!</a></li>

Not very nice. I’ve taken to adding data-* attributes to describe the data, so if we had jQuery we could at least bind to all the anchor tags and use the data-flavor attribute to figure out which one was really clicked:

Pick Yer Flavor, Matey!

  <ul class="mehexp-2">
    <li><a data-flavor="Vanilla">Vanilla!</a></li>
    <li><a data-flavor="Chocolate">Chocolate!</a></li>
    <li><a data-flavor="Rrrrrrasberry"> Rrrrrrasberry!</a></li>
  </ul>
  <script type="text/javascript">
    (function ($) {
      $(".mehexp-2 a").click(function (event) {
        var flavor = $(this).attr("data-flavor");
        alert('Arr... ' + flavor + '!');
      });
    })(jQuery);
  </script>

That’s better, but what if the list grows dynamically? Well, sure, there’s the new jQuery live() event handler, but my latest pattern seems to work just as well. Instead of binding the click on each element, just bind the click on the container. In this case, the container is the UL tag. And for each element you assign its action using data-action.

Pick Yer Flavor, Matey!


<h3>Pick Yer Flavor, Matey!</h3>
<ul class="mehexp-3" style="color: #00a; text-decoration: underline;">
  <li><a data-flavor="Vanilla" data-action="flavor-select">Vanilla!</a> <span data-action="flavor-delete">X</span></li>
  <li><a data-flavor="Chocolate" data-action="flavor-select">Chocolate!</a> <span data-action="flavor-delete">X</span></li>
  <li><a data-flavor="Rrrrrrasberry" data-action="flavor-select">Rrrrrrasberry!</a> <span data-action="flavor-delete">X</span></li>
</ul>
<input type="button" class="mehexp-3" data-action="flavor-add" value="Add anotherrrr" />
<script type="text/javascript">
  (function ($) {
    $(".mehexp-3").click(function (event) {
      // Do we have a data-action?
      var action = $(event.target).attr("data-action");
      switch (action) {
        case "flavor-add":
          var flavor = $.trim(window.prompt(
              "So ya want ta add a flavorrr? Enter it here!"
              ));
          if (flavor !== "") {
            // Add it. Sorry it's so verbose. Stupid
            // WordPress keeps editing the text.
            var aa = document.createElement("A");
            $(aa).attr("data-action", "flavor-select")
                .attr("data-flavor", flavor)
                .text(flavor + "!");
            var li = document.createElement("LI");
            $(li).append(aa);
            $("ul.mehexp-3").append(li);
          }
          break;
      case "flavor-delete":
            // Bubble up until we hit the LI.
            var t = event.target;
            while (t.tagName != "LI") { t = t.parentNode; }
            if (t) {
              $(t).empty();
              // For SOME reason I can't remove(). Oh well
            }
            break;
        case "flavor-select":
          var flavor = $(event.target).attr("data-flavor");
          alert("Arr... " + flavor + "!");
          break;
        default:
          alert("Avast! I know not what ye wanted! (Action: " + action + ")");
          break;
      }
    });
  })(jQuery);
</script>

The interesting thing about this is that you can put actions at any stage. And since events bubble up, if you have an image inside an anchor tag, you could modify the above code to walk up the DOM’s parent element chain until it hits the nearest data-action.

So, MEH isn’t so “meh” after all…

There’s always a better way

News flash: I’m not perfect. Chances are you aren’t either. When it comes to writing programs there’s (almost) always a better way to write code. And by this I mean the solutions for solving problems evolve. Sometimes it’s a new bit of information, sometimes a new tool, more often it’s a bug.

Despite the technical nature of laying down code I tend to think of it more as an art form, of sculpting paths of logic and structures of data. It’s the decomposing of a large, complex, multifaceted task into bite-sized components that can be written in reasonable time and perform without fail. Everything from what is the class hierarchy to what CSS class names you ought to give to DOM elements. And when you stand back and look at what has been created there is almost always room for improvement.

I love DRY (Don’t Repeat Yourself) and that’s pretty much been my motto ever since I started learning to do this. I abhor cut-and-paste solutions—I’d rather make a reusable code chunk. I love using flexible parameter lists that allow future compatibility. I love neatly-indented code that reads cleanly. I love enforcement of naming conventions and directory stuctures—things are always predictable. I am strict about writing comments and notes. I love refactoring.

But the Business side of the company often doesn’t like that word “refactor”. It means more work. The question could be: “why didn’t you write it correctly the first time?” Or perhaps: “we’re already tight on resources, why do you have to waste time rewriting something that will inevitably change?” I ask myself the same questions every time I approach a situation where I see the opportunity to refactor or clean something up. In the end if my gut feeling says “yes, refactor” (and I can make the time) I usually do it.

Ever since quitting the big companies and joining startups in 2006 there has been this constant pressure to produce, produce, produce. There’s never enough time and we’re always short on every resource imaginable—so why bother to refactor? It’s more important to get to market! Every microsecond that we don’t get this version of our product out there means potential lost customers or revenue!

While it’s true that sometimes you do have to make sacrifices to rush to the deadline, what I’ve learned in the world of startups is that there is always a deadline. And more often than not they end up being soft deadlines. There is always something that goes wrong and there’s always some business circumstance that pops up at the last moment. So the deadline just keeps slipping out. Eventually someone gets fed up enough and says “OK, we’re going to start cutting features in this release so we get stability!”, and a line is drawn in the sand. It’s like running the 100-yard dash but the finish line keeps moving until everyone realizes that the runners are completely out of breath. Worse yet, after crossing the finish line the announcer says “OK, we’re starting the next round now, so keep sprinting!” This is just unsustainable. You never get the time you need to revise your work. Ever.

So, I’ve taken to a new line of thinking: when you see a problem or an opportunity to refactor AND it’s something that is going to be really important later then STOP and refactor now. Since there’s never going to be enough time you just have to make the time. The PMs and business side hate that. It means more features slip. It means deadlines get missed sometimes. But what I’ve found, especially with my current project (where I’m the primary/only coder), is that the code gets better and better. Sure, it started out a little raw because you weren’t necessarily aware of all the places to modularize, repackage, consolidate, rename. But over time these pain points become obvious. Plus, there’s always improvements in performance, improvements in how you pass objects around, better ways to lay out the user interface. Oh and all the while you are learning new things and new open-source solutions are always being unveiled on the Internet.

Bottom line: it’s better to harden the code at every opportunity.

User interface lessons from a coffee grinder

I love coffee and I drink a lot of it. Since working more from home I’ve been going through 3, 4 cups a day. And I used to have a coffee mill that would atomize my coffee beans into a fine dust. My boyfriend (the awesome guy that he is) got me a coffee grinder so I could enjoy better coffee. Honestly, I haven’t used it that much because I’ve still be working through my stash of ground coffee from Sweden. But today I finally finished the stash and I was all ready to enjoy hot, freshly-ground coffee when … nothing. I pressed the power button on the top front and nothing happened.

coffee_fail_1.jpg

A litany of questions rolled through my mind.

Did it have power? It was plugged in (and the outlet doesn’t have a GFCI switch). Maybe try another outlet? Click… nothing.

Are there beans? There were beans in the loader bin. The beans weren’t stuck in the bottom of the loader bin either—I emptied all the beans into a bowl so there was nothing in there. Click… nothing.

Maybe something wasn’t closed? The coffee grounds hopper was fully flush, the lid on the loader bin was on. Click… nothing.

I opened the manual and even it says as part of its troubleshooting guide if the appliance isn’t working to check that the loader bin lid is “correctly positioned”. I mean it was down all the way, right? I took the lid off and put it back on. Nothing!

And then … I pressed down on the lid hard and pressed the start button. IT WORKED! I was aghast! Why does it work now? I mean the lid was down all the way, no? Actually, NO, it was NOT. I pressed down on the lid a second time and with the grinder off I could hear it: a tiny “click”. So I went to go try to find the trigger button.

Do you see it?

coffee_fail_2.jpg

… how about now?

coffee_fail_3.jpg

Still don’t see it? It’s right there! OK, one last time:

coffee_fail_4.jpg

Yes, that’s it. The tiny pin. A pin on the right side in the tiny gap between the loader bin hopper and the machine case that the hopper lid slides into. It is so tiny and so nonobvious where this switch is that if the lid is ever slightly tilted left or not pushed to completely this pin may not be depressed.

coffee_fail_4b.jpg

So what does this have to do with user interfaces?

I’m a big advocate of visual feedback when it comes to human-machine interaction. I believe that the state of a machine should be completely obvious to the user. When the user does something to the machine/website there should be some immediate indication that the action succeeded or it failed. And if it failed the user should be given a hint as to what failed. Either an indicator light, error text, or some part of the machine should look “wrong” so that the user can see something isn’t fitting properly.

Applying this to the coffee machine:

  • Does the device have power? Have a light indicating so. Or a soft glow under the start button would have been nice. At least that would have tipped me off the device had electricity.
  • Are all bins and lids fully secure in place? Pushing the start button might have flashed the start button to indicate there was an error condition.
  • Where is the error? It would have been nice if the light blinked differently for different errors.

Life with a Macbook Air, a week and a half later

Honest. I ordered the MBA for travel purposes. Really, I did.

1.5 weeks later …

I have almost not turned on the Pro. Sure it was on for a while so I could transfer files off it, but other than that it has been patiently sitting idle on my bedroom floor, sleep light gently pulsing. What I’ve found is that despite the Air’s limitations it is “good enough” for almost everything I do, the Pro is just waiting…

img_6460_2.jpg

I was fearing that lugging my 5.5 lb. Macbook Pro would be impractical for the upcoming vacation, and I really wanted a computer with a solid state drive in it. This Air is the 128 GB SSD version and the slightly faster 1.86 Core 2 Duo. All Airs have the relatively tiny 13″ screen and the 1 USB port. No DVD drive, no audio input—not even a place to put a Kensington lock. I felt that this was a travel computer and that’s it. Not a work computer since its performance still pales in comparison to my 2-year-old Macbook Pro. But I wanted to try the experiment of using it as my main computer.

I type in TextMate most of the time, browse the web with Firefox and Safari 4, run Parallels occasionally to check my web pages in MSIE, run a few Rails’ Mongrel instances, and do some light photo editing. I don’t compile Java programs (at the moment), I don’t run Oracle+Tomcat+[insert big Java memory hog library here], and I don’t work on large-scale photos. I am highly mobile, working from cafe to cafe, office to couch—I have a lot of other things in my backpack and having a laptop that’s 2.5 lbs. lighter really does make a difference. I don’t listen to audio through the Air’s lopsided mono speaker and I don’t have a flock of USB devices needing to connect to the computer directly. And I hardly ever need to use a CD/DVD. I always sleep my computer, never shutting it down fully.

The Air is almost always freakishly quiet since its fan isn’t running. It is heavy enough that it has a nice weight when I’m typing on my lap. It is light enough that it doesn’t wear me out. I really only have 1 thing connected to my USB all of the time: my iPhone recharging. I listen to audio through my iPhone earbuds because it’s convenient and the sound is decent. I sometimes miss the separate Enter key, but I only used it in Photoshop. The absent lock port is definitely something I miss since I’m paranoid about leaving it in cafes. The front edge cuts into my forearms. Thanks to disabling hibernate the Air is functional under 2 seconds when the lid is opened. The SSD is just large enough that I can mirror my iPhone’s music library, download a couple of movies, and still have a whopping amount of space for incoming photos from my DSLR. The screen size is large enough that I don’t feel too cramped, even when using Photoshop and having most of the palettes up. I love that it’s as thin as my padfolio—I can put both the Air and the padfolio in my old neoprene sleeve I used for my Pro.

All in all it turns out that this is wholly adequate for everything I do. It is a little bit of an adaptation to not have all of my old applications available nor my entire iTunes library. But I think I’ll survive.

UPDATE 2009.03.08: I forgot to add: the absence of a Kensington-style lock port is really a bummer. As a result you really can’t leave the laptop alone. Not that there would be any room for a lock port on the device but still. Someone is selling a lock bracket, though. It seems to me that a simple slide-out tab would be the ideal way to expose a way to secure the Air.