named-parameters 0.0.6 → 0.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.md CHANGED
@@ -33,7 +33,7 @@ Then include the `NamedParameters` module into your class:
33
33
  include NamedParameters
34
34
  end
35
35
 
36
- Either way, you would now be able to use the `has_named_parameters` clause
36
+ Either way, you would now be able to use the **`has_named_parameters`** clause
37
37
  as needed:
38
38
 
39
39
  class YourClass
@@ -101,44 +101,151 @@ And is applicable to both class and instance methods:
101
101
  end
102
102
  end
103
103
 
104
- Gotchas
105
- -------
106
- When `has_named_parameters` is declared in a class, it instruments the class
107
- so that when the method in the declaration is invoked, a validation is
108
- performed on the first `Hash` argument that was received by the method.
104
+ Shortcuts
105
+ ---------
106
+ In addition to the `has_named_parameters` method, `NamedParameters` also comes
107
+ with two convenience methods for applying a parameter spec for constructors:
108
+
109
+ Use the **`requires`** clause to declare what parameters a class expects when it
110
+ is instantiated:
111
+
112
+ class GoogleStorage
113
+ requires [ :'access-key', :'secret-key' ]
114
+
115
+ def initialize options
116
+ # ... do googly stuff here ...
117
+ end
118
+ end
119
+
120
+ Use the **`recognizes`** clause to specify optional parameters for constructors:
121
+
122
+ class GoogleStorage
123
+ recognizes [ :'group-email', :'apps-domain' ]
124
+
125
+ def initialize options
126
+ # ... do googly stuff here ...
127
+ end
128
+ end
129
+
130
+ You may also specify default values for parameters when using these clauses:
131
+
132
+ class GoogleStorage
133
+ requires [ :'access-key', :'secret-key' ]
134
+ recognizes [ [ :'group-email', 'group@example.org' ], [ :'apps-domain', 'example.org' ] ]
135
+
136
+ def initialize options
137
+ # ... do googly stuff here ...
138
+ end
139
+ end
140
+
141
+ Permissive Mode
142
+ ---------------
143
+ When a method is declared with `has_named_parameters` that method will only
144
+ accept keys that were listed as `:required`, `:optional`, or `:oneof` -
145
+ passing any other key to the `Hash` arg will raise an `ArgumentError` on
146
+ method call:
147
+
148
+ has_named_parameters :exec, :required => :w, :optional => [ :x, :y ]
149
+ def exec opts
150
+ # ...
151
+ end
152
+
153
+ # the following will raise an ArgumentError since :z was not declared
154
+ exec :w => 1, :x => 2, :y => 3, :z => 4
155
+
156
+ But sometimes you need to be able to pass additional keys and you don't know
157
+ what those keys are. Setting the optional `mode` parameter for
158
+ `has_named_parameters` to `:permissive` will relax this restriction:
159
+
160
+ has_named_parameters :exec, { :required => :w, :optional => [ :x, :y ] }, :permissive
161
+ def exec opts
162
+ # ...
163
+ end
109
164
 
110
- It really has no idea about the position of the argument that is supposed
111
- to carry the named parameters.
165
+ # the following will no longer raise an ArgumentError
166
+ exec :w => 1, :x => 2, :y => 3, :z => 4
167
+
168
+ The `:required` and `:oneof` parameters will still be expected:
169
+
170
+ # the following will still raise an ArgumentError since :w is required
171
+ exec :x => 2, :y => 3, :z => 4
172
+
173
+ For clarity you should skip the `:optional` parameters list altogether when
174
+ using the `:permissive` mode.
175
+
176
+ The `requires` and `recognizes` clause for constructors will also accept a
177
+ `mode` setting:
178
+
179
+ requires [ :x ], :permissive
180
+ recognizes [ :y, :z ], :permissive
181
+
182
+ And just like the `:optional` parameter list in the `has_named_parameters`
183
+ clause, when `:permissive` mode is used, it's clearer to omit the `recognizes`
184
+ clause altogether.
185
+
186
+ How It Works
187
+ ------------
188
+ When the `has_named_parameters` is declared in a class, it instruments the
189
+ class so that when the method in the declaration is invoked, a validation is
190
+ performed on the last `Hash` argument that was received by the method.
191
+
192
+ It expects that the last argument is the the `Hash` args representing the
193
+ named parameters when a method is invoked. If no `Hash` args was supplied
194
+ then it creates one.
112
195
 
113
196
  So you can mix-and-match argument types in a method, and still declare that
114
197
  it `has_named_parameters`:
115
198
 
116
- has_named_parameters :request, :required => :accesskey
199
+ has_named_parameters :request,
200
+ :required => :key,
201
+ :optional => [ :etc, 'howl' ]
117
202
  def request path, options
