db_mod 0.0.5 → 0.0.6

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.
@@ -24,10 +24,12 @@ module DbMod
24
24
  }
25
25
 
26
26
  # Extend the given method definition with additional
27
- # result coercion.
27
+ # result coercion, if specified using {MethodConfiguration#as}.
28
28
  #
29
29
  # @param definition [Proc] base method definition
30
30
  # @param config [MethodConfiguration] method configuration
31
+ # @return [Proc] wrapped method definition, or the original
32
+ # definition if no coercion has been specified
31
33
  def self.extend(definition, config)
32
34
  type = config[:as]
33
35
  return definition if type.nil?
@@ -14,6 +14,11 @@ module DbMod
14
14
  # RETURNING p, q, r
15
15
  # )) { defaults(5, 6).single(:row) }
16
16
  #
17
+ # def_prepared(:c, 'SELECT a FROM b WHERE d = $e AND f > $g') do
18
+ # # procs may be used
19
+ # defaults g: ->(args) { args[:e] * 10 }
20
+ # end
21
+ #
17
22
  # # ...
18
23
  #
19
24
  # a # y => 10
@@ -21,6 +26,9 @@ module DbMod
21
26
  #
22
27
  # # defaults filled in from the right
23
28
  # b 1, 2 # => { 'p' => '1', 'q' => '2', 'r' => '6' }
29
+ #
30
+ # # proc defaults executed when method is called
31
+ # c e: 2 # === c e: 2, g: 20
24
32
  module Defaults
25
33
  # Extend a method definition by wrapping it with a proc that will
26
34
  # try to fill in any omitted arguments with given defaults.
@@ -32,7 +40,10 @@ module DbMod
32
40
  # @param defaults [Hash<Symbol,value,Array<value>]
33
41
  # default values, in the same form as they would be provided
34
42
  # to the original method definition except that some values
35
- # may be omitted
43
+ # may be omitted. Defaults may either be constant values, or
44
+ # a lambda proc may be given, which will be passed the
45
+ # entirety of the argument list and should return a single
46
+ # value to be used when none is given
36
47
  # @return [Proc] new method definition, or the same one
37
48
  # if no default values have been appended
38
49
  def self.extend(definition, params, defaults)
@@ -57,14 +68,15 @@ module DbMod
57
68
  # @param definition [Proc] base method definition,
58
69
  # with parameter validation already attached
59
70
  # @param defaults [Hash<Symbol,value>]
60
- # default parameter values
71
+ # see {Defaults.extend}
72
+ # @return [Proc] wrapped method definition
61
73
  def self.extend_named_args_method(definition, defaults)
62
74
  unless defaults.is_a? Hash
63
75
  fail ArgumentError, 'hash expected for defaults'
64
76
  end
65
77
 
66
78
  lambda do |*args|
67
- Defaults.use_named_defaults(args, defaults)
79
+ Defaults.use_named_defaults(self, args, defaults)
68
80
  instance_exec(*args, &definition)
69
81
  end
70
82
  end
@@ -72,11 +84,16 @@ module DbMod
72
84
  # Fill in any missing parameter arguments using default
73
85
  # values where available.
74
86
  #
87
+ # @param scope [Object] scope to be used for executing
88
+ # default values that are lambda procedures. Should
89
+ # be the instance object where the method has been
90
+ # defined.
75
91
  # @param args [[Hash<Symbol,value>]] method arguments
76
92
  # before processing and validation
77
93
  # @param defaults [Hash<Symbol,value>]
78
94
  # default parameter values
79
- def self.use_named_defaults(args, defaults)
95
+ # @see Defaults.extend_named_args_method
96
+ def self.use_named_defaults(scope, args, defaults)
80
97
  # Special case when no args given.
81
98
  args << {} if args.empty?
82
99
 
@@ -85,7 +102,30 @@ module DbMod
85
102
  return args unless args.last.is_a? Hash
86
103
 
87
104
  defaults.each do |arg, value|
