db_mod 0.0.4 → 0.0.5

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.
Files changed (28) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +22 -0
  3. data/README.md +46 -14
  4. data/lib/db_mod/exceptions/no_results.rb +2 -1
  5. data/lib/db_mod/exceptions/too_many_results.rb +2 -1
  6. data/lib/db_mod/statements/configuration.rb +95 -24
  7. data/lib/db_mod/statements/configuration/as.rb +9 -16
  8. data/lib/db_mod/statements/configuration/as/csv.rb +4 -7
  9. data/lib/db_mod/statements/configuration/as/json.rb +8 -10
  10. data/lib/db_mod/statements/configuration/defaults.rb +140 -0
  11. data/lib/db_mod/statements/configuration/method_configuration.rb +109 -0
  12. data/lib/db_mod/statements/configuration/single.rb +16 -22
  13. data/lib/db_mod/statements/configuration/single/column.rb +1 -1
  14. data/lib/db_mod/statements/configuration/single/required_row.rb +1 -1
  15. data/lib/db_mod/statements/configuration/single/required_value.rb +1 -1
  16. data/lib/db_mod/statements/configuration/single/row.rb +1 -1
  17. data/lib/db_mod/statements/configuration/single/value.rb +1 -1
  18. data/lib/db_mod/statements/parameters.rb +3 -1
  19. data/lib/db_mod/statements/prepared.rb +18 -46
  20. data/lib/db_mod/statements/statement.rb +9 -56
  21. data/lib/db_mod/version.rb +1 -1
  22. data/spec/db_mod/statements/configuration/as/csv_spec.rb +2 -2
  23. data/spec/db_mod/statements/configuration/as/json_spec.rb +29 -2
  24. data/spec/db_mod/statements/configuration/as_spec.rb +2 -2
  25. data/spec/db_mod/statements/configuration/defaults_spec.rb +203 -0
  26. data/spec/db_mod/statements/configuration/single_spec.rb +7 -7
  27. metadata +6 -3
  28. data/lib/db_mod/statements/configuration/configurable_method.rb +0 -78
@@ -0,0 +1,109 @@
1
+ require_relative 'as'
2
+ require_relative 'defaults'
3
+ require_relative 'single'
4
+
5
+ module DbMod
6
+ module Statements
7
+ module Configuration
8
+ # Collects settings given at time of definition for statement
9
+ # and prepared methods. If a block is passed to +def_statement+
10
+ # or +def_prepared+ it will be evaluated using an instance of
11
+ # this class, allowing methods such as {#as} or {#single} to
12
+ # be used to shape the behaviour of the defined method.
13
+ class MethodConfiguration
14
+ # Creates a new configuration object to be used as the scope for
15
+ # blocks passed to +def_statement+ and +def_prepared+ declarations.
16
+ #
17
+ # @yield executes the block using +self+ as scope
18
+ def initialize(&block)
19
+ @settings = {}
20
+ instance_exec(&block) if block_given?
21
+ end
22
+
23
+ # Extend the method by converting results into a given
24
+ # format, using one of the coercion methods defined
25
+ # under {DbMod::Statements::Configuration::As}.
26
+ #
27
+ # @param type [:csv,:json] output format for the method
28
+ # @return [self]
29
+ def as(type)
30
+ one_of! type, Configuration::As::COERCERS
31
+ set_once! :as, type
32
+
33
+ self
34
+ end
35
+
36
+ # Extend the method by extracting a singular part of
37
+ # the result set, for queries expected to only return
38
+ # one row, one column, or one row with a single value.
39
+ # See {DbMod::Statements::Configuration::Single} for
40
+ # more details.
41
+ #
42
+ # @param type [Symbol] see {Configuration::Single::COERCERS}
43
+ # @return [self]
44
+ def single(type)
45
+ one_of! type, Configuration::Single::COERCERS
46
+ set_once! :single, type
47
+
48
+ self
49
+ end
50
+
51
+ # Declares default values for method parameters.
52
+ # For methods with named parameters, a hash of argument
53
+ # names and default values should be provided.
54
+ # For methods with indexed parameters, an array of 1..n
55
+ # default values should be provided, where n is the
56
+ # method's arity. In this case default values will be
57
+ # applied to the right-hand side of the argument list,
58
+ # as with normal parameter default rules.
59
+ #
60
+ # @param defaults [Hash<Symbol,value>,Array<value>]
61
+ # default parameter values
62
+ def defaults(*defaults)
63
+ if defaults.size == 1 && defaults.first.is_a?(Hash)
64
+ defaults = defaults.first
65
+ elsif defaults.last.is_a? Hash
66
+ fail ArgumentError, 'mixed default declaration not allowed'
67
+ end
68
+
69
+ set_once! :defaults, defaults
70
+
71
+ self
72
+ end
73
+
74
+ # Return all given settings in a hash.
75
+ # @return [Hash]
76
+ def to_hash
77
+ @settings
78
+ end
79
+
80
+ private
81
+
82
+ # Guard method which asserts that a configuration method
83
+ # may not be called more than once, or else raises
84
+ # {DbMod::Exceptions::BadMethodConfiguration}.
85
+ #
86
+ # @param setting [Symbol] setting name
87
+ # @param value [Object] setting value
88
+ def set_once!(setting, value)
89
+ if @settings.key? setting
90
+ fail Exceptions::BadMethodConfiguration, "#{setting} already called"
91
+ end
92
+
93
+ @settings[setting] = value
94
+ end
95
+
96
+ # Guard method which asserts that a configuration setting
97
+ # is one of the allowed values in the given hash.
98
+ #
99
+ # @param value [key] configuration setting
100
+ # @param allowed [Hash] set of allowed configuration settings
101
+ def one_of!(value, allowed)
102
+ return if allowed.key? value
103
+
104
+ fail ArgumentError, "#{value} not in #{allowed.keys.join ', '}"
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
@@ -10,17 +10,17 @@ module DbMod
10
10
  # Provides convenience extensions for statement and
