github-ds 0.2.10 → 0.5.2

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: 58267b1fe40225f667e6639dd0df6830b9397853
4
- data.tar.gz: 641de968bea62624f96c2dce9233d01e970050b5
2
+ SHA256:
3
+ metadata.gz: 4b95473248771ff3545335efd43524ac0cabeb2f2ec8ec838f7f7537149e9c5e
4
+ data.tar.gz: dbf07fa010c19fde19540f22f54e0f6f5fec6d4861b31cac059f92c081f7d9bd
5
5
  SHA512:
6
- metadata.gz: cd9e18016d02099599946110ae6814e78fc12d82de1d1bf964ad4a9e1fe60d7dd213a20f1867d8c6fe563cdd3731ab6a940db89dbcd8bbe2ac2548fa435e8839
7
- data.tar.gz: 6768c6a9631f0f7520370bd879da57d23d3d1c830e0d07349065f12b88896f5e8671aca9be43d1d8cb26a5175cf8394481499f789f6725e35ef8a5a7e6ee3e88
6
+ metadata.gz: 433f178728cbc1c7748e3b0b7985781bb8d39aabb338169cbc08eb6a954a914a80796b6bcbeda711d1a1de8c0bb87809102b7022c07e2ba97f381bafcdf0c450
7
+ data.tar.gz: 47586ed750c3af0cbbf0dc34820c99da0c9c69f81946a4f505ba9100c9368ab16e094e0483c7cf37692ad4c0db317b1c0b499c34c5e536e9fb62a1f3e1e4fd1f
data/.travis.yml CHANGED
@@ -3,13 +3,12 @@ before_install:
3
3
  - gem uninstall -v '>= 2' -i $(rvm gemdir)@global -ax bundler || true
4
4
  - gem install bundler -v '<2'
5
5
  rvm:
6
- - 2.3
7
- - 2.4
8
6
  - 2.5
7
+ - 2.6
8
+ - 2.7
9
9
  script: bundle exec rake
10
10
  env:
11
- - RAILS_VERSION=6.0.0.rc1
11
+ - RAILS_VERSION=6.0.3.5
12
12
  - RAILS_VERSION=5.2.0
13
- - RAILS_VERSION=5.1.5
14
- - RAILS_VERSION=5.0.6
15
- - RAILS_VERSION=4.2.10
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'
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
@@ -86,6 +94,8 @@ pp kv.mdel(["foo", "bar"])
86
94
  # nil
87
95
  ```
88
96
 
97
+ Note that due to MySQL's default collation, KV keys are case-insensitive.
98
+
89
99
  ### GitHub::SQL
90
100
 
91
101
  ```ruby
data/github-ds.gemspec CHANGED
@@ -33,10 +33,12 @@ Gem::Specification.new do |spec|
33
33
  spec.add_dependency "activerecord", ">= 3.2"
34
34
 
35
35
  spec.add_development_dependency "bundler", ">= 1.14"
36
- spec.add_development_dependency "rake", "~> 10.0"
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.10"
3
+ VERSION = "0.5.2"
4
4
  end
5
5
  end
data/lib/github/kv.rb CHANGED
@@ -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,11 +125,12 @@ 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
- keys.map { |key| kvs[key] }
132
+ kvs.keys.each { |key| kvs[key.downcase] = kvs[key] }
133
+ keys.map { |key| kvs[key.downcase] }
101
134
  }
102
135
  end
103
136
 
@@ -137,12 +170,12 @@ module GitHub
137
170
 
138
171
  rows = kvs.map { |key, value|
139
172
  value = value.is_a?(GitHub::SQL::Literal) ? value : GitHub::SQL::BINARY(value)
140
- [key, value, GitHub::SQL::NOW, GitHub::SQL::NOW, expires || GitHub::SQL::NULL]
173
+ [key, value, now, now, expires || GitHub::SQL::NULL]
141
174
  }
142
175
 
143
176
  encapsulate_error do
144
177
  GitHub::SQL.run(<<-SQL, :rows => GitHub::SQL::ROWS(rows), :connection => connection)
