switchman 3.4.2 → 3.6.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +15 -14
  3. data/db/migrate/20180828183945_add_default_shard_index.rb +1 -1
  4. data/db/migrate/20190114212900_add_unique_name_indexes.rb +10 -4
  5. data/lib/switchman/active_record/abstract_adapter.rb +4 -2
  6. data/lib/switchman/active_record/associations.rb +89 -16
  7. data/lib/switchman/active_record/attribute_methods.rb +67 -22
  8. data/lib/switchman/active_record/base.rb +112 -22
  9. data/lib/switchman/active_record/calculations.rb +93 -37
  10. data/lib/switchman/active_record/connection_handler.rb +18 -0
  11. data/lib/switchman/active_record/connection_pool.rb +18 -14
  12. data/lib/switchman/active_record/database_configurations.rb +37 -15
  13. data/lib/switchman/active_record/finder_methods.rb +44 -14
  14. data/lib/switchman/active_record/log_subscriber.rb +11 -5
  15. data/lib/switchman/active_record/migration.rb +28 -9
  16. data/lib/switchman/active_record/pending_migration_connection.rb +17 -0
  17. data/lib/switchman/active_record/persistence.rb +22 -0
  18. data/lib/switchman/active_record/postgresql_adapter.rb +11 -10
  19. data/lib/switchman/active_record/predicate_builder.rb +2 -2
  20. data/lib/switchman/active_record/query_cache.rb +49 -20
  21. data/lib/switchman/active_record/query_methods.rb +93 -30
  22. data/lib/switchman/active_record/relation.rb +22 -11
  23. data/lib/switchman/active_record/spawn_methods.rb +2 -2
  24. data/lib/switchman/active_record/statement_cache.rb +2 -2
  25. data/lib/switchman/active_record/tasks/database_tasks.rb +6 -1
  26. data/lib/switchman/active_record/test_fixtures.rb +26 -16
  27. data/lib/switchman/active_support/cache.rb +9 -4
  28. data/lib/switchman/arel.rb +34 -18
  29. data/lib/switchman/call_super.rb +2 -8
  30. data/lib/switchman/database_server.rb +68 -21
  31. data/lib/switchman/default_shard.rb +14 -3
  32. data/lib/switchman/engine.rb +39 -19
  33. data/lib/switchman/environment.rb +2 -2
  34. data/lib/switchman/errors.rb +4 -1
  35. data/lib/switchman/guard_rail/relation.rb +1 -2
  36. data/lib/switchman/parallel.rb +5 -5
  37. data/lib/switchman/r_spec_helper.rb +11 -11
  38. data/lib/switchman/shard.rb +166 -64
  39. data/lib/switchman/sharded_instrumenter.rb +7 -3
  40. data/lib/switchman/standard_error.rb +4 -0
  41. data/lib/switchman/test_helper.rb +2 -2
  42. data/lib/switchman/version.rb +1 -1
  43. data/lib/switchman.rb +27 -15
  44. data/lib/tasks/switchman.rake +117 -51
  45. metadata +19 -44
data/lib/switchman.rb CHANGED
@@ -1,12 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'guard_rail'
4
- require 'zeitwerk'
3
+ require "guard_rail"
4
+ require "zeitwerk"
5
5
 
6
6
  class SwitchmanInflector < Zeitwerk::GemInflector
7
7
  def camelize(basename, abspath)
8
8
  if basename =~ /\Apostgresql_(.*)/
9
- 'PostgreSQL' + super($1, abspath)
9
+ "PostgreSQL" + super($1, abspath)
10
10
  else
11
11
  super
12
12
  end
@@ -18,21 +18,33 @@ loader.inflector = SwitchmanInflector.new(__FILE__)
18
18
  loader.setup
19
19
 
20
20
  module Switchman
21
- def self.config
22
- # TODO: load from yaml
23
- @config ||= {}
24
- end
21
+ Deprecation = ::ActiveSupport::Deprecation.new("4.0", "Switchman")
25
22
 
26
- def self.cache
27
- (@cache.respond_to?(:call) ? @cache.call : @cache) || ::Rails.cache
28
- end
23
+ class << self
24
+ attr_writer :cache
29
25
 
