Thursday, July 14, 2011

Sometimes you can be too DRY

In a Rails app, my favorite idiom for describing what users can and cannot do goes like this:

# In view code, for example...
  link_to "Edit Widget", edit_widget_path if current_user.can_edit?(widget)

  # which calls this method
  class User
    def can_edit?(resource)
      resource.editable_by?(self)
    end
  end

 # ... which in turn calls this method
 class Widget
   def editable_by?(user)
     # whatever logic makes sense in this particular app
   end
 end

I first saw this approach a couple of years ago, when I was new to Rails and found Nick Kallen's post about from 2007. John Nunemaker has since created the Canable gem to make this even easier.

Defining these methods yourself is not that hard. But because they are all so similar, you might be tempted to use metaprogramming to shorten the code and make it more DRY.

This is a rare example where sacrificing a little bit of DRY can make the code a lot more readable and maintainable: readable because your eyes don't have to bounce around as much, and maintainable because you can edit each method separately.

  class User
     # Metaprogramming way
    {
      :create => 'creatable', 
      :read => 'readable', 
      :edit => 'editable', 
      :delete => 'deletable', 
      :assign => 'assignable'
    }.each do |verb, adjective|
      define_method "can_#{verb}?".to_sym do |resource|
        resource.send("#{adjective}_by?", self)
      end
    end

    # Long-form way: less DRY but easier 
    # to understand at a glance
    def can_create?(resource)
      resource.creatable_by?(self)
    end

    def can_read?(resource)
      resource.readable_by?(self)
    end
  
    def can_edit?(resource)
      resource.editable_by?(self)
    end

    def can_delete?(resource)
      resource.deletable_by?(self)
    end

    def can_assign?(resource)
      resource.assignable_by?(self)
    end
  end