db_mod 0.0.4 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![GitHub version](https://badge.fury.io/gh/dslh%2Fdb_mod.svg)](https://github.com/dslh/db_mod)
|
6
|
-
[![Gem Version](https://badge.fury.io/rb/db_mod.svg)](https://rubygems.org/gems/db_mod)
|
7
6
|
[![Travis CI](https://img.shields.io/travis/dslh/db_mod/master.svg)](https://travis-ci.org/dslh/db_mod)
|
8
|
-
![Gem downloads](https://img.shields.io/gem/dt/db_mod.svg)
|
7
|
+
[![Gem downloads](https://img.shields.io/gem/dt/db_mod.svg)](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
|