118
203
  "path: #{path}, options: #{options.inspect}"
119
204
  end
120
205
 
121
206
  # invocation:
122
- request "/xxx", :accesskey => '0925'
207
+ request "/xxx", :key => '0925'
123
208
 
124
209
  # result:
125
- # => path: /xxx, options: {:accesskey => '0925'}
126
-
127
- But be careful when you have something like the following:
210
+ # => path: /xxx, options: {:key => '0925', :etc => 'howl'}
211
+
212
+ Gotchas
213
+ -------
214
+ It has no idea if the last argument really is the last argument. So be careful
215
+ when you have something similar to the following:
128
216
 
129
- has_named_parameters :request, :required => :accesskey
130
- def request path, options = { }
217
+ has_named_parameters :request, :optional => :key
218
+ def request path = "/xxx", options = { }
131
219
  "path: #{path}, options: #{options.inspect}"
132
220
  end
133
221
 
134
222
  # invocation:
135
- request :accesskey => '0925'
223
+ request :key => '0925'
136
224
 
137
- # result isn't what's expected:
225
+ # expecting:
226
+ # => path: /xxx, options: {:key => '0925'}
227
+
228
+ # but actual result is:
138
229
  # => path: {:accesskey => '0925'}, options: {}
139
230
 
140
- The next release of the gem will adopt the convention of having the `Hash`
141
- argument as the last argument passed to the method.
231
+ For the above case, it might be better to refactor:
232
+
233
+ has_named_parameters :request, :optional => [ :key, [ :path, "/xxx" ] ]
234
+ def request options = { }
235
+ "path: #{options.delete :path}, options: #{options.inspect}"
236
+ end
237
+
238
+ # invocation:
239
+ request :key => '0925'
240
+
241
+ # result:
242
+ # => path: /xxx, options: {:key => '0925'}
243
+
244
+ # invocation:
245
+ request
246
+
247
+ # result:
248
+ # => path: /xxx, options: {}
142
249
 
143
250
  Dependencies
144
251
  ------------
@@ -1,3 +1,27 @@
1
+ 0.0.7 [Nov 18, 2010]
2
+ - [FEATURE] Added support to distinguish named parameters declaration for
3
+ class and instance methods that uses the same names. So now you could do:
4
+
5
+ class Mailer
6
+ has_named_parameters :send_mail,
7
+ :required => :to,
8
+ :optional => [ :from, 'yourself@example.org' ]
9
+ def send_mail options = { }
10
+ Mailer.send_mail options
11
+ end
12
+
13
+ has_named_parameters :'self.send_mail', :required => [ :to, :from ]
14
+ def self.send_mail options = { }
15
+ # ... do mail sending stuff here ...
16
+ end
17
+ end
18
+
19
+ - [FEATURE] Added support for requires clause.
20
+ - [FEATURE] Added support for recognizes clause.
21
+ - [FEATURE] Added support for permissive evaluation of optional parameters.
22
+ - [INTERNAL] Will now expect that the Hash args for the named parameters is
23
+ the last argument of the method.
24
+
1
25
  0.0.6 [Nov 13, 2010]
2
26
  - [FEATURE] Added support for requiring one-of from a list of named
3
27
  parameters.
@@ -8,27 +32,21 @@
8
32
  - [INTERNAL] Updated tests.
9
33
 
10
34
  0.0.5 [Nov 11, 2010]
11
- --------------------
12
35
  - [BUGFIX] Same as 0.0.4 - except this time it's really fixed :-)
13
36
 
14
37
  0.0.4 [Nov 11, 2010]
15
- --------------------
16
38
  - [BUGFIX] Reported method name when ArgumentError is raised was ugly.
17
39
 
18
40
  0.0.3 [Nov 11, 2010]
19
- --------------------
20
41
  - [FEATURE] Added support for has_named_parameter declaration for class
21
42
  methods.
22
43
 
23
44
  0.0.2 [Nov 10, 2010]
24
- --------------------
25
45
  - [BUGFIX] ArgumentError incorrectly references Module instead of actual class
26
46
  that raised it.
27
47
 
28
48
  0.0.1 [Nov 10, 2010]
29
- --------------------
30
49
  - Initial release.
31
50
 
32
51
  0.0.0 [Nov 10, 2010]
33
- --------------------
34
52
  - Initial release. (yanked)
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.6
1
+ 0.0.7
@@ -37,14 +37,14 @@ module NamedParameters
37
37
  required = spec[:required].map &mapper
38
38
  oneof = spec[:oneof].map &mapper
39
39
 
