Rails has this thing called the “flash”. It’s like a special subset of the session hash. It’s a key/value store with an enforced short lifespan. Stuff you put in the hash lasts for exactly one render or redirect, and then goes away. It’s handy for stuff like notifications. You can set it in the controller:

# ...
flash[:notice] = "Your order has been submitted."
# ...

…and then you can reference it in a view:

<%= flash[:notice] %>

You can then be confident that after the view is shown, the message will go away and not be seen again.

Recently on the Ruby Rogues we talked to Michel Martens about keeping libraries small and simple. It was a good show and he had a lot of good things to say. You should give it a listen.

One of the assertions he made on the show is that the Rails flash is an example of pointless bloat. It adds hundreds of lines of code, but you can accomplish the exact same thing with just some basic Ruby knowledge.

That is, in the controller, you could just use the regular session store:

# ...
session[:notice] = "Your order has been submitted."
# ...

And then in the view, you could use Hash#delete() (or the equivalent on whatever Hash-like object Rails uses for the session) to render and delete the entry all at once.

<%= session.delete(:notice) %>

It’s as simple as that to render the flash feature completely superfluous.

…or is it?

Imagine we move the rendering code above into a view partial named notice . And then we use it in a view to render notices both at the top and the bottom of the page.

<%= render "notice" %>
<!-- ...lots of page content goes here... -->
<%= render "notice" %>

There’s now a bug here. And we might not even notice it for a while. But sooner or later someone is going to realize that notices are only displaying at the top of the page, and not the bottom.

Bugs like these can be tricky to diagnose, if we don’t have recent familiarity with the code. That’s because we tend to think of views as being referentially transparent, like a function: you pass data in, and you get HTML out. The same HTML for the same data. We don’t expect a view to modify the data going in.

This isn’t the only potential gotcha, either. Rails carefully manages the lifetime of flash messages, expiring them whether they are used or not. But this flash-free technique means that so long as the view that uses it isn’t rendered (for whatever reason), the notice may linger on. Until finally it is rendered, in a context where it no longer applies.

I can definitely think of scenarios in which this sequence of events might occur.

So using this approach, we can easily end up with a site which sometimes fails to render notices, and sometimes renders notices belatedly. This sort of thing tends to be perceived as “flaky” and low-quality by users.

Don’t get me wrong: I love small, sharp tools. I almost always choose Sinatra over Rails for my own projects.

But this is something that has happened to me over and over again in my programming career: I’ll look at a “fat” tool, and think “this is stupid. It’s obvious we don’t need all that code. I can just use the language better and avoid all that waste.”

So I’ll plow ahead, and then run into an edge case that I hadn’t thought of. And then another, and then another. And before I know it, I’ve recreated the original “fat” code, only badly.

The point here isn’t that Michel is wrong. If we are careful, we can get along just fine without the flash. And for many applications, we may never need it.

But Rails isn’t wrong either. The flash has a non-triviai implementation because it handles some non-obvious cases in a way that ensures the programmer never has to worry about them.

More often than not, code exists for a reason. This is especially true of code in libraries and frameworks that receive a lot of code review and discussion, as is the case for Rails. We can choose to make use of that code, or we can choose to go with smaller, sharper tools. But whichever way we go, it usually pays to take a little time and understand why the “fat” code exists.

Published by Avdi Grimm

5 Comments

  1. One thing that might help with understanding of “fat” tools might be “fat” documentation. When there are “edge cases”, ideally they are documented through well-commented test code that we can look up when we want to use an API. For example, I love the growing culture around “doctest” as tests embedded in comments in code for auto-generation of both tests and API documentation (I’m using doctests in all new code I write in Python, Scala, Haskell, and Rust). Even doctest is not enough, of course: they are most suitable for very simple unit tests. In any case, I would like to see more documentation that is basically a readable extraction of actual tests. That way, I can get an idea of what edge cases have been considered and implemented.

    Reply
  2. Thank you for this post.

    I really wanted to hear your objection to the sweeping claims about the flash as pointless bloat. I felt that you got cut short on the podcast and it is nice that you took the time to explain your point of view on this matter.

    Reply
  3. I think you crafted a situation where a bug could occur, and the reasoning is sound. The idea is to try to find a flaw in the proposed approach of using the session directly, and thus justify the existence of flash. But I think that situation of running into that bug is too artificial, because if it indeed is a bug it means that it never worked. If you test the changes you do (either with a test suite or just by trying it out), you will spot the defect instantly. If you want to render a notification both at the top and the bottom of the page, as artificial as it may be, at least you want to check if what you are doing works. As soon as you check, you will verify that it doesn’t work, and spotting the error should be trivial.

    But aside from the fact that the situation may be a bit too artificial, there’s the issue that the fat tool wins by default. I don’t have the time and energy now to construct an scenario where flash may trick you in a similar way, and if I did, I think it could lead into an endless discussion. But I can say I’m not the only one not using flash, and we are not a bunch of masochists, so maybe we can assume both solutions are as good and as convenient from the outside. If we can agree on that, that I’m not a masochist and that I’m honest in what I propose, then the bottom line could be this: is all the code in flash really necessary? I think it could be improved, but it’s an open question and people that use that tool can try to answer it.

    Reply
  4. “Rails carefully manages the lifetime of flash messages, expiring them whether they are used or not”

    Are you sure? If I set flash[:notice] in one action, then execute lots of actions that don’t touch the flash, then render flash[:notice], it returns the value. It doesn’t seem to expire the flash until it is used.

    See https://gist.github.com/anthonyjsmith/53fccd30ee0873b06c8b

    Reply
  5. Thank you for this. I’m a fan of fat tools as well, especially fat tools with a simple interface. “Iceberg” tools, I’ve heard them called, as most of the tool is “under the water.”

    In some sense, sure, more lines of code means more complexity. But in another sense, the flash is a great example of “small, simple, reasonably intuitive interface.”

    I’ll admit some bias when wrangling about 100 lines of code as well. I cut my teeth on C and other old-style systems programming languages, so 100 lines of code will always seem like a pretty minor cost to me 😉

    Rails is full of not-very-iceberg tools — tools that add a lot of complexity, sometimes non-local complexity, to other code, and that have an extensive interface. Those are real criticisms and Rails certainly has plenty to criticize on those counts. But I think Rails is often at its strongest when using code (sometimes a fair bit of it) to produce a simple, uniform interface even when there’s a just-fine interface already available and they’re not improving things much. You could also have a single object that was used to pass variables from controller to view, for instance, yet Rails chooses to waste a few lines passing all your instance variables through. To me, these decisions are often brilliant, even when wasteful in code.

    “You can reason it out, and it’s slightly awkward, but the implementation is simple” is fine for experts who expect to dig into the implementation. It’s death to novices. There’s a reason Rails absolutely caught fire with people who didn’t already know Ruby and who couldn’t build this stuff for themselves.

    In any case, if you’re going to criticize Rails for added useless complexity, do not justify with the size of the implementation. Justify with the size or complexity of the interface or the number of constructs that cause non-local headaches, or…

    And again, Avdi, thanks for this post!

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *