db_mod 0.0.4 → 0.0.5

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