db_mod 0.0.5 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -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!