github-ds 0.2.9 → 0.5.0

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
- SHA1:
3
- metadata.gz: 716ec52fb178302c762091225dc4ab8b1b61a793
4
- data.tar.gz: dd79687dfd3a302ebff1978a9f2ea18adf649a05
2
+ SHA256:
3
+ metadata.gz: 88208f2b8cc24184dce9a984af467c44d9560196e9832d637ee99aac34082da8
4
+ data.tar.gz: 70f8c5e30c4b237e33e0d96ff747c4719bcf300f5a67472fe7d347e6779ff0f7
5
5
  SHA512:
6
- metadata.gz: f23f9d2e7467159b263ec39352c22f1e400198a81dc5294fea08c0e99361f9fe76a44f5a694979fe72359cf8d174dd9655503e8c7f8ea3aa3a0bc8cbb4c28468
7
- data.tar.gz: 732716e7a5a28b5526934ad854e1148b17c35322546d01fce321b26417067dd9542d026ef69dfed371dede8a744871a3a505053fc93a7c560de851a845e663e5
6
+ metadata.gz: 5afcb251c8dcbb250db23eb758f67a38baa8933fcf6325f2c9a419ae1dcee5455d7571a0eb0cfed0b401134225e482c74cab421b0bd116931996b5b954710c78
7
+ data.tar.gz: bda6aa58192ec5d29d3a909f1d14d939fcb4b069019c7d617ede8f3e580eceedaddb180cf2c266cf9e8d21e262d7752d3d04d20ecfe1e5491cf75f544a611395
@@ -1,11 +1,14 @@
1
1
  language: ruby
2
+ before_install:
3
+ - gem uninstall -v '>= 2' -i $(rvm gemdir)@global -ax bundler || true
4
+ - gem install bundler -v '<2'
2
5
  rvm:
3
- - 2.2
4
- - 2.3
5
- - 2.4
6
6
  - 2.5
7
+ - 2.6
8
+ - 2.7
7
9
  script: bundle exec rake
8
10
  env:
9
- - RAILS_VERSION=5.1.5
10
- - RAILS_VERSION=5.0.6
11
- - RAILS_VERSION=4.2.10
11
+ - RAILS_VERSION=6.0.3.1
12
+ - RAILS_VERSION=5.2.0
13
+ services:
14
+ - mysql
data/Gemfile CHANGED
@@ -1,12 +1,18 @@
1
1
  source "https://rubygems.org"
2
2
  gemspec
3
3
 
4
- DEFAULT_RAILS_VERSION = '5.0.2'
4
+ DEFAULT_RAILS_VERSION = '6.0.3'
5
+ ENV['RAILS_VERSION'] ||= DEFAULT_RAILS_VERSION
5
6
 
6
- if ENV['RAILS_VERSION'] = '4.2.10'
7
+ if ENV['RAILS_VERSION'] == '4.2.10'
7
8
  gem 'mysql2', '~> 0.3.18'
8
9
  else
9
10
  gem "mysql2"
10
11
  end
11
- gem "rails", "~> #{ENV['RAILS_VERSION'] || DEFAULT_RAILS_VERSION}"
12
- gem "activerecord", "~> #{ENV['RAILS_VERSION'] || DEFAULT_RAILS_VERSION}"
12
+
13
+ if ENV['RAILS_VERSION'] == 'master'
14
+ gem "activerecord", git: "https://github.com/rails/rails"
15
+ else
16
+ gem "rails", "~> #{ENV['RAILS_VERSION'] || DEFAULT_RAILS_VERSION}"
17
+ gem "activerecord", "~> #{ENV['RAILS_VERSION'] || DEFAULT_RAILS_VERSION}"
18
+ end
data/README.md CHANGED
@@ -37,6 +37,14 @@ rails generate github:ds:active_record
37
37
  rails db:migrate