88
- args.last[arg] = value unless args.last.key? arg
105
+ next if args.last.key? arg
106
+
107
+ args.last[arg] = value! value, scope, args.last
108
+ end
109
+ end
110
+
111
+ # Execute the default 'value' if it is a +Proc+,
112
+ # or just return it.
113
+ #
114
+ # @param value [Proc,Object] default value as specified
115
+ # by {MethodConfiguration#defaults}
116
+ # @param scope [Object] scope to be used for executing
117
+ # default values that are lambda procedures. Should
118
+ # be the instance object where the method has been
119
+ # defined.
120
+ # @param args [Hash,Array] fixed or named argument
121
+ # list, to be passed to the 'value' if it is a proc
122
+ # @return [Object] value to be passed to the parametered
123
+ # database query
124
+ def self.value!(value, scope, args)
125
+ if value.is_a? Proc
126
+ scope.instance_exec(args, &value)
127
+ else
128
+ value
89
129
  end
90
130
  end
91
131
 
@@ -101,6 +141,7 @@ module DbMod
101
141
  # by the base method definition
102
142
  # @param defaults [Array]
103
143
  # default parameter values
144
+ # @return [Proc] wrapped method definition
104
145
  def self.extend_fixed_args_method(definition, arity, defaults)
105
146
  fail ArgumentError, 'too many defaults' if defaults.size > arity
106
147
 
@@ -112,7 +153,7 @@ module DbMod
112
153
  fail ArgumentError, 'too many defaults' if arity.min < 0
113
154
 
114
155
  lambda do |*args|
115
- Defaults.use_fixed_defaults(args, defaults, arity)
156
+ Defaults.use_fixed_defaults(self, args, defaults, arity)
116
157
  instance_exec(*args, &definition)
117
158
  end
118
159
  end
@@ -120,18 +161,24 @@ module DbMod
120
161
  # Fill in any missing parameter arguments using default values
121
162
  # where available.
122
163
  #
164
+ # @param scope [Object] scope to be used for executing
165
+ # default values that are lambda procedures. Should
166
+ # be the instance object where the method has been
167
+ # defined.
123
168
  # @param args [Array] method arguments
124
169
  # before processing and validation
125
170
  # @param defaults [Array] default parameter values
126
171
  # @param arity [Range<Fixnum>] number of arguments
127
172
  # expected by the base method definition
128
- def self.use_fixed_defaults(args, defaults, arity)
173
+ # @raise [ArgumentError] if there are not enough or too many args
174
+ # @see Defaults.extend_fixed_args_method
175
+ def self.use_fixed_defaults(scope, args, defaults, arity)
129
176
  unless arity.include? args.count
130
177
  fail ArgumentError, "#{args.count} given, (#{arity}) expected"
131
178
  end
132
179
 
133
180
  defaults[args.size - arity.min...defaults.size].each do |arg|
134
- args << arg
181
+ args << value!(arg, scope, args)
135
182
  end
136
183
  end
137
184
  end
@@ -1,5 +1,6 @@
1
1
  require_relative 'as'
2
2
  require_relative 'defaults'
3
+ require_relative 'returning'
3
4
  require_relative 'single'
4
5
 
5
6
  module DbMod
@@ -14,10 +15,15 @@ module DbMod
14
15
  # Creates a new configuration object to be used as the scope for
15
16
  # blocks passed to +def_statement+ and +def_prepared+ declarations.
16
17
  #
18
+ # @param args [*] one or more +MethodConfiguration+ objects or hashes
19
+ # from which existing settings will be merged
20
+ # @param block [proc] block containing method configuration declaration
17
21
  # @yield executes the block using +self+ as scope
18
- def initialize(&block)
22
+ def initialize(*args, &block)
19
23
  @settings = {}
20
24
  instance_exec(&block) if block_given?
25
+
26
+ merge_settings(args)
21
27
  end
22
28
 
23
29
  # Extend the method by converting results into a given
@@ -25,6 +31,8 @@ module DbMod
25
31
  # under {DbMod::Statements::Configuration::As}.
26
32
  #
27
33
  # @param type [:csv,:json] output format for the method
