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

Drawing Sierpinski's Triangle in Minecraft Using Python

In his keynote at PyCon, Eben Upton, the Executive Director of the Rasberry Pi Foundation, mentioned that not only has Minecraft been ported to the Rasberry Pi, but you can even control it with Python . Since four of my kids are avid Minecraft fans, I figured this might be a good time to teach them to program using Python. So I started yesterday with the goal of programming something cool for Minecraft and then showing it off at the San Francisco Python Meetup in the evening. The first problem that I faced was that I didn't have a Rasberry Pi. You can't hack Minecraft by just installing the Minecraft client. Speaking of which, I didn't have the Minecraft client installed either ;) My kids always play it on their Nexus 7s. I found an open source Minecraft server called Bukkit that "provides the means to extend the popular Minecraft multiplayer server." Then I found a plugin called RaspberryJuice that implements a subset of the Minecraft Pi modding API for B

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

Creating Windows 10 Boot Media for a Lenovo Thinkpad T410 Using Only a Mac and a Linux Machine

TL;DR: Giovanni and I struggled trying to get Windows 10 installed on the Lenovo Thinkpad T410. We struggled a lot trying to create the installation media because we only had a Mac and a Linux machine to work with. Everytime we tried to boot the USB thumb drive, it just showed us a blinking cursor. At the end, we finally realized that Windows 10 wasn't supported on this laptop :-/ I've heard that it took Thomas Edison 100 tries to figure out the right material to use as a lightbulb filament. Well, I'm no Thomas Edison, but I thought it might be noteworthy to document our attempts at getting it to boot off a USB thumb drive: Download the ISO. Attempt 1: Use Etcher. Etcher says it doesn't work for Windows. Attempt 2: Use Boot Camp Assistant. It doesn't have that feature anymore. Attempt 3: Use Disk Utility on a Mac. Erase a USB thumb drive: Format: ExFAT Scheme: GUID Partition Map Mount the ISO. Copy everything from