145
- INSERT INTO key_values (`key`, value, created_at, updated_at, expires_at)
178
+ INSERT INTO #{@table_name} (`key`, value, created_at, updated_at, expires_at)
146
179
  VALUES :rows
147
180
  ON DUPLICATE KEY UPDATE
148
181
  value = VALUES(value),
@@ -186,8 +219,8 @@ module GitHub
186
219
  validate_key_array(keys)
187
220
 
188
221
  Result.new {
189
- existing_keys = GitHub::SQL.values(<<-SQL, :keys => keys, :connection => connection).to_set
190
- SELECT `key` FROM key_values WHERE `key` IN :keys AND (`expires_at` IS NULL OR `expires_at` > NOW())
222
+ existing_keys = GitHub::SQL.values(<<-SQL, :keys => keys, :now => now, :connection => connection).to_set
223
+ SELECT `key` FROM #{@table_name} WHERE `key` IN :keys AND (`expires_at` IS NULL OR `expires_at` > :now)
191
224
  SQL
192
225
 
193
226
  keys.map { |key| existing_keys.include?(key) }
@@ -222,20 +255,116 @@ module GitHub
222
255
  # achieve the same thing with the right INSERT ... ON DUPLICATE KEY UPDATE
223
256
  # query, but then we would not be able to rely on affected_rows
224
257
 
225
- GitHub::SQL.run(<<-SQL, :key => key, :connection => connection)
226
- DELETE FROM key_values WHERE `key` = :key AND expires_at <= NOW()
258
+ GitHub::SQL.run(<<-SQL, :key => key, :now => now, :connection => connection)
259
+ DELETE FROM #{@table_name} WHERE `key` = :key AND expires_at <= :now
227
260
  SQL
228
261
 
229
262
  value = value.is_a?(GitHub::SQL::Literal) ? value : GitHub::SQL::BINARY(value)
230
- sql = GitHub::SQL.run(<<-SQL, :key => key, :value => value, :expires => expires || GitHub::SQL::NULL, :connection => connection)
231
- INSERT IGNORE INTO key_values (`key`, value, created_at, updated_at, expires_at)
232
- VALUES (:key, :value, NOW(), NOW(), :expires)
263
+ sql = GitHub::SQL.run(<<-SQL, :key => key, :value => value, :now => now, :expires => expires || GitHub::SQL::NULL, :connection => connection)
264
+ INSERT IGNORE INTO #{@table_name} (`key`, value, created_at, updated_at, expires_at)
265
+ VALUES (:key, :value, :now, :now, :expires)
233
266
  SQL
234
267
 
235
268
  sql.affected_rows > 0
236
269
  }
237
270
  end
238
271
 