11
11
  # prepared methods that return only a single result,
12
12
  # row, or column. The normal way to access this functionality
13
- # is via {ConfigurableMethod#single}, which is available
13
+ # is via {MethodConfiguration#single}, which is available
14
14
  # when defining a statement method or prepared method:
15
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)
16
+ # def_statement(:a, 'SELECT name FROM a WHERE id=$1') { single(:value) }
17
+ # def_prepared(:b, 'SELECT id FROM b WHERE x > $y') { single(:column) }
18
+ # def_prepared(:c, 'SELECT * FROM c WHERE id = $id') { single(:row) }
19
19
  #
20
20
  # def do_stuff
21
- # a # => "foo"
22
- # b # => ['1','2','3',...]
23
- # c # => Hash
21
+ # a(1) # => "foo"
22
+ # b(y: 2) # => ['1','2','3',...]
23
+ # c id: 3 # => Hash
24
24
  # end
25
25
  #
26
26
  # +.single(:row)+ and +.single(:value)+ will return the first
@@ -30,10 +30,7 @@ module DbMod
30
30
  # instead of returning +nil+, use +.single(:row!)+ or
31
31
  # +.single(:value!)+.
32
32
  module Single
33
- # For process_method_results
34
- Configuration = DbMod::Statements::Configuration
35
-
36
- # List of allowed parameters for {#single},
33
+ # List of allowed parameters for {MethodConfiguration#single},
37
34
  # and the methods used to process them.
38
35
  COERCERS = {
39
36
  value: Single::Value,
@@ -43,19 +40,16 @@ module DbMod
43
40
  column: Single::Column
44
41
  }
45
42
 
46
- # Extend a method so that only some singular part of
47
- # the SQL result set is returned.
48
- # See above for more details.
43
+ # Extend the given method definition with additional
44
+ # result coercion.
49
45
  #
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
46
+ # @param definition [Proc] base method definition
47
+ # @param config [MethodConfiguration] method configuration
48
+ def self.extend(definition, config)
49
+ type = config[:single]
50
+ return definition if type.nil?
57
51
 
58
- Configuration.process_method_results(mod, name, COERCERS[type])
52
+ Configuration.attach_result_processor definition, COERCERS[type]
59
53
  end
60
54
  end
61
55
  end
@@ -13,7 +13,7 @@ module DbMod
13
13
  # end
14
14
  module Column
15
15
  # Enables this module to be passed to
16
- # {DbMod::Statements::Configuration.process_method_results} as the
16
+ # {DbMod::Statements::Configuration.attach_result_processor} as the
17
17
  # +wrapper+ function, where it will return an array of the first