34
+ # may be set to +nil+ or +false+ to un-set any
35
+ # inherited setting
28
36
  # @return [self]
29
37
  def as(type)
30
38
  one_of! type, Configuration::As::COERCERS
@@ -40,6 +48,8 @@ module DbMod
40
48
  # more details.
41
49
  #
42
50
  # @param type [Symbol] see {Configuration::Single::COERCERS}
51
+ # may be set to +nil+ or +false+ to un-set any
52
+ # inherited setting
43
53
  # @return [self]
44
54
  def single(type)
45
55
  one_of! type, Configuration::Single::COERCERS
@@ -57,8 +67,17 @@ module DbMod
57
67
  # applied to the right-hand side of the argument list,
58
68
  # as with normal parameter default rules.
59
69
  #
70
+ # In place of a fixed default value, a lambda +Proc+
71
+ # may be supplied. In this case the proc will be executed,
72
+ # given the partially constructed argument list/hash and
73
+ # scoped against the instance variable where the prepared
74
+ # or statement method is defined. It should return a single
75
+ # value to be used for that particular execution of the
76
+ # method.
77
+ #
60
78
  # @param defaults [Hash<Symbol,value>,Array<value>]
61
79
  # default parameter values
80
+ # @return [self]
62
81
  def defaults(*defaults)
63
82
  if defaults.size == 1 && defaults.first.is_a?(Hash)
64
83
  defaults = defaults.first
@@ -71,6 +90,24 @@ module DbMod
71
90
  self
72
91
  end
73
92
 
93
+ # Declares a block that will be used to transform or replace
94
+ # the SQL result set before it is returned from the defined
95
+ # method. The block should accept a single parameter and can
96
+ # return pretty much whatever it wants.
97
+ #
98
+ # The block will be applied after any transforms specified by
99
+ # {#as} or {#single} have already been applied.
100
+ #
101
+ # @param block [Proc] block to be executed on the method's result set
102
+ # @return [self]
103
+ def returning(&block)
104
+ fail ArgumentError, 'block required' unless block_given?
105
+
106
+ set_once! :returning, block
107
+
108
+ self
109
+ end
110
+
74
111
  # Return all given settings in a hash.
75
112
  # @return [Hash]
76
113
  def to_hash
@@ -79,12 +116,47 @@ module DbMod
79
116
 
80
117
  private
81
118
 
119
+ # Merge settings from constructor arguments. Allowed arguments
120
+ # are hashes, other {MethodConfiguration} objects, or procs that
121
+ # will be executed with a {MethodConfiguration} object as the
122
+ # scope.
123
+ #
124
+ # @param args [Array] array of objects containing method
125
+ # configuration settings
126
+ # @return [Hash] == `@settings`
127
+ # @raise [ArgumentError] if any args are invalid (see {#arg_to_hash})
128
+ def merge_settings(args)
129
+ inherited_settings = {}
130
+ args.each do |arg|
131
+ inherited_settings.merge! arg_to_hash arg
132
+ end
133
+
134
+ @settings = inherited_settings.merge @settings
135
+ end
136
+
137
+ # Convert a single constructor argument into a hash of settings
138
+ # that may be merged into this object's settings hash.
139
+ #
140
+ # @param arg [Object] see {#merge_settings}
141
+ # @return [Hash] a hash of settings derived from the object
142
+ # @raise [ArgumentError] if an unexpected argement is encountered
143
+ # @see #merge_settings
144
+ def arg_to_hash(arg)
145
+ return arg if arg.is_a? Hash
146
+ return arg.to_hash if arg.is_a? MethodConfiguration
147
+ return MethodConfiguration.new(&arg).to_hash if arg.is_a? Proc
148
+
149
+ fail ArgumentError, "unknown method setting #{arg.inspect}"
150
+ end
151
+
82
152
  # Guard method which asserts that a configuration method
83
153
  # may not be called more than once, or else raises
84
154
  # {DbMod::Exceptions::BadMethodConfiguration}.
85
155
  #
86
156
  # @param setting [Symbol] setting name