38
38
  ```
39
39
 
40
+ If you need to change the name of the table used for storing the key-values, you can configure your table name as such, before running the migration:
41
+
42
+ ```
43
+ GitHub::KV.configure do |config|
44
+ config.table_name = "new_key_values_table"
45
+ end
46
+ ```
47
+
40
48
  Once you have created and executed the migration, KV can do neat things like this:
41
49
 
42
50
  ```ruby
@@ -30,13 +30,15 @@ Gem::Specification.new do |spec|
30
30
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
31
31
  spec.require_paths = ["lib"]
32
32
 
33
- spec.add_dependency "activerecord", ">= 3.2", "< 6.0"
33
+ spec.add_dependency "activerecord", ">= 3.2"
34
34
 
35
- spec.add_development_dependency "bundler", "~> 1.14"
36
- spec.add_development_dependency "rake", "~> 10.0"
35
+ spec.add_development_dependency "bundler", ">= 1.14"
36
+ spec.add_development_dependency "rake", "~> 12.0"
37
37
  spec.add_development_dependency "minitest", "~> 5.0"
38
38
  spec.add_development_dependency "timecop", "~> 0.8.1"
39
39
  spec.add_development_dependency "activesupport"
40
40
  spec.add_development_dependency "mysql2"
41
41
  spec.add_development_dependency "mocha", "~> 1.2.1"
42
+ spec.add_development_dependency "minitest-focus", "~> 1.1.2"
43
+ spec.add_development_dependency "pry", "~> 0.12.2"
42
44
  end
@@ -18,7 +18,7 @@ module Github
18
18
  end
19
19
 
20
20
  def self.migration_version
21
- if Rails.version.start_with?('5')
21
+ if Rails::VERSION::MAJOR.to_i >= 5
22
22
  "[#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}]"
23
23
  end
24
24
  end
@@ -26,6 +26,14 @@ module Github
26
26
  def migration_version
27
27
  self.class.migration_version
28
28
  end
29
+
30
+ def self.table_name
31
+ ":#{GitHub::KV.config.table_name}"
32
+ end
33
+
34
+ def table_name
35
+ self.class.table_name
36
+ end
29
37
  end
30
38
  end
31
39
  end
@@ -1,19 +1,19 @@
1
1
  class CreateKeyValuesTable < ActiveRecord::Migration<%= migration_version %>
2
2
  def self.up
3
- create_table :key_values do |t|
3
+ create_table <%= table_name %> do |t|
4
4
  t.string :key, :null => false
5
5
  t.binary :value, :null => false
6
6
  t.datetime :expires_at, :null => true
7
7
  t.timestamps :null => false
8
8
  end
9
9
 
10
- add_index :key_values, :key, :unique => true
11
- add_index :key_values, :expires_at
10
+ add_index <%= table_name %>, :key, :unique => true
11
+ add_index <%= table_name %>, :expires_at
12
12
 
13
- change_column :key_values, :id, "bigint(20) NOT NULL AUTO_INCREMENT"
13
+ change_column <%= table_name %>, :id, "bigint(20) NOT NULL AUTO_INCREMENT"
14
14
  end
15
15
 
16
16
  def self.down
17
- drop_table :key_values
17
+ drop_table <%= table_name %>
18
18
  end
19
19
  end
@@ -1,5 +1,5 @@
1
1
  module GitHub
2
2
  module DS
3
- VERSION = "0.2.9"
3
+ VERSION = "0.5.0"
4
4
  end
5
5
  end
@@ -1,3 +1,4 @@
1
+ require_relative "kv/config"
1
2
  require_relative "result"
2
3
  require_relative "sql"
3
4
 
@@ -48,11 +49,42 @@ module GitHub
48
49
  KeyLengthError = Class.new(StandardError)
49
50
  ValueLengthError = Class.new(StandardError)
50
51
  UnavailableError = Class.new(StandardError)
52
+ InvalidValueError = Class.new(StandardError)
51
53
 
