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.
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