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:
I've been in the same situation in Python (using Pylons), and I coded something like:
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.
I was reading some code in a book, and it had the following:
def if_found(obj)Here's how you call it:
if obj
yield
else
render :text => "Not found.", :status => "404 Not Found"
false
end
end
if_found(obj) doThe 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.
# We have a valid obj. Render something with it.
end
I've been in the same situation in Python (using Pylons), and I coded something like:
def handle_not_found(obj):Here's how you call it:
if not obj:
return render_404_page()
return None
response = handle_not_found(obj)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.
if response:
return response
# Otherwise, continue normally.
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.
Comments
def handle_not_found(obj):
return False if obj else render_404_page()
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.
But you and Ian got there before I could. So. Yeah!
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.
obj and do_something()
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
JM2C.
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!
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.