272
+ # increment :: String, Integer, expires: Time? -> Integer
273
+ #
274
+ # Increment the key's value by an amount.
275
+ #
276
+ # key - The key to increment.
277
+ # amount - The amount to increment the key's value by.
278
+ # The user can increment by both positive and
279
+ # negative values
280
+ # expires - When the key should expire.
281
+ # touch_on_insert - Only when expires is specified. When true
282
+ # the expires value is only touched upon
283
+ # inserts. Otherwise the record is always
284
+ # touched.
285
+ #
286
+ # Returns the key's value after incrementing.
287
+ def increment(key, amount: 1, expires: nil, touch_on_insert: false)
288
+ validate_key(key)
289
+ validate_amount(amount) if amount
290
+ validate_expires(expires) if expires
291
+ validate_touch(touch_on_insert, expires)
292
+
293
+ expires ||= GitHub::SQL::NULL
294
+
295
+ # This query uses a few MySQL "hacks" to ensure that the incrementing
296
+ # is done atomically and the value is returned. The first trick is done
297
+ # using the `LAST_INSERT_ID` function. This allows us to manually set
298
+ # the LAST_INSERT_ID returned by the query. Here we are able to set it
299
+ # to the new value when an increment takes place, essentially allowing us
300
+ # to do: `UPDATE...;SELECT value from key_value where key=:key` in a
301
+ # single step.
302
+ #
303
+ # However the `LAST_INSERT_ID` trick is only used when the value is
304
+ # updated. Upon a fresh insert we know the amount is going to be set
305
+ # to the amount specified.
306
+ #
307
+ # Lastly we only do these tricks when the value at the key is an integer.
308
+ # If the value is not an integer the update ensures the values remain the
309
+ # same and we raise an error.
310
+ encapsulate_error {
311
+ sql = GitHub::SQL.run(<<-SQL, key: key, amount: amount, now: now, expires: expires, touch: !touch_on_insert, connection: connection)
312
+ INSERT INTO #{@table_name} (`key`, `value`, `created_at`, `updated_at`, `expires_at`)
313
+ VALUES(:key, :amount, :now, :now, :expires)
314
+ ON DUPLICATE KEY UPDATE
315
+ `value`=IF(
316
+ concat('',`value`*1) = `value`,
317
+ LAST_INSERT_ID(IF(
318
+ `expires_at` IS NULL OR `expires_at`>=:now,
319
+ `value`+:amount,
320
+ :amount
321
+ )),
322
+ `value`
323
+ ),
324
+ `updated_at`=IF(
325
+ concat('',`value`*1) = `value`,
326
+ :now,
327
+ `updated_at`
328
+ ),
329
+ `expires_at`=IF(
330
+ concat('',`value`*1) = `value`,
331
+ IF(
332
+ :touch OR (`expires_at` IS NULL OR `expires_at`<:now),
333
+ :expires,
334
+ `expires_at`
335
+ ),
336
+ `expires_at`
337
+ )
338
+ SQL
339
+
340
+ # The ordering of these statements is extremely important if we are to
341
+ # support incrementing a negative amount. The checks occur in this order:
342
+ # 1. Check if an update with new values occurred? If so return the result
343
+ # This could potentially result in `sql.last_insert_id` with a value
344
+ # of 0, thus it must be before the second check.
345
+ # 2. Check if an update took place but nothing changed (I.E. no new value
346
+ # was set)
347
+ # 3. Check if an insert took place.
348
+ #
349
+ # See https://dev.mysql.com/doc/refman/8.0/en/insert-on-duplicate.html for
350
+ # more information (NOTE: CLIENT_FOUND_ROWS is set)
351
+ if sql.affected_rows == 2
352
+ # An update took place in which data changed. We use a hack to set
353
+ # the last insert ID to be the new value.
354
+ sql.last_insert_id
355
+ elsif sql.affected_rows == 0 || (sql.affected_rows == 1 && sql.last_insert_id == 0)
356
+ # No insert took place nor did any update occur. This means that
357
+ # the value was not an integer thus not incremented.
358
+ raise InvalidValueError
359
+ elsif sql.affected_rows == 1
360
+ # If the number of affected_rows is 1 then a new value was inserted
361
+ # thus we can just return the amount given to us since that is the
362
+ # value at the key
363
+ amount
364
+ end
365
+ }
366
+ end
367
+
239
368
  # del :: String -> nil
240
369
  #
241
370
  # Deletes the specified key. Returns nil. Raises on error.
@@ -265,7 +394,7 @@ module GitHub
265
394
 
266
395
  encapsulate_error do
267
396
  GitHub::SQL.run(<<-SQL, :keys => keys, :connection => connection)
268
- DELETE FROM key_values WHERE `key` IN :keys
397
+ DELETE FROM #{@table_name} WHERE `key` IN :keys
269
398
  SQL
270
399
  end
271
400
 
@@ -288,14 +417,40 @@ module GitHub
288
417
  validate_key(key)
289
418
 
290
419
  Result.new {
291
- GitHub::SQL.value(<<-SQL, :key => key, :connection => connection)
292
- SELECT expires_at FROM key_values
293
- WHERE `key` = :key AND (expires_at IS NULL OR expires_at > NOW())
420
+ GitHub::SQL.value(<<-SQL, :key => key, :now => now, :connection => connection)
421
+ SELECT expires_at FROM #{@table_name}
422
+ WHERE `key` = :key AND (expires_at IS NULL OR expires_at > :now)
294
423
  SQL
295
424
  }
296
425
  end
297
426
 