30
- def self.cache=(cache)
31
- @cache = cache
32
- end
26
+ def config
27
+ # TODO: load from yaml
28
+ @config ||= {}
29
+ end
30
+
31
+ def cache
32
+ (@cache.respond_to?(:call) ? @cache.call : @cache) || ::Rails.cache
33
+ end
34
+
35
+ def region
36
+ config[:region]
37
+ end
33
38
 
34
- def self.foreign_key_check(name, type, limit: nil)
35
- puts "WARNING: All foreign keys need to be 8-byte integers. #{name} looks like a foreign key. If so, please add the option: `:limit => 8`" if name.to_s =~ /_id\z/ && type.to_s == 'integer' && limit.to_i < 8
39
+ def foreign_key_check(name, type, limit: nil)
40
+ return unless name.to_s.end_with?("_id") && type.to_s == "integer" && limit.to_i < 8
41
+
42
+ puts <<~TEXT.squish
43
+ WARNING: All foreign keys need to be 8-byte integers.
44
+ #{name} looks like a foreign key.
45
+ If so, please add the option: `:limit => 8`
46
+ TEXT
47
+ end
36
48
  end
37
49
 
38
50
  class OrderOnMultiShardQuery < RuntimeError; end
@@ -2,8 +2,8 @@
2
2
 
3
3
  # In rails 7.0+ if you have only 1 db in the env it doesn't try to do explicit activation
4
4
  # (and for rails purposes we only have one db per env because each database server is a separate env)
5
- if Rails.version < '7.0'
6
- task_prefix = Rake::Task.task_defined?('app:db:migrate') ? 'app:db' : 'db'
5
+ if Rails.version < "7.0"
6
+ task_prefix = Rake::Task.task_defined?("app:db:migrate") ? "app:db" : "db"
7
7
  Rake::Task["#{task_prefix}:migrate"].clear_actions.enhance do
8
8
  ActiveRecord::Tasks::DatabaseTasks.migrate
9
9
  # Ensure this doesn't blow up when running inside the dummy app
@@ -13,28 +13,29 @@ end
13
13
 
14
14
  module Switchman
15
15
  module Rake
16
- def self.filter_database_servers(&block)
17
- chain = filter_database_servers_chain # use a local variable so that the current chain is closed over in the following lambda
18
- @filter_database_servers_chain = ->(servers) { block.call(servers, chain) }
16
+ def self.filter_database_servers
17
+ # use a local variable so that the current chain is closed over in the following lambda
18
+ chain = filter_database_servers_chain
19
+ @filter_database_servers_chain = ->(servers) { yield(servers, chain) }
19
20
  end
20
21
 
21
22
  def self.scope(base_scope = Shard,
22
- database_server: ENV.fetch('DATABASE_SERVER', nil),
23
- shard: ENV.fetch('SHARD', nil))
23
+ database_server: ENV.fetch("DATABASE_SERVER", nil),
24
+ shard: ENV.fetch("SHARD", nil))
24
25
  servers = DatabaseServer.all
25
26
 
26
27
  if database_server
27
28
  servers = database_server
28
- if servers.first == '-'
29
+ if servers.first == "-"
29
30
  negative = true
30
31
  servers = servers[1..]
31
32
  end
32
- servers = servers.split(',')
33
- open = servers.delete('open')
33
+ servers = servers.split(",")
34
+ open = servers.delete("open")
34
35
 
35
- servers = servers.map { |server| DatabaseServer.find(server) }.compact
36
+ servers = servers.filter_map { |server| DatabaseServer.find(server) }
36
37
  if open
37
- open_servers = DatabaseServer.all.select { |server| server.config[:open] }
38
+ open_servers = DatabaseServer.select { |server| server.config[:open] }
38
39
  servers.concat(open_servers)
39
40
  servers << DatabaseServer.find(nil) if open_servers.empty?
40
41
  servers.uniq!
@@ -42,9 +43,22 @@ module Switchman
42
43
  servers = DatabaseServer.all - servers if negative
43
44
  end
44
45
 
46
+ ENV["REGION"]&.split(",")&.each do |region|
47
+ method = :select!
48
+ if region[0] == "-"
49
+ method = :reject!
50
+ region = region[1..]
51
+ end
52
+ if region == "self"
53
+ servers.send(method, &:in_current_region?)
54
+ else
55
+ servers.send(method) { |server| server.in_region?(region) }
56
+ end
57
+ end
58
+
45
59
  servers = filter_database_servers_chain.call(servers)