18
18
  # value from every row in the result set.
19
19
  #
@@ -14,7 +14,7 @@ module DbMod
14
14
  # end
15
15
  module RequiredRow
16
16
  # Enables this module to be passed to
17
- # {DbMod::Statements::Configuration.process_method_results} as the
17
+ # {DbMod::Statements::Configuration.attach_result_processor} as the
18
18
  # +wrapper+ function, where it will return the first row of the
19
19
  # result set, or raise an exception if exactly one row is not
20
20
  # returned.
@@ -14,7 +14,7 @@ module DbMod
14
14
  # end
15
15
  module RequiredValue
16
16
  # Enables this module to be passed to
17
- # {DbMod::Statements::Configuration.process_method_results} as the
17
+ # {DbMod::Statements::Configuration.attach_result_processor} as the
18
18
  # +wrapper+ function, where it will return the first column of the
19
19
  # first row of the result set, or fail if anything other than
20
20
  # exactly one row is returned.
@@ -14,7 +14,7 @@ module DbMod
14
14
  # end
15
15
  module Row
16
16
  # Enables this module to be passed to
17
- # {DbMod::Statements::Configuration.process_method_results} as the
17
+ # {DbMod::Statements::Configuration.attach_result_processor} as the
18
18
  # +wrapper+ function, where it will return the first row of the
19
19
  # result set, or +nil+ if the result set is empty.
20
20
  #
@@ -13,7 +13,7 @@ module DbMod
13
13
  # end
14
14
  module Value
15
15
  # Enables this module to be passed to
16
- # {DbMod::Statements::Configuration.process_method_results} as the
16
+ # {DbMod::Statements::Configuration.attach_result_processor} as the
17
17
  # +wrapper+ function, where it will return the first column of the
18
18
  # first row of the result set, or +nil+ if no results are returned.
19
19
  #
@@ -101,7 +101,9 @@ module DbMod
101
101
  # @return [Array] values to be passed to the prepared statement
102
102
  def self.parameter_array(expected, args)
103
103
  expected.map do |arg|
104
- args[arg] || fail(ArgumentError, "missing arg #{arg}")
104
+ fail(ArgumentError, "missing arg #{arg}") unless args.key? arg
105
+
106
+ args[arg]
105
107
  end
106
108
  end
107
109
 
@@ -65,13 +65,13 @@ module DbMod
65
65
  # @param mod [Module] a module with {DbMod} included
66
66
  def self.define_def_prepared(mod)
67
67
  mod.class.instance_eval do
68
- define_method(:def_prepared) do |name, sql|
68
+ define_method(:def_prepared) do |name, sql, &block|
69
69
  sql = sql.dup
70
70
  name = name.to_sym
71
71
 
72
72
  params = Parameters.parse_params! sql
73
73
  prepared_statements[name] = sql
74
- Prepared.define_prepared_method(mod, name, params)
74
+ Prepared.define_prepared_method(mod, name, params, &block)
75
75
  end
76
76
  end
77
77
  end
@@ -119,15 +119,13 @@ module DbMod
119
119
  # @param params [Fixnum,Array<Symbol>]
120
120
  # expected parameter count, or a list of argument names.
121
121
  # An empty array produces a no-argument method.
122
- def self.define_prepared_method(mod, name, params)
123
- if params.is_a?(Array)
124
- if params.empty?
125
- define_no_args_prepared_method(mod, name)
126
- else
127
- define_named_args_prepared_method(mod, name, params)
128
- end
122
+ # @yield dsl block may be passed, which will be evaluated using a
123
+ # {Configuration::MethodConfiguration} object as scope
124
+ def self.define_prepared_method(mod, name, params, &block)
125
+ if params == []
126
+ define_no_args_prepared_method(mod, name, &block)
129
127
  else
130
- define_fixed_args_prepared_method(mod, name, params)
128
+ define_prepared_method_with_args(mod, name, params, &block)
131
129
  end
132
130
  end
133
131
 
@@ -139,48 +137,22 @@ module DbMod
139
137
  # where the method will be defined
140
138
  # @param name [Symbol] name of the method to be defined
141
139
  # and the prepared query to be called.
