Skip to main content

Ruby: An Interesting Block Pattern

Ruby has blocks, which enable all sorts of interesting idioms. I'm going to show one that will be familiar to Rails enthusiasts, but was new to me.

I was reading some code in a book, and it had the following:
def if_found(obj)
if obj
render :text => "Not found.", :status => "404 Not Found"
Here's how you call it:
if_found(obj) do
# We have a valid obj. Render something with it.
The code in the block will only execute if the obj was found. If it wasn't found, the response will already have been taken care of.

I've been in the same situation in Python (using Pylons), and I coded something like:
def handle_not_found(obj):
if not obj:
return render_404_page()
return None
Here's how you call it:
response = handle_not_found(obj)
if response:
return response
# Otherwise, continue normally.
Pylons likes to return responses, whereas render in Ruby works as a side effect whose return value isn't important. However, that's not my point.

My point is that the Python code uses "if response:" whereas the Ruby code uses "if_found(obj) do". Python uses an explicit if statement, whereas Ruby hides the actual if statement in a block. Similarly, Rubyists tend to write "my_list.each do |i|..." (even though Ruby has a for statement), whereas Pythonistas use "for i in my_list".

Ok, now that I've totally made a mountain out of a molehill, please note that I'm not saying either is better than the other. I'm just saying it's interesting to note the difference.


jjinux said…
You know, actually, this reminds me of the maybe monad in Haskell. In Haskell, there's this thing called the maybe monad that basically says, "I might have something, or it might be null." When you operate within the context of the maybe monad, if you actually have something, it runs your code. If you don't have something, it just ignores your code. That way you never get an exception for having a NULL pointer when you weren't expecting one.
Anonymous said…
Here is a shorter Version:
def handle_not_found(obj):
return False if obj else render_404_page()
Ian Bicking said…
Actually I'd expect a Python system to use an exception (some form of HTTPNotfound), and to do so at the point of fetching. I.e., "obj = get_obj_from_db(id)" and that method might raise an exception. This is in part because returning None for not-found isn't that common in Python. OTOH, typically a fetching method would raise LookupError or something, which doesn't itself render as a response. Django actually builds that exception in directly, I think (at least for one of its fetching methods)

I've also seen decorators that do this, populating the function signature with the database object and returning 404 if it can't.

There is a funny sort of contract to the Rails one, which is that all your output goes in that block. If you don't put all your output in the block then it looks like you could get a weird response with a mixture of 404 and normal output. Exceptions are more atomic in this sense.
Bill Mill said…
Well I had two points, the first one was that it was a place where the maybe monad is very clean and awesome, and that I would leap before I looked in Python and handle an exception that occurred.

But you and Ian got there before I could. So. Yeah!
jjinux said…
Ian, I agree with all your points.
jjinux said…
> Actually I'd expect a Python system to use an exception (some form of HTTPNotfound), and to do so at the point of fetching

I've actually been in situations where I wanted the handle_ function to interrupt and take over processing. I was trying to use Paste's exception mechanism to raise an exception with an HTTP 200. That was weird.

> There is a funny sort of contract to the Rails one, which is that all your output goes in that block.

Yep, that's the point of my post.
Anonymous said…
even shorter...

obj and do_something()
This is a maybe monad :-)

If you prefer an OO Syntax, the andand gem lets you write foo.andand do...end as well as a few other forms. Ruby Facets contains a nearly identical implementation called ergo.

I use the and keyword for some of this stuff as well, but only when don't need to recompute an expensive operation. So I do not write:

Person.find(...) and Person.find(...).title

Also of interest:

Writing Self-Confident Code

Anonymous said…
i'm not really a fan of the exception stack approach to error handling in languages like python (even though I build a lot of software in it). if you get to the top of that stack there's not a whole lot your program can do. i prefer lisp in this way -- restarts are very handy.

continuations, another lispy feature, seem to be the inspiration of the 'with' protocol being introduced in py3k. that should really clean up this kind of scenario where one is constantly checking for NULL by hiding the exception stack in the objects themselves.

no more dealing with leaky object data in the application!
jjinux said…
> i prefer lisp in this way -- restarts are very handy.

Heh, I was talking about this with someone just yesterday; i.e. that Lisp provides a different way of handling errors using continuations that let you start right where you left off when something goes wrong.

Popular posts from this blog

Ubuntu 20.04 on a 2015 15" MacBook Pro

I decided to give Ubuntu 20.04 a try on my 2015 15" MacBook Pro. I didn't actually install it; I just live booted from a USB thumb drive which was enough to try out everything I wanted. In summary, it's not perfect, and issues with my camera would prevent me from switching, but given the right hardware, I think it's a really viable option. The first thing I wanted to try was what would happen if I plugged in a non-HiDPI screen given that my laptop has a HiDPI screen. Without sub-pixel scaling, whatever scale rate I picked for one screen would apply to the other. However, once I turned on sub-pixel scaling, I was able to pick different scale rates for the internal and external displays. That looked ok. I tried plugging in and unplugging multiple times, and it didn't crash. I doubt it'd work with my Thunderbolt display at work, but it worked fine for my HDMI displays at home. I even plugged it into my TV, and it stuck to the 100% scaling I picked for the othe

ERNOS: Erlang Networked Operating System

I've been reading Dreaming in Code lately, and I really like it. If you're not a dreamer, you may safely skip the rest of this post ;) In Chapter 10, "Engineers and Artists", Alan Kay, John Backus, and Jaron Lanier really got me thinking. I've also been thinking a lot about Minix 3 , Erlang , and the original Lisp machine . The ideas are beginning to synthesize into something cohesive--more than just the sum of their parts. Now, I'm sure that many of these ideas have already been envisioned within , LLVM , Microsoft's Singularity project, or in some other place that I haven't managed to discover or fully read, but I'm going to blog them anyway. Rather than wax philosophical, let me just dump out some ideas: Start with Minix 3. It's a new microkernel, and it's meant for real use, unlike the original Minix. "This new OS is extremely small, with the part that runs in kernel mode under 4000 lines of executable code.&quo

Haskell or Erlang?

I've coded in both Erlang and Haskell. Erlang is practical, efficient, and useful. It's got a wonderful niche in the distributed world, and it has some real success stories such as CouchDB and Haskell is elegant and beautiful. It's been successful in various programming language competitions. I have some experience in both, but I'm thinking it's time to really commit to learning one of them on a professional level. They both have good books out now, and it's probably time I read one of those books cover to cover. My question is which? Back in 2000, Perl had established a real niche for systems administration, CGI, and text processing. The syntax wasn't exactly beautiful (unless you're into that sort of thing), but it was popular and mature. Python hadn't really become popular, nor did it really have a strong niche (at least as far as I could see). I went with Python because of its elegance, but since then, I've coded both p