46
60
 
47
- scope = base_scope.order(::Arel.sql('database_server_id IS NOT NULL, database_server_id, id'))
61
+ scope = base_scope.order(::Arel.sql("database_server_id IS NOT NULL, database_server_id, id"))
48
62
  if servers != DatabaseServer.all
49
63
  database_server_ids = servers.map(&:id)
50
64
  database_server_ids << nil if servers.include?(Shard.default.database_server)
@@ -57,36 +71,81 @@ module Switchman
57
71
  end
58
72
 
59
73
  def self.options
60
- { parallel: ENV['PARALLEL'].to_i }
74
+ { exception: (ENV["FAIL_FAST"] == "0") ? :defer : :raise, parallel: ENV["PARALLEL"].to_i }
61
75
  end
62
76
 
63
77
  # classes - an array or proc, to activate as the current shard during the
64
78
  # task.
65
79
  def self.shardify_task(task_name, classes: [::ActiveRecord::Base])
80
+ log_format = ENV.fetch("LOG_FORMAT", nil)
66
81
  old_task = ::Rake::Task[task_name]
67
82
  old_actions = old_task.actions.dup
68
83
  old_task.actions.clear
69
84
 
70
85
  old_task.enhance do |*task_args|
71
86
  if ::Rails.env.test?
72
- require 'switchman/test_helper'
87
+ require "switchman/test_helper"
73
88
  TestHelper.recreate_persistent_test_shards(dont_create: true)
74
89
  end
75
90
 
76
91
  ::GuardRail.activate(:deploy) do
77
92
  Shard.default.database_server.unguard do
78
93
  classes = classes.call if classes.respond_to?(:call)
79
- Shard.with_each_shard(scope, classes, **options) do
94
+
95
+ # We don't want the shard status messages to be wrapped using a custom log transfomer
96
+ original_stderr = $stderr
97
+ original_stdout = $stdout
98
+ output = if log_format == "json"
99
+ lambda { |msg|
100
+ JSON.dump(shard: Shard.current.id,
101
+ database_server: Shard.current.database_server.id,
102
+ type: "log",
103
+ message: msg)
104
+ }
105
+ else
106
+ nil
107
+ end
108
+ Shard.with_each_shard(scope, classes, output: output, **options) do
80
109
  shard = Shard.current
81
- puts "#{shard.id}: #{shard.description}"
110
+
111
+ if log_format == "json"
112
+ original_stdout.puts JSON.dump(
113
+ shard: shard.id,
114
+ database_server: shard.database_server.id,
115
+ type: "started"
116
+ )
117
+ else
118
+ original_stdout.puts "#{shard.id}: #{shard.description}"
119
+ end
82
120
 
83
121
  shard.database_server.unguard do
84
122
  old_actions.each { |action| action.call(*task_args) }
85
123
  end
124
+
125
+ if log_format == "json"
126
+ original_stdout.puts JSON.dump(
127
+ shard: shard.id,
128
+ database_server: shard.database_server.id,
129
+ type: "completed"
130
+ )
131
+ end
86
132
  nil
133
+ rescue => e
134
+ if log_format == "json"
135
+ original_stderr.puts JSON.dump(
136
+ shard: shard.id,
137
+ database_server: shard.database_server.id,
138
+ type: "failed",
139
+ message: e.full_message
140
+ )
141
+ end
142
+
143
+ raise
87
144
  end
88
145
  rescue => e
89
- warn "Exception from #{e.current_shard.id}: #{e.current_shard.description}:\n#{e.full_message}" if options[:parallel] != 0
146
+ if options[:parallel] != 0
147
+ warn "Exception from #{e.current_shard.id}: #{e.current_shard.description}:\n#{e.full_message}"
148
+ end
90
149
  raise
91
150
  end
92
151
  end
@@ -98,7 +157,7 @@ module Switchman
98
157
  end
99
158
 
100
159
  def self.shard_scope(scope, raw_shard_ids)
101
- raw_shard_ids = raw_shard_ids.split(',')
160
+ raw_shard_ids = raw_shard_ids.split(",")
102
161
 