142
- def self.define_no_args_prepared_method(mod, name)
143
- method = ->() { conn.exec_prepared(name.to_s) }
144
- Configuration.def_configurable mod, name, method
145
- end
146
-
147
- # Define a method with the given name that accepts the
148
- # given set of named parameters, that will call the prepared
149
- # statement with the same name.
150
- #
151
- # @param mod [Module] {DbMod} enabled module
152
- # where the method will be defined
153
- # @param name [Symbol] name of the method to be defined
154
- # and the prepared query to be called.
155
- # @param params [Array<Symbol>] list of parameter names
156
- def self.define_named_args_prepared_method(mod, name, params)
157
- method = lambda do |*args|
158
- args = Parameters.valid_named_args! params, args
159
- conn.exec_prepared(name.to_s, args)
160
- end
161
-
162
- Configuration.def_configurable mod, name, method
140
+ # @yield dsl method configuration object may be passed
141
+ def self.define_no_args_prepared_method(mod, name, &block)
142
+ method = ->(*) { conn.exec_prepared(name.to_s) }
143
+ Configuration.def_configurable mod, name, method, &block
163
144
  end
164
145
 
165
- # Define a method with the given name that accepts a fixed
166
- # number of arguments, that will call the prepared statement
167
- # with the same name.
168
- #
169
146
  # @param mod [Module] {DbMod} enabled module
170
147
  # where the method will be defined
171
148
  # @param name [Symbol] name of the method to be defined
172
149
  # and the prepared query to be called.
173
- # @param count [Fixnum] arity of the defined method,
174
- # the number of parameters that the prepared statement
175
- # requires
176
- def self.define_fixed_args_prepared_method(mod, name, count)
177
- method = lambda do |*args|
178
- Parameters.valid_fixed_args!(count, args)
179
-
180
- conn.exec_prepared(name.to_s, args)
181
- end
182
-
183
- Configuration.def_configurable(mod, name, method)
150
+ # @param params [Fixnum,Array<Symbol>]
151
+ # expected parameter count, or a list of argument names.
152
+ # An empty array produces a no-argument method.
153
+ def self.define_prepared_method_with_args(mod, name, params, &block)
154
+ method = ->(*args) { conn.exec_prepared(name.to_s, args) }
155
+ Configuration.def_configurable(mod, name, method, params, &block)
184
156
  end
185
157
 
186
158
  # Adds +prepared_statements+ to a module. This list of named
@@ -61,12 +61,12 @@ module DbMod
61
61
  # @param mod [Module] a module with {DbMod} included
62
62
  def self.define_def_statement(mod)
63
63
  mod.class.instance_eval do
64
- define_method(:def_statement) do |name, sql|
64
+ define_method(:def_statement) do |name, sql, &block|
65
65
  sql = sql.dup
66
66
  name = name.to_sym
67
67
 
68
68
  params = Parameters.parse_params! sql
69
- Statement.define_statement_method(mod, name, params, sql)
69
+ Statement.define_statement_method(mod, name, params, sql, &block)
70
70
  end
71
71
  end
72
72
  end
@@ -80,62 +80,15 @@ module DbMod
80
80
  # @param params [Fixnum,Array<Symbol>]
81
81
  # expected parameter count, or a list of argument names.
82
82
  # An empty array produces a no-argument method.
83
- def self.define_statement_method(mod, name, params, sql)
84
- if params.is_a?(Array)
85
- if params.empty?
86
- define_no_args_statement_method(mod, name, sql)
87
- else
88
- define_named_args_statement_method(mod, name, params, sql)
89
- end
83
+ # @yield dsl block may be passed, which will be evaluated using a
84
+ # {Configuration::MethodConfiguration} object as scope
85
+ def self.define_statement_method(mod, name, params, sql, &block)
86
+ if params == []
87
+ Configuration.def_configurable(mod, name, ->(*) { query sql }, &block)
90
88
  else