40
- # determine what keys are allowed
41
- order = lambda{ |x, y| x.to_s <=> y.to_s }
42
- allowed = (optional + required + oneof).sort &order
40
+ # determine what keys are allowed, unless mode is :permissive
41
+ # in which case we don't care unless its listed as required or oneof
42
+ order = lambda{ |x, y| x.to_s <=> y.to_s }
43
+ allowed = spec[:mode] == :permissive ? [] : (optional + required + oneof).sort(&order)
43
44
 
44
45
  # determine what keys were passed;
45
46
  # also, plugin the names of parameters assigned with default values
46
- #keys = (params.keys.map{ |k| k.to_sym } + defaults.keys).uniq
47
- keys = params.keys.map{ |k| k.to_sym }
47
+ keys = params.keys.map{ |k| k.to_sym }
48
48
  keys.sort! &order
49
49
  required.sort! &order
50
50
 
@@ -103,7 +103,12 @@ module NamedParameters
103
103
  # * `:optional` means that all or none of these parameters may be used.
104
104
  # * `:oneof` means that one of these parameters must be specified.
105
105
  #
106
- def has_named_parameters method, spec = { }
106
+ # @param [Symbol] mode enforces that only parameters that were named in
107
+ # either the `:required`, `:optional`, and `:oneof` list may be allowed.
108
+ # Set it to `:permissive` to relax the requirement - `:required` and `:oneof`
109
+ # parameters will still be expected.
110
+ #
111
+ def has_named_parameters method, spec, mode = :strict
107
112
  # ensure spec entries are initialized and the proper types
108
113
  [ :required, :optional, :oneof ].each{ |k| spec[k] ||= [] }
109
114
  spec = Hash[ spec.map{ |k, v|
@@ -111,7 +116,52 @@ module NamedParameters
111
116
  v.map!{ |entry| entry.instance_of?(Array) ? Hash[*entry] : entry }
112
117
  [ k, v ]
113
118
  } ]
114
- specs[method] = spec
119
+ spec[:mode] = mode
120
+ specs[key_for(method)] = spec
121
+ end
122
+
123
+ # Convenience method, equivalent to declaring:
124
+ #
125
+ # has_named_parameters :'self.new', :required => params
126
+ # has_named_parameters :initialize, :required => params
127
+ #
128
+ # @param [Array] params the lists of parameters. The list is expected
129
+ # to be an `Array` of symbols matching the names of the required
130
+ # parameters.
131
+ #
132
+ # @param [Symbol] mode enforces that only parameters that were named in
133
+ # either the `:required`, `:optional`, and `:oneof` list may be allowed.
134
+ # Set it to `:permissive` to relax the requirement - `:required` and `:oneof`
135
+ # parameters will still be expected.
136
+ #
137
+ def requires params, mode = :strict
138
+ [ :new, :initialize ].each do |method|
139
+ spec = specs[key_for method] || { }
140
+ spec.merge!(:required => params)
141
+ has_named_parameters method, spec, mode
142
+ end
143
+ end
144
+
145
+ # Convenience method, equivalent to declaring:
146
+ #
147
+ # has_named_parameters :'self.new', :optional => params
148
+ # has_named_parameters :initialize, :optional => params
149
+ #
150
+ # @param [Array] params the lists of parameters. The list is expected
151
+ # to be an `Array` of symbols matching the names of the optional
152
+ # parameters.
153
+ #
154
+ # @param [Symbol] mode enforces that only parameters that were named in
155
+ # either the `:required`, `:optional`, and `:oneof` list may be allowed.
156
+ # Set it to `:permissive` to relax the requirement - `:required` and `:oneof`
157
+ # parameters will still be expected.
158
+ #
159
+ def recognizes params, mode = :strict
160
+ [ :new, :initialize ].each do |method|
161
+ spec = specs[key_for method] || { }
162
+ spec.merge!(:optional => params)
163
+ has_named_parameters method, spec, mode
164
+ end
115
165
  end
116
166
 
117
167
  protected
@@ -151,7 +201,7 @@ module NamedParameters
151
201
  def singleton_method_added name # :nodoc:
152
202
  instrument name do
153
203
  method = self.eigenclass.instance_method name
154
- spec = specs.delete name
204
+ spec = specs[key_for :"self.#{name}"] || specs[key_for name]
155
205
  owner = "#{self.name}::"
156
206
  eigenclass.instance_eval do
157
207
  intercept method, owner, name, spec
@@ -164,7 +214,7 @@ module NamedParameters
164
214
  def method_added name # :nodoc:
165
215
  instrument name do
166
216
  method = instance_method name
167
- spec = specs.delete name
217
+ spec = specs[key_for name] || specs[key_for :"self.#{name}"]
168
218
  owner = "#{self.name}#"
169
219
  intercept method, owner, name, spec