103
162
  shard_ids = []
104
163
  negative_shard_ids = []
@@ -108,13 +167,13 @@ module Switchman
108
167
 
109
168
  raw_shard_ids.each do |id|
110
169
  case id
111
- when 'default'
170
+ when "default"
112
171
  shard_ids << Shard.default.id
113
- when '-default'
172
+ when "-default"
114
173
  negative_shard_ids << Shard.default.id
115
- when 'primary'
174
+ when "primary"
116
175
  shard_ids.concat(Shard.primary.pluck(:id))
117
- when '-primary'
176
+ when "-primary"
118
177
  negative_shard_ids.concat(Shard.primary.pluck(:id))
119
178
  when /^(-?)(\d+)?\.\.(\.)?(\d+)?$/
120
179
  negative, start, open, finish = $1.present?, $2, $3.present?, $4
@@ -122,8 +181,8 @@ module Switchman
122
181
 
123
182
  range = []
124
183
  range << "id>=#{start}" if start
125
- range << "id<#{'=' unless open}#{finish}" if finish
126
- (negative ? negative_ranges : ranges) << "(#{range.join(' AND ')})"
184
+ range << "id<#{"=" unless open}#{finish}" if finish
185
+ (negative ? negative_ranges : ranges) << "(#{range.join(" AND ")})"
127
186
  when /^-(\d+)$/
128
187
  negative_shard_ids << $1.to_i
129
188
  when /^\d+$/
@@ -151,21 +210,21 @@ module Switchman
151
210
  select = []
152
211
  if index != 1
153
212
  subscope = subscope.offset(per_chunk * (index - 1))
154
- select << 'MIN(id) AS min_id'
213
+ select << "MIN(id) AS min_id"
155
214
  end
156
215
  if index != denominator
157
216
  subscope = subscope.limit(per_chunk)
158
- select << 'MAX(id) AS max_id'
217
+ select << "MAX(id) AS max_id"
159
218
  end
160
219
 
161
- result = Shard.from(subscope).select(select.join(', ')).to_a.first
220
+ result = Shard.from(subscope).select(select.join(", ")).to_a.first
162
221
  range = case index
163
222
  when 1
164
- "id<=#{result['max_id']}"
223
+ "id<=#{result["max_id"]}"
165
224
  when denominator
166
- "id>=#{result['min_id']}"
225
+ "id>=#{result["min_id"]}"
167
226
  else
168
- "(id>=#{result['min_id']} AND id<=#{result['max_id']})"
227
+ "(id>=#{result["min_id"]} AND id<=#{result["max_id"]})"
169
228
  end
170
229
 
171
230
  (numerator.negative? ? negative_ranges : ranges) << range
@@ -186,16 +245,16 @@ module Switchman
186
245
 
187
246
  conditions = []
188
247
  positive_queries = []
189
- positive_queries << ranges.join(' OR ') unless ranges.empty?
248
+ positive_queries << ranges.join(" OR ") unless ranges.empty?
190
249
  unless shard_ids.empty?
191
- positive_queries << 'id IN (?)'
250
+ positive_queries << "id IN (?)"
192
251
  conditions << shard_ids
193
252
  end
194
- positive_query = positive_queries.join(' OR ')
253
+ positive_query = positive_queries.join(" OR ")
195
254
  scope = scope.where(positive_query, *conditions) unless positive_queries.empty?
196
255
 
197
- scope = scope.where("NOT (#{negative_ranges.join(' OR')})") unless negative_ranges.empty?
198
- scope = scope.where('id NOT IN (?)', negative_shard_ids) unless negative_shard_ids.empty?
256
+ scope = scope.where("NOT (#{negative_ranges.join(" OR")})") unless negative_ranges.empty?
257
+ scope = scope.where("id NOT IN (?)", negative_shard_ids) unless negative_shard_ids.empty?
199
258
  scope
200
259
  end
201
260
 
@@ -206,21 +265,28 @@ module Switchman
206
265
 
207
266
  module ActiveRecord
208
267
  module PostgreSQLDatabaseTasks
