Skip to content

Instantly share code, notes, and snippets.

@jurisgalang
Created November 10, 2010 21:48
Show Gist options
  • Save jurisgalang/671589 to your computer and use it in GitHub Desktop.
Save jurisgalang/671589 to your computer and use it in GitHub Desktop.
Module to enable/simulated named parameters support in Ruby
# NamedParameters
# ---------------
# This module enables/simulates named parameters support in Ruby
# see: http://en.wikipedia.org/wiki/named_parameter
#
# This is also available as a Ruby gem, the project is available at
# https://github.com/jurisgalang/named-parameters - and is more up-to-date
# than this gist.
#
# To install the gem:
#
# gem install named-parameters
#
# - then do:
#
# require 'named-parameters'
#
# Author: Juris Galang
# Copyright © 2007 - 2010 Juris Galang. All Rights Reserved.
#
module NamedParameters
def self.included base
base.extend ClassMethods
end
private
def self.validate name, params, spec
spec[:required] ||= []
spec[:optional] ||= []
spec = Hash[ spec.map{ |k, v|
v = [ v ] unless v.instance_of? Array
[ k, v ]
} ]
sorter = lambda{ |x, y| x.to_s <=> y.to_s }
allowed = (spec[:optional] + spec[:required]).sort &sorter
keys = params.keys.map{ |k| k.to_sym }
keys.sort! &sorter
spec[:required].sort! &sorter
list = lambda{ |params| params.join(", ") }
unless spec[:required].empty?
k = spec[:required] & keys
raise ArgumentError, \
"#{name} requires arguments for parameters: #{list[spec[:required] - k]}" \
unless k == spec[:required]
end
k = keys - allowed
raise ArgumentError, \
"Unrecognized parameter specified on call to #{name}: #{list[k]}" \
unless k.empty?
end
module ClassMethods
protected
def method_added name
if specs.include?(name) && !instrumenting?
@instrumenting = true
method = instance_method(name)
spec = specs.delete(name)
define_method name do |*args, &block|
params = args.find{ |arg| arg.instance_of? Hash }
name = "#{self.class.name}##{name}"
NamedParameters::validate name, params || {}, spec
method.bind(self).call(*args, &block)
end
@instrumenting = false
end
super
end
def has_named_parameters method, spec = { }
specs[method] = spec
end
private
def specs
@specs ||= { }
end
def instrumenting?
@instrumenting
end
def self.extended base
base.instance_variable_set :@instrumenting , false
end
end
end
# Extend Object to automatically make it available for all
# user defined classes
#
Object.extend NamedParameters::ClassMethods
# Or if that's not your bag, do:
#
# class FooBar
# include NamedParameters
# end
#
# Sample usage: the following declares class FooBar and specifies
# named parameter requirements for its #initialize and #method methods
#
class FooBar
has_named_parameters :initialize, :optional => [ :x, :y, :z ]
def initialize opts = { }
# --- blah ---
end
has_named_parameters :method, :required => :x, :optional => [ :y, :z ]
def method param, opts = { }
# --- blah ---
end
end
# The following invocations show that the named parameters are enforced
# when the FooBar class is instantiated...
obj = FooBar.new :q => 1 # ArgumentError, required parameter missing
obj = FooBar.new :x => 1 # ok
# The following shows that the named parameters are enforced when the methods
# from the FooBar instance is called...
obj.method 1, :x => 1, :q => 2 # ArgumentError! unrecognized parameter specified
obj.method 1, :x => 1, :y => 2 # ok
# Sexy!
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment