This gem simulates named-parameters in Ruby. It's a complement to the common
Ruby idiom of using Hash
args to emulate the use of named parameters.
Related: Named Parameters in Ruby
Get It
You know you want it:
gem install named-parameters
Use It
Make it available everywhere:
require 'named-parameters'
But if you want to be selective, do:
require 'named-parameters/module'
Then include the NamedParameters
module into your class:
class YourClass
include NamedParameters
end
Either way, you would now be able to use the has_named_parameters
clause
as needed:
class YourClass
has_named_parameters :your_method, :require => :param
def your_method
puts .inspect
end
end
So when you invoke your_method
, its parameter requirements will now be
enforced:
obj = YourClass.new
obj.your_method :param => 'Et tu, Babe?' # will spit out: 'Et tu, Babe?'
obj.your_method # will raise an ArgumentError because the required :param was not specified
Abuse It
Declare required parameters:
has_named_parameters :send_mail, :required => :to
has_named_parameters :send_mail, :required => [ :to, :subject ]
Declare optional parameters:
has_named_parameters :send_mail, :optional => :subject
has_named_parameters :send_mail, :optional => [ :subject, :bcc, :from ]
Declare one of a set of parameters as required (ie: require one and only one from a list):
has_named_parameters :send_mail, :oneof => [ :signature, :alias ]
Declare default values for optional parameters:
has_named_parameters :send_mail, :optional => [ :subject, :bcc, { :from => '[email protected]' } ]
has_named_parameters :send_mail, :optional => [ :subject, :bcc, [ :from, '[email protected]' ] ]
You can also declare default values for :required
and :oneof
parameters,
but really, that's just silly.
With has_named_parameters
, you can mix-and-match parameter requirements:
has_named_parameters :send_mail,
:required => [ :to, :subject, ],
:oneof => [ :signature, :alias ],
:optional => [ :subject, :bcc, [ :from, '[email protected]' ] ]
And is applicable to both class and instance methods:
require 'named-parameters'
class Mailer
has_named_parameters :send_mail,
:required => [ :to, :subject, ],
:oneof => [ :signature, :alias ],
:optional => [ :subject, :bcc, [ :from, '[email protected]' ] ]
def send_mail
# ... do send mail stuff here ...
end
has_named_parameters :'self.archive', :optional => [ :method => 'zip' ]
def self.archive = { }
# ... do mail archiving stuff here ...
end
end
Shortcuts
In addition to the has_named_parameters
method, NamedParameters
also comes
with two convenience methods for implicitly applying parameter specs for
constructors.
Use the requires
clause to declare what parameters a class expects when it
is instantiated:
class GoogleStorage
requires :'access-key', :'secret-key'
def initialize
# ... do googly stuff here ...
end
end
Use the recognizes
clause to specify the optional parameters for a class
when it is instantiated:
class GoogleStorage
recognizes :'group-email', :'apps-domain'
def initialize
# ... do googly stuff here ...
end
end
You may also specify default values for parameters when using these clauses:
class GoogleStorage
requires :'access-key', :'secret-key'
recognizes [ :'group-email', '[email protected]' ], [ :'apps-domain', 'example.org' ]
def initialize
# ... do googly stuff here ...
end
end
The requires
and recognizes
clause is equivalent to declaring the lines
in a class definition:
has_named_parameters :'self.new', :required => params, :strict
has_named_parameters :initialize, :required => params, :strict
has_named_parameters :'self.new', :optional => params, :strict
has_named_parameters :initialize, :optional => params, :strict
What Was Declared?
You can get a list of declared parameters via the declared_parameters
method:
class GoogleStorage
requires :'access-key', :'secret-key'
recognizes [ :'group-email', '[email protected]' ], [ :'apps-domain', 'example.org' ]
def initialize
# list the parameters declared
puts "#{declared_parameters.join(' ')}"
# ... now do the googly stuff ...
end
end
# create an instance of GoogleStorage
# and print: [ :access-key, :secret-key, :group-email, :apps-domain ]
GoogleStorage.new :'access-key' => '...', :'secret-key' => '...'
declared_parameters
is also available from the class methods of user defined
classes.
You can also pass a list of parameter types to limit the result to specific parameter types:
class GoogleStorage
requires :'access-key', :'secret-key'
recognizes [ :'group-email', '[email protected]' ], [ :'apps-domain', 'example.org' ]
def initialize
# list the parameters declared
puts "#{declared_parameters(:required).join(' ')}"
# ... now do the googly stuff ...
end
end
# create an instance of GoogleStorage
# and print: [ :access-key, :secret-key ]
GoogleStorage.new :'access-key' => '...', :'secret-key' => '...'
The method declared_parameters
is context specific. It returns the list of
parameters for the current method. To get a list of parameters for a specific
method, use declared_parameters_for
:
class GoogleStorage
requires :'access-key', :'secret-key'
recognizes [ :'group-email', '[email protected]' ], [ :'apps-domain', 'example.org' ]
def initialize
# list the parameters declared
puts "#{declared_parameters(:required).join(' ')}"
# ... now do the googly stuff ...
end
def self.required_parameters
declared_parameters_for :new, :required
end
def self.optional_parameters
declared_parameters_for :new, :optional
end
def self.all_parameters
declared_parameters_for :new
end
end
# list the required parameters for the class
GoogleStorage.required_parameters # => [ :access-key, :secret-key ]
# list the optional parameters for the class
GoogleStorage.required_parameters # => [ :group-email, :apps-domain ]
# list all of the recognized parameters for the class
GoogleStorage.all_parameters # => [ :access-key, :secret-key, :group-email, :apps-domain ]
Notice that both methods may receive a filter of parameter types.
Filtering Arguments
Sometimes you'll have a Hash
object that will have a bunch of keys that may
or may not comply to the parameter declaration of a method, but you may to use
the same object everywhere without having to figure out what keys are
applicable to a method call:
= { :x => 1, :y => :z, :a => 'somevalue' }
has_named_parameters :foo, :required => [ :x ]
def foo = { }
.inspect
end
has_named_parameters :bar, :required => [ :y ], :optional => [ :a ]
def = { }
.inspect
end
foo # => ArgumentError! :y and :a not recognized
# => ArgumentError! :x not recognized
Use the filter_parameters
method against the options object to make it
comply to the declaration:
foo filter_parameters() # => [ :x ]
filter_parameters() # => [ :a, :y ]
Permissive Mode
When a method is declared with has_named_parameters
that method will only
accept keys that were listed as :required
, :optional
, or :oneof
-
passing any other key to the Hash
arg will raise an ArgumentError
on
method call:
has_named_parameters :exec, :required => :w, :optional => [ :x, :y ]
def exec opts
# ...
end
# the following will raise an ArgumentError since :z was not declared
exec :w => 1, :x => 2, :y => 3, :z => 4
But sometimes you need to be able to pass additional keys and you don't know
what those keys are. Setting the optional mode
parameter for
has_named_parameters
to :permissive
will relax this restriction:
has_named_parameters :exec, { :required => :w, :optional => [ :x, :y ] }, :permissive
def exec opts
# ...
end
# the following will no longer raise an ArgumentError
exec :w => 1, :x => 2, :y => 3, :z => 4
The :required
and :oneof
parameters will still be expected:
# the following will still raise an ArgumentError since :w is required
exec :x => 2, :y => 3, :z => 4
For clarity you should skip the :optional
parameters list altogether when
using the :permissive
mode.
However, the requires
and recognizes
clauses does not accept the mode
argument.
If you need to make a constructor's optional parameter spec permissive, use
the has_named_parameters
clause instead:
has_named_parameters :'self.new', :required => params, :permissive
has_named_parameters :initialize, :required => params, :permissive
For brevity, since the mode is :permissive
, the :optional
parameters list
is skipped.
How It Works
When the has_named_parameters
is declared in a class, it instruments the
class so that when the method in the declaration is invoked, a validation is
performed on the last Hash
argument that was received by the method.
It expects that the last argument is the the Hash
args representing the
named parameters when a method is invoked. If no Hash
args was supplied
then it creates one.
So you can mix-and-match argument types in a method, and still declare that
it has_named_parameters
:
has_named_parameters :request,
:required => :key,
:optional => [ :etc, 'howl' ]
def request path,
"path: #{path}, options: #{.inspect}"
end
# invocation:
request "/xxx", :key => '0925'
# result:
# => path: /xxx, options: {:key => '0925', :etc => 'howl'}
Gotchas
It has no idea if the last argument really is the last argument. So be careful when you have something similar to the following:
has_named_parameters :request, :optional => :key
def request path = "/xxx", = { }
"path: #{path}, options: #{.inspect}"
end
# invocation:
request :key => '0925'
# expecting:
# => path: /xxx, options: {:key => '0925'}
# but actual result is:
# => path: {:accesskey => '0925'}, options: {}
For the above case, it might be better to refactor:
has_named_parameters :request, :optional => [ :key, [ :path, "/xxx" ] ]
def request = { }
"path: #{.delete :path}, options: #{.inspect}"
end
# invocation:
request :key => '0925'
# result:
# => path: /xxx, options: {:key => '0925'}
# invocation:
request
# result:
# => path: /xxx, options: {}
Class vs Instance Methods
Parameter spec declarations are not shared between class and instance methods even if they share the same name.
For example, the following has_named_parameters
declaration below is only
applicable to the instance method exec
:
class Command
has_named_parameters :exec, :required => :x
def self.exec opts
# ...
end
def exec opts
# ...
end
end
# the following will *not* raise an ArgumentError because
# the has_named_parameter declaration applies only to the
# instance method exec...
Command.exec
# the following will raise an ArgumentError (as expected)
command = Command.new
command.exec
Prefix the method name with self.
to apply parameter spec for class methods:
class Command
has_named_parameters :'self.exec', :required => :x
def self.exec opts
# ...
end
end
# the following will now raise an ArgumentError (as expected)
Command.exec
In general, however, when a class has an instance and a class method using the same name, for most cases, one simply delegates to another and will share the same requirements. So the examples cited above can be refactored:
class Command
has_named_parameters :'self.exec', :required => :x
def self.exec opts
# ...
end
def exec opts
Command.exec
end
end
# the following will raise an ArgumentError (as expected)
Command.exec
# the following will also raise an ArgumentError as it delegates to the
# class method and violates the parameter requirements
command = Command.new
command.exec
Dependencies
Development:
yard >= 0
rspec >= 1.2.9
Download
You can download this project in either zip or tar formats.
You can also clone the project with Git by running:
git clone git://github.com/jurisgalang/named-parameters
Note on Patches/Pull Requests
- Fork the project.
- Make your feature addition or bug fix.
- Add tests for it. This is important so I don't break it in a future version unintentionally.
- Commit, do not mess with rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
- Send me a pull request. Bonus points for topic branches.
Releases
Please read the RELEASENOTES
file for the details of each release.
In general: patch releases will not include breaking changes while releases that bumps the major or minor components of the version string may or may not include breaking changes.
Author
License
Dual licensed under the MIT or GPL Version 2 licenses.
See the MIT-LICENSE and GPL-LICENSE files for details.
Copyright (c) 2010 Juris Galang. All Rights Reserved.