427
+ # mttl :: [String] -> Result<[Time | nil]>
428
+ #
429
+ # Returns the expires_at time for the specified key or nil.
430
+ #
431
+ # Example:
432
+ #
433
+ # kv.mttl(["foo", "octocat"])
434
+ # # => #<Result value: [2018-04-23 11:34:54 +0200, nil]>
435
+ #
436
+ def mttl(keys)
437
+ validate_key_array(keys)
438
+
439
+ Result.new {
440
+ kvs = GitHub::SQL.results(<<-SQL, :keys => keys, :now => now, :connection => connection).to_h
441
+ SELECT `key`, expires_at FROM #{@table_name}
442
+ WHERE `key` in :keys AND (expires_at IS NULL OR expires_at > :now)
443
+ SQL
444
+
445
+ keys.map { |key| kvs[key] }
446
+ }
447
+ end
448
+
298
449
  private
450
+ def now
451
+ use_local_time ? Time.now : GitHub::SQL::NOW
452
+ end
453
+
299
454
  def validate_key(key, error_message: nil)
300
455
  unless key.is_a?(String)
301
456
  raise TypeError, error_message || "key must be a String in #{self.class.name}, but was #{key.class}"
@@ -349,6 +504,19 @@ module GitHub
349
504
  end
350
505
  end
351
506
 
507
+ def validate_amount(amount)
508
+ raise ArgumentError.new("The amount specified must be an integer") unless amount.is_a? Integer
509
+ raise ArgumentError.new("The amount specified cannot be 0") if amount == 0
510
+ end
511
+
512
+ def validate_touch(touch, expires)
513
+ raise ArgumentError.new("touch_on_insert must be a boolean value") unless [true, false].include?(touch)
514
+
515
+ if touch && expires.nil?
516
+ raise ArgumentError.new("Please specify an expires value if you wish to touch on insert")
517
+ end
518
+ end
519
+
352
520
  def validate_expires(expires)
353
521
  unless expires.respond_to?(:to_time)
354
522
  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
data/lib/github/sql.rb CHANGED
@@ -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.10
4
+ version: 0.5.2
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: 2019-05-17 00:00:00.000000000 Z
12
+ date: 2021-04-07 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activerecord
@@ -45,14 +45,14 @@ dependencies:
45
45
  requirements:
46
46
  - - "~>"
47
47
  - !ruby/object:Gem::Version
48
- version: '10.0'
48
+ version: '12.0'
49
49
  type: :development
50
50
  prerelease: false
51
51
  version_requirements: !ruby/object:Gem::Requirement
52
52
  requirements:
53
53
  - - "~>"
54
54
  - !ruby/object:Gem::Version
55
- version: '10.0'
55
+ version: '12.0'
56
56
  - !ruby/object:Gem::Dependency
57
57
  name: minitest
58
58
  requirement: !ruby/object:Gem::Requirement
@@ -123,6 +123,34 @@ dependencies:
123
123
  - - "~>"
124
124
  - !ruby/object:Gem::Version
125
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
126
154
  description: A collection of libraries for working with SQL on top of ActiveRecord's
127
155
  connection.
128
156
  email:
@@ -155,6 +183,7 @@ files:
155
183
  - lib/github/ds.rb
156
184
  - lib/github/ds/version.rb
157
185
  - lib/github/kv.rb
186
+ - lib/github/kv/config.rb
158
187
  - lib/github/result.rb
159
188
  - lib/github/sql.rb
160
189
  - lib/github/sql/errors.rb
@@ -170,7 +199,7 @@ licenses:
170
199
  - MIT
171
200
  metadata:
172
201
  allowed_push_host: https://rubygems.org
173
- post_install_message:
202
+ post_install_message:
174
203
  rdoc_options: []
175
204
  require_paths:
176
205
  - lib
@@ -185,9 +214,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
185
214
  - !ruby/object:Gem::Version
186
215
  version: '0'
187
216
  requirements: []
188
- rubyforge_project:
189
- rubygems_version: 2.4.5
190
- signing_key:
217
+ rubygems_version: 3.1.4
218
+ signing_key:
191
219
  specification_version: 4
192
220
  summary: A collection of libraries for working with SQL on top of ActiveRecord's connection.
193
221
  test_files: []