52
54
  class MissingConnectionError < StandardError; end
53
55
 
54
- def initialize(encapsulated_errors = [SystemCallError], &conn_block)
55
- @encapsulated_errors = encapsulated_errors
56
+ attr_accessor :use_local_time
57
+ attr_writer :config
58
+
59
+ def self.config
60
+ @config ||= Config.new
61
+ end
62
+
63
+ def self.reset
64
+ @config = Config.new
65
+ end
66
+
67
+ def self.configure
68
+ yield(config)
69
+ end
70
+
71
+ # initialize :: [Exception], Boolean, Proc -> nil
72
+ #
73
+ # Initialize a new KV instance.
74
+ #
75
+ # encapsulated_errors - An Array of Exception subclasses that, when raised,
76
+ # will be replaced with UnavailableError.
77
+ # use_local_time: - Whether to use Ruby's `Time.now` instaed of MySQL's
78
+ # `NOW()` function. This is mostly useful in testing
79
+ # where time needs to be modified (eg. Timecop).
80
+ # Default false.
81
+ # &conn_block - A block to call to open a new database connection.
82
+ #
83
+ # Returns nothing.
84
+ def initialize(config: GitHub::KV.config, &conn_block)
85
+ @encapsulated_errors = config.encapsulated_errors
86
+ @use_local_time = config.use_local_time
87
+ @table_name = config.table_name
56
88
  @conn_block = conn_block
57
89
  end
58
90
 
@@ -93,8 +125,8 @@ module GitHub
93
125
  validate_key_array(keys)
94
126
 