91
- define_fixed_args_statement_method(mod, name, params, sql)
92
- end
93
- end
94
-
95
- # Define a no-argument method with the given name
96
- # that will execute the given sql statement and return
97
- # the result.
98
- #
99
- # @param mod [Module] {DbMod} enabled module
100
- # where the method will be defined
101
- # @param name [Symbol] name of the method to be defined
102
- # @param sql [String] parameterless SQL statement to execute
103
- def self.define_no_args_statement_method(mod, name, sql)
104
- Configuration.def_configurable mod, name, ->() { query(sql) }
105
- end
106
-
107
- # Define a method with the given name, that accepts the
108
- # given set of named parameters that will be used to execute
109
- # the given SQL query.
110
- #
111
- # @param mod [Module] {DbMod} enabled module
112
- # @param name [Symbol] name of the method to be defined
113
- # @param params [Array<Symbol>] parameter names and order
114
- def self.define_named_args_statement_method(mod, name, params, sql)
115
- method = lambda do |*args|
116
- args = Parameters.valid_named_args! params, args
117
- conn.exec_params(sql, args)
89
+ method = ->(*args) { conn.exec_params(sql, args) }
90
+ Configuration.def_configurable(mod, name, method, params, &block)
118
91
  end
119
-
120
- Configuration.def_configurable mod, name, method
121
- end
122
-
123
- # Define a method with the given name that accepts a fixed number
124
- # of arguments, that will be used to execute the given SQL query.
125
- #
126
- # @param mod [Module] {DbMod} enabled module where the method
127
- # will be defined
128
- # @param name [Symbol] name of the method to be defined
129
- # @param count [Fixnum] arity of the defined method,
130
- # the number of parameters that the SQL statement requires
131
- def self.define_fixed_args_statement_method(mod, name, count, sql)
132
- method = lambda do |*args|
133
- Parameters.valid_fixed_args!(count, args)
134
-
135
- conn.exec_params(sql, args)
136
- end
137
-
138
- Configuration.def_configurable mod, name, method
139
92
  end
140
93
  end
141
94
  end
@@ -1,5 +1,5 @@
1
1
  # Version information
2
2
  module DbMod
3
3
  # The current version of db_mod.
4
- VERSION = '0.0.4'
4
+ VERSION = '0.0.5'
5
5
  end
@@ -6,8 +6,8 @@ describe DbMod::Statements::Configuration::As::Csv do
6
6
  Module.new do
7
7
  include DbMod
8
8
 
9
- def_statement(:statement, 'SELECT a, b FROM foo').as(:csv)
10
- def_prepared(:prepared, 'SELECT a, b FROM bar').as(:csv)
9
+ def_statement(:statement, 'SELECT a, b FROM foo') { as(:csv) }
10
+ def_prepared(:prepared, 'SELECT a, b FROM bar') { as(:csv) }
11
11
  end.create(db: 'testdb')
12
12
  end
13
13
 
@@ -6,8 +6,20 @@ describe DbMod::Statements::Configuration::As::Json do
6
6
  Module.new do
7
7
  include DbMod
8
8
 
9
- def_statement(:statement, 'SELECT a, b FROM foo').as(:json)
10
- def_prepared(:prepared, 'SELECT a, b FROM bar').as(:json)
9
+ def_statement(:statement, 'SELECT a, b FROM foo') { as(:json) }
10
+ def_prepared(:prepared, 'SELECT a, b FROM bar') { as(:json) }
11
+
12
+ def_statement(
13
+ :single,
14
+ 'SELECT * FROM foo WHERE a = $1'
15
+ ) { single(:row).as(:json) }
16
+
17
+ def_statement(
18
+ :single!,
19
+ 'SELECT * FROM foo WHERE a = $1'
20
+ ) { single(:row!).as(:json) }
21
+
22
+ def_statement(:col, 'SELECT a FROM foo') { single(:column).as(:json) }
11
23
  end.create(db: 'testdb')
12
24
  end
13
25
 
@@ -35,4 +47,19 @@ describe DbMod::Statements::Configuration::As::Json do
35
47
  end
36
48
  end
37
49
  end
50
+
51
+ it 'can be chained with single(:row)' do
52
+ result = [{ 'a' => '1', 'b' => '2' }]
53
+ expected = '{"a":"1","b":"2"}'
54
+
55
+ expect(@conn).to receive(:exec_params).exactly(2).times.and_return(result)
56
+ expect(subject.single(1)).to eq(expected)
57
+ expect(subject.single!(2)).to eq(expected)
58
+ end
59
+
60
+ it 'can be chained with single(:column)' do
61
+ result = [{ 'a' => '1' }, { 'a' => '2' }, { 'a' => '3' }]
62
+ expect(@conn).to receive(:query).and_return(result)
63
+ expect(subject.col).to eq('["1","2","3"]')
64
+ end
38
65
  end