Ruby Block Goodness
February 14th, 2007
I uncover something new in Ruby nearly every day. Here’s a neat trick I learned today:
Suppose you have a User model, and that model has several methods that determine a user’s rights. Maybe something like User#can_delete?(object) and User#can_create_pages?.
Now suppose that in your view, you want to display a certain link only if a user is logged in AND if that user has rights to do that thing. So the delete link would look like this:
<%= link_to("delete", page_url(@page), :method => :delete)
if User.current_user and User.current_user.can_delete?(@page) %>
Yuk. Wordy. If you don’t check for User.current_user first, ActionView will raise an error because you’ll be requesting the can_delete? method from a nil object. So every time you want to display the delete link, you have to remember to process multiple conditions.
There is a better way.
In my ApplicationHelper, I defined a method called user?.
def user?(*args, &block)
return false unless User.current_user
return true if args.first.blank?
return User.current_user.send(*args, &block)
end
alias_method :user?, :user
Now we can rewrite that link code.
<%= link_to("delete", page_url(@page), :method => :delete)
if user(:can_delete?, @page) %>
Nice, eh?
We can do things likeuser? #=> true | false
user(:can_create_pages?) #=> true | false
user(:can_edit?, @page) #=> true | false
The trick is in the *args variable. We pass the name of the method we’re calling as the first argument, and then any number of additional arguments, which will then be passed to the method specified in the first argument. Simple and clean.
[update] just fixed a couple of formatting issues.
February 15th, 2007 at 09:47 AM
that does look nice, this is one of those tasks that is hard to implement cleanly.
is the &block parameter for the user method used in this example? I’m not familiar with the use of parameters with the ampersand.
February 15th, 2007 at 10:49 AM
The &block parameter allows you to pass around any block given to the method call, so you can do more than just yield foo if block_given?
I believe the original is missing an asterisk, however: “def user?(*args, &block)” is what you want, if I’m correct
February 15th, 2007 at 10:58 AM
Ron: You are right – it was missing an asterisk. I forgot to enclose the method in a <pre> tag, so textile was editing out the asterisk. Thanks!
February 15th, 2007 at 11:00 AM
I haven’t tried it yet, but you could pass a block to this function. Something like
February 16th, 2007 at 11:43 AM
Sweet. Cool little tip!
February 17th, 2007 at 10:58 AM
I don’t know why I chose to name this post, which has nothing to do with blocks, “Ruby Block Goodness”. It just goes to show that I have no blog post naming skills.