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](http://www.jurisgalang.com/2010/11/09/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 options puts options.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 => 'yourself@example.org' } ] has_named_parameters :send_mail, :optional => [ :subject, :bcc, [ :from, 'yourself@example.org' ] ] 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, 'yourself@example.org' ] ] 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, 'yourself@example.org' ] ] def send_mail options # ... do send mail stuff here ... end has_named_parameters :'self.archive', :optional => [ :method => 'zip' ] def self.archive options = { } # ... 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 options # ... 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 options # ... 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', 'group@example.org' ], [ :'apps-domain', 'example.org' ] def initialize options # ... 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 for a method by invoking `recognized_parameters`: class GoogleStorage requires :'access-key', :'secret-key' recognizes [ :'group-email', 'group@example.org' ], [ :'apps-domain', 'example.org' ] def initialize options # list the parameters declared puts "#{recognized_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' => '...' `recognized_parameters` is also accessible from singleton methods. 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, options "path: #{path}, options: #{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", options = { } "path: #{path}, options: #{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 options = { } "path: #{options.delete :path}, options: #{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](http://github.com/jurisgalang/named-parameters/zipball/master) or [tar](http://github.com/jurisgalang/named-parameters/tarball/master") formats. You can also clone the project with [Git](http://git-scm.com) 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 ------ [Juris Galang](http://github.com/jurisgalang/) 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.