db_mod 0.0.5 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
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