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 like
user? #=> 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.


6 Responses to “Ruby Block Goodness”

  1. dan c. Says:

    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.

  2. Ron Says:

    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

  3. Ryan Says:

    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!

  4. Ryan Says:

    I haven’t tried it yet, but you could pass a block to this function. Something like

    
    user(:can_delete?, @page) { |u| do.something.nifty.here }
    
  5. Robert Says:

    Sweet. Cool little tip!

  6. Ryan Says:

    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.

Leave a Reply