87
157
  # @param value [Object] setting value
158
+ # @raise [Exceptions::BadMethodConfiguration] if the settings has
159
+ # already been set
88
160
  def set_once!(setting, value)
89
161
  if @settings.key? setting
90
162
  fail Exceptions::BadMethodConfiguration, "#{setting} already called"
@@ -98,6 +170,7 @@ module DbMod
98
170
  #
99
171
  # @param value [key] configuration setting
100
172
  # @param allowed [Hash] set of allowed configuration settings
173
+ # @raise [ArgumentError] if the value is not allowed
101
174
  def one_of!(value, allowed)
102
175
  return if allowed.key? value
103
176
 
@@ -0,0 +1,31 @@
1
+ module DbMod
2
+ module Statements
3
+ module Configuration
4
+ # Provides functionality backing the {MethodConfiguration#returning}
5
+ # setting. Allows a block to be declared that may perform additional
6
+ # processing on the SQL result set (the result of whatever other
7
+ # result transformations have been specified using
8
+ # {MethodConfiguration#as} or {MethodConfiguration#single}), and
9
+ # which may transform or replace entirely the method return value.
10
+ #
11
+ # def_statement(:csv_email, 'SELECT * FROM foo') do
12
+ # as(:csv)
13
+ # returning { |csv| build_email(csv) }
14
+ # end
15
+ module Returning
16
+ # Extend the given method definition with additional result
17
+ # coercion, if specified using {MethodConfiguration#returning}.
18
+ #
19
+ # @param definition [Proc] base method definition
20
+ # @param config [MethodConfiguration] method configuration
21
+ # @return [Proc] wrapped method definition, or the original
22
+ # definition if no coercion has been specified
23
+ def self.extend(definition, config)
24
+ return definition unless config.key? :returning
25
+
26
+ Configuration.attach_result_processor definition, config[:returning]
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -41,10 +41,12 @@ module DbMod
41
41
  }
42
42
 
43
43
  # Extend the given method definition with additional
44
- # result coercion.
44
+ # result coercion, if specified using {MethodConfiguration#single.
45
45
  #
46
46
  # @param definition [Proc] base method definition
47
47
  # @param config [MethodConfiguration] method configuration
48
+ # @return [Proc] wrapped method definition, or the original
49
+ # definition if no coercion has been specified
48
50
  def self.extend(definition, config)
49
51
  type = config[:single]
50
52
  return definition if type.nil?
@@ -0,0 +1,81 @@
1
+ require_relative 'configuration/method_configuration'
2
+
3
+ module DbMod
4
+ module Statements
5
+ # Allows modules to declare a block of default settings that
6
+ # will be applied to all methods declared in the module using
7
+ # +def_prepared+ or +def_statement+. These default settings will
8
+ # be used for all methods in the module where no overriding
9
+ # settings are declared with the method definition.
10
+ #
11
+ # module JsonAccessors
12
+ # include DbMod
13
+ #
14
+ # default_method_settings do
15
+ # single(:row).as(:json).returning { |json| do_whatever(json) }
16
+ # end
17
+ #
18
+ # # Normal access to instance scope for `returning`
19
+ # def do_whatever(thing)
20
+ # # ...
21
+ # end
22
+ #
23
+ # def_prepared(:foo, 'SELECT * FROM foo WHERE id = $1')
24
+ #
25
+ # def_prepared(:bar, 'SELECT * FROM bar WHERE id = $1')
26
+ #
27
+ # # Overrides can be provided for any setting
28
+ # def_prepared(:all_foos, 'SELECT * FROM foo') { single(false) }
29
+ # def_prepared(:csv_foo, 'SELECT * FROM foo WHERE id = $1') do
30
+ # as(:csv)
31
+ # end
32
+ # end
33
+ #
34
+ # Existing {Configuration::MethodConfiguration} objects may be
35
+ # passed directly to +default_method_settings+ instead of supplying
36
+ # a block. This allows configurations to be reused between modules.
37
+ #
38
+ # SETTINGS = DbMod::Statements::Configuration::MethodConfiguration.new do
39
+ # single(:row).as(:json)
40
+ # end
41
+ #
42
+ # module A
43
+ # include DbMod
44
+ #
45
+ # default_method_settings(SETTINGS)
46
+ #
47
+ # # ...
48
+ #
49
+ # end
50
+ #
51
+ # module B
52
+ # include A
53
+ #
54
+ # # This also works
55
+ # default_method_settings(A.default_method_settings)
56
+ #
57
+ # # ...
58
+ #
59
+ # end
60
+ module DefaultMethodSettings
61
+ # Defines a module-specific +default_method_settings+ function
62
+ # for a module that has just had {DbMod} included.
63
+ #
64
+ # @param mod [Module] module including {DbMod}
65
+ # @see DbMod.included
66
+ def self.setup(mod)
67
+ class << mod
68
+ define_method(:default_method_settings) do |*args, &block|
69
+ unless args.any? || block
70
+ return @default_method_settings ||=
71
+ Configuration::MethodConfiguration.new
72
+ end
73
+
74
+ @default_method_settings =
75
+ Configuration::MethodConfiguration.new(*args, &block)
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -30,6 +30,7 @@ module DbMod
30
30
  #
