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.
Files changed (28) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +22 -0
  3. data/README.md +46 -14
  4. data/lib/db_mod/exceptions/no_results.rb +2 -1
  5. data/lib/db_mod/exceptions/too_many_results.rb +2 -1
  6. data/lib/db_mod/statements/configuration.rb +95 -24
  7. data/lib/db_mod/statements/configuration/as.rb +9 -16
  8. data/lib/db_mod/statements/configuration/as/csv.rb +4 -7
  9. data/lib/db_mod/statements/configuration/as/json.rb +8 -10
  10. data/lib/db_mod/statements/configuration/defaults.rb +140 -0
  11. data/lib/db_mod/statements/configuration/method_configuration.rb +109 -0
  12. data/lib/db_mod/statements/configuration/single.rb +16 -22
  13. data/lib/db_mod/statements/configuration/single/column.rb +1 -1
  14. data/lib/db_mod/statements/configuration/single/required_row.rb +1 -1
  15. data/lib/db_mod/statements/configuration/single/required_value.rb +1 -1
  16. data/lib/db_mod/statements/configuration/single/row.rb +1 -1
  17. data/lib/db_mod/statements/configuration/single/value.rb +1 -1
  18. data/lib/db_mod/statements/parameters.rb +3 -1
  19. data/lib/db_mod/statements/prepared.rb +18 -46
  20. data/lib/db_mod/statements/statement.rb +9 -56
  21. data/lib/db_mod/version.rb +1 -1
  22. data/spec/db_mod/statements/configuration/as/csv_spec.rb +2 -2
  23. data/spec/db_mod/statements/configuration/as/json_spec.rb +29 -2
  24. data/spec/db_mod/statements/configuration/as_spec.rb +2 -2
  25. data/spec/db_mod/statements/configuration/defaults_spec.rb +203 -0
  26. data/spec/db_mod/statements/configuration/single_spec.rb +7 -7
  27. metadata +6 -3
  28. 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: 9f9ed4ad3824fbe0a2b09444c5aa9b4dde35ee94
4
- data.tar.gz: 4a70f5c7416895da2fb5cc58608d5aaabeb610ce
3
+ metadata.gz: e4a3d4f97f88694b3cde2f3d7d76dfdb303b9a2b
4
+ data.tar.gz: 9bb085e3601b0bd356b5595402006cbdf09be6fa
5
5
  SHA512:
6
- metadata.gz: 22b0da036c7e04ee7f70d21c79a5dcb70db35895485ee8b1a9bae150a258be8a5c529023c0b71c1f6e74c80ddb911d8f0915d4ef341db88286c48bfaab8bf182
7
- data.tar.gz: 23a8967663138b1b10d83d994b7b397dd00b8c536663d1a58b1227f6434a47334d7f1d77fcb58a86048d5bfcdbf30e8ab6800eb818d9f37116083cce35b2c0e1
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 simple
198
- chained-method syntax is used
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
- `.as(:csv)` and `.as(:json)`, which will convert the result set to a string
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').as(:csv)
211
- def_statement(:bar, 'SElECT c, d FROM bar WHERE foo_id = $1').as(:json)
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 `.single` extension.
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').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)
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').single(:value)
249
- def_statement(:b, 'SELECT 1 WHERE true = false').single(:value!)
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 {ConfigurableMethod#single},
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 {ConfigurableMethod#single},
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 {ConfigurableMethod}.
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
- # @return [DbMod::Statements::ConfigurableMethod] dsl object for
17
- # further extending the method
18
- def self.def_configurable(mod, name, definition)
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
- ConfigurableMethod.new(mod, name)
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
- # Used by {ConfigurableMethod} (and associated code) to wrap a defined
25
- # statement method or prepared method with additional result processing.
26
- # A method should be provided, which accepts an SQL result set and
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 mod [Module] the module where the method has been defined
32
- # @param name [Symbol] the method name
33
- # @param wrapper [#call]
34
- # a function that processes the SQL results in some way
35
- def self.process_method_results(mod, name, wrapper)
36
- mod.instance_eval do
37
- wrapped = instance_method(name)
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/configurable_method'
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 {ConfigurableMethod#as},
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 a method so that the SQL result set it
30
- # returns will be coerced to the given type.
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 mod [Module] module where the method has been defined
35
- # @param name [Symbol] method name
36
- # @param type [Symbol] type to which result set should be coerced
37
- def self.extend_method(mod, name, type)
38
- unless COERCERS.key? type
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.process_method_results(mod, name, COERCERS[type])
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').as(:csv)
11
- # def_prepared(:b, 'SELECT b, c FROM bar').as(:csv)
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
- module Csv
17
- # Enables this module to be passed to
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').as(:json)
11
- # def_prepared(:b, 'SELECT b, c FROM bar').as(:json)
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
- module Json
17
- # Enables this module to be passed to
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
- # .map turns the result object into an array
27
- results.map { |x| x }.to_json
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