db_mod 0.0.4 → 0.0.5
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +22 -0
- data/README.md +46 -14
- data/lib/db_mod/exceptions/no_results.rb +2 -1
- data/lib/db_mod/exceptions/too_many_results.rb +2 -1
- data/lib/db_mod/statements/configuration.rb +95 -24
- data/lib/db_mod/statements/configuration/as.rb +9 -16
- data/lib/db_mod/statements/configuration/as/csv.rb +4 -7
- data/lib/db_mod/statements/configuration/as/json.rb +8 -10
- data/lib/db_mod/statements/configuration/defaults.rb +140 -0
- data/lib/db_mod/statements/configuration/method_configuration.rb +109 -0
- data/lib/db_mod/statements/configuration/single.rb +16 -22
- data/lib/db_mod/statements/configuration/single/column.rb +1 -1
- data/lib/db_mod/statements/configuration/single/required_row.rb +1 -1
- data/lib/db_mod/statements/configuration/single/required_value.rb +1 -1
- data/lib/db_mod/statements/configuration/single/row.rb +1 -1
- data/lib/db_mod/statements/configuration/single/value.rb +1 -1
- data/lib/db_mod/statements/parameters.rb +3 -1
- data/lib/db_mod/statements/prepared.rb +18 -46
- data/lib/db_mod/statements/statement.rb +9 -56
- data/lib/db_mod/version.rb +1 -1
- data/spec/db_mod/statements/configuration/as/csv_spec.rb +2 -2
- data/spec/db_mod/statements/configuration/as/json_spec.rb +29 -2
- data/spec/db_mod/statements/configuration/as_spec.rb +2 -2
- data/spec/db_mod/statements/configuration/defaults_spec.rb +203 -0
- data/spec/db_mod/statements/configuration/single_spec.rb +7 -7
- metadata +6 -3
- data/lib/db_mod/statements/configuration/configurable_method.rb +0 -78
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e4a3d4f97f88694b3cde2f3d7d76dfdb303b9a2b
|
4
|
+
data.tar.gz: 9bb085e3601b0bd356b5595402006cbdf09be6fa
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 644b2c6f41aa82dc5088ece70d6b962497308b5344c2237329df5ffc93754238ff42b5e85410eb793e8d414a23bf627a149df23f00c6a06af8f461664be6b964
|
7
|
+
data.tar.gz: 949071580278f7d612ceeefcc06b0b8260897cc5546ce558d207b0723943bff8a519ffd8d8fa7aeab59724774bc3dfe949df9a03665f39de74b0f4a88a9bd8ce
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,25 @@
|
|
1
|
+
0.0.5 (2015-10-19)
|
2
|
+
==================
|
3
|
+
|
4
|
+
Breaking changes. Any statement or prepared methods with additional configuration
|
5
|
+
should have such configuration declared in a block, rather than a method chain
|
6
|
+
(although methods can be chained inside the method block). E.g.
|
7
|
+
|
8
|
+
```ruby
|
9
|
+
def_statement(:a, 'SELECT * FROM foo WHERE id = $1').single(:row).as(:json)
|
10
|
+
|
11
|
+
# ... becomes ...
|
12
|
+
|
13
|
+
def_statement(:a, 'SELECT * FROM foo WHERE id = $1') { single(:row).as(:json) }
|
14
|
+
```
|
15
|
+
|
16
|
+
* +def_statement+ and +def_prepared+ method configuration must now be supplied
|
17
|
+
as a block. This allows method configuration to be collected ahead of
|
18
|
+
method definition, and hence a bit more smarts during the method declaration
|
19
|
+
process - [@dslh](https://github.com/dslh).
|
20
|
+
* `defaults` - default parameter values for statement and prepared methods - [@dslh](https://github.com/dslh).
|
21
|
+
* `single(:row/:column).as(:json)` now works - [@dslh](https://github.com/dslh).
|
22
|
+
|
1
23
|
0.0.4 (2015-10-15)
|
2
24
|
==================
|
3
25
|
|
data/README.md
CHANGED
@@ -3,9 +3,8 @@
|
|
3
3
|
Database enabled modules for ruby.
|
4
4
|
|
5
5
|
[](https://github.com/dslh/db_mod)
|
6
|
-
[](https://rubygems.org/gems/db_mod)
|
7
6
|
[](https://travis-ci.org/dslh/db_mod)
|
8
|
-

|
7
|
+
[](https://rubygems.org/gems/db_mod)
|
9
8
|
|
10
9
|
[Rubydoc.info documentation](http://www.rubydoc.info/gems/db_mod)
|
11
10
|
|
@@ -19,7 +18,7 @@ For the moment `db_mod` only supports PostgreSQL databases via the
|
|
19
18
|
`pg` gem. This gem is still in the early stages of development and no
|
20
19
|
guarantees will be made about backwards compatibility until v0.1.0.
|
21
20
|
|
22
|
-
Issues, pull requests, comments and feedback all welcomed.
|
21
|
+
Issues, feature or pull requests, comments and feedback all welcomed.
|
23
22
|
|
24
23
|
## Usage
|
25
24
|
|
@@ -194,21 +193,22 @@ or twice during a program's execution.
|
|
194
193
|
#### Configuring defined statements
|
195
194
|
|
196
195
|
`db_mod` contains a simple framework for extending these statement methods
|
197
|
-
and prepared methods with additional result processing. A
|
198
|
-
|
196
|
+
and prepared methods with additional result processing. A block can be
|
197
|
+
passed to +def_prepared+ and +def_statement+ definitions, where a basic
|
198
|
+
DSL is made available for additional method configuration.
|
199
199
|
|
200
200
|
##### JSON and CSV formatting
|
201
201
|
|
202
202
|
Statement and prepared methods can be configured on declaration by using
|
203
|
-
|
203
|
+
`as(:csv)` and `as(:json)`, which will convert the result set to a string
|
204
204
|
formatted as either a CSV document or an array of JSON objects, respectively.
|
205
205
|
|
206
206
|
```ruby
|
207
207
|
module Reports
|
208
208
|
include DbMod
|
209
209
|
|
210
|
-
def_prepared(:foo, 'SELECT a, b FROM foo WHERE bar_id = $id')
|
211
|
-
def_statement(:bar, 'SElECT c, d FROM bar WHERE foo_id = $1')
|
210
|
+
def_prepared(:foo, 'SELECT a, b FROM foo WHERE bar_id = $id') { as(:csv) }
|
211
|
+
def_statement(:bar, 'SElECT c, d FROM bar WHERE foo_id = $1') { as(:json) }
|
212
212
|
end
|
213
213
|
|
214
214
|
include Reports
|
@@ -222,15 +222,15 @@ bar(2) # => '[{"c":"5","d":"6"},...]'
|
|
222
222
|
|
223
223
|
To save a lot of repetetive unboxing of query results, methods that return
|
224
224
|
only one row, or rows with only one column, or only one row with a single
|
225
|
-
value, can be marked as such using the
|
225
|
+
value, can be marked as such using the `single` extension.
|
226
226
|
|
227
227
|
```ruby
|
228
228
|
module Getters
|
229
229
|
include DbMod
|
230
230
|
|
231
|
-
def_prepared(:user, 'SELECT * FROM user WHERE id = $1')
|
232
|
-
def_prepared(:name, 'SELECT name FROM user WHERE id = $1')
|
233
|
-
def_statement(:ids, 'SELECT id FROM user')
|
231
|
+
def_prepared(:user, 'SELECT * FROM user WHERE id = $1') { single(:row) }
|
232
|
+
def_prepared(:name, 'SELECT name FROM user WHERE id = $1') { single(:value) }
|
233
|
+
def_statement(:ids, 'SELECT id FROM user') { single(:column) }
|
234
234
|
end
|
235
235
|
|
236
236
|
# ...
|
@@ -245,9 +245,41 @@ When no results are returned, `:column` returns `[]` while `:row` and
|
|
245
245
|
`nil`, use `:row!` and `:value!` instead.
|
246
246
|
|
247
247
|
```ruby
|
248
|
-
def_statement(:a, 'SELECT 1 WHERE true = false')
|
249
|
-
def_statement(:b, 'SELECT 1 WHERE true = false')
|
248
|
+
def_statement(:a, 'SELECT 1 WHERE true = false') { single(:value) }
|
249
|
+
def_statement(:b, 'SELECT 1 WHERE true = false') { single(:value!) }
|
250
250
|
|
251
251
|
a # => nil
|
252
252
|
b # => fail
|
253
253
|
```
|
254
|
+
|
255
|
+
##### Default parameter values
|
256
|
+
|
257
|
+
Arbitrary default parameter values can be supplied using `defaults`.
|
258
|
+
|
259
|
+
For methods with named parameters:
|
260
|
+
|
261
|
+
```ruby
|
262
|
+
def_statement(:a, 'SELECT * FROM foo WHERE id = $id AND y > $min') do
|
263
|
+
defaults min: 10
|
264
|
+
end
|
265
|
+
|
266
|
+
# ...
|
267
|
+
|
268
|
+
a(id: 1) # === a(id: 1, min: 10)
|
269
|
+
```
|
270
|
+
|
271
|
+
For methods with fixed parameters:
|
272
|
+
|
273
|
+
```ruby
|
274
|
+
def_statement(:a, %(
|
275
|
+
SELECT *
|
276
|
+
FROM foo
|
277
|
+
WHERE x = $1
|
278
|
+
AND y < $2
|
279
|
+
AND z > $3
|
280
|
+
)) { defaults 4, 5, 6 }
|
281
|
+
|
282
|
+
# ...
|
283
|
+
|
284
|
+
a(1) # === a(1, 5, 6)
|
285
|
+
```
|
@@ -3,7 +3,8 @@ require_relative 'base'
|
|
3
3
|
module DbMod
|
4
4
|
module Exceptions
|
5
5
|
# Raised by a statement or prepared method that
|
6
|
-
# has been configured using
|
6
|
+
# has been configured using
|
7
|
+
# {Statements::Configuration::MethodConfiguration#single},
|
7
8
|
# when a result set expected to contain at least one
|
8
9
|
# result does not.
|
9
10
|
class NoResults < Base
|
@@ -3,7 +3,8 @@ require_relative 'base'
|
|
3
3
|
module DbMod
|
4
4
|
module Exceptions
|
5
5
|
# Raised by a statement or prepared method that
|
6
|
-
# has been configured using
|
6
|
+
# has been configured using
|
7
|
+
# {Statements::Configuration::MethodConfiguration#single},
|
7
8
|
# when a result set expected to contain not more than
|
8
9
|
# one result, in fact, does.
|
9
10
|
class TooManyResults < Base
|
@@ -3,7 +3,7 @@ module DbMod
|
|
3
3
|
# Provides additional functionality to statement and
|
4
4
|
# prepared methods, allowing additional processing of
|
5
5
|
# arguments and results using the dsl extensions
|
6
|
-
# exposed via {
|
6
|
+
# exposed via {MethodConfiguration}.
|
7
7
|
module Configuration
|
8
8
|
# Used by submodules to when defining a method as declared by
|
9
9
|
# +def_statement+ or +def_prepared+. Wraps the defined method
|
@@ -12,37 +12,108 @@ module DbMod
|
|
12
12
|
#
|
13
13
|
# @param mod [Module] the module where the method has been declared
|
14
14
|
# @param name [Symbol] the name of the module that has been defined
|
15
|
-
# @param definition [Proc] method definition
|
16
|
-
#
|
17
|
-
#
|
18
|
-
|
15
|
+
# @param definition [Proc] method definition, the base function which
|
16
|
+
# will perform database interaction and return an SQL result object
|
17
|
+
# @param params [Array<Symbol>,Fixnum] declares the parameters that
|
18
|
+
# the method will accept. Can either be an array of named parameters
|
19
|
+
# or a integer giving the arity of the function. +[]+ may also be
|
20
|
+
# given to denote a no-argument method.
|
21
|
+
# @yield dsl block may be passed, which will be evaluated using a
|
22
|
+
# {MethodConfiguration} object as scope
|
23
|
+
def self.def_configurable(mod, name, definition, params = 0, &block)
|
24
|
+
config = MethodConfiguration.new(&block).to_hash if block_given?
|
25
|
+
|
26
|
+
definition = attach_result_processors(definition, config) if config
|
27
|
+
definition = attach_param_processor(definition, params, config)
|
28
|
+
|
19
29
|
mod.instance_eval { define_method(name, definition) }
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
# Attaches any required parameter processing and validation to
|
35
|
+
# the method definition by wrapping it in a further proc as required.
|
36
|
+
#
|
37
|
+
# @param definition [Proc] base method definition
|
38
|
+
# @param params see {Configuration.def_configurable}
|
39
|
+
# @param config [MethodConfiguration] for default values
|
40
|
+
# @return [Proc] a new wrapper for +definition+
|
41
|
+
def self.attach_param_processor(definition, params, config)
|
42
|
+
wrapped =
|
43
|
+
if params.is_a?(Array) && !params.empty?
|
44
|
+
define_named_args_method(definition, params)
|
45
|
+
|
46
|
+
elsif params.is_a?(Fixnum) && params > 0
|
47
|
+
define_fixed_args_method(definition, params)
|
48
|
+
else
|
49
|
+
->() { instance_exec(&definition) }
|
50
|
+
end
|
51
|
+
|
52
|
+
return wrapped unless config
|
53
|
+
Defaults.extend(wrapped, params, config[:defaults])
|
54
|
+
end
|
20
55
|
|
21
|
-
|
56
|
+
# Wrap the given definition in a procedure that will validate any
|
57
|
+
# passed arguments, and transform them into an array that can be
|
58
|
+
# passed directly to +PGconn.exec_params+ or +PGconn.exec_prepared+.
|
59
|
+
#
|
60
|
+
# @param definition [Proc] base method definition
|
61
|
+
# @param params [Array<Symbol>] list of method parameter names
|
62
|
+
# @return [Proc] new method definition
|
63
|
+
def self.define_named_args_method(definition, params)
|
64
|
+
lambda do |*args|
|
65
|
+
args = Parameters.valid_named_args! params, args
|
66
|
+
instance_exec(*args, &definition)
|
67
|
+
end
|
22
68
|
end
|
23
69
|
|
24
|
-
#
|
25
|
-
#
|
26
|
-
#
|
27
|
-
# returns some transformation of the results. The original method
|
28
|
-
# declaration will be replaced, so that the original method definition
|
29
|
-
# is called and the results are passed through this given method.
|
70
|
+
# Wrap the given definition in a procedure that will validate that
|
71
|
+
# the correct number of arguments has been passed, before passing them
|
72
|
+
# on to the original method definition.
|
30
73
|
#
|
31
|
-
# @param
|
32
|
-
# @param
|
33
|
-
# @
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
define_method(name, lambda do |*args|
|
40
|
-
wrapper.call wrapped.bind(self).call(*args)
|
41
|
-
end)
|
74
|
+
# @param definition [Proc] base method definition
|
75
|
+
# @param arity [Fixnum] expected number of arguments
|
76
|
+
# @return [Proc] new method definition
|
77
|
+
def self.define_fixed_args_method(definition, arity)
|
78
|
+
lambda do |*args|
|
79
|
+
Parameters.valid_fixed_args!(arity, args)
|
80
|
+
instance_exec(*args, &definition)
|
42
81
|
end
|
43
82
|
end
|
83
|
+
|
84
|
+
# Attaches any required result processing to the method definition,
|
85
|
+
# as may have been defined in a block passed to either of +def_statement+
|
86
|
+
# or +def_prepared+. This method is called before
|
87
|
+
# {Configuration.attach_param_processor}, so that the method definition
|
88
|
+
# can be wrapped by the parameter processor. In this way processors
|
89
|
+
# attached here are assured access to method parameters after any
|
90
|
+
# initial processing and validation has taken place.
|
91
|
+
#
|
92
|
+
# @param definition [Proc] base method definition
|
93
|
+
# @param config [MethodConfiguration] configuration declared at
|
94
|
+
# method definition time
|
95
|
+
def self.attach_result_processors(definition, config)
|
96
|
+
definition = Single.extend(definition, config)
|
97
|
+
definition = As.extend(definition, config)
|
98
|
+
|
99
|
+
definition
|
100
|
+
end
|
101
|
+
|
102
|
+
# Attach a processor to the chain of result processors for a method.
|
103
|
+
# The pattern here is something similar to rack's middleware.
|
104
|
+
# A result processor is constructed with a method definition, and
|
105
|
+
# then acts as a replacement for the method, responding to +#call+.
|
106
|
+
# Subclasses must implement a +process+ method, which should accept
|
107
|
+
# an SQL result set (or possibly, the result of other upstream
|
108
|
+
# processing), perform some transform on it and return the result.
|
109
|
+
#
|
110
|
+
# @param definition [Proc] base method definition
|
111
|
+
# @param processor [#call] result processor
|
112
|
+
def self.attach_result_processor(definition, processor)
|
113
|
+
->(*args) { processor.call instance_exec(*args, &definition) }
|
114
|
+
end
|
44
115
|
end
|
45
116
|
end
|
46
117
|
end
|
47
118
|
|
48
|
-
require_relative 'configuration/
|
119
|
+
require_relative 'configuration/method_configuration'
|
@@ -8,16 +8,13 @@ module DbMod
|
|
8
8
|
# module instance methods returning an SQL result set
|
9
9
|
# to be extended with additional result coercion and
|
10
10
|
# formatting. The normal way to access this functionality
|
11
|
-
# is via {
|
11
|
+
# is via {MethodConfiguration#as},
|
12
12
|
# which is available when defining a statement method
|
13
13
|
# or prepared method:
|
14
14
|
#
|
15
15
|
# def_statement(:a, 'SELECT a, b, c FROM foo').as(:csv)
|
16
16
|
# def_prepared(:b, 'SELECT d, e, f FROM bar').as(:csv)
|
17
17
|
module As
|
18
|
-
# For extend_method
|
19
|
-
Configuration = DbMod::Statements::Configuration
|
20
|
-
|
21
18
|
# List of available result coercion methods.
|
22
19
|
# Only keys defined here are allowed as arguments
|
23
20
|
# to {DbMod::Statements::Configuration::ConfigurableMethod#as}.
|
@@ -26,20 +23,16 @@ module DbMod
|
|
26
23
|
json: As::Json
|
27
24
|
}
|
28
25
|
|
29
|
-
# Extend
|
30
|
-
#
|
31
|
-
# See {COERCERS} for a list of defined coercion
|
32
|
-
# methods.
|
26
|
+
# Extend the given method definition with additional
|
27
|
+
# result coercion.
|
33
28
|
#
|
34
|
-
# @param
|
35
|
-
# @param
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
fail ArgumentError, "#{type} not in #{COERCERS.keys.join ', '}"
|
40
|
-
end
|
29
|
+
# @param definition [Proc] base method definition
|
30
|
+
# @param config [MethodConfiguration] method configuration
|
31
|
+
def self.extend(definition, config)
|
32
|
+
type = config[:as]
|
33
|
+
return definition if type.nil?
|
41
34
|
|
42
|
-
Configuration.
|
35
|
+
Configuration.attach_result_processor definition, COERCERS[type]
|
43
36
|
end
|
44
37
|
end
|
45
38
|
end
|
@@ -7,17 +7,14 @@ module DbMod
|
|
7
7
|
# May be enabled for a prepared method or
|
8
8
|
# statement method using +.as(:csv)+:
|
9
9
|
#
|
10
|
-
# def_statement(:a, 'SELECT a, b FROM foo')
|
11
|
-
# def_prepared(:b, 'SELECT b, c FROM bar')
|
10
|
+
# def_statement(:a, 'SELECT a, b FROM foo') { as(:csv) }
|
11
|
+
# def_prepared(:b, 'SELECT b, c FROM bar') { as(:csv) }
|
12
12
|
#
|
13
13
|
# def do_stuff
|
14
14
|
# a # => "a,b\r\n1,2\r\n3,4\r\n..."
|
15
15
|
# end
|
16
|
-
|
17
|
-
#
|
18
|
-
# {DbMod::Statements::Configuration.process_method_results} as the
|
19
|
-
# +wrapper+ function, in which case it will retrieve the results
|
20
|
-
# and format them as a CSV document using the column names
|
16
|
+
class Csv
|
17
|
+
# Formats the results as a CSV document using the column names
|
21
18
|
# from the result set.
|
22
19
|
#
|
23
20
|
# @param results [Object] SQL result set
|
@@ -7,24 +7,22 @@ module DbMod
|
|
7
7
|
# May be enabled for a prepared method or
|
8
8
|
# statement method using +.as(:json)+:
|
9
9
|
#
|
10
|
-
# def_statement(:a, 'SELECT a, b FROM foo')
|
11
|
-
# def_prepared(:b, 'SELECT b, c FROM bar')
|
10
|
+
# def_statement(:a, 'SELECT a, b FROM foo') { as(:json) }
|
11
|
+
# def_prepared(:b, 'SELECT b, c FROM bar') { as(:json) }
|
12
12
|
#
|
13
13
|
# def do_stuff
|
14
14
|
# a # => '[{"a":"x","b":"y"},...]'
|
15
15
|
# end
|
16
|
-
|
17
|
-
#
|
18
|
-
# {DbMod::Statements::Configuration.process_method_results} as the
|
19
|
-
# +wrapper+ function, in which case it will retrieve the results
|
20
|
-
# and format them as a JSON string using the column names
|
21
|
-
# from the result set for the keys of each object.
|
16
|
+
class Json
|
17
|
+
# Formats the SQL results as a JSON object.
|
22
18
|
#
|
23
19
|
# @param results [Object] SQL result set
|
24
20
|
# @return [String] a JSON formatted string
|
25
21
|
def self.call(results)
|
26
|
-
#
|
27
|
-
results.
|
22
|
+
# For compatibility with single(:row)
|
23
|
+
return results.to_json if results.is_a? Hash
|
24
|
+
|
25
|
+
results.to_a.to_json
|
28
26
|
end
|
29
27
|
end
|
30
28
|
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
module DbMod
|
2
|
+
module Statements
|
3
|
+
module Configuration
|
4
|
+
# Provides functionality backing the {MethodConfiguration#defaults}
|
5
|
+
# setting. Allows certain statement or prepared method parameters to
|
6
|
+
# be omitted, supplying default values where necessary:
|
7
|
+
#
|
8
|
+
# def_statement(:a, 'SELECT id FROM a WHERE x > $y') { defaults y: 10 }
|
9
|
+
# def_prepared(:b, %(
|
10
|
+
# INSERT INTO b
|
11
|
+
# (p, q, r)
|
12
|
+
# VALUES
|
13
|
+
# ($1, $2, $3)
|
14
|
+
# RETURNING p, q, r
|
15
|
+
# )) { defaults(5, 6).single(:row) }
|
16
|
+
#
|
17
|
+
# # ...
|
18
|
+
#
|
19
|
+
# a # y => 10
|
20
|
+
# a 11 # y => 11
|
21
|
+
#
|
22
|
+
# # defaults filled in from the right
|
23
|
+
# b 1, 2 # => { 'p' => '1', 'q' => '2', 'r' => '6' }
|
24
|
+
module Defaults
|
25
|
+
# Extend a method definition by wrapping it with a proc that will
|
26
|
+
# try to fill in any omitted arguments with given defaults.
|
27
|
+
#
|
28
|
+
# @param definition [Proc] base method definition,
|
29
|
+
# with parameter validation already attached
|
30
|
+
# @param params [Hash<Symbol,value>,Array<value>]
|
31
|
+
# see {Configuration.def_configurable}
|
32
|
+
# @param defaults [Hash<Symbol,value,Array<value>]
|
33
|
+
# default values, in the same form as they would be provided
|
34
|
+
# to the original method definition except that some values
|
35
|
+
# may be omitted
|
36
|
+
# @return [Proc] new method definition, or the same one
|
37
|
+
# if no default values have been appended
|
38
|
+
def self.extend(definition, params, defaults)
|
39
|
+
return definition if defaults.nil?
|
40
|
+
|
41
|
+
if [[], 0].include? params
|
42
|
+
fail ArgumentError, 'defaults not allowed for no-args methods'
|
43
|
+
end
|
44
|
+
|
45
|
+
if params.is_a? Array
|
46
|
+
extend_named_args_method(definition, defaults)
|
47
|
+
else
|
48
|
+
extend_fixed_args_method(definition, params, defaults)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
# Extend a method with named parameters, providing default
|
55
|
+
# argument values for one or more parameters.
|
56
|
+
#
|
57
|
+
# @param definition [Proc] base method definition,
|
58
|
+
# with parameter validation already attached
|
59
|
+
# @param defaults [Hash<Symbol,value>]
|
60
|
+
# default parameter values
|
61
|
+
def self.extend_named_args_method(definition, defaults)
|
62
|
+
unless defaults.is_a? Hash
|
63
|
+
fail ArgumentError, 'hash expected for defaults'
|
64
|
+
end
|
65
|
+
|
66
|
+
lambda do |*args|
|
67
|
+
Defaults.use_named_defaults(args, defaults)
|
68
|
+
instance_exec(*args, &definition)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Fill in any missing parameter arguments using default
|
73
|
+
# values where available.
|
74
|
+
#
|
75
|
+
# @param args [[Hash<Symbol,value>]] method arguments
|
76
|
+
# before processing and validation
|
77
|
+
# @param defaults [Hash<Symbol,value>]
|
78
|
+
# default parameter values
|
79
|
+
def self.use_named_defaults(args, defaults)
|
80
|
+
# Special case when no args given.
|
81
|
+
args << {} if args.empty?
|
82
|
+
|
83
|
+
# If the args are weird, expect normal parameter validation
|
84
|
+
# to pick it up.
|
85
|
+
return args unless args.last.is_a? Hash
|
86
|
+
|
87
|
+
defaults.each do |arg, value|
|
88
|
+
args.last[arg] = value unless args.last.key? arg
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# Extend a method with numbered parameters, providing
|
93
|
+
# default argument values for one or more parameters.
|
94
|
+
# Defaults will be applied, in the same left-to-right
|
95
|
+
# order, but at the right-hand side of the parameter
|
96
|
+
# list, as with normal method default arguments.
|
97
|
+
#
|
98
|
+
# @param definition [Proc] base method definition,
|
99
|
+
# with parameter validation already attached
|
100
|
+
# @param arity [Fixnum] number of arguments expected
|
101
|
+
# by the base method definition
|
102
|
+
# @param defaults [Array]
|
103
|
+
# default parameter values
|
104
|
+
def self.extend_fixed_args_method(definition, arity, defaults)
|
105
|
+
fail ArgumentError, 'too many defaults' if defaults.size > arity
|
106
|
+
|
107
|
+
unless defaults.is_a? Array
|
108
|
+
fail ArgumentError, 'array expected for defaults'
|
109
|
+
end
|
110
|
+
|
111
|
+
arity = (arity - defaults.size)..arity
|
112
|
+
fail ArgumentError, 'too many defaults' if arity.min < 0
|
113
|
+
|
114
|
+
lambda do |*args|
|
115
|
+
Defaults.use_fixed_defaults(args, defaults, arity)
|
116
|
+
instance_exec(*args, &definition)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
# Fill in any missing parameter arguments using default values
|
121
|
+
# where available.
|
122
|
+
#
|
123
|
+
# @param args [Array] method arguments
|
124
|
+
# before processing and validation
|
125
|
+
# @param defaults [Array] default parameter values
|
126
|
+
# @param arity [Range<Fixnum>] number of arguments
|
127
|
+
# expected by the base method definition
|
128
|
+
def self.use_fixed_defaults(args, defaults, arity)
|
129
|
+
unless arity.include? args.count
|
130
|
+
fail ArgumentError, "#{args.count} given, (#{arity}) expected"
|
131
|
+
end
|
132
|
+
|
133
|
+
defaults[args.size - arity.min...defaults.size].each do |arg|
|
134
|
+
args << arg
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|