31
31
  # @param count [Fixnum] arity of the method being called.
32
32
  # @param args [Array] list of arguments given.
33
+ # @raise [ArgumentError] if the wrong number of arguments is given
33
34
  def self.valid_fixed_args!(count, args)
34
35
  unless args.size == count
35
36
  fail ArgumentError, "#{args.size} args given, #{count} expected"
@@ -51,6 +52,8 @@ module DbMod
51
52
  # @param sql [String] statement to prepare
52
53
  # @return [Fixnum,Array<Symbol>] description of
53
54
  # prepared statement's parameters
55
+ # @raise [ArgumentError] if there is any sort of problem
56
+ # with the parameters declared in the sql statement
54
57
  def self.parse_params!(sql)
55
58
  Parameters.valid_sql_params! sql
56
59
  numbered = sql.scan NUMBERED_PARAM
@@ -81,6 +84,8 @@ module DbMod
81
84
  # Raises +ArgumentError+ otherwise.
82
85
  #
83
86
  # @param args [Array<Hash<Symbol>>] method arguments being validated
87
+ # @raise [ArgumentError] if the arguments passed to the method
88
+ # do not pass validation
84
89
  def self.wrapped_hash!(args)
85
90
  unless args.size == 1
86
91
  fail ArgumentError, "unexpected arguments: #{args.inspect}"
@@ -99,6 +104,7 @@ module DbMod
99
104
  # @param expected [Array<Symbol>] the parameters expected to be present
100
105
  # @param args [Hash] given parameters
101
106
  # @return [Array] values to be passed to the prepared statement
107
+ # @raise [ArgumentError] if any arguments are missing
102
108
  def self.parameter_array(expected, args)
103
109
  expected.map do |arg|
104
110
  fail(ArgumentError, "missing arg #{arg}") unless args.key? arg
@@ -110,6 +116,11 @@ module DbMod
110
116
  # Fails if any parameters in an sql query aren't
111
117
  # in the expected format. They must either be
112
118
  # lower_case_a_to_z or digits only.
119
+ #
120
+ # @param sql [String] sql statement that may or may
121
+ # not contain parameters
122
+ # @raise [ArgumentError] if there are any invalid
123
+ # parameter declarations
113
124
  def self.valid_sql_params!(sql)
114
125
  sql.scan(/\$[A-Za-z0-9_]+/) do |param|
115
126
  unless param =~ NAMED_OR_NUMBERED
@@ -123,6 +134,8 @@ module DbMod
123
134
  #
124
135
  # @param params [Array<String>] '$1','$2', etc...
125
136
  # @return [Fixnum] parameter count
137
+ # @raise [ArgumentError] if there are any problems with the
138
+ # given argument list
126
139
  def self.parse_numbered_params!(params)
127
140
  params.sort!
128
141
  params.uniq!