95
127
  Result.new {
96
- kvs = GitHub::SQL.results(<<-SQL, :keys => keys, :connection => connection).to_h
97
- SELECT `key`, value FROM key_values WHERE `key` IN :keys AND (`expires_at` IS NULL OR `expires_at` > NOW())
128
+ kvs = GitHub::SQL.results(<<-SQL, :keys => keys, :now => now, :connection => connection).to_h
129
+ SELECT `key`, value FROM #{@table_name} WHERE `key` IN :keys AND (`expires_at` IS NULL OR `expires_at` > :now)
98
130
  SQL
99
131
 
100
132
  keys.map { |key| kvs[key] }
@@ -136,12 +168,13 @@ module GitHub
136
168
  validate_expires(expires) if expires
137
169
 
138
170
  rows = kvs.map { |key, value|
139
- [key, value, GitHub::SQL::NOW, GitHub::SQL::NOW, expires || GitHub::SQL::NULL]
171
+ value = value.is_a?(GitHub::SQL::Literal) ? value : GitHub::SQL::BINARY(value)
172
+ [key, value, now, now, expires || GitHub::SQL::NULL]
140
173
  }
141
174
 
142
175
  encapsulate_error do
143
176
  GitHub::SQL.run(<<-SQL, :rows => GitHub::SQL::ROWS(rows), :connection => connection)
144
- INSERT INTO key_values (`key`, value, created_at, updated_at, expires_at)
177
+ INSERT INTO #{@table_name} (`key`, value, created_at, updated_at, expires_at)
145
178
  VALUES :rows
146
179
  ON DUPLICATE KEY UPDATE
147
180
  value = VALUES(value),
@@ -185,8 +218,8 @@ module GitHub
185
218
  validate_key_array(keys)
186
219
 
187
220
  Result.new {
188
- existing_keys = GitHub::SQL.values(<<-SQL, :keys => keys, :connection => connection).to_set
189
- SELECT `key` FROM key_values WHERE `key` IN :keys AND (`expires_at` IS NULL OR `expires_at` > NOW())
221
+ existing_keys = GitHub::SQL.values(<<-SQL, :keys => keys, :now => now, :connection => connection).to_set
222
+ SELECT `key` FROM #{@table_name} WHERE `key` IN :keys AND (`expires_at` IS NULL OR `expires_at` > :now)
190
223
  SQL
191
224
 
192
225
  keys.map { |key| existing_keys.include?(key) }
@@ -221,19 +254,116 @@ module GitHub
221
254
  # achieve the same thing with the right INSERT ... ON DUPLICATE KEY UPDATE
222
255
  # query, but then we would not be able to rely on affected_rows
223
256
 
224
- GitHub::SQL.run(<<-SQL, :key => key, :connection => connection)
225
- DELETE FROM key_values WHERE `key` = :key AND expires_at <= NOW()
257
+ GitHub::SQL.run(<<-SQL, :key => key, :now => now, :connection => connection)
258
+ DELETE FROM #{@table_name} WHERE `key` = :key AND expires_at <= :now
226
259
  SQL
227
260
 
228
- sql = GitHub::SQL.run(<<-SQL, :key => key, :value => value, :expires => expires || GitHub::SQL::NULL, :connection => connection)
229
- INSERT IGNORE INTO key_values (`key`, value, created_at, updated_at, expires_at)
230
- VALUES (:key, :value, NOW(), NOW(), :expires)
261
+ value = value.is_a?(GitHub::SQL::Literal) ? value : GitHub::SQL::BINARY(value)
262
+ sql = GitHub::SQL.run(<<-SQL, :key => key, :value => value, :now => now, :expires => expires || GitHub::SQL::NULL, :connection => connection)
263
+ INSERT IGNORE INTO #{@table_name} (`key`, value, created_at, updated_at, expires_at)
264
+ VALUES (:key, :value, :now, :now, :expires)
231
265
  SQL
232
266
 
233
267
  sql.affected_rows > 0
234
268
  }
235
269
  end
236
270
 
271
+ # increment :: String, Integer, expires: Time? -> Integer
272
+ #
273
+ # Increment the key's value by an amount.
274
+ #
275
+ # key - The key to increment.
276
+ # amount - The amount to increment the key's value by.
277
+ # The user can increment by both positive and
278
+ # negative values
279
+ # expires - When the key should expire.
280
+ # touch_on_insert - Only when expires is specified. When true
281
+ # the expires value is only touched upon
282
+ # inserts. Otherwise the record is always
283
+ # touched.
284
+ #
285
+ # Returns the key's value after incrementing.
286
+ def increment(key, amount: 1, expires: nil, touch_on_insert: false)
287
+ validate_key(key)
288
+ validate_amount(amount) if amount
289
+ validate_expires(expires) if expires
290
+ validate_touch(touch_on_insert, expires)
291
+
292
+ expires ||= GitHub::SQL::NULL
293
+
294
+ # This query uses a few MySQL "hacks" to ensure that the incrementing
295
+ # is done atomically and the value is returned. The first trick is done
296
+ # using the `LAST_INSERT_ID` function. This allows us to manually set
297
+ # the LAST_INSERT_ID returned by the query. Here we are able to set it
298
+ # to the new value when an increment takes place, essentially allowing us
299
+ # to do: `UPDATE...;SELECT value from key_value where key=:key` in a
300
+ # single step.
301
+ #
302
+ # However the `LAST_INSERT_ID` trick is only used when the value is
303
+ # updated. Upon a fresh insert we know the amount is going to be set
304
+ # to the amount specified.
305
+ #
306
+ # Lastly we only do these tricks when the value at the key is an integer.
307
+ # If the value is not an integer the update ensures the values remain the
308
+ # same and we raise an error.
309
+ encapsulate_error {
310
+ sql = GitHub::SQL.run(<<-SQL, key: key, amount: amount, now: now, expires: expires, touch: !touch_on_insert, connection: connection)
311
+ INSERT INTO #{@table_name} (`key`, `value`, `created_at`, `updated_at`, `expires_at`)
312
+ VALUES(:key, :amount, :now, :now, :expires)
313
+ ON DUPLICATE KEY UPDATE
314
+ `value`=IF(
315
+ concat('',`value`*1) = `value`,
316
+ LAST_INSERT_ID(IF(
317
+ `expires_at` IS NULL OR `expires_at`>=:now,
318
+ `value`+:amount,
319
+ :amount
320
+ )),
321
+ `value`
322
+ ),
323
+ `updated_at`=IF(
324
+ concat('',`value`*1) = `value`,
325
+ :now,
326
+ `updated_at`
327
+ ),
328
+ `expires_at`=IF(
329
+ concat('',`value`*1) = `value`,
330
+ IF(
331
+ :touch,
332
+ :expires,
333
+ `expires_at`
334
+ ),
335
+ `expires_at`
336
+ )
337
+ SQL
338
+
339
+ # The ordering of these statements is extremely important if we are to
340
+ # support incrementing a negative amount. The checks occur in this order:
341
+ # 1. Check if an update with new values occurred? If so return the result
342
+ # This could potentially result in `sql.last_insert_id` with a value
343
+ # of 0, thus it must be before the second check.
344
+ # 2. Check if an update took place but nothing changed (I.E. no new value
345
+ # was set)
346
+ # 3. Check if an insert took place.
347
+ #
348
+ # See https://dev.mysql.com/doc/refman/8.0/en/insert-on-duplicate.html for
349
+ # more information (NOTE: CLIENT_FOUND_ROWS is set)
350
+ if sql.affected_rows == 2
351
+ # An update took place in which data changed. We use a hack to set
352
+ # the last insert ID to be the new value.
353
+ sql.last_insert_id
354
+ elsif sql.affected_rows == 0 || (sql.affected_rows == 1 && sql.last_insert_id == 0)
355
+ # No insert took place nor did any update occur. This means that
356
+ # the value was not an integer thus not incremented.
357
+ raise InvalidValueError
358
+ elsif sql.affected_rows == 1
359
+ # If the number of affected_rows is 1 then a new value was inserted
360
+ # thus we can just return the amount given to us since that is the
361
+ # value at the key
362
+ amount
363
+ end
364
+ }
365
+ end
366
+
237
367
  # del :: String -> nil
238
368
  #
239
369
  # Deletes the specified key. Returns nil. Raises on error.
@@ -263,7 +393,7 @@ module GitHub
263
393
 
264
394
  encapsulate_error do
265
395
  GitHub::SQL.run(<<-SQL, :keys => keys, :connection => connection)
266
- DELETE FROM key_values WHERE `key` IN :keys
396
+ DELETE FROM #{@table_name} WHERE `key` IN :keys
267
397
  SQL
268
398
  end
269
399
 
@@ -286,14 +416,40 @@ module GitHub
286
416
  validate_key(key)
287
417
 
288
418
  Result.new {
289
- GitHub::SQL.value(<<-SQL, :key => key, :connection => connection)
290
- SELECT expires_at FROM key_values
291
- WHERE `key` = :key AND (expires_at IS NULL OR expires_at > NOW())
419
+ GitHub::SQL.value(<<-SQL, :key => key, :now => now, :connection => connection)
420
+ SELECT expires_at FROM #{@table_name}
421
+ WHERE `key` = :key AND (expires_at IS NULL OR expires_at > :now)
292
422
  SQL
293
423
  }
294
424
  end
295
425
 
426
+ # mttl :: [String] -> Result<[Time | nil]>
427
+ #
428
+ # Returns the expires_at time for the specified key or nil.
429
+ #
430
+ # Example:
431
+ #
432
+ # kv.mttl(["foo", "octocat"])
433
+ # # => #<Result value: [2018-04-23 11:34:54 +0200, nil]>
434
+ #
435
+ def mttl(keys)
436
+ validate_key_array(keys)
437
+
438
+ Result.new {
439
+ kvs = GitHub::SQL.results(<<-SQL, :keys => keys, :now => now, :connection => connection).to_h
440
+ SELECT `key`, expires_at FROM #{@table_name}
441
+ WHERE `key` in :keys AND (expires_at IS NULL OR expires_at > :now)
442
+ SQL
443
+
444
+ keys.map { |key| kvs[key] }
445
+ }
446
+ end
447
+
296
448
  private
449
+ def now
450
+ use_local_time ? Time.now : GitHub::SQL::NOW
451
+ end
452
+
297
453
  def validate_key(key, error_message: nil)
298
454
  unless key.is_a?(String)
299
455
  raise TypeError, error_message || "key must be a String in #{self.class.name}, but was #{key.class}"
@@ -347,6 +503,19 @@ module GitHub
347
503
  end
348
504
  end
349
505
 
506
+ def validate_amount(amount)
507
+ raise ArgumentError.new("The amount specified must be an integer") unless amount.is_a? Integer
508
+ raise ArgumentError.new("The amount specified cannot be 0") if amount == 0
509
+ end
510
+
511
+ def validate_touch(touch, expires)
512
+ raise ArgumentError.new("touch_on_insert must be a boolean value") unless [true, false].include?(touch)
513
+
514
+ if touch && expires.nil?
515
+ raise ArgumentError.new("Please specify an expires value if you wish to touch on insert")
516
+ end
517
+ end
518
+
350
519
  def validate_expires(expires)
351
520
  unless expires.respond_to?(:to_time)
352
521
  raise TypeError, "expires must be a time of some sort (Time, DateTime, ActiveSupport::TimeWithZone, etc.), but was #{expires.class}"
@@ -0,0 +1,13 @@
1
+ module GitHub
2
+ class KV
3
+ class Config
4
+ attr_accessor :table_name, :encapsulated_errors, :use_local_time
5
+
6
+ def initialize
7
+ @table_name = 'key_values'
8
+ @encapsulated_errors = [SystemCallError]
9
+ @use_local_time = false
10
+ end
11
+ end
12
+ end
13
+ end
@@ -43,7 +43,7 @@ module GitHub
43
43
  # GitHub::SQL#initialize or overriding connection then you'll need to use
44
44
  # the instance version.
45
45
  def self.transaction(options = {}, &block)
46
- ActiveRecord::Base.connection.transaction(options, &block)
46
+ ActiveRecord::Base.connection.transaction(**options, &block)
47
47
  end
48
48
 
49
49
  # Public: Instantiate a literal SQL value.
@@ -213,7 +213,7 @@ module GitHub
213
213
  # Use select_all to retrieve hashes for each row instead of arrays of values.
214
214
  @models = connection.
215
215
  select_all(query, "#{klass.name} Load via #{self.class.name}").
216
- collect! { |record| klass.send :instantiate, record }
216
+ map { |record| klass.send :instantiate, record }
217
217
 
218
218
  retrieve_found_row_count
219
219
  freeze
@@ -299,7 +299,7 @@ module GitHub
299
299
 
300
300
  # Public: Run inside a transaction for the connection.
301
301
  def transaction(options = {}, &block)
302
- connection.transaction(options, &block)
302
+ connection.transaction(**options, &block)
303
303
  end
304
304
 
305
305
  # Internal: The object we use to execute SQL and retrieve results. Defaults
metadata CHANGED
@@ -1,15 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: github-ds
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.9
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - GitHub Open Source
8
8
  - John Nunemaker
9
- autorequire:
9
+ autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2018-05-11 00:00:00.000000000 Z
12
+ date: 2020-06-16 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activerecord
@@ -18,9 +18,6 @@ dependencies:
18
18
  - - ">="
19
19
  - !ruby/object:Gem::Version
20
20
  version: '3.2'
21
- - - "<"
22
- - !ruby/object:Gem::Version
23
- version: '6.0'
24
21
  type: :runtime
25
22
  prerelease: false
26
23
  version_requirements: !ruby/object:Gem::Requirement
@@ -28,21 +25,18 @@ dependencies:
28
25
  - - ">="
29
26
  - !ruby/object:Gem::Version
30
27
  version: '3.2'
31
- - - "<"
32
- - !ruby/object:Gem::Version
33
- version: '6.0'
34
28
  - !ruby/object:Gem::Dependency
35
29
  name: bundler
36
30
  requirement: !ruby/object:Gem::Requirement
37
31
  requirements:
38
- - - "~>"
32
+ - - ">="
39
33
  - !ruby/object:Gem::Version
40
34
  version: '1.14'
41
35
  type: :development
42
36
  prerelease: false
43
37
  version_requirements: !ruby/object:Gem::Requirement
44
38
  requirements:
45
- - - "~>"
39
+ - - ">="
46
40
  - !ruby/object:Gem::Version
47
41
  version: '1.14'
48
42
  - !ruby/object:Gem::Dependency
@@ -51,14 +45,14 @@ dependencies:
51
45
  requirements:
52
46
  - - "~>"
53
47
  - !ruby/object:Gem::Version
54
- version: '10.0'
48
+ version: '12.0'
55
49
  type: :development
56
50
  prerelease: false
57
51
  version_requirements: !ruby/object:Gem::Requirement
58
52
  requirements:
59
53
  - - "~>"
60
54
  - !ruby/object:Gem::Version
61
- version: '10.0'
55
+ version: '12.0'
62
56
  - !ruby/object:Gem::Dependency
63
57
  name: minitest
64
58
  requirement: !ruby/object:Gem::Requirement
@@ -129,6 +123,34 @@ dependencies:
129
123
  - - "~>"
130
124
  - !ruby/object:Gem::Version
131
125
  version: 1.2.1
126
+ - !ruby/object:Gem::Dependency
127
+ name: minitest-focus
128
+ requirement: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - "~>"
131
+ - !ruby/object:Gem::Version
132
+ version: 1.1.2
133
+ type: :development
134
+ prerelease: false
135
+ version_requirements: !ruby/object:Gem::Requirement
136
+ requirements:
137
+ - - "~>"
138
+ - !ruby/object:Gem::Version
139
+ version: 1.1.2
140
+ - !ruby/object:Gem::Dependency
141
+ name: pry
142
+ requirement: !ruby/object:Gem::Requirement
143
+ requirements:
144
+ - - "~>"
145
+ - !ruby/object:Gem::Version
146
+ version: 0.12.2
147
+ type: :development
148
+ prerelease: false
149
+ version_requirements: !ruby/object:Gem::Requirement
150
+ requirements:
151
+ - - "~>"
152
+ - !ruby/object:Gem::Version
153
+ version: 0.12.2
132
154
  description: A collection of libraries for working with SQL on top of ActiveRecord's
133
155
  connection.
134
156
  email:
@@ -161,6 +183,7 @@ files:
161
183
  - lib/github/ds.rb
162
184
  - lib/github/ds/version.rb
163
185
  - lib/github/kv.rb
186
+ - lib/github/kv/config.rb
164
187
  - lib/github/result.rb
165
188
  - lib/github/sql.rb
166
189
  - lib/github/sql/errors.rb
@@ -176,7 +199,7 @@ licenses:
176
199
  - MIT
177
200
  metadata:
178
201
  allowed_push_host: https://rubygems.org
179
- post_install_message:
202
+ post_install_message:
180
203
  rdoc_options: []
181
204
  require_paths:
182
205
  - lib
@@ -191,9 +214,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
191
214
  - !ruby/object:Gem::Version
192
215
  version: '0'
193
216
  requirements: []
194
- rubyforge_project:
195
- rubygems_version: 2.4.5
196
- signing_key:
217
+ rubygems_version: 3.1.2
218
+ signing_key:
197
219
  specification_version: 4
198
220
  summary: A collection of libraries for working with SQL on top of ActiveRecord's connection.
199
221
  test_files: []