209
- def structure_dump(filename, extra_flags = nil)
210
- set_psql_env
211
- args = ['--schema-only', '--no-privileges', '--no-owner', '--file', filename]
212
- args.concat(Array(extra_flags)) if extra_flags
213
- shard = Shard.current.name
214
- serialized_search_path = shard
215
- args << "--schema=#{Shellwords.escape(shard)}"
216
-
217
- ignore_tables = ::ActiveRecord::SchemaDumper.ignore_tables
218
- args += ignore_tables.flat_map { |table| ['-T', table] } if ignore_tables.any?
219
-
220
- args << db_config.database
221
- run_cmd('pg_dump', args, 'dumping')
222
- remove_sql_header_comments(filename)
223
- File.open(filename, 'a') { |f| f << "SET search_path TO #{serialized_search_path};\n\n" }
268
+ if ::Rails.version < "7.0"
269
+ def structure_dump(filename, extra_flags = nil)
270
+ set_psql_env
271
+ args = ["--schema-only", "--no-privileges", "--no-owner", "--file", filename]
272
+ args.concat(Array(extra_flags)) if extra_flags
273
+ shard = Shard.current.name
274
+ serialized_search_path = shard
275
+ args << "--schema=#{Shellwords.escape(shard)}"
276
+
277
+ ignore_tables = ::ActiveRecord::SchemaDumper.ignore_tables
278
+ args += ignore_tables.flat_map { |table| ["-T", table] } if ignore_tables.any?
279
+
280
+ args << db_config.database
281
+ run_cmd("pg_dump", args, "dumping")
282
+ remove_sql_header_comments(filename)
283
+ File.open(filename, "a") { |f| f << "SET search_path TO #{serialized_search_path};\n\n" }
284
+ end
285
+ else
286
+ def structure_dump(...)
287
+ ::ActiveRecord.dump_schemas = Switchman::Shard.current.name
288
+ super
289
+ end
224
290
  end
225
291
  end
226
292
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: switchman
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.4.2
4
+ version: 3.6.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cody Cutrer
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2023-04-17 00:00:00.000000000 Z
13
+ date: 2024-09-11 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: activerecord
@@ -21,7 +21,7 @@ dependencies:
21
21
  version: 6.1.4
22
22
  - - "<"
23
23
  - !ruby/object:Gem::Version
24
- version: '7.1'
24
+ version: '7.2'
25
25
  type: :runtime
26
26
  prerelease: false
27
27
  version_requirements: !ruby/object:Gem::Requirement
@@ -31,7 +31,7 @@ dependencies:
31
31
  version: 6.1.4
32
32
  - - "<"
33
33
  - !ruby/object:Gem::Version
34
- version: '7.1'
34
+ version: '7.2'
35
35
  - !ruby/object:Gem::Dependency
36
36
  name: guardrail
37
37
  requirement: !ruby/object:Gem::Requirement
@@ -69,7 +69,7 @@ dependencies:
69
69
  version: '6.1'
70
70
  - - "<"
71
71
  - !ruby/object:Gem::Version
72
- version: '7.1'
72
+ version: '7.2'
73
73
  type: :runtime
74
74
  prerelease: false
75
75
  version_requirements: !ruby/object:Gem::Requirement
@@ -79,35 +79,21 @@ dependencies:
79
79
  version: '6.1'
80
80
  - - "<"
81
81
  - !ruby/object:Gem::Version
82
- version: '7.1'
82
+ version: '7.2'
83
83
  - !ruby/object:Gem::Dependency
84
- name: appraisal
84
+ name: debug
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
87
  - - "~>"
88
88
  - !ruby/object:Gem::Version
89
- version: 2.3.0
89
+ version: '1.8'
90
90
  type: :development
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
- version: 2.3.0
97
- - !ruby/object:Gem::Dependency
98
- name: byebug
99
- requirement: !ruby/object:Gem::Requirement
100
- requirements:
101
- - - ">="
102
- - !ruby/object:Gem::Version
103
- version: '0'
104
- type: :development
105
- prerelease: false
106
- version_requirements: !ruby/object:Gem::Requirement
107
- requirements:
108
- - - ">="
109
- - !ruby/object:Gem::Version
110
- version: '0'
96
+ version: '1.8'
111
97
  - !ruby/object:Gem::Dependency
112
98
  name: pg
113
99
  requirement: !ruby/object:Gem::Requirement