170
220
  end
@@ -174,7 +224,7 @@ module NamedParameters
174
224
  private
175
225
  # apply instrumentation to method
176
226
  def instrument method # :nodoc:
177
- if specs.include? method and !instrumenting?
227
+ if specs.include? key_for(method) and !instrumenting?
178
228
  @instrumenting = true
179
229
  yield method
180
230
  @instrumenting = false
@@ -187,13 +237,14 @@ module NamedParameters
187
237
  define_method name do |*args, &block|
188
238
  # locate the argument representing the named parameters value
189
239
  # for the method invocation
190
- params = args.find{ |arg| arg.instance_of? Hash }
191
- args << (params = { }) if params.nil?
240
+ params = args.last
241
+ args << (params = { }) unless params.instance_of? Hash
192
242
 
193
243
  # merge the declared default values for params into the arguments
194
244
  # used when the method is invoked
195
245
  defaults = { }
196
246
  spec.each do |k, v|
247
+ next if k == :mode
197
248
  v.each{ |entry| defaults.merge! entry if entry.instance_of? Hash }
198
249
  end
199
250
  params = defaults.merge params
@@ -203,7 +254,7 @@ module NamedParameters
203
254
 
204
255
  # inject the updated argument values for params into the arguments
205
256
  # before actually making method invocation
206
- args.map!{ |arg| arg.instance_of?(Hash) ? params : arg }
257
+ args[args.length - 1] = params
207
258
  method.bind(self).call(*args, &block)
208
259
  end
209
260
  end
@@ -213,6 +264,11 @@ module NamedParameters
213
264
  @specs ||= { }
214
265
  end
215
266
 
267
+ def key_for method
268
+ type = method.to_s =~ /^self\./ ? :singleton : :instance
269
+ :"#{self.name}::#{type}.#{method}"
270
+ end
271
+
216
272
  # check if in the process of instrumenting a method
217
273
  def instrumenting? # :nodoc:
218
274
  @instrumenting
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{named-parameters}
8
- s.version = "0.0.6"
8
+ s.version = "0.0.7"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Juris Galang"]
12
- s.date = %q{2010-11-13}
12
+ s.date = %q{2010-11-18}
13
13
  s.description = %q{This gem simulates named-parameters in Ruby. It's a complement to the common
14
14
  Ruby idiom of using `Hash` args to emulate the use of named parameters.
15
15
 
@@ -5,7 +5,7 @@ describe "NamedParameters" do
5
5
  class Foo
6
6
  has_named_parameters :initialize, :required => :x, :optional => [ :y, :z ]
7
7
  def initialize opts = {}; end
8
-
8
+
9
9
  has_named_parameters :method_one, :required => :x, :optional => [ :y, :z ]
10
10
  def method_one x, y, opts = {}; end
11
11
 
@@ -172,4 +172,29 @@ describe "NamedParameters" do
172
172
  lambda { Quux.new :x => :x, :y => :y }.should raise_error ArgumentError
173
173
  lambda { Quux.new :x => :x }.should_not raise_error
174
174
  end
175
+
176
+ it "should be able to specify optional parameters using the recognizes method" do
177
+ class Recognizes
178
+ recognizes [ :x, :y ]
179
+ def self.new opts = { }; end
180
+ def initialize opts = { }; end
181
+ end
182
+ lambda { Recognizes.new }.should_not raise_error
183
+ lambda { Recognizes.new :x => :x }.should_not raise_error
184
+ lambda { Recognizes.new :y => :y }.should_not raise_error
185
+ lambda { Recognizes.new :x => :x, :y => :y }.should_not raise_error
186
+ lambda { Recognizes.new :z => :z }.should raise_error ArgumentError
187
+ end
188
+
189
+ it "should be able to specify required parameters using the recognizes method" do
190
+ class Required
191
+ requires [ :x, :y ]
192
+ def self.new opts = { }; end
193
+ def initialize opts = { }; end
194
+ end
195
+ lambda { Required.new }.should raise_error ArgumentError
196
+ lambda { Required.new :x => :x }.should raise_error ArgumentError
197
+ lambda { Required.new :y => :y }.should raise_error ArgumentError
198
+ lambda { Required.new :x => :x, :y => :y }.should_not raise_error
199
+ end
175
200
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: named-parameters
3
3
  version: !ruby/object:Gem::Version
4
- hash: 19
4
+ hash: 17
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
8
  - 0
9
- - 6
10
- version: 0.0.6
9
+ - 7
10
+ version: 0.0.7
11
11
  platform: ruby
12
12
  authors:
13
13
  - Juris Galang
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-11-13 00:00:00 -08:00
18
+ date: 2010-11-18 00:00:00 -08:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency