db_mod 0.0.3 → 0.0.4
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.
- checksums.yaml +4 -4
- data/.yardopts +1 -0
- data/CHANGELOG.md +7 -1
- data/README.md +53 -5
- data/lib/db_mod/exceptions/bad_method_configuration.rb +11 -0
- data/lib/db_mod/exceptions/no_results.rb +12 -0
- data/lib/db_mod/exceptions/too_many_results.rb +12 -0
- data/lib/db_mod/exceptions.rb +4 -1
- data/lib/db_mod/statements/configuration/as/csv.rb +39 -0
- data/lib/db_mod/statements/configuration/as/json.rb +33 -0
- data/lib/db_mod/statements/configuration/as.rb +47 -0
- data/lib/db_mod/statements/configuration/configurable_method.rb +78 -0
- data/lib/db_mod/statements/configuration/single/column.rb +29 -0
- data/lib/db_mod/statements/configuration/single/required_row.rb +35 -0
- data/lib/db_mod/statements/configuration/single/required_value.rb +35 -0
- data/lib/db_mod/statements/configuration/single/row.rb +33 -0
- data/lib/db_mod/statements/configuration/single/value.rb +32 -0
- data/lib/db_mod/statements/configuration/single.rb +63 -0
- data/lib/db_mod/statements/configuration.rb +48 -0
- data/lib/db_mod/statements/parameters.rb +1 -1
- data/lib/db_mod/statements/prepared.rb +6 -11
- data/lib/db_mod/statements/statement.rb +28 -10
- data/lib/db_mod/statements.rb +1 -38
- data/lib/db_mod/version.rb +1 -1
- data/lib/db_mod.rb +4 -2
- data/spec/db_mod/{as → statements/configuration/as}/csv_spec.rb +7 -6
- data/spec/db_mod/statements/configuration/as/json_spec.rb +38 -0
- data/spec/db_mod/statements/configuration/as_spec.rb +24 -0
- data/spec/db_mod/statements/configuration/single_spec.rb +106 -0
- metadata +25 -9
- data/lib/db_mod/as/csv.rb +0 -37
- data/lib/db_mod/as.rb +0 -38
- data/lib/db_mod/statements/configurable_method.rb +0 -41
- data/spec/db_mod/as_spec.rb +0 -14
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9f9ed4ad3824fbe0a2b09444c5aa9b4dde35ee94
|
4
|
+
data.tar.gz: 4a70f5c7416895da2fb5cc58608d5aaabeb610ce
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 22b0da036c7e04ee7f70d21c79a5dcb70db35895485ee8b1a9bae150a258be8a5c529023c0b71c1f6e74c80ddb911d8f0915d4ef341db88286c48bfaab8bf182
|
7
|
+
data.tar.gz: 23a8967663138b1b10d83d994b7b397dd00b8c536663d1a58b1227f6434a47334d7f1d77fcb58a86048d5bfcdbf30e8ab6800eb818d9f37116083cce35b2c0e1
|
data/.yardopts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--private --protected lib/**/*.rb - *.md LICENSE
|
data/CHANGELOG.md
CHANGED
@@ -1,7 +1,13 @@
|
|
1
|
+
0.0.4 (2015-10-15)
|
2
|
+
==================
|
3
|
+
|
4
|
+
* Adds `def_prepared/statement.single(:value/:row/:column)` - [@dslh](https://github.com/dslh).
|
5
|
+
* Adds `.as(:json)` to go with `.as(:csv)` - [@dslh](https://github.com/dslh).
|
6
|
+
|
1
7
|
0.0.3 (2015-10-13)
|
2
8
|
==================
|
3
9
|
|
4
|
-
* Configurable method framework. Adds `def_prepared/statement.as(:csv)` - [@dslh](https://github.com/dslh)
|
10
|
+
* Configurable method framework. Adds `def_prepared/statement.as(:csv)` - [@dslh](https://github.com/dslh).
|
5
11
|
|
6
12
|
0.0.2 (2015-10-12)
|
7
13
|
==================
|
data/README.md
CHANGED
@@ -2,6 +2,13 @@
|
|
2
2
|
|
3
3
|
Database enabled modules for ruby.
|
4
4
|
|
5
|
+
[](https://github.com/dslh/db_mod)
|
6
|
+
[](https://rubygems.org/gems/db_mod)
|
7
|
+
[](https://travis-ci.org/dslh/db_mod)
|
8
|
+

|
9
|
+
|
10
|
+
[Rubydoc.info documentation](http://www.rubydoc.info/gems/db_mod)
|
11
|
+
|
5
12
|
## Description
|
6
13
|
|
7
14
|
The `db_mod` gem is a simple framework that helps you organise your
|
@@ -187,19 +194,60 @@ or twice during a program's execution.
|
|
187
194
|
#### Configuring defined statements
|
188
195
|
|
189
196
|
`db_mod` contains a simple framework for extending these statement methods
|
190
|
-
and prepared methods with additional
|
191
|
-
|
192
|
-
|
197
|
+
and prepared methods with additional result processing. A simple
|
198
|
+
chained-method syntax is used
|
199
|
+
|
200
|
+
##### JSON and CSV formatting
|
201
|
+
|
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
|
204
|
+
formatted as either a CSV document or an array of JSON objects, respectively.
|
193
205
|
|
194
206
|
```ruby
|
195
|
-
module
|
207
|
+
module Reports
|
196
208
|
include DbMod
|
197
209
|
|
198
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)
|
199
212
|
end
|
200
213
|
|
201
|
-
include
|
214
|
+
include Reports
|
202
215
|
db_connect db: 'testdb'
|
203
216
|
|
204
217
|
foo(id: 1) # => "a,b\n1,2\n3,4\n..."
|
218
|
+
bar(2) # => '[{"c":"5","d":"6"},...]'
|
219
|
+
```
|
220
|
+
|
221
|
+
##### Queries returning one row, column or value
|
222
|
+
|
223
|
+
To save a lot of repetetive unboxing of query results, methods that return
|
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.
|
226
|
+
|
227
|
+
```ruby
|
228
|
+
module Getters
|
229
|
+
include DbMod
|
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)
|
234
|
+
end
|
235
|
+
|
236
|
+
# ...
|
237
|
+
|
238
|
+
user(1) # => { "id" => "1", "name" => "username" }
|
239
|
+
name(1) # => "username"
|
240
|
+
ids # => ['1', '2', '3', ...]
|
241
|
+
```
|
242
|
+
|
243
|
+
When no results are returned, `:column` returns `[]` while `:row` and
|
244
|
+
`:value` will return `nil`. To raise an exception instead of returning
|
245
|
+
`nil`, use `:row!` and `:value!` instead.
|
246
|
+
|
247
|
+
```ruby
|
248
|
+
def_statement(:a, 'SELECT 1 WHERE true = false').single(:value)
|
249
|
+
def_statement(:b, 'SELECT 1 WHERE true = false').single(:value!)
|
250
|
+
|
251
|
+
a # => nil
|
252
|
+
b # => fail
|
205
253
|
```
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require_relative 'base'
|
2
|
+
|
3
|
+
module DbMod
|
4
|
+
module Exceptions
|
5
|
+
# Raised by a statement or prepared method that
|
6
|
+
# has been configured using {ConfigurableMethod#single},
|
7
|
+
# when a result set expected to contain at least one
|
8
|
+
# result does not.
|
9
|
+
class NoResults < Base
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require_relative 'base'
|
2
|
+
|
3
|
+
module DbMod
|
4
|
+
module Exceptions
|
5
|
+
# Raised by a statement or prepared method that
|
6
|
+
# has been configured using {ConfigurableMethod#single},
|
7
|
+
# when a result set expected to contain not more than
|
8
|
+
# one result, in fact, does.
|
9
|
+
class TooManyResults < Base
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
data/lib/db_mod/exceptions.rb
CHANGED
@@ -1,6 +1,9 @@
|
|
1
|
-
require_relative 'exceptions/connection_not_set'
|
2
1
|
require_relative 'exceptions/already_in_transaction'
|
2
|
+
require_relative 'exceptions/bad_method_configuration'
|
3
|
+
require_relative 'exceptions/connection_not_set'
|
3
4
|
require_relative 'exceptions/duplicate_statement_name'
|
5
|
+
require_relative 'exceptions/no_results'
|
6
|
+
require_relative 'exceptions/too_many_results'
|
4
7
|
|
5
8
|
module DbMod
|
6
9
|
# Non-standard errors raised by db_mod
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module DbMod
|
2
|
+
module Statements
|
3
|
+
module Configuration
|
4
|
+
module As
|
5
|
+
# Coercer which converts an SQL result set
|
6
|
+
# into a string formatted as a CSV document.
|
7
|
+
# May be enabled for a prepared method or
|
8
|
+
# statement method using +.as(:csv)+:
|
9
|
+
#
|
10
|
+
# def_statement(:a, 'SELECT a, b FROM foo').as(:csv)
|
11
|
+
# def_prepared(:b, 'SELECT b, c FROM bar').as(:csv)
|
12
|
+
#
|
13
|
+
# def do_stuff
|
14
|
+
# a # => "a,b\r\n1,2\r\n3,4\r\n..."
|
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
|
21
|
+
# from the result set.
|
22
|
+
#
|
23
|
+
# @param results [Object] SQL result set
|
24
|
+
# @return [String] a CSV formatted document
|
25
|
+
def self.call(results)
|
26
|
+
headers = nil
|
27
|
+
CSV.generate do |csv|
|
28
|
+
results.each do |row|
|
29
|
+
csv << (headers = row.keys) unless headers
|
30
|
+
|
31
|
+
csv << headers.map { |col| row[col] }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module DbMod
|
2
|
+
module Statements
|
3
|
+
module Configuration
|
4
|
+
module As
|
5
|
+
# Coercer which converts an SQL result set
|
6
|
+
# into a string formatted as a JSON array.
|
7
|
+
# May be enabled for a prepared method or
|
8
|
+
# statement method using +.as(:json)+:
|
9
|
+
#
|
10
|
+
# def_statement(:a, 'SELECT a, b FROM foo').as(:json)
|
11
|
+
# def_prepared(:b, 'SELECT b, c FROM bar').as(:json)
|
12
|
+
#
|
13
|
+
# def do_stuff
|
14
|
+
# a # => '[{"a":"x","b":"y"},...]'
|
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.
|
22
|
+
#
|
23
|
+
# @param results [Object] SQL result set
|
24
|
+
# @return [String] a JSON formatted string
|
25
|
+
def self.call(results)
|
26
|
+
# .map turns the result object into an array
|
27
|
+
results.map { |x| x }.to_json
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require_relative 'as/csv'
|
2
|
+
require_relative 'as/json'
|
3
|
+
|
4
|
+
module DbMod
|
5
|
+
module Statements
|
6
|
+
module Configuration
|
7
|
+
# Contains coercers and other functions that allow
|
8
|
+
# module instance methods returning an SQL result set
|
9
|
+
# to be extended with additional result coercion and
|
10
|
+
# formatting. The normal way to access this functionality
|
11
|
+
# is via {ConfigurableMethod#as},
|
12
|
+
# which is available when defining a statement method
|
13
|
+
# or prepared method:
|
14
|
+
#
|
15
|
+
# def_statement(:a, 'SELECT a, b, c FROM foo').as(:csv)
|
16
|
+
# def_prepared(:b, 'SELECT d, e, f FROM bar').as(:csv)
|
17
|
+
module As
|
18
|
+
# For extend_method
|
19
|
+
Configuration = DbMod::Statements::Configuration
|
20
|
+
|
21
|
+
# List of available result coercion methods.
|
22
|
+
# Only keys defined here are allowed as arguments
|
23
|
+
# to {DbMod::Statements::Configuration::ConfigurableMethod#as}.
|
24
|
+
COERCERS = {
|
25
|
+
csv: As::Csv,
|
26
|
+
json: As::Json
|
27
|
+
}
|
28
|
+
|
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.
|
33
|
+
#
|
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
|
41
|
+
|
42
|
+
Configuration.process_method_results(mod, name, COERCERS[type])
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require_relative 'as'
|
2
|
+
require_relative 'single'
|
3
|
+
|
4
|
+
module DbMod
|
5
|
+
module Statements
|
6
|
+
module Configuration
|
7
|
+
# Encapsulates a method that has just been defined
|
8
|
+
# via the dsl exposed in {DbMod::Statements} so that
|
9
|
+
# it can be extended with additional processing such
|
10
|
+
# as result coercion.
|
11
|
+
#
|
12
|
+
# The pattern here is something similar to rack's middleware.
|
13
|
+
# Calling any of the extension methods below will replace
|
14
|
+
# the original method defined by +def_prepared+ or +def_statement+
|
15
|
+
# with a wrapper function that may perform processing on given
|
16
|
+
# arguments, pass them to the original function, then perform
|
17
|
+
# additional processing on the result.
|
18
|
+
class ConfigurableMethod
|
19
|
+
# Encapsulate a method that has been newly defined
|
20
|
+
# by a {DbMod} dsl function, for additional configuration.
|
21
|
+
#
|
22
|
+
# @param mod [Module] the {DbMod} enabled module
|
23
|
+
# where the method was defined
|
24
|
+
# @param name [Symbol] the method name
|
25
|
+
def initialize(mod, name)
|
26
|
+
@mod = mod
|
27
|
+
@name = name
|
28
|
+
@already_called = {}
|
29
|
+
end
|
30
|
+
|
31
|
+
# Extend the method by converting results into a given
|
32
|
+
# format, using one of the coercion methods defined
|
33
|
+
# under {DbMod::Statements::Configuration::As}.
|
34
|
+
#
|
35
|
+
# @param type [:csv,:json] output format for the method
|
36
|
+
# @return [self]
|
37
|
+
def as(type)
|
38
|
+
called! :as
|
39
|
+
|
40
|
+
Configuration::As.extend_method(@mod, @name, type)
|
41
|
+
|
42
|
+
self
|
43
|
+
end
|
44
|
+
|
45
|
+
# Extend the method by extracting a singular part of
|
46
|
+
# the result set, for queries expected to only return
|
47
|
+
# one row, one column, or one row with a single value.
|
48
|
+
# See {DbMod::Statements::Configuration::Single} for
|
49
|
+
# more details.
|
50
|
+
#
|
51
|
+
# @param type [Symbol] see {SINGLE_TYPES}
|
52
|
+
# @return [self]
|
53
|
+
def single(type)
|
54
|
+
called! :single
|
55
|
+
|
56
|
+
Configuration::Single.extend_method(@mod, @name, type)
|
57
|
+
|
58
|
+
self
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
# Guard method which asserts that a configuration method
|
64
|
+
# may not be called more than once, or else raises
|
65
|
+
# {DbMod::Exceptions::BadMethodConfiguration}.
|
66
|
+
#
|
67
|
+
# @param method [Symbol] method being called
|
68
|
+
def called!(method)
|
69
|
+
if @already_called[method]
|
70
|
+
fail Exceptions::BadMethodConfiguration, "#{method} already called"
|
71
|
+
end
|
72
|
+
|
73
|
+
@already_called[method] = true
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module DbMod
|
2
|
+
module Statements
|
3
|
+
module Configuration
|
4
|
+
module Single
|
5
|
+
# Wrapper for a statement or prepared method that returns
|
6
|
+
# an array of the values from the first value of every row
|
7
|
+
# returned by the SQL statement.
|
8
|
+
#
|
9
|
+
# def_statement(:a, 'SELECT a FROM b').single(:column)
|
10
|
+
#
|
11
|
+
# def do_stuff
|
12
|
+
# a # => ['a', 'b', 'c']
|
13
|
+
# end
|
14
|
+
module Column
|
15
|
+
# Enables this module to be passed to
|
16
|
+
# {DbMod::Statements::Configuration.process_method_results} as the
|
17
|
+
# +wrapper+ function, where it will return an array of the first
|
18
|
+
# value from every row in the result set.
|
19
|
+
#
|
20
|
+
# @param results [Object] SQL result set
|
21
|
+
# @return [Array<String>] an array of values from the first column
|
22
|
+
def self.call(results)
|
23
|
+
results.map { |row| row[row.keys.first] }
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module DbMod
|
2
|
+
module Statements
|
3
|
+
module Configuration
|
4
|
+
module Single
|
5
|
+
# Wrapper for a statement or prepared method that returns
|
6
|
+
# only the first row of the result set as a hash, to save
|
7
|
+
# manual unboxing. Raises an error unless exactly one row
|
8
|
+
# is returned.
|
9
|
+
#
|
10
|
+
# def_statement(:a, 'SELECT a, b FROM foo').single(:row)
|
11
|
+
#
|
12
|
+
# def do_stuff
|
13
|
+
# a # => { 'a' => '1', 'b' => '2'
|
14
|
+
# end
|
15
|
+
module RequiredRow
|
16
|
+
# Enables this module to be passed to
|
17
|
+
# {DbMod::Statements::Configuration.process_method_results} as the
|
18
|
+
# +wrapper+ function, where it will return the first row of the
|
19
|
+
# result set, or raise an exception if exactly one row is not
|
20
|
+
# returned.
|
21
|
+
#
|
22
|
+
# @param results [Object] SQL result set
|
23
|
+
# @return [Hash<String,String>]
|
24
|
+
# the first row of the SQL result set returned by the query
|
25
|
+
def self.call(results)
|
26
|
+
fail DbMod::Exceptions::NoResults unless results.any?
|
27
|
+
fail DbMod::Exceptions::TooManyResults if results.count > 1
|
28
|
+
|
29
|
+
results[0]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module DbMod
|
2
|
+
module Statements
|
3
|
+
module Configuration
|
4
|
+
module Single
|
5
|
+
# Wrapper for a statement or prepared method that returns
|
6
|
+
# the first column of the first returned row. Strictly enforces
|
7
|
+
# that exactly one row should be returned by the SQL result, and
|
8
|
+
# will fail if zero or more than one row is returned.
|
9
|
+
#
|
10
|
+
# def_statement(:a, 'SELECT 1').single(:value!)
|
11
|
+
#
|
12
|
+
# def do_stuff
|
13
|
+
# a # => '1'
|
14
|
+
# end
|
15
|
+
module RequiredValue
|
16
|
+
# Enables this module to be passed to
|
17
|
+
# {DbMod::Statements::Configuration.process_method_results} as the
|
18
|
+
# +wrapper+ function, where it will return the first column of the
|
19
|
+
# first row of the result set, or fail if anything other than
|
20
|
+
# exactly one row is returned.
|
21
|
+
#
|
22
|
+
# @param results [Object] SQL result set
|
23
|
+
# @return [String] the first column of the first returned row
|
24
|
+
def self.call(results)
|
25
|
+
fail DbMod::Exceptions::NoResults unless results.any?
|
26
|
+
fail DbMod::Exceptions::TooManyResults if results.count > 1
|
27
|
+
|
28
|
+
row = results[0]
|
29
|
+
row[row.keys.first]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module DbMod
|
2
|
+
module Statements
|
3
|
+
module Configuration
|
4
|
+
module Single
|
5
|
+
# Wrapper for a statement or prepared method that returns
|
6
|
+
# only the first row of the result set as a hash, to save
|
7
|
+
# manual unboxing. Returns +nil+ if the query returns no
|
8
|
+
# results.
|
9
|
+
#
|
10
|
+
# def_statement(:a, 'SELECT a, b FROM foo').single(:row)
|
11
|
+
#
|
12
|
+
# def do_stuff
|
13
|
+
# a # => { 'a' => '1', 'b' => '2'
|
14
|
+
# end
|
15
|
+
module Row
|
16
|
+
# Enables this module to be passed to
|
17
|
+
# {DbMod::Statements::Configuration.process_method_results} as the
|
18
|
+
# +wrapper+ function, where it will return the first row of the
|
19
|
+
# result set, or +nil+ if the result set is empty.
|
20
|
+
#
|
21
|
+
# @param results [Object] SQL result set
|
22
|
+
# @return [Hash<String,String>,nil]
|
23
|
+
# the first row of the SQL result set returned by the query
|
24
|
+
def self.call(results)
|
25
|
+
return nil unless results.any?
|
26
|
+
|
27
|
+
results[0]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module DbMod
|
2
|
+
module Statements
|
3
|
+
module Configuration
|
4
|
+
module Single
|
5
|
+
# Wrapper for a statement or prepared method that
|
6
|
+
# returns the first column of the first returned row,
|
7
|
+
# or +nil+ if no rows are returned by the query.
|
8
|
+
#
|
9
|
+
# def_statement(:a, 'SELECT 1').single(:value)
|
10
|
+
#
|
11
|
+
# def do_stuff
|
12
|
+
# a # => '1'
|
13
|
+
# end
|
14
|
+
module Value
|
15
|
+
# Enables this module to be passed to
|
16
|
+
# {DbMod::Statements::Configuration.process_method_results} as the
|
17
|
+
# +wrapper+ function, where it will return the first column of the
|
18
|
+
# first row of the result set, or +nil+ if no results are returned.
|
19
|
+
#
|
20
|
+
# @param results [Object] SQL result set
|
21
|
+
# @return [String,nil] the first column of the first returned row
|
22
|
+
def self.call(results)
|
23
|
+
return nil unless results.any?
|
24
|
+
|
25
|
+
row = results[0]
|
26
|
+
row[row.keys.first]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require_relative 'single/value'
|
2
|
+
require_relative 'single/required_value'
|
3
|
+
require_relative 'single/row'
|
4
|
+
require_relative 'single/required_row'
|
5
|
+
require_relative 'single/column'
|
6
|
+
|
7
|
+
module DbMod
|
8
|
+
module Statements
|
9
|
+
module Configuration
|
10
|
+
# Provides convenience extensions for statement and
|
11
|
+
# prepared methods that return only a single result,
|
12
|
+
# row, or column. The normal way to access this functionality
|
13
|
+
# is via {ConfigurableMethod#single}, which is available
|
14
|
+
# when defining a statement method or prepared method:
|
15
|
+
#
|
16
|
+
# def_statement(:a, 'SELECT name FROM a WHERE id=$1').single(:value)
|
17
|
+
# def_prepared(:b, 'SELECT id FROM b WHERE value > $min').single(:column)
|
18
|
+
# def_prepared(:c, 'SELECT * FROM c WHERE id = $id').single(:row)
|
19
|
+
#
|
20
|
+
# def do_stuff
|
21
|
+
# a # => "foo"
|
22
|
+
# b # => ['1','2','3',...]
|
23
|
+
# c # => Hash
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# +.single(:row)+ and +.single(:value)+ will return the first
|
27
|
+
# row or the first value of the first row respectively, or +nil+
|
28
|
+
# if no results are found. To generate a
|
29
|
+
# {DbMod::Exceptions::NoResults} failure
|
30
|
+
# instead of returning +nil+, use +.single(:row!)+ or
|
31
|
+
# +.single(:value!)+.
|
32
|
+
module Single
|
33
|
+
# For process_method_results
|
34
|
+
Configuration = DbMod::Statements::Configuration
|
35
|
+
|
36
|
+
# List of allowed parameters for {#single},
|
37
|
+
# and the methods used to process them.
|
38
|
+
COERCERS = {
|
39
|
+
value: Single::Value,
|
40
|
+
value!: Single::RequiredValue,
|
41
|
+
row: Single::Row,
|
42
|
+
row!: Single::RequiredRow,
|
43
|
+
column: Single::Column
|
44
|
+
}
|
45
|
+
|
46
|
+
# Extend a method so that only some singular part of
|
47
|
+
# the SQL result set is returned.
|
48
|
+
# See above for more details.
|
49
|
+
#
|
50
|
+
# @param mod [Module] module where the method has been defined
|
51
|
+
# @param name [Symbol] method name
|
52
|
+
# @param type [Symbol] one of {SINGLE_TYPES}
|
53
|
+
def self.extend_method(mod, name, type)
|
54
|
+
unless COERCERS.key? type
|
55
|
+
fail ArgumentError, "#{type} not in #{COERCERS.keys.join ', '}"
|
56
|
+
end
|
57
|
+
|
58
|
+
Configuration.process_method_results(mod, name, COERCERS[type])
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module DbMod
|
2
|
+
module Statements
|
3
|
+
# Provides additional functionality to statement and
|
4
|
+
# prepared methods, allowing additional processing of
|
5
|
+
# arguments and results using the dsl extensions
|
6
|
+
# exposed via {ConfigurableMethod}.
|
7
|
+
module Configuration
|
8
|
+
# Used by submodules to when defining a method as declared by
|
9
|
+
# +def_statement+ or +def_prepared+. Wraps the defined method
|
10
|
+
# so that it may be extended with additional argument and
|
11
|
+
# result processing.
|
12
|
+
#
|
13
|
+
# @param mod [Module] the module where the method has been declared
|
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)
|
19
|
+
mod.instance_eval { define_method(name, definition) }
|
20
|
+
|
21
|
+
ConfigurableMethod.new(mod, name)
|
22
|
+
end
|
23
|
+
|
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.
|
30
|
+
#
|
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)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
require_relative 'configuration/configurable_method'
|
@@ -78,7 +78,7 @@ module DbMod
|
|
78
78
|
# Assert that the given parameter list is an array
|
79
79
|
# containing a single hash of named parameter values.
|
80
80
|
#
|
81
|
-
# Raises
|
81
|
+
# Raises +ArgumentError+ otherwise.
|
82
82
|
#
|
83
83
|
# @param args [Array<Hash<Symbol>>] method arguments being validated
|
84
84
|
def self.wrapped_hash!(args)
|
@@ -10,23 +10,18 @@ module DbMod
|
|
10
10
|
# For statements that are not prepared ahead of execution,
|
11
11
|
# see +def_statement+ in {DbMod::Statements::Statement}.
|
12
12
|
#
|
13
|
-
# def_prepared
|
14
|
-
# ------------
|
15
|
-
#
|
16
13
|
# +def_prepared+ accepts two parameters:
|
17
|
-
# *
|
14
|
+
# * *name* [Symbol]: The name that will be given to
|
18
15
|
# the prepared statement. A method will also be defined
|
19
16
|
# on the module with the same name which will call the
|
20
17
|
# statement and return the result.
|
21
|
-
# *
|
18
|
+
# * *sql* [String]: The SQL statement to be prepared.
|
22
19
|
# Parameters may be declared using the $ symbol followed
|
23
|
-
# by a number ($1, $2, $3) or a name ($one, $two, $
|
20
|
+
# by a number +($1, $2, $3)+ or a name +($one, $two, $a_b)+.
|
24
21
|
# The two styles may not be mixed in the same statement.
|
25
22
|
# The defined function can then be passed parameters
|
26
23
|
# that will be used when the statement is executed.
|
27
24
|
#
|
28
|
-
# ### example
|
29
|
-
#
|
30
25
|
# module MyModule
|
31
26
|
# include DbMod
|
32
27
|
#
|
@@ -146,7 +141,7 @@ module DbMod
|
|
146
141
|
# and the prepared query to be called.
|
147
142
|
def self.define_no_args_prepared_method(mod, name)
|
148
143
|
method = ->() { conn.exec_prepared(name.to_s) }
|
149
|
-
|
144
|
+
Configuration.def_configurable mod, name, method
|
150
145
|
end
|
151
146
|
|
152
147
|
# Define a method with the given name that accepts the
|
@@ -164,7 +159,7 @@ module DbMod
|
|
164
159
|
conn.exec_prepared(name.to_s, args)
|
165
160
|
end
|
166
161
|
|
167
|
-
|
162
|
+
Configuration.def_configurable mod, name, method
|
168
163
|
end
|
169
164
|
|
170
165
|
# Define a method with the given name that accepts a fixed
|
@@ -185,7 +180,7 @@ module DbMod
|
|
185
180
|
conn.exec_prepared(name.to_s, args)
|
186
181
|
end
|
187
182
|
|
188
|
-
|
183
|
+
Configuration.def_configurable(mod, name, method)
|
189
184
|
end
|
190
185
|
|
191
186
|
# Adds +prepared_statements+ to a module. This list of named
|
@@ -10,20 +10,38 @@ module DbMod
|
|
10
10
|
# To declare prepared statements, see +def_prepared+
|
11
11
|
# in {DbMod::Statements::Prepared}.
|
12
12
|
#
|
13
|
-
# def_statement
|
14
|
-
# -------------
|
15
|
-
#
|
16
13
|
# +def_statement+ accepts two parameters:
|
17
|
-
# *
|
14
|
+
# * *name* [Symbol]: The name that will be given to the
|
18
15
|
# method that can be used to execute the SQL statement
|
19
16
|
# and return the result.
|
20
|
-
# *
|
17
|
+
# * *sql* [String]: The SQL statement that shoul be executed
|
21
18
|
# when the method is called. Parameters may be declared
|
22
|
-
# using the $ symbol followed by a number ($1, $2, $3) or
|
23
|
-
# a name ($one, $two, $under_scores)
|
19
|
+
# using the $ symbol followed by a number +($1, $2, $3)+ or
|
20
|
+
# a name +($one, $two, $under_scores)+. The two styles may
|
24
21
|
# not be mixed in the same statement. The defined function
|
25
22
|
# can then be passed parameters that will be used to fill
|
26
23
|
# in the statement before execution.
|
24
|
+
#
|
25
|
+
# module MyModule
|
26
|
+
# include DbMod
|
27
|
+
#
|
28
|
+
# def_prepared :my_prepared, <<-SQL
|
29
|
+
# SELECT *
|
30
|
+
# FROM stuff
|
31
|
+
# WHERE a = $1 AND b = $2
|
32
|
+
# SQL
|
33
|
+
#
|
34
|
+
# def_prepared :my_named_prepared, <<-SQL
|
35
|
+
# SELECT *
|
36
|
+
# FROM stuff
|
37
|
+
# WHERE a = $a AND b = $b
|
38
|
+
# SQL
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
# include MyModule
|
42
|
+
# db_connect db: 'mydb'
|
43
|
+
# my_prepared(1,2)
|
44
|
+
# my_named_prepared(a: 1, b: 2)
|
27
45
|
module Statement
|
28
46
|
# Defines a module-specific +def_statement+ function
|
29
47
|
# for a module that has just had {DbMod} included.
|
@@ -83,7 +101,7 @@ module DbMod
|
|
83
101
|
# @param name [Symbol] name of the method to be defined
|
84
102
|
# @param sql [String] parameterless SQL statement to execute
|
85
103
|
def self.define_no_args_statement_method(mod, name, sql)
|
86
|
-
|
104
|
+
Configuration.def_configurable mod, name, ->() { query(sql) }
|
87
105
|
end
|
88
106
|
|
89
107
|
# Define a method with the given name, that accepts the
|
@@ -99,7 +117,7 @@ module DbMod
|
|
99
117
|
conn.exec_params(sql, args)
|
100
118
|
end
|
101
119
|
|
102
|
-
|
120
|
+
Configuration.def_configurable mod, name, method
|
103
121
|
end
|
104
122
|
|
105
123
|
# Define a method with the given name that accepts a fixed number
|
@@ -117,7 +135,7 @@ module DbMod
|
|
117
135
|
conn.exec_params(sql, args)
|
118
136
|
end
|
119
137
|
|
120
|
-
|
138
|
+
Configuration.def_configurable mod, name, method
|
121
139
|
end
|
122
140
|
end
|
123
141
|
end
|
data/lib/db_mod/statements.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require_relative 'statements/
|
1
|
+
require_relative 'statements/configuration'
|
2
2
|
require_relative 'statements/statement'
|
3
3
|
require_relative 'statements/prepared'
|
4
4
|
|
@@ -16,42 +16,5 @@ module DbMod
|
|
16
16
|
DbMod::Statements::Prepared.setup(mod)
|
17
17
|
DbMod::Statements::Statement.setup(mod)
|
18
18
|
end
|
19
|
-
|
20
|
-
# Used by submodules to when defining a method as declared by
|
21
|
-
# +def_statement+ or +def_prepared+. Wraps the defined method
|
22
|
-
# so that it may be extended with additional argument and
|
23
|
-
# result processing.
|
24
|
-
#
|
25
|
-
# @param mod [Module] the module where the method has been declared
|
26
|
-
# @param name [Symbol] the name of the module that has been defined
|
27
|
-
# @param definition [Proc] method definition
|
28
|
-
# @return [DbMod::Statements::ConfigurableMethod] dsl object for
|
29
|
-
# further extending the method
|
30
|
-
def self.configurable_method(mod, name, definition)
|
31
|
-
mod.instance_eval { define_method(name, definition) }
|
32
|
-
|
33
|
-
ConfigurableMethod.new(mod, name)
|
34
|
-
end
|
35
|
-
|
36
|
-
# Used by {ConfigurableMethod} (and associated code) to wrap a defined
|
37
|
-
# statement method or prepared method with additional parameter or result
|
38
|
-
# processing. A wrapper method definition should be provided, which will
|
39
|
-
# be called in place of the original method. It will be called with the
|
40
|
-
# original method proc as a first argument followed by the original
|
41
|
-
# method arguments (before +DbMod+ has made any attempt to validate them!).
|
42
|
-
# It is expected to yield to the original proc at some point, although it
|
43
|
-
# is allowed to do whatever it wants with the results before returning them.
|
44
|
-
#
|
45
|
-
# @param mod [Module] the module where the method has been defined
|
46
|
-
# @param name [Symbol] the method name
|
47
|
-
# @param wrapper [Proc] a function which will be used to wrap the
|
48
|
-
# original method definition
|
49
|
-
def self.extend_method(mod, name, wrapper)
|
50
|
-
mod.instance_eval do
|
51
|
-
wrapped = instance_method(name)
|
52
|
-
|
53
|
-
define_method name, ->(*args) { wrapper.call wrapped.bind(self), *args }
|
54
|
-
end
|
55
|
-
end
|
56
19
|
end
|
57
20
|
end
|
data/lib/db_mod/version.rb
CHANGED
data/lib/db_mod.rb
CHANGED
@@ -1,8 +1,10 @@
|
|
1
1
|
require 'pg'
|
2
|
-
|
3
|
-
require_relative 'db_mod/transaction'
|
2
|
+
|
4
3
|
require_relative 'db_mod/create'
|
4
|
+
require_relative 'db_mod/exceptions'
|
5
5
|
require_relative 'db_mod/statements'
|
6
|
+
require_relative 'db_mod/transaction'
|
7
|
+
require_relative 'db_mod/version'
|
6
8
|
|
7
9
|
# This is the foundation module for enabling db_mod
|
8
10
|
# support in an application. Including this module
|
@@ -1,14 +1,14 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
require 'csv'
|
3
3
|
|
4
|
-
describe DbMod::As::Csv do
|
4
|
+
describe DbMod::Statements::Configuration::As::Csv do
|
5
5
|
subject do
|
6
6
|
Module.new do
|
7
7
|
include DbMod
|
8
8
|
|
9
9
|
def_statement(:statement, 'SELECT a, b FROM foo').as(:csv)
|
10
|
-
def_prepared(:prepared, 'SELECT
|
11
|
-
end
|
10
|
+
def_prepared(:prepared, 'SELECT a, b FROM bar').as(:csv)
|
11
|
+
end.create(db: 'testdb')
|
12
12
|
end
|
13
13
|
|
14
14
|
before do
|
@@ -25,11 +25,12 @@ describe DbMod::As::Csv do
|
|
25
25
|
it 'coerces results to csv' do
|
26
26
|
expect(@conn).to receive(exec_type).and_return([
|
27
27
|
{ 'a' => '1', 'b' => '2' },
|
28
|
-
{ 'a' => '3', 'b' => '4' }
|
28
|
+
{ 'a' => '3', 'b' => '4' },
|
29
|
+
{ 'a' => nil, 'b' => '5' }
|
29
30
|
])
|
30
31
|
|
31
|
-
csv = subject.
|
32
|
-
expect(csv).to eq("a,b\n1,2\n3,4\n")
|
32
|
+
csv = subject.send(method_type)
|
33
|
+
expect(csv).to eq("a,b\n1,2\n3,4\n,5\n")
|
33
34
|
end
|
34
35
|
end
|
35
36
|
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
describe DbMod::Statements::Configuration::As::Json do
|
5
|
+
subject do
|
6
|
+
Module.new do
|
7
|
+
include DbMod
|
8
|
+
|
9
|
+
def_statement(:statement, 'SELECT a, b FROM foo').as(:json)
|
10
|
+
def_prepared(:prepared, 'SELECT a, b FROM bar').as(:json)
|
11
|
+
end.create(db: 'testdb')
|
12
|
+
end
|
13
|
+
|
14
|
+
before do
|
15
|
+
@conn = instance_double 'PGconn'
|
16
|
+
allow(@conn).to receive(:prepare)
|
17
|
+
allow(PGconn).to receive(:connect).and_return @conn
|
18
|
+
end
|
19
|
+
|
20
|
+
{
|
21
|
+
statement: :query,
|
22
|
+
prepared: :exec_prepared
|
23
|
+
}.each do |method_type, exec_type|
|
24
|
+
context "#{method_type} methods" do
|
25
|
+
it 'coerces results to json' do
|
26
|
+
expect(@conn).to receive(exec_type).and_return([
|
27
|
+
{ 'a' => '1', 'b' => 'foo' },
|
28
|
+
{ 'a' => '2', 'b' => nil }
|
29
|
+
])
|
30
|
+
|
31
|
+
json = subject.send(method_type)
|
32
|
+
expect(json).to eq(
|
33
|
+
'[{"a":"1","b":"foo"},{"a":"2","b":null}]'
|
34
|
+
)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
# See submodules for more
|
4
|
+
describe DbMod::Statements::Configuration::As do
|
5
|
+
it 'disallows unknown coercions' do
|
6
|
+
expect do
|
7
|
+
Module.new do
|
8
|
+
include DbMod
|
9
|
+
|
10
|
+
def_statement(:foo, 'SELECT 1').as(:lolwut)
|
11
|
+
end
|
12
|
+
end.to raise_exception ArgumentError
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'disallows multiple coercions' do
|
16
|
+
expect do
|
17
|
+
Module.new do
|
18
|
+
include DbMod
|
19
|
+
|
20
|
+
def_statement(:foo, 'SELECT 1').as(:json).as(:csv)
|
21
|
+
end
|
22
|
+
end.to raise_exception DbMod::Exceptions::BadMethodConfiguration
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe DbMod::Statements::Configuration::Single do
|
4
|
+
subject do
|
5
|
+
Module.new do
|
6
|
+
include DbMod
|
7
|
+
|
8
|
+
def_statement(:v, 'SELECT a FROM foo').single(:value)
|
9
|
+
def_prepared(:v!, 'SELECT c FROM d WHERE e = $f').single(:value!)
|
10
|
+
|
11
|
+
def_prepared(:r, 'SELECT a, b FROM foo WHERE c = $1').single(:row)
|
12
|
+
def_statement(:r!, 'SELECT a, b FROM foo').single(:row!)
|
13
|
+
|
14
|
+
def_statement(:c, 'SELECT a FROM foo WHERE c > $min').single(:column)
|
15
|
+
end.create(db: 'testdb')
|
16
|
+
end
|
17
|
+
|
18
|
+
before do
|
19
|
+
@conn = instance_double 'PGconn'
|
20
|
+
allow(@conn).to receive(:prepare)
|
21
|
+
allow(PGconn).to receive(:connect).and_return(@conn)
|
22
|
+
end
|
23
|
+
|
24
|
+
context ':value, :value!' do
|
25
|
+
it 'extracts single values' do
|
26
|
+
expect(@conn).to receive(:query).and_return([{ 'a' => '1' }])
|
27
|
+
expect(subject.v).to eq('1')
|
28
|
+
|
29
|
+
expect(@conn).to receive(:exec_prepared).and_return([{ 'c' => '2' }])
|
30
|
+
expect(subject.v! f: 1).to eq('2')
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'can assert a single result was returned' do
|
34
|
+
expect(@conn).to receive(:exec_prepared).and_return([])
|
35
|
+
expect { subject.v! f: 2 }.to raise_exception DbMod::Exceptions::NoResults
|
36
|
+
|
37
|
+
expect(@conn).to receive(:exec_prepared).and_return([
|
38
|
+
{ 'c' => '3' },
|
39
|
+
{ 'c' => '4' }
|
40
|
+
])
|
41
|
+
expect { subject.v! f: 3 }.to raise_exception(
|
42
|
+
DbMod::Exceptions::TooManyResults
|
43
|
+
)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
context ':row, :row!' do
|
48
|
+
it 'extracts single rows' do
|
49
|
+
result = [{ 'a' => '1', 'b' => '2' }]
|
50
|
+
expected = { 'a' => '1', 'b' => '2' }
|
51
|
+
|
52
|
+
expect(@conn).to receive(:exec_prepared).and_return(result)
|
53
|
+
expect(subject.r(1)).to eq(expected)
|
54
|
+
|
55
|
+
expect(@conn).to receive(:query).and_return(result)
|
56
|
+
expect(subject.r!).to eq(expected)
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'can assert that a single result was returned' do
|
60
|
+
expect(@conn).to receive(:query).and_return([])
|
61
|
+
expect { subject.r! }.to raise_exception DbMod::Exceptions::NoResults
|
62
|
+
|
63
|
+
expect(@conn).to receive(:query).and_return([
|
64
|
+
{ 'a' => '3', 'b' => '4' },
|
65
|
+
{ 'a' => '5', 'b' => '6' }
|
66
|
+
])
|
67
|
+
expect { subject.r! }.to raise_exception(
|
68
|
+
DbMod::Exceptions::TooManyResults
|
69
|
+
)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
context ':column' do
|
74
|
+
it 'returns the column as an array' do
|
75
|
+
expect(@conn).to receive(:exec_params).and_return([
|
76
|
+
{ 'a' => '1' },
|
77
|
+
{ 'a' => '2' },
|
78
|
+
{ 'a' => '3' }
|
79
|
+
])
|
80
|
+
expect(subject.c min: 1).to eq(%w(1 2 3))
|
81
|
+
|
82
|
+
expect(@conn).to receive(:exec_params).and_return([])
|
83
|
+
expect(subject.c min: 2).to eq([])
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'rejects unknown types' do
|
88
|
+
expect do
|
89
|
+
Module.new do
|
90
|
+
include DbMod
|
91
|
+
|
92
|
+
def_statement(:a, 'SELECT 1').single(:lolwut)
|
93
|
+
end
|
94
|
+
end.to raise_exception ArgumentError
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'cannot be called twice' do
|
98
|
+
expect do
|
99
|
+
Module.new do
|
100
|
+
include DbMod
|
101
|
+
|
102
|
+
def_statement(:a, 'SELECT 1').single(:row).single(:value)
|
103
|
+
end
|
104
|
+
end.to raise_exception DbMod::Exceptions::BadMethodConfiguration
|
105
|
+
end
|
106
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: db_mod
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Doug Hammond
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-10-
|
11
|
+
date: 2015-10-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: pg
|
@@ -105,6 +105,7 @@ files:
|
|
105
105
|
- ".rspec"
|
106
106
|
- ".rubocop.yml"
|
107
107
|
- ".travis.yml"
|
108
|
+
- ".yardopts"
|
108
109
|
- CHANGELOG.md
|
109
110
|
- CODE_OF_CONDUCT.md
|
110
111
|
- Gemfile
|
@@ -114,24 +115,37 @@ files:
|
|
114
115
|
- Rakefile
|
115
116
|
- db_mod.gemspec
|
116
117
|
- lib/db_mod.rb
|
117
|
-
- lib/db_mod/as.rb
|
118
|
-
- lib/db_mod/as/csv.rb
|
119
118
|
- lib/db_mod/create.rb
|
120
119
|
- lib/db_mod/exceptions.rb
|
121
120
|
- lib/db_mod/exceptions/already_in_transaction.rb
|
121
|
+
- lib/db_mod/exceptions/bad_method_configuration.rb
|
122
122
|
- lib/db_mod/exceptions/base.rb
|
123
123
|
- lib/db_mod/exceptions/connection_not_set.rb
|
124
124
|
- lib/db_mod/exceptions/duplicate_statement_name.rb
|
125
|
+
- lib/db_mod/exceptions/no_results.rb
|
126
|
+
- lib/db_mod/exceptions/too_many_results.rb
|
125
127
|
- lib/db_mod/statements.rb
|
126
|
-
- lib/db_mod/statements/
|
128
|
+
- lib/db_mod/statements/configuration.rb
|
129
|
+
- lib/db_mod/statements/configuration/as.rb
|
130
|
+
- lib/db_mod/statements/configuration/as/csv.rb
|
131
|
+
- lib/db_mod/statements/configuration/as/json.rb
|
132
|
+
- lib/db_mod/statements/configuration/configurable_method.rb
|
133
|
+
- lib/db_mod/statements/configuration/single.rb
|
134
|
+
- lib/db_mod/statements/configuration/single/column.rb
|
135
|
+
- lib/db_mod/statements/configuration/single/required_row.rb
|
136
|
+
- lib/db_mod/statements/configuration/single/required_value.rb
|
137
|
+
- lib/db_mod/statements/configuration/single/row.rb
|
138
|
+
- lib/db_mod/statements/configuration/single/value.rb
|
127
139
|
- lib/db_mod/statements/parameters.rb
|
128
140
|
- lib/db_mod/statements/prepared.rb
|
129
141
|
- lib/db_mod/statements/statement.rb
|
130
142
|
- lib/db_mod/transaction.rb
|
131
143
|
- lib/db_mod/version.rb
|
132
|
-
- spec/db_mod/as/csv_spec.rb
|
133
|
-
- spec/db_mod/as_spec.rb
|
134
144
|
- spec/db_mod/create_spec.rb
|
145
|
+
- spec/db_mod/statements/configuration/as/csv_spec.rb
|
146
|
+
- spec/db_mod/statements/configuration/as/json_spec.rb
|
147
|
+
- spec/db_mod/statements/configuration/as_spec.rb
|
148
|
+
- spec/db_mod/statements/configuration/single_spec.rb
|
135
149
|
- spec/db_mod/statements/prepared_spec.rb
|
136
150
|
- spec/db_mod/statements/statement_spec.rb
|
137
151
|
- spec/db_mod/transaction_spec.rb
|
@@ -162,9 +176,11 @@ signing_key:
|
|
162
176
|
specification_version: 4
|
163
177
|
summary: Declarative, modular database library framework.
|
164
178
|
test_files:
|
165
|
-
- spec/db_mod/as/csv_spec.rb
|
166
|
-
- spec/db_mod/as_spec.rb
|
167
179
|
- spec/db_mod/create_spec.rb
|
180
|
+
- spec/db_mod/statements/configuration/as/csv_spec.rb
|
181
|
+
- spec/db_mod/statements/configuration/as/json_spec.rb
|
182
|
+
- spec/db_mod/statements/configuration/as_spec.rb
|
183
|
+
- spec/db_mod/statements/configuration/single_spec.rb
|
168
184
|
- spec/db_mod/statements/prepared_spec.rb
|
169
185
|
- spec/db_mod/statements/statement_spec.rb
|
170
186
|
- spec/db_mod/transaction_spec.rb
|
data/lib/db_mod/as/csv.rb
DELETED
@@ -1,37 +0,0 @@
|
|
1
|
-
module DbMod
|
2
|
-
module As
|
3
|
-
# Coercer which converts an SQL result set
|
4
|
-
# into a string formatted as a CSV document.
|
5
|
-
# May be enabled for a prepared method or
|
6
|
-
# statement method using +.as(:csv)+:
|
7
|
-
#
|
8
|
-
# def_statement(:a, 'SELECT a, b FROM foo').as(:csv)
|
9
|
-
# def_prepared(:b, 'SELECT b, c FROM bar').as(:csv)
|
10
|
-
#
|
11
|
-
# def do_stuff
|
12
|
-
# a # => "a,b\r\n1,2\r\n3,4\r\n..."
|
13
|
-
# end
|
14
|
-
module Csv
|
15
|
-
# Enables this module to be passed to {DbMod::Statements.extend_method}
|
16
|
-
# as the +wrapper+ function, in which case it will retrieve the results
|
17
|
-
# and format them as a CSV document using the column names from the
|
18
|
-
# result set.
|
19
|
-
#
|
20
|
-
# @param wrapped_method [Method] the method that has been wrapped
|
21
|
-
# @param args [*] arguments expected to be passed to the wrapped method
|
22
|
-
# @return [String] a CSV formatted document
|
23
|
-
def self.call(wrapped_method, *args)
|
24
|
-
results = wrapped_method.call(*args)
|
25
|
-
|
26
|
-
headers = nil
|
27
|
-
CSV.generate do |csv|
|
28
|
-
results.each do |row|
|
29
|
-
csv << (headers = row.keys) unless headers
|
30
|
-
|
31
|
-
csv << headers.map { |col| row[col] }
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
data/lib/db_mod/as.rb
DELETED
@@ -1,38 +0,0 @@
|
|
1
|
-
require_relative 'as/csv'
|
2
|
-
|
3
|
-
module DbMod
|
4
|
-
# Contains coercers and other functions that allow
|
5
|
-
# module instance methods returning an SQL result set
|
6
|
-
# to be extended with additional result coercion and
|
7
|
-
# formatting. The normal way to access this functionality
|
8
|
-
# is via {DbMod::Statements::ConfigurableMethod#as},
|
9
|
-
# which is available when defining a statement method
|
10
|
-
# or prepared method:
|
11
|
-
#
|
12
|
-
# def_statement(:a, 'SELECT a, b, c FROM foo').as(:csv)
|
13
|
-
# def_prepared(:b, 'SELECT d, e, f FROM bar').as(:csv)
|
14
|
-
module As
|
15
|
-
# List of available result coercion methods.
|
16
|
-
# Only keys defined here are allowed as arguments
|
17
|
-
# to {DbMod::Statements::ConfigurableMethod#as}.
|
18
|
-
COERCERS = {
|
19
|
-
csv: DbMod::As::Csv
|
20
|
-
}
|
21
|
-
|
22
|
-
# Extend a method so that the SQL result set it
|
23
|
-
# returns will be coerced to the given type.
|
24
|
-
# See {COERCERS} for a list of defined coercion
|
25
|
-
# methods.
|
26
|
-
#
|
27
|
-
# @param mod [Module] module where the method has been defined
|
28
|
-
# @param name [Symbol] method name
|
29
|
-
# @param type [Symbol] type to which result set should be coerced
|
30
|
-
def self.extend_method(mod, name, type)
|
31
|
-
unless COERCERS.key? type
|
32
|
-
fail ArgumentError, "#{type} not in #{COERCERS.keys.join ', '}"
|
33
|
-
end
|
34
|
-
|
35
|
-
Statements.extend_method(mod, name, COERCERS[type])
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|
@@ -1,41 +0,0 @@
|
|
1
|
-
require 'db_mod/as'
|
2
|
-
|
3
|
-
module DbMod
|
4
|
-
module Statements
|
5
|
-
# Encapsulates a method that has just been defined
|
6
|
-
# via the dsl exposed in {DbMod::Statements} so that
|
7
|
-
# it can be extended with additional processing such
|
8
|
-
# as result coercion.
|
9
|
-
#
|
10
|
-
# The pattern here is something similar to rack's middleware.
|
11
|
-
# Calling any of the extension methods below will replace
|
12
|
-
# the original method defined by +def_prepared+ or +def_statement+
|
13
|
-
# with a wrapper function that may perform processing on given
|
14
|
-
# arguments, pass them to the original function, then perform
|
15
|
-
# additional processing on the result.
|
16
|
-
class ConfigurableMethod
|
17
|
-
# Encapsulate a method that has been newly defined
|
18
|
-
# by a {DbMod} dsl function, for additional configuration.
|
19
|
-
#
|
20
|
-
# @param mod [Module] the {DbMod} enabled module
|
21
|
-
# where the method was defined
|
22
|
-
# @param name [Symbol] the method name
|
23
|
-
def initialize(mod, name)
|
24
|
-
@mod = mod
|
25
|
-
@name = name
|
26
|
-
end
|
27
|
-
|
28
|
-
# Extend the method by converting results into a given
|
29
|
-
# format, using one of the coercion methods defined
|
30
|
-
# under {DbMod::As}.
|
31
|
-
#
|
32
|
-
# @param type [Symbol] for now, only :csv is accepted
|
33
|
-
# @return [self]
|
34
|
-
def as(type)
|
35
|
-
DbMod::As.extend_method(@mod, @name, type)
|
36
|
-
|
37
|
-
self
|
38
|
-
end
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
data/spec/db_mod/as_spec.rb
DELETED
@@ -1,14 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
# See submodules for more
|
4
|
-
describe DbMod::As do
|
5
|
-
it 'disallows unknown coercions' do
|
6
|
-
expect do
|
7
|
-
Module.new do
|
8
|
-
include DbMod
|
9
|
-
|
10
|
-
def_statement(:foo, 'SELECT 1').as(:lolwut)
|
11
|
-
end
|
12
|
-
end.to raise_exception ArgumentError
|
13
|
-
end
|
14
|
-
end
|