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
@@ -0,0 +1,109 @@
|
|
1
|
+
require_relative 'as'
|
2
|
+
require_relative 'defaults'
|
3
|
+
require_relative 'single'
|
4
|
+
|
5
|
+
module DbMod
|
6
|
+
module Statements
|
7
|
+
module Configuration
|
8
|
+
# Collects settings given at time of definition for statement
|
9
|
+
# and prepared methods. If a block is passed to +def_statement+
|
10
|
+
# or +def_prepared+ it will be evaluated using an instance of
|
11
|
+
# this class, allowing methods such as {#as} or {#single} to
|
12
|
+
# be used to shape the behaviour of the defined method.
|
13
|
+
class MethodConfiguration
|
14
|
+
# Creates a new configuration object to be used as the scope for
|
15
|
+
# blocks passed to +def_statement+ and +def_prepared+ declarations.
|
16
|
+
#
|
17
|
+
# @yield executes the block using +self+ as scope
|
18
|
+
def initialize(&block)
|
19
|
+
@settings = {}
|
20
|
+
instance_exec(&block) if block_given?
|
21
|
+
end
|
22
|
+
|
23
|
+
# Extend the method by converting results into a given
|
24
|
+
# format, using one of the coercion methods defined
|
25
|
+
# under {DbMod::Statements::Configuration::As}.
|
26
|
+
#
|
27
|
+
# @param type [:csv,:json] output format for the method
|
28
|
+
# @return [self]
|
29
|
+
def as(type)
|
30
|
+
one_of! type, Configuration::As::COERCERS
|
31
|
+
set_once! :as, type
|
32
|
+
|
33
|
+
self
|
34
|
+
end
|
35
|
+
|
36
|
+
# Extend the method by extracting a singular part of
|
37
|
+
# the result set, for queries expected to only return
|
38
|
+
# one row, one column, or one row with a single value.
|
39
|
+
# See {DbMod::Statements::Configuration::Single} for
|
40
|
+
# more details.
|
41
|
+
#
|
42
|
+
# @param type [Symbol] see {Configuration::Single::COERCERS}
|
43
|
+
# @return [self]
|
44
|
+
def single(type)
|
45
|
+
one_of! type, Configuration::Single::COERCERS
|
46
|
+
set_once! :single, type
|
47
|
+
|
48
|
+
self
|
49
|
+
end
|
50
|
+
|
51
|
+
# Declares default values for method parameters.
|
52
|
+
# For methods with named parameters, a hash of argument
|
53
|
+
# names and default values should be provided.
|
54
|
+
# For methods with indexed parameters, an array of 1..n
|
55
|
+
# default values should be provided, where n is the
|
56
|
+
# method's arity. In this case default values will be
|
57
|
+
# applied to the right-hand side of the argument list,
|
58
|
+
# as with normal parameter default rules.
|
59
|
+
#
|
60
|
+
# @param defaults [Hash<Symbol,value>,Array<value>]
|
61
|
+
# default parameter values
|
62
|
+
def defaults(*defaults)
|
63
|
+
if defaults.size == 1 && defaults.first.is_a?(Hash)
|
64
|
+
defaults = defaults.first
|
65
|
+
elsif defaults.last.is_a? Hash
|
66
|
+
fail ArgumentError, 'mixed default declaration not allowed'
|
67
|
+
end
|
68
|
+
|
69
|
+
set_once! :defaults, defaults
|
70
|
+
|
71
|
+
self
|
72
|
+
end
|
73
|
+
|
74
|
+
# Return all given settings in a hash.
|
75
|
+
# @return [Hash]
|
76
|
+
def to_hash
|
77
|
+
@settings
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
# Guard method which asserts that a configuration method
|
83
|
+
# may not be called more than once, or else raises
|
84
|
+
# {DbMod::Exceptions::BadMethodConfiguration}.
|
85
|
+
#
|
86
|
+
# @param setting [Symbol] setting name
|
87
|
+
# @param value [Object] setting value
|
88
|
+
def set_once!(setting, value)
|
89
|
+
if @settings.key? setting
|
90
|
+
fail Exceptions::BadMethodConfiguration, "#{setting} already called"
|
91
|
+
end
|
92
|
+
|
93
|
+
@settings[setting] = value
|
94
|
+
end
|
95
|
+
|
96
|
+
# Guard method which asserts that a configuration setting
|
97
|
+
# is one of the allowed values in the given hash.
|
98
|
+
#
|
99
|
+
# @param value [key] configuration setting
|
100
|
+
# @param allowed [Hash] set of allowed configuration settings
|
101
|
+
def one_of!(value, allowed)
|
102
|
+
return if allowed.key? value
|
103
|
+
|
104
|
+
fail ArgumentError, "#{value} not in #{allowed.keys.join ', '}"
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -10,17 +10,17 @@ module DbMod
|
|
10
10
|
# Provides convenience extensions for statement and
|
11
11
|
# prepared methods that return only a single result,
|
12
12
|
# row, or column. The normal way to access this functionality
|
13
|
-
# is via {
|
13
|
+
# is via {MethodConfiguration#single}, which is available
|
14
14
|
# when defining a statement method or prepared method:
|
15
15
|
#
|
16
|
-
# def_statement(:a, 'SELECT name FROM a WHERE id=$1')
|
17
|
-
# def_prepared(:b, 'SELECT id FROM b WHERE
|
18
|
-
# def_prepared(:c, 'SELECT * FROM c WHERE id = $id')
|
16
|
+
# def_statement(:a, 'SELECT name FROM a WHERE id=$1') { single(:value) }
|
17
|
+
# def_prepared(:b, 'SELECT id FROM b WHERE x > $y') { single(:column) }
|
18
|
+
# def_prepared(:c, 'SELECT * FROM c WHERE id = $id') { single(:row) }
|
19
19
|
#
|
20
20
|
# def do_stuff
|
21
|
-
# a
|
22
|
-
# b # => ['1','2','3',...]
|
23
|
-
# c # => Hash
|
21
|
+
# a(1) # => "foo"
|
22
|
+
# b(y: 2) # => ['1','2','3',...]
|
23
|
+
# c id: 3 # => Hash
|
24
24
|
# end
|
25
25
|
#
|
26
26
|
# +.single(:row)+ and +.single(:value)+ will return the first
|
@@ -30,10 +30,7 @@ module DbMod
|
|
30
30
|
# instead of returning +nil+, use +.single(:row!)+ or
|
31
31
|
# +.single(:value!)+.
|
32
32
|
module Single
|
33
|
-
#
|
34
|
-
Configuration = DbMod::Statements::Configuration
|
35
|
-
|
36
|
-
# List of allowed parameters for {#single},
|
33
|
+
# List of allowed parameters for {MethodConfiguration#single},
|
37
34
|
# and the methods used to process them.
|
38
35
|
COERCERS = {
|
39
36
|
value: Single::Value,
|
@@ -43,19 +40,16 @@ module DbMod
|
|
43
40
|
column: Single::Column
|
44
41
|
}
|
45
42
|
|
46
|
-
# Extend
|
47
|
-
#
|
48
|
-
# See above for more details.
|
43
|
+
# Extend the given method definition with additional
|
44
|
+
# result coercion.
|
49
45
|
#
|
50
|
-
# @param
|
51
|
-
# @param
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
fail ArgumentError, "#{type} not in #{COERCERS.keys.join ', '}"
|
56
|
-
end
|
46
|
+
# @param definition [Proc] base method definition
|
47
|
+
# @param config [MethodConfiguration] method configuration
|
48
|
+
def self.extend(definition, config)
|
49
|
+
type = config[:single]
|
50
|
+
return definition if type.nil?
|
57
51
|
|
58
|
-
Configuration.
|
52
|
+
Configuration.attach_result_processor definition, COERCERS[type]
|
59
53
|
end
|
60
54
|
end
|
61
55
|
end
|
@@ -13,7 +13,7 @@ module DbMod
|
|
13
13
|
# end
|
14
14
|
module Column
|
15
15
|
# Enables this module to be passed to
|
16
|
-
# {DbMod::Statements::Configuration.
|
16
|
+
# {DbMod::Statements::Configuration.attach_result_processor} as the
|
17
17
|
# +wrapper+ function, where it will return an array of the first
|
18
18
|
# value from every row in the result set.
|
19
19
|
#
|
@@ -14,7 +14,7 @@ module DbMod
|
|
14
14
|
# end
|
15
15
|
module RequiredRow
|
16
16
|
# Enables this module to be passed to
|
17
|
-
# {DbMod::Statements::Configuration.
|
17
|
+
# {DbMod::Statements::Configuration.attach_result_processor} as the
|
18
18
|
# +wrapper+ function, where it will return the first row of the
|
19
19
|
# result set, or raise an exception if exactly one row is not
|
20
20
|
# returned.
|
@@ -14,7 +14,7 @@ module DbMod
|
|
14
14
|
# end
|
15
15
|
module RequiredValue
|
16
16
|
# Enables this module to be passed to
|
17
|
-
# {DbMod::Statements::Configuration.
|
17
|
+
# {DbMod::Statements::Configuration.attach_result_processor} as the
|
18
18
|
# +wrapper+ function, where it will return the first column of the
|
19
19
|
# first row of the result set, or fail if anything other than
|
20
20
|
# exactly one row is returned.
|
@@ -14,7 +14,7 @@ module DbMod
|
|
14
14
|
# end
|
15
15
|
module Row
|
16
16
|
# Enables this module to be passed to
|
17
|
-
# {DbMod::Statements::Configuration.
|
17
|
+
# {DbMod::Statements::Configuration.attach_result_processor} as the
|
18
18
|
# +wrapper+ function, where it will return the first row of the
|
19
19
|
# result set, or +nil+ if the result set is empty.
|
20
20
|
#
|
@@ -13,7 +13,7 @@ module DbMod
|
|
13
13
|
# end
|
14
14
|
module Value
|
15
15
|
# Enables this module to be passed to
|
16
|
-
# {DbMod::Statements::Configuration.
|
16
|
+
# {DbMod::Statements::Configuration.attach_result_processor} as the
|
17
17
|
# +wrapper+ function, where it will return the first column of the
|
18
18
|
# first row of the result set, or +nil+ if no results are returned.
|
19
19
|
#
|
@@ -101,7 +101,9 @@ module DbMod
|
|
101
101
|
# @return [Array] values to be passed to the prepared statement
|
102
102
|
def self.parameter_array(expected, args)
|
103
103
|
expected.map do |arg|
|
104
|
-
|
104
|
+
fail(ArgumentError, "missing arg #{arg}") unless args.key? arg
|
105
|
+
|
106
|
+
args[arg]
|
105
107
|
end
|
106
108
|
end
|
107
109
|
|
@@ -65,13 +65,13 @@ module DbMod
|
|
65
65
|
# @param mod [Module] a module with {DbMod} included
|
66
66
|
def self.define_def_prepared(mod)
|
67
67
|
mod.class.instance_eval do
|
68
|
-
define_method(:def_prepared) do |name, sql|
|
68
|
+
define_method(:def_prepared) do |name, sql, &block|
|
69
69
|
sql = sql.dup
|
70
70
|
name = name.to_sym
|
71
71
|
|
72
72
|
params = Parameters.parse_params! sql
|
73
73
|
prepared_statements[name] = sql
|
74
|
-
Prepared.define_prepared_method(mod, name, params)
|
74
|
+
Prepared.define_prepared_method(mod, name, params, &block)
|
75
75
|
end
|
76
76
|
end
|
77
77
|
end
|
@@ -119,15 +119,13 @@ module DbMod
|
|
119
119
|
# @param params [Fixnum,Array<Symbol>]
|
120
120
|
# expected parameter count, or a list of argument names.
|
121
121
|
# An empty array produces a no-argument method.
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
define_named_args_prepared_method(mod, name, params)
|
128
|
-
end
|
122
|
+
# @yield dsl block may be passed, which will be evaluated using a
|
123
|
+
# {Configuration::MethodConfiguration} object as scope
|
124
|
+
def self.define_prepared_method(mod, name, params, &block)
|
125
|
+
if params == []
|
126
|
+
define_no_args_prepared_method(mod, name, &block)
|
129
127
|
else
|
130
|
-
|
128
|
+
define_prepared_method_with_args(mod, name, params, &block)
|
131
129
|
end
|
132
130
|
end
|
133
131
|
|
@@ -139,48 +137,22 @@ module DbMod
|
|
139
137
|
# where the method will be defined
|
140
138
|
# @param name [Symbol] name of the method to be defined
|
141
139
|
# and the prepared query to be called.
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
# Define a method with the given name that accepts the
|
148
|
-
# given set of named parameters, that will call the prepared
|
149
|
-
# statement with the same name.
|
150
|
-
#
|
151
|
-
# @param mod [Module] {DbMod} enabled module
|
152
|
-
# where the method will be defined
|
153
|
-
# @param name [Symbol] name of the method to be defined
|
154
|
-
# and the prepared query to be called.
|
155
|
-
# @param params [Array<Symbol>] list of parameter names
|
156
|
-
def self.define_named_args_prepared_method(mod, name, params)
|
157
|
-
method = lambda do |*args|
|
158
|
-
args = Parameters.valid_named_args! params, args
|
159
|
-
conn.exec_prepared(name.to_s, args)
|
160
|
-
end
|
161
|
-
|
162
|
-
Configuration.def_configurable mod, name, method
|
140
|
+
# @yield dsl method configuration object may be passed
|
141
|
+
def self.define_no_args_prepared_method(mod, name, &block)
|
142
|
+
method = ->(*) { conn.exec_prepared(name.to_s) }
|
143
|
+
Configuration.def_configurable mod, name, method, &block
|
163
144
|
end
|
164
145
|
|
165
|
-
# Define a method with the given name that accepts a fixed
|
166
|
-
# number of arguments, that will call the prepared statement
|
167
|
-
# with the same name.
|
168
|
-
#
|
169
146
|
# @param mod [Module] {DbMod} enabled module
|
170
147
|
# where the method will be defined
|
171
148
|
# @param name [Symbol] name of the method to be defined
|
172
149
|
# and the prepared query to be called.
|
173
|
-
# @param
|
174
|
-
#
|
175
|
-
#
|
176
|
-
def self.
|
177
|
-
method =
|
178
|
-
|
179
|
-
|
180
|
-
conn.exec_prepared(name.to_s, args)
|
181
|
-
end
|
182
|
-
|
183
|
-
Configuration.def_configurable(mod, name, method)
|
150
|
+
# @param params [Fixnum,Array<Symbol>]
|
151
|
+
# expected parameter count, or a list of argument names.
|
152
|
+
# An empty array produces a no-argument method.
|
153
|
+
def self.define_prepared_method_with_args(mod, name, params, &block)
|
154
|
+
method = ->(*args) { conn.exec_prepared(name.to_s, args) }
|
155
|
+
Configuration.def_configurable(mod, name, method, params, &block)
|
184
156
|
end
|
185
157
|
|
186
158
|
# Adds +prepared_statements+ to a module. This list of named
|
@@ -61,12 +61,12 @@ module DbMod
|
|
61
61
|
# @param mod [Module] a module with {DbMod} included
|
62
62
|
def self.define_def_statement(mod)
|
63
63
|
mod.class.instance_eval do
|
64
|
-
define_method(:def_statement) do |name, sql|
|
64
|
+
define_method(:def_statement) do |name, sql, &block|
|
65
65
|
sql = sql.dup
|
66
66
|
name = name.to_sym
|
67
67
|
|
68
68
|
params = Parameters.parse_params! sql
|
69
|
-
Statement.define_statement_method(mod, name, params, sql)
|
69
|
+
Statement.define_statement_method(mod, name, params, sql, &block)
|
70
70
|
end
|
71
71
|
end
|
72
72
|
end
|
@@ -80,62 +80,15 @@ module DbMod
|
|
80
80
|
# @param params [Fixnum,Array<Symbol>]
|
81
81
|
# expected parameter count, or a list of argument names.
|
82
82
|
# An empty array produces a no-argument method.
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
define_named_args_statement_method(mod, name, params, sql)
|
89
|
-
end
|
83
|
+
# @yield dsl block may be passed, which will be evaluated using a
|
84
|
+
# {Configuration::MethodConfiguration} object as scope
|
85
|
+
def self.define_statement_method(mod, name, params, sql, &block)
|
86
|
+
if params == []
|
87
|
+
Configuration.def_configurable(mod, name, ->(*) { query sql }, &block)
|
90
88
|
else
|
91
|
-
|
92
|
-
|
93
|
-
end
|
94
|
-
|
95
|
-
# Define a no-argument method with the given name
|
96
|
-
# that will execute the given sql statement and return
|
97
|
-
# the result.
|
98
|
-
#
|
99
|
-
# @param mod [Module] {DbMod} enabled module
|
100
|
-
# where the method will be defined
|
101
|
-
# @param name [Symbol] name of the method to be defined
|
102
|
-
# @param sql [String] parameterless SQL statement to execute
|
103
|
-
def self.define_no_args_statement_method(mod, name, sql)
|
104
|
-
Configuration.def_configurable mod, name, ->() { query(sql) }
|
105
|
-
end
|
106
|
-
|
107
|
-
# Define a method with the given name, that accepts the
|
108
|
-
# given set of named parameters that will be used to execute
|
109
|
-
# the given SQL query.
|
110
|
-
#
|
111
|
-
# @param mod [Module] {DbMod} enabled module
|
112
|
-
# @param name [Symbol] name of the method to be defined
|
113
|
-
# @param params [Array<Symbol>] parameter names and order
|
114
|
-
def self.define_named_args_statement_method(mod, name, params, sql)
|
115
|
-
method = lambda do |*args|
|
116
|
-
args = Parameters.valid_named_args! params, args
|
117
|
-
conn.exec_params(sql, args)
|
89
|
+
method = ->(*args) { conn.exec_params(sql, args) }
|
90
|
+
Configuration.def_configurable(mod, name, method, params, &block)
|
118
91
|
end
|
119
|
-
|
120
|
-
Configuration.def_configurable mod, name, method
|
121
|
-
end
|
122
|
-
|
123
|
-
# Define a method with the given name that accepts a fixed number
|
124
|
-
# of arguments, that will be used to execute the given SQL query.
|
125
|
-
#
|
126
|
-
# @param mod [Module] {DbMod} enabled module where the method
|
127
|
-
# will be defined
|
128
|
-
# @param name [Symbol] name of the method to be defined
|
129
|
-
# @param count [Fixnum] arity of the defined method,
|
130
|
-
# the number of parameters that the SQL statement requires
|
131
|
-
def self.define_fixed_args_statement_method(mod, name, count, sql)
|
132
|
-
method = lambda do |*args|
|
133
|
-
Parameters.valid_fixed_args!(count, args)
|
134
|
-
|
135
|
-
conn.exec_params(sql, args)
|
136
|
-
end
|
137
|
-
|
138
|
-
Configuration.def_configurable mod, name, method
|
139
92
|
end
|
140
93
|
end
|
141
94
|
end
|
data/lib/db_mod/version.rb
CHANGED
@@ -6,8 +6,8 @@ describe DbMod::Statements::Configuration::As::Csv do
|
|
6
6
|
Module.new do
|
7
7
|
include DbMod
|
8
8
|
|
9
|
-
def_statement(:statement, 'SELECT a, b FROM foo')
|
10
|
-
def_prepared(:prepared, 'SELECT a, b FROM bar')
|
9
|
+
def_statement(:statement, 'SELECT a, b FROM foo') { as(:csv) }
|
10
|
+
def_prepared(:prepared, 'SELECT a, b FROM bar') { as(:csv) }
|
11
11
|
end.create(db: 'testdb')
|
12
12
|
end
|
13
13
|
|
@@ -6,8 +6,20 @@ describe DbMod::Statements::Configuration::As::Json do
|
|
6
6
|
Module.new do
|
7
7
|
include DbMod
|
8
8
|
|
9
|
-
def_statement(:statement, 'SELECT a, b FROM foo')
|
10
|
-
def_prepared(:prepared, 'SELECT a, b FROM bar')
|
9
|
+
def_statement(:statement, 'SELECT a, b FROM foo') { as(:json) }
|
10
|
+
def_prepared(:prepared, 'SELECT a, b FROM bar') { as(:json) }
|
11
|
+
|
12
|
+
def_statement(
|
13
|
+
:single,
|
14
|
+
'SELECT * FROM foo WHERE a = $1'
|
15
|
+
) { single(:row).as(:json) }
|
16
|
+
|
17
|
+
def_statement(
|
18
|
+
:single!,
|
19
|
+
'SELECT * FROM foo WHERE a = $1'
|
20
|
+
) { single(:row!).as(:json) }
|
21
|
+
|
22
|
+
def_statement(:col, 'SELECT a FROM foo') { single(:column).as(:json) }
|
11
23
|
end.create(db: 'testdb')
|
12
24
|
end
|
13
25
|
|
@@ -35,4 +47,19 @@ describe DbMod::Statements::Configuration::As::Json do
|
|
35
47
|
end
|
36
48
|
end
|
37
49
|
end
|
50
|
+
|
51
|
+
it 'can be chained with single(:row)' do
|
52
|
+
result = [{ 'a' => '1', 'b' => '2' }]
|
53
|
+
expected = '{"a":"1","b":"2"}'
|
54
|
+
|
55
|
+
expect(@conn).to receive(:exec_params).exactly(2).times.and_return(result)
|
56
|
+
expect(subject.single(1)).to eq(expected)
|
57
|
+
expect(subject.single!(2)).to eq(expected)
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'can be chained with single(:column)' do
|
61
|
+
result = [{ 'a' => '1' }, { 'a' => '2' }, { 'a' => '3' }]
|
62
|
+
expect(@conn).to receive(:query).and_return(result)
|
63
|
+
expect(subject.col).to eq('["1","2","3"]')
|
64
|
+
end
|
38
65
|
end
|