named-parameters 0.0.6 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
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