Created
November 10, 2010 21:48
-
-
Save jurisgalang/671589 to your computer and use it in GitHub Desktop.
Module to enable/simulated named parameters support in Ruby
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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