@@ -122,20 +108,6 @@ dependencies:
122
108
  - - "~>"
123
109
  - !ruby/object:Gem::Version
124
110
  version: '1.2'
125
- - !ruby/object:Gem::Dependency
126
- name: pry
127
- requirement: !ruby/object:Gem::Requirement
128
- requirements:
129
- - - ">="
130
- - !ruby/object:Gem::Version
131
- version: '0'
132
- type: :development
133
- prerelease: false
134
- version_requirements: !ruby/object:Gem::Requirement
135
- requirements:
136
- - - ">="
137
- - !ruby/object:Gem::Version
138
- version: '0'
139
111
  - !ruby/object:Gem::Dependency
140
112
  name: rake
141
113
  requirement: !ruby/object:Gem::Requirement
@@ -170,14 +142,14 @@ dependencies:
170
142
  requirements:
171
143
  - - "~>"
172
144
  - !ruby/object:Gem::Version
173
- version: '4.0'
145
+ version: '6.0'
174
146
  type: :development
175
147
  prerelease: false
176
148
  version_requirements: !ruby/object:Gem::Requirement
177
149
  requirements:
178
150
  - - "~>"
179
151
  - !ruby/object:Gem::Version
180
- version: '4.0'
152
+ version: '6.0'
181
153
  - !ruby/object:Gem::Dependency
182
154
  name: rubocop
183
155
  requirement: !ruby/object:Gem::Requirement
@@ -193,19 +165,19 @@ dependencies:
193
165
  - !ruby/object:Gem::Version
194
166
  version: '1.10'
195
167
  - !ruby/object:Gem::Dependency
196
- name: rubocop-performance
168
+ name: rubocop-inst
197
169
  requirement: !ruby/object:Gem::Requirement
198
170
  requirements:
199
171
  - - "~>"
200
172
  - !ruby/object:Gem::Version
201
- version: '1.16'
173
+ version: '1'
202
174
  type: :development
203
175
  prerelease: false
204
176
  version_requirements: !ruby/object:Gem::Requirement
205
177
  requirements:
206
178
  - - "~>"
207
179
  - !ruby/object:Gem::Version
208
- version: '1.16'
180
+ version: '1'
209
181
  - !ruby/object:Gem::Dependency
210
182
  name: rubocop-rake
211
183
  requirement: !ruby/object:Gem::Requirement
@@ -269,6 +241,7 @@ files:
269
241
  - lib/switchman/active_record/attribute_methods.rb
270
242
  - lib/switchman/active_record/base.rb
271
243
  - lib/switchman/active_record/calculations.rb
244
+ - lib/switchman/active_record/connection_handler.rb
272
245
  - lib/switchman/active_record/connection_pool.rb
273
246
  - lib/switchman/active_record/database_configurations.rb
274
247
  - lib/switchman/active_record/database_configurations/database_config.rb
@@ -276,6 +249,7 @@ files:
276
249
  - lib/switchman/active_record/log_subscriber.rb
277
250
  - lib/switchman/active_record/migration.rb
278
251
  - lib/switchman/active_record/model_schema.rb
252
+ - lib/switchman/active_record/pending_migration_connection.rb
279
253
  - lib/switchman/active_record/persistence.rb
280
254
  - lib/switchman/active_record/postgresql_adapter.rb
281
255
  - lib/switchman/active_record/predicate_builder.rb
@@ -315,6 +289,7 @@ licenses:
315
289
  - MIT
316
290
  metadata:
317
291
  rubygems_mfa_required: 'true'
292
+ source_code_uri: https://github.com/instructure/switchman
318
293
  post_install_message:
319
294
  rdoc_options: []
320
295
  require_paths:
@@ -323,14 +298,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
323
298
  requirements:
324
299
  - - ">="
325
300
  - !ruby/object:Gem::Version
326
- version: '2.7'
301
+ version: '3.0'
327
302
  required_rubygems_version: !ruby/object:Gem::Requirement
328
303
  requirements:
329
304
  - - ">="
330
305
  - !ruby/object:Gem::Version
331
306
  version: '0'
332
307
  requirements: []
333
- rubygems_version: 3.1.6
308
+ rubygems_version: 3.2.33
334
309
  signing_key:
335
310
  specification_version: 4
336
311
  summary: Rails sharding magic