db_mod 0.0.3 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![GitHub version](https://badge.fury.io/gh/dslh%2Fdb_mod.svg)](https://github.com/dslh/db_mod)
|
6
|
+
[![Gem Version](https://badge.fury.io/rb/db_mod.svg)](https://rubygems.org/gems/db_mod)
|
7
|
+
[![Travis CI](https://img.shields.io/travis/dslh/db_mod/master.svg)](https://travis-ci.org/dslh/db_mod)
|
8
|
+
![Gem downloads](https://img.shields.io/gem/dt/db_mod.svg)
|
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
|