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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e4a3d4f97f88694b3cde2f3d7d76dfdb303b9a2b
4
- data.tar.gz: 9bb085e3601b0bd356b5595402006cbdf09be6fa
3
+ metadata.gz: 7f28c5a3e9353c2ae963580ca872095598e926dd
4
+ data.tar.gz: 77b781ec8ba80b3d1adc89c6c515708f0ed5bda3
5
5
  SHA512:
6
- metadata.gz: 644b2c6f41aa82dc5088ece70d6b962497308b5344c2237329df5ffc93754238ff42b5e85410eb793e8d414a23bf627a149df23f00c6a06af8f461664be6b964
7
- data.tar.gz: 949071580278f7d612ceeefcc06b0b8260897cc5546ce558d207b0723943bff8a519ffd8d8fa7aeab59724774bc3dfe949df9a03665f39de74b0f4a88a9bd8ce
6
+ metadata.gz: f722a8ef3716308bf5232811298b4dc72cbebcb487299859b62d7acf4416f171fea909cbd282ada834d440a0fd06d718c8e7808bb4b6f28c00991694fd0c5df8
7
+ data.tar.gz: 4fe5b2cd8272462a7f902de07cf4210ac38aa681cbc6223418d24b7371f1da1484f5d4fa3963fe30c9fa750dd32466dd871b59d151876c80690de9e21ebfe1ca
@@ -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
@@ -4,11 +4,9 @@ gemspec
4
4
 
5
5
  group :development, :test do
6
6
  gem 'byebug' unless RUBY_VERSION < '2.0.0'
7
- gem 'rake'
8
- gem 'redcarpet'
9
- gem 'rubocop'
10
7
  gem 'guard'
11
8
  gem 'guard-bundler'
9
+ gem 'guard-inch'
12
10
  gem 'guard-rspec'
13
11
  gem 'guard-rubocop'
14
12
  gem 'guard-yard'
data/Guardfile CHANGED
@@ -92,3 +92,7 @@ guard :bundler do
92
92
  # Assume files are symlinked from somewhere
93
93
  files.each { |file| watch(helper.real_path(file)) }
94
94
  end
95
+
96
+ guard :inch, pedantic: true, private: true do
97
+ watch(/.+\.rb/)
98
+ end
data/README.md CHANGED
@@ -1,9 +1,12 @@
1
- ## `db_mod`
1
+ ## DbMod
2
2
 
3
3
  Database enabled modules for ruby.
4
4
 
5
5
  [![GitHub version](https://badge.fury.io/gh/dslh%2Fdb_mod.svg)](https://github.com/dslh/db_mod)
6
6
  [![Travis CI](https://img.shields.io/travis/dslh/db_mod/master.svg)](https://travis-ci.org/dslh/db_mod)
7
+ [![Code Climate](https://codeclimate.com/github/dslh/db_mod/badges/gpa.svg)](https://codeclimate.com/github/dslh/db_mod)
8
+ [![Test Coverage](https://codeclimate.com/github/dslh/db_mod/badges/coverage.svg)](https://codeclimate.com/github/dslh/db_mod/coverage)
9
+ [![Inline docs](http://inch-ci.org/github/dslh/db_mod.svg?branch=master)](http://inch-ci.org/github/dslh/db_mod)
7
10
  [![Gem downloads](https://img.shields.io/gem/dt/db_mod.svg)](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(1) # === a(1, 5, 6)
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
@@ -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 'simplecov'
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")
@@ -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 {#def_prepared}.
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
- attr_accessor :conn
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],
@@ -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
- mod.class.instance_eval do
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
@@ -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 = MethodConfiguration.new(&block).to_hash if block_given?
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 [#call] result processor
121
+ # @param processor [Proc,#call] result processor
122
+ # @return [Proc] wrapped method definition
112
123
  def self.attach_result_processor(definition, processor)
113
- ->(*args) { processor.call instance_exec(*args, &definition) }
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