db_mod 0.0.5 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +9 -0
- data/Gemfile +1 -3
- data/Guardfile +4 -0
- data/README.md +173 -2
- data/Rakefile +7 -1
- data/db_mod.gemspec +7 -2
- data/lib/db_mod.rb +35 -2
- data/lib/db_mod/create.rb +6 -3
- data/lib/db_mod/statements.rb +8 -0
- data/lib/db_mod/statements/configuration.rb +20 -3
- data/lib/db_mod/statements/configuration/as.rb +3 -1
- data/lib/db_mod/statements/configuration/defaults.rb +55 -8
- data/lib/db_mod/statements/configuration/method_configuration.rb +74 -1
- data/lib/db_mod/statements/configuration/returning.rb +31 -0
- data/lib/db_mod/statements/configuration/single.rb +3 -1
- data/lib/db_mod/statements/default_method_settings.rb +81 -0
- data/lib/db_mod/statements/parameters.rb +13 -0
- data/lib/db_mod/statements/prepared.rb +27 -8
- data/lib/db_mod/statements/statement.rb +10 -5
- data/lib/db_mod/transaction.rb +5 -0
- data/lib/db_mod/version.rb +1 -1
- data/spec/db_mod/statements/configuration/defaults_spec.rb +61 -0
- data/spec/db_mod/statements/configuration/method_configuration_spec.rb +41 -0
- data/spec/db_mod/statements/configuration/returning_spec.rb +69 -0
- data/spec/db_mod/statements/default_method_settings_spec.rb +63 -0
- data/spec/spec_helper.rb +8 -2
- metadata +83 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7f28c5a3e9353c2ae963580ca872095598e926dd
|
4
|
+
data.tar.gz: 77b781ec8ba80b3d1adc89c6c515708f0ed5bda3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f722a8ef3716308bf5232811298b4dc72cbebcb487299859b62d7acf4416f171fea909cbd282ada834d440a0fd06d718c8e7808bb4b6f28c00991694fd0c5df8
|
7
|
+
data.tar.gz: 4fe5b2cd8272462a7f902de07cf4210ac38aa681cbc6223418d24b7371f1da1484f5d4fa3963fe30c9fa750dd32466dd871b59d151876c80690de9e21ebfe1ca
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,12 @@
|
|
1
|
+
0.0.6 (2015-10-21)
|
2
|
+
==================
|
3
|
+
|
4
|
+
* Procs now allowed for default parameter values - [@dslh](https://github.com/dslh).
|
5
|
+
* Adds `returning` block to method configuration options - [@dslh](https://github.com/dslh).
|
6
|
+
* Adds module-level `default_method_settings` for default prepared and statement method
|
7
|
+
configuration - [@dslh](https://github.com/dslh).
|
8
|
+
* Fixes a bug with dynamic module method declarations - [@dslh](https://github.com/dslh).
|
9
|
+
|
1
10
|
0.0.5 (2015-10-19)
|
2
11
|
==================
|
3
12
|
|
data/Gemfile
CHANGED
data/Guardfile
CHANGED
data/README.md
CHANGED
@@ -1,9 +1,12 @@
|
|
1
|
-
##
|
1
|
+
## DbMod
|
2
2
|
|
3
3
|
Database enabled modules for ruby.
|
4
4
|
|
5
5
|
[](https://github.com/dslh/db_mod)
|
6
6
|
[](https://travis-ci.org/dslh/db_mod)
|
7
|
+
[](https://codeclimate.com/github/dslh/db_mod)
|
8
|
+
[](https://codeclimate.com/github/dslh/db_mod/coverage)
|
9
|
+
[](http://inch-ci.org/github/dslh/db_mod)
|
7
10
|
[](https://rubygems.org/gems/db_mod)
|
8
11
|
|
9
12
|
[Rubydoc.info documentation](http://www.rubydoc.info/gems/db_mod)
|
@@ -20,6 +23,26 @@ guarantees will be made about backwards compatibility until v0.1.0.
|
|
20
23
|
|
21
24
|
Issues, feature or pull requests, comments and feedback all welcomed.
|
22
25
|
|
26
|
+
## Installation
|
27
|
+
|
28
|
+
From the command line:
|
29
|
+
|
30
|
+
```
|
31
|
+
gem install db_mod
|
32
|
+
```
|
33
|
+
|
34
|
+
Or in your `Gemfile`:
|
35
|
+
|
36
|
+
```ruby
|
37
|
+
gem 'db_mod'
|
38
|
+
```
|
39
|
+
|
40
|
+
And then in your script:
|
41
|
+
|
42
|
+
```ruby
|
43
|
+
require 'db_mod'
|
44
|
+
```
|
45
|
+
|
23
46
|
## Usage
|
24
47
|
|
25
48
|
### The database connection
|
@@ -204,6 +227,10 @@ Statement and prepared methods can be configured on declaration by using
|
|
204
227
|
formatted as either a CSV document or an array of JSON objects, respectively.
|
205
228
|
|
206
229
|
```ruby
|
230
|
+
# db_mod makes no attempt to load these
|
231
|
+
require 'csv'
|
232
|
+
require 'json'
|
233
|
+
|
207
234
|
module Reports
|
208
235
|
include DbMod
|
209
236
|
|
@@ -281,5 +308,149 @@ def_statement(:a, %(
|
|
281
308
|
|
282
309
|
# ...
|
283
310
|
|
284
|
-
a
|
311
|
+
a # === a(4, 5, 6)
|
312
|
+
a(1) # === a(1, 5, 6)
|
313
|
+
a(1, 2) # === a(1, 2, 6)
|
314
|
+
a(1, 2, 3) # === a(1, 2, 3)
|
315
|
+
```
|
316
|
+
|
317
|
+
A proc may be given as the default value for any parameter.
|
318
|
+
The proc should accept one parameter, which will be the argument list/hash
|
319
|
+
(depending on if the statement uses numbered or named arguments)
|
320
|
+
and should return a single value to be used for this execution
|
321
|
+
of the query.
|
322
|
+
|
323
|
+
```ruby
|
324
|
+
def_prepared(:default_min, %(
|
325
|
+
SELECT default_min
|
326
|
+
FROM defaults
|
327
|
+
WHERE foo_id = $1
|
328
|
+
)) { single(:value) }
|
329
|
+
|
330
|
+
def_prepared(:report, 'SELECT * FROM a WHERE b = $c AND d > $e') do
|
331
|
+
defaults min: ->(args) { default_min(args[:c]) }
|
332
|
+
as :json
|
333
|
+
end
|
334
|
+
```
|
335
|
+
|
336
|
+
The above example shows how the proc will be executed using the instance
|
337
|
+
of the `DbMod` module as scope, so statement, prepared, or other methods
|
338
|
+
may be accessed.
|
339
|
+
|
340
|
+
Note that the argument list will be partially constructed at the time it
|
341
|
+
is received by the proc; other default values may or may not yet have been
|
342
|
+
populated. Defaults will be populated in the order that they are declared
|
343
|
+
using `defaults`.
|
344
|
+
|
345
|
+
##### Custom method return values
|
346
|
+
|
347
|
+
Besides the built-in result transformations provided by `as` and `single`,
|
348
|
+
`db_mod` also allows arbitrary control over the return value of statement
|
349
|
+
and prepared methods using a block provided via `returning`:
|
350
|
+
|
351
|
+
```ruby
|
352
|
+
def_prepared(:a, 'SELECT name, sound FROM animals') do
|
353
|
+
# Block parameter is the SQL result set
|
354
|
+
returning do |animals|
|
355
|
+
animals.map do |animal|
|
356
|
+
"the #{animal['name']} goes #{animal['sound']}"
|
357
|
+
end.join ' and '
|
358
|
+
end
|
359
|
+
end
|
360
|
+
|
361
|
+
def_prepared(:important_report!, 'SELECT address FROM email WHERE id = $1') do
|
362
|
+
# 'single' and 'as' will transform the result set
|
363
|
+
# before it is passed to the block
|
364
|
+
single(:value)
|
365
|
+
|
366
|
+
returning { |email| send_email(email, a) }
|
367
|
+
end
|
368
|
+
|
369
|
+
# Block has instance-level scope
|
370
|
+
def send_email(address, body)
|
371
|
+
# ...
|
372
|
+
end
|
373
|
+
|
374
|
+
# ...
|
375
|
+
|
376
|
+
important_report!(123)
|
377
|
+
# === send_email('ex@mp.le', 'the sheep goes baa and the cow goes moo')
|
378
|
+
```
|
379
|
+
|
380
|
+
##### Default configuration settings
|
381
|
+
|
382
|
+
To save typing, modules may declare a `default_method_settings` block that will
|
383
|
+
be applied to all following `def_statement` and `def_prepared` definitions. The
|
384
|
+
dsl used is the same as for individual method configuration blocks.
|
385
|
+
|
386
|
+
```ruby
|
387
|
+
require 'csv'
|
388
|
+
|
389
|
+
module CsvReports
|
390
|
+
include DbMod
|
391
|
+
|
392
|
+
default_method_settings do
|
393
|
+
as(:csv).returning { |csv| send_report(csv) }
|
394
|
+
end
|
395
|
+
|
396
|
+
def send_report(csv)
|
397
|
+
# ...
|
398
|
+
end
|
399
|
+
|
400
|
+
def_prepared(:report_a, 'SELECT * FROM report_a WHERE user_id = $1')
|
401
|
+
def_prepared(:report_b, 'SELECT * FROM report_b WHERE user_id = $1')
|
402
|
+
def_prepared(:report_c, 'SELECT * FROM report_c WHERE user_id = $1')
|
403
|
+
end
|
404
|
+
|
405
|
+
module ReportEmailer
|
406
|
+
include CsvReports
|
407
|
+
|
408
|
+
default_method_settings do
|
409
|
+
single(:column)
|
410
|
+
returning do |ids|
|
411
|
+
ids.each { |id| send_all_reports_for(id) }
|
412
|
+
end
|
413
|
+
end
|
414
|
+
|
415
|
+
def send_all_reports_for(user)
|
416
|
+
report_a(user)
|
417
|
+
report_b(user)
|
418
|
+
report_c(user)
|
419
|
+
end
|
420
|
+
|
421
|
+
def_statement(:send_all_reports, 'SELECT id FROM user')
|
422
|
+
def_statement(:send_priority_reports, 'SELECT id FROM user WHERE priority')
|
423
|
+
|
424
|
+
# individual settings may be overridden
|
425
|
+
def_statement(:send_all_a_reports, 'SELECT id FROM user') do
|
426
|
+
returning { |ids| ids.each { |id| report_a(id) } }
|
427
|
+
end
|
428
|
+
def_statement(:send_reports, 'SELECT id FROM user WHERE name = $name') do
|
429
|
+
single(:value).returning { |id| send_all_reports_for(id) }
|
430
|
+
end
|
431
|
+
end
|
432
|
+
```
|
433
|
+
|
434
|
+
Defaults don't cascade automatically from one module to another module that
|
435
|
+
has included it. However the following sorts of things work if you need more
|
436
|
+
flexibility in reusing default settings:
|
437
|
+
|
438
|
+
```ruby
|
439
|
+
|
440
|
+
BASE_SETTINGS = ->() { single(:row).as(:json) }
|
441
|
+
module A
|
442
|
+
include DbMod
|
443
|
+
|
444
|
+
# Use base settings with overrides in block
|
445
|
+
default_method_settings(BASE_SETTINGS) { as(:csv) }
|
446
|
+
end
|
447
|
+
|
448
|
+
A.default_method_settings # => { as: :csv, single: :row }
|
449
|
+
|
450
|
+
module B
|
451
|
+
include A
|
452
|
+
|
453
|
+
# Inherit settings from A, overrides as named args
|
454
|
+
default_method_settings(A.default_method_settings, as: :csv)
|
455
|
+
end
|
285
456
|
```
|
data/Rakefile
CHANGED
@@ -15,7 +15,7 @@ require 'rainbow/ext/string' unless String.respond_to?(:color)
|
|
15
15
|
require 'rubocop/rake_task'
|
16
16
|
RuboCop::RakeTask.new
|
17
17
|
|
18
|
-
task default: [:rubocop, :spec]
|
18
|
+
task default: [:rubocop, :inch, :spec]
|
19
19
|
|
20
20
|
require 'yard'
|
21
21
|
DOC_FILES = ['lib/**/*.rb', 'README.md']
|
@@ -23,3 +23,9 @@ DOC_FILES = ['lib/**/*.rb', 'README.md']
|
|
23
23
|
YARD::Rake::YardocTask.new(:doc) do |t|
|
24
24
|
t.files = DOC_FILES
|
25
25
|
end
|
26
|
+
|
27
|
+
require 'inch/rake'
|
28
|
+
Inch::Rake::Suggest.new do |inch|
|
29
|
+
inch.args << '--private'
|
30
|
+
inch.args << '--pedantic'
|
31
|
+
end
|
data/db_mod.gemspec
CHANGED
@@ -16,11 +16,16 @@ Gem::Specification.new do |s|
|
|
16
16
|
|
17
17
|
s.add_runtime_dependency 'pg'
|
18
18
|
|
19
|
-
s.add_development_dependency '
|
19
|
+
s.add_development_dependency 'bundler'
|
20
|
+
s.add_development_dependency 'codeclimate-test-reporter'
|
21
|
+
s.add_development_dependency 'inch'
|
22
|
+
s.add_development_dependency 'rake'
|
23
|
+
s.add_development_dependency 'redcarpet'
|
20
24
|
s.add_development_dependency 'rspec'
|
21
25
|
s.add_development_dependency 'rspec-mocks'
|
26
|
+
s.add_development_dependency 'rubocop'
|
27
|
+
s.add_development_dependency 'simplecov'
|
22
28
|
s.add_development_dependency 'yard'
|
23
|
-
s.add_development_dependency 'bundler'
|
24
29
|
|
25
30
|
s.files = `git ls-files`.split("\n")
|
26
31
|
s.test_files = `git ls-files -- spec/*`.split("\n")
|
data/lib/db_mod.rb
CHANGED
@@ -11,15 +11,32 @@ require_relative 'db_mod/version'
|
|
11
11
|
# will give your class or object the protected methods
|
12
12
|
# {#db_connect} and {#conn=}, allowing the connection
|
13
13
|
# to be set or created, as well as the methods {#conn},
|
14
|
-
# {#query}, {#transaction}, and
|
14
|
+
# {#query}, {#transaction}, and +def_prepared+.
|
15
15
|
module DbMod
|
16
16
|
include Transaction
|
17
17
|
|
18
18
|
# When a module includes {DbMod}, we define some
|
19
19
|
# class-level functions specific to the module.
|
20
|
+
# This technique is required where it is not
|
21
|
+
# sufficient to simply define a module method
|
22
|
+
# on {DbMod} itself due to metaprogramming techniques
|
23
|
+
# requiring access to the module as +self+.
|
24
|
+
#
|
25
|
+
# See {DbMod::Create.setup}
|
26
|
+
# and {DbMod::Statements.setup}
|
27
|
+
#
|
28
|
+
# @param mod [Module] module which has had {DbMod} included
|
29
|
+
# @see http://ruby-doc.org/core-2.2.3/Module.html#method-i-included
|
30
|
+
# Module#included
|
20
31
|
def self.included(mod)
|
21
32
|
DbMod::Create.setup(mod)
|
22
33
|
DbMod::Statements.setup(mod)
|
34
|
+
|
35
|
+
# Ensure that these definitions cascade when
|
36
|
+
# submodules are included in subsequent submodules.
|
37
|
+
class << mod
|
38
|
+
define_method(:included) { |sub_mod| DbMod.included(sub_mod) }
|
39
|
+
end
|
23
40
|
end
|
24
41
|
|
25
42
|
protected
|
@@ -27,9 +44,23 @@ module DbMod
|
|
27
44
|
# Database object to be used for all database
|
28
45
|
# interactions in this module.
|
29
46
|
# Use {#db_connect} to initialize the object.
|
30
|
-
|
47
|
+
#
|
48
|
+
# @return [PGconn] for now, only PostgreSQL is supported
|
49
|
+
attr_reader :conn
|
50
|
+
|
51
|
+
# A custom-built connection object
|
52
|
+
# may be supplied in place of calling {#db_connect}.
|
53
|
+
# Be aware in this case that certain responsibilities
|
54
|
+
# of {#db_connect} may need to be taken care of manually,
|
55
|
+
# in particular preparing SQL statements.
|
56
|
+
#
|
57
|
+
# @param value [PGconn] for now, only PostgreSQL is supported.
|
58
|
+
attr_writer :conn
|
31
59
|
|
32
60
|
# Shorthand for +conn.query+
|
61
|
+
#
|
62
|
+
# @param sql [String] SQL query to execute
|
63
|
+
# @return [Object] SQL result set
|
33
64
|
def query(sql)
|
34
65
|
unless @conn
|
35
66
|
fail DbMod::Exceptions::ConnectionNotSet, 'db_connect not called'
|
@@ -65,6 +96,7 @@ module DbMod
|
|
65
96
|
# Load any missing options from defaults
|
66
97
|
#
|
67
98
|
# @param options [Hash] see {#db_connect}
|
99
|
+
# @see #db_connect
|
68
100
|
def db_defaults!(options)
|
69
101
|
fail ArgumentError, 'database name :db not supplied' unless options[:db]
|
70
102
|
options[:port] ||= 5432
|
@@ -75,6 +107,7 @@ module DbMod
|
|
75
107
|
# Create the database object itself.
|
76
108
|
#
|
77
109
|
# @param options [Hash] see {#db_connect}
|
110
|
+
# @return [PGconn] a new database connection
|
78
111
|
def db_connect!(options)
|
79
112
|
PGconn.connect(
|
80
113
|
options[:host],
|
data/lib/db_mod/create.rb
CHANGED
@@ -16,9 +16,11 @@ module DbMod
|
|
16
16
|
# for a module that has just had {DbMod}
|
17
17
|
# included.
|
18
18
|
#
|
19
|
-
# @param mod [Module]
|
19
|
+
# @param mod [Module] the module where {DbMod}
|
20
|
+
# has been included
|
21
|
+
# @see DbMod.included
|
20
22
|
def self.setup(mod)
|
21
|
-
|
23
|
+
class << mod
|
22
24
|
define_method(:create) do |options = {}|
|
23
25
|
@instantiable_class ||= Create.instantiable_class(self)
|
24
26
|
|
@@ -33,7 +35,8 @@ module DbMod
|
|
33
35
|
# and can be instantiated with either a connection object
|
34
36
|
# or some connection options.
|
35
37
|
#
|
36
|
-
# @param mod [Module]
|
38
|
+
# @param mod [Module] the module to build a class for
|
39
|
+
# @return [Class] a singleton instantiable class that includes +mod+
|
37
40
|
def self.instantiable_class(mod)
|
38
41
|
Class.new do
|
39
42
|
include mod
|
data/lib/db_mod/statements.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require_relative 'statements/configuration'
|
2
|
+
require_relative 'statements/default_method_settings'
|
2
3
|
require_relative 'statements/statement'
|
3
4
|
require_relative 'statements/prepared'
|
4
5
|
|
@@ -12,7 +13,14 @@ module DbMod
|
|
12
13
|
module Statements
|
13
14
|
# Called when a module includes {DbMod},
|
14
15
|
# defines module-level +def_statement+ and +def_prepared+ dsl methods.
|
16
|
+
#
|
17
|
+
# @param mod [Module] module that has had {DbMod} included
|
18
|
+
# @see DbMod.included
|
19
|
+
# @see DefaultMethodSettings
|
20
|
+
# @see Prepared
|
21
|
+
# @see Statement
|
15
22
|
def self.setup(mod)
|
23
|
+
DbMod::Statements::DefaultMethodSettings.setup(mod)
|
16
24
|
DbMod::Statements::Prepared.setup(mod)
|
17
25
|
DbMod::Statements::Statement.setup(mod)
|
18
26
|
end
|
@@ -21,7 +21,14 @@ module DbMod
|
|
21
21
|
# @yield dsl block may be passed, which will be evaluated using a
|
22
22
|
# {MethodConfiguration} object as scope
|
23
23
|
def self.def_configurable(mod, name, definition, params = 0, &block)
|
24
|
-
config =
|
24
|
+
config =
|
25
|
+
if block_given?
|
26
|
+
MethodConfiguration.new(mod.default_method_settings, &block)
|
27
|
+
else
|
28
|
+
mod.default_method_settings
|
29
|
+
end
|
30
|
+
|
31
|
+
config &&= config.to_hash
|
25
32
|
|
26
33
|
definition = attach_result_processors(definition, config) if config
|
27
34
|
definition = attach_param_processor(definition, params, config)
|
@@ -45,6 +52,7 @@ module DbMod
|
|
45
52
|
|
46
53
|
elsif params.is_a?(Fixnum) && params > 0
|
47
54
|
define_fixed_args_method(definition, params)
|
55
|
+
|
48
56
|
else
|
49
57
|
->() { instance_exec(&definition) }
|
50
58
|
end
|
@@ -92,9 +100,11 @@ module DbMod
|
|
92
100
|
# @param definition [Proc] base method definition
|
93
101
|
# @param config [MethodConfiguration] configuration declared at
|
94
102
|
# method definition time
|
103
|
+
# @return [Proc] extended method definition
|
95
104
|
def self.attach_result_processors(definition, config)
|
96
105
|
definition = Single.extend(definition, config)
|
97
106
|
definition = As.extend(definition, config)
|
107
|
+
definition = Returning.extend(definition, config)
|
98
108
|
|
99
109
|
definition
|
100
110
|
end
|
@@ -108,9 +118,16 @@ module DbMod
|
|
108
118
|
# processing), perform some transform on it and return the result.
|
109
119
|
#
|
110
120
|
# @param definition [Proc] base method definition
|
111
|
-
# @param processor [
|
121
|
+
# @param processor [Proc,#call] result processor
|
122
|
+
# @return [Proc] wrapped method definition
|
112
123
|
def self.attach_result_processor(definition, processor)
|
113
|
-
|
124
|
+
if processor.is_a? Proc
|
125
|
+
lambda do |*args|
|
126
|
+
instance_exec(instance_exec(*args, &definition), &processor)
|
127
|
+
end
|
128
|
+
else
|
129
|
+
->(*args) { processor.call instance_exec(*args, &definition) }
|
130
|
+
end
|
114
131
|
end
|
115
132
|
end
|
116
133
|
end
|