Sometimes you can be too DRY

coding
Posted on: 2011-07-14

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