Sonntag, 18. Januar 2015

Filter the named parameters!

Named parameters are great for reducing complexity of method parameters in Ruby. Filtering the named parameters for relevance, by limiting the keys to the expected few, makes sense in some cases. Especially, if logic is bound to the API keywords.
An example Team class API could be:
class Team
  def initialize name, roles={}
    @name = name
    @roles = roles
  end

  def roles
    @roles.keys
  end
end
Team.new('Scrum', product_owner: 'Alice', 
  scrum_master: 'Bob').roles
=> [:product_owner, :scrum_master]
looks plausible.
But:
Team.new('Scrum', product_owner: 'Alice', 
  sales: 'Sarah').roles
=> [:product_owner, :sales]
is definitely not a Scrum team.
Every time the API expects certain named parameters, those have to be filtered, like:
class Team
  def initialize name, roles={}
    @name = name
    @roles = [:product_owner, :scrum_master, :developer].
      each_with_object(Hash.new) { |role, hash| 
        hash[role] = roles[role] if roles.has_key?(role) 
      }
  end

  def roles
    @roles.keys
  end
end
Team.new('Scrum', product_owner: 'Alice', 
  sales: 'Sarah').roles
=> [:product_owner]
Please note, that :sales was filtered out, like any would have been except :product_owner, :scrum_master or :developer.
The Hash filtering logic is likely supposed to be repeated. And that is why extending the Hash is plausible.
Ruby on Rails already has in their ActiveSupport gem. The Hash#slice does exactly filter a Hash for keys. So the example in Ruby On Rails would rather look like:
class Team
  def initialize name, roles={}
    @name = name
    @roles = roles.slice :product_owner, :scrum_master, :developer
  end

  def roles
    @roles.keys
  end
end
Team.new('Scrum', product_owner: 'Alice', 
  sales: 'Sarah').roles
=> [:product_owner]
Furthermore Hash#slice! even replaces the hash itself with the given keys. But beware it returns a hash with the removed key/ value pairs.
Further articles of interest:

Supported by Ruby 2.1.1 and Ruby on Rails 4.1.8