guardrail 2.0.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 737326a16630468262a493961348ecc21dd8b4f8d82c6526bfafe1c0b66c294c
4
+ data.tar.gz: c90574a6e50c6556e678b08acc766ef74c36be693e6d012989bb6fcb71e93c77
5
+ SHA512:
6
+ metadata.gz: d9761c6d3817b19a980827c71fceca3051cc8cbaf2b86bd959bf110f954a18c271e433bd433d92352d455e6af38234a1945369bbac118b142e1bcdfe45bbd78d
7
+ data.tar.gz: 20aab4abcd5ea1743df249d58d7772ac186277d6e8eb25e9772dd7aed46e41fabc1ac1870a4e802c048428f27f2bb7eb38bbbeffc1c951dd22e177c5f92d54ab
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2013 Instructure, Inc.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,76 @@
1
+ GuardRail
2
+ ==========
3
+
4
+ ## About
5
+
6
+ GuardRail allows multiple database environments and environment overrides to
7
+ ActiveRecord, allowing least-privilege best practices.
8
+
9
+ ## Installation
10
+
11
+ Add `gem 'guardrail'` to your Gemfile (tested with Rails 4.x and 5.x, and also
12
+ with the release candidate for Rails 6.0)
13
+
14
+ ## Usage
15
+
16
+ There are two major use cases for guardrail. The first is for primary/replica(/deploy) environments.
17
+ Using a replica is as simple as adding a replica block (underneath your main environment block) in
18
+ database.yml, then wrapping stuff you want to query the replica in GuardRail.activate(:replica) blocks.
19
+ You can extend this to a deploy environment so that migrations will run as a deploy user that
20
+ permission to modify schema, while your normal app runs with lower privileges that cannot modify
21
+ schema. This is defense-in-depth in practice, so that *if* you happen to have a SQL injection
22
+ bug, it would be impossible to do something like dropping tables.
23
+
24
+ The other major use case is more defense-in-depth. By carefully setting up your environment, you
25
+ can default to script/console sessions for regular users to use their own database user, and the
26
+ replica.
27
+
28
+ Example database.yml file:
29
+
30
+ ```yaml
31
+ production:
32
+ adapter: postgresql
33
+ username: myapp
34
+ database: myapp
35
+ host: db-primary
36
+ replica:
37
+ host: db-replica
38
+ deploy:
39
+ username: deploy
40
+ ```
41
+
42
+ Using an initializer, you can achieve the default environment settings (in tandem with profile
43
+ changes):
44
+
45
+ ```ruby
46
+ if ENV['RAILS_DATABASE_ENVIRONMENT']
47
+ GuardRail.activate!(ENV['RAILS_DATABASE_ENVIRONMENT'].to_sym)
48
+ end
49
+ if ENV['RAILS_DATABASE_USER']
50
+ GuardRail.apply_config!(:username => ENV['RAILS_DATABASE_USER'])
51
+ end
52
+ ```
53
+
54
+ Additionally **in Ruby 2.0+** you can include GuardRail::HelperMethods and use several helpers
55
+ to execute methods on specific environments:
56
+
57
+ ```ruby
58
+ class SomeModel
59
+ include GuardRail::HelperMethods
60
+
61
+ def expensive_read_only
62
+ ...
63
+ end
64
+ guard_rail_method :expensive_read_only, environment: :replica
65
+
66
+ def self.class_level_expensive_read_only
67
+ ...
68
+ end
69
+ gaurd_rail_class_method :class_level_expensive_read_only, environment: :replica
70
+
71
+ # helpers for multiple methods are also available
72
+
73
+ guard_rail_methods :instance_method_foo, :instance_method_bar, environment: :replica
74
+ guard_rail_class_methods :class_method_foo, :class_method_bar, environment: :replica
75
+ end
76
+ ```
@@ -0,0 +1,114 @@
1
+ require 'set'
2
+
3
+ module GuardRail
4
+ class << self
5
+ attr_accessor :primary_environment_name
6
+ GuardRail.primary_environment_name = :primary
7
+
8
+ def environment
9
+ Thread.current[:guard_rail_environment] ||= primary_environment_name
10
+ end
11
+
12
+ def global_config
13
+ @global_config ||= {}
14
+ end
15
+
16
+ def activated_environments
17
+ @activated_environments ||= Set.new()
18
+ end
19
+
20
+ # semi-private
21
+ def initialize!
22
+ require 'guard_rail/connection_handler'
23
+ require 'guard_rail/connection_specification'
24
+ require 'guard_rail/helper_methods'
25
+
26
+ activated_environments << GuardRail.environment
27
+
28
+ ActiveRecord::ConnectionAdapters::ConnectionHandler.prepend(ConnectionHandler)
29
+ ActiveRecord::ConnectionAdapters::ConnectionSpecification.prepend(ConnectionSpecification)
30
+ end
31
+
32
+ def global_config_sequence
33
+ @global_config_sequence ||= 1
34
+ end
35
+
36
+ def bump_sequence
37
+ @global_config_sequence ||= 1
38
+ @global_config_sequence += 1
39
+ ActiveRecord::Base::connection_handler.clear_all_connections!
40
+ end
41
+
42
+ # for altering other pieces of config (i.e. username)
43
+ # will force a disconnect
44
+ def apply_config!(hash)
45
+ global_config.merge!(hash)
46
+ bump_sequence
47
+ end
48
+
49
+ def remove_config!(key)
50
+ global_config.delete(key)
51
+ bump_sequence
52
+ end
53
+
54
+ def connection_handlers
55
+ save_handler
56
+ @connection_handlers
57
+ end
58
+
59
+ # switch environment for the duration of the block
60
+ # will keep the old connections around
61
+ def activate(environment)
62
+ environment ||= primary_environment_name
63
+ return yield if environment == self.environment
64
+ begin
65
+ old_environment = activate!(environment)
66
+ activated_environments << environment
67
+ yield
68
+ ensure
69
+ Thread.current[:guard_rail_environment] = old_environment
70
+ ActiveRecord::Base.connection_handler = ensure_handler unless test?
71
+ end
72
+ end
73
+
74
+ # for use from script/console ONLY
75
+ def activate!(environment)
76
+ environment ||= primary_environment_name
77
+ save_handler
78
+ old_environment = self.environment
79
+ Thread.current[:guard_rail_environment] = environment
80
+ ActiveRecord::Base.connection_handler = ensure_handler unless test?
81
+ old_environment
82
+ end
83
+
84
+ private
85
+
86
+ def test?
87
+ Rails.env.test?
88
+ end
89
+
90
+ def save_handler
91
+ @connection_handlers ||= {}
92
+ @connection_handlers[environment] ||= ActiveRecord::Base.connection_handler
93
+ end
94
+
95
+ def ensure_handler
96
+ new_handler = @connection_handlers[environment]
97
+ if !new_handler
98
+ new_handler = @connection_handlers[environment] = ActiveRecord::ConnectionAdapters::ConnectionHandler.new
99
+ pools = ActiveRecord::Base.connection_handler.send(:owner_to_pool)
100
+ pools.each_pair do |model, pool|
101
+ new_handler.establish_connection(pool.spec.config)
102
+ end
103
+ end
104
+ new_handler
105
+ end
106
+ end
107
+ end
108
+
109
+ if defined?(Rails::Railtie)
110
+ require "guard_rail/railtie"
111
+ else
112
+ # just load everything immediately for Rails 2
113
+ GuardRail.initialize!
114
+ end
@@ -0,0 +1,13 @@
1
+ module GuardRail
2
+ module ConnectionHandler
3
+ %w{clear_active_connections clear_reloadable_connections
4
+ clear_all_connections verify_active_connections }.each do |method|
5
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
6
+ def #{method}!(super_method: false)
7
+ return super() if super_method
8
+ ::GuardRail.connection_handlers.values.each { |handler| handler.#{method}!(super_method: true) }
9
+ end
10
+ RUBY
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,77 @@
1
+ require 'i18n/core_ext/hash' unless Hash.method_defined?(:deep_symbolize_keys)
2
+
3
+ module GuardRail
4
+ module ConnectionSpecification
5
+ class CacheCoherentHash < Hash
6
+ def initialize(spec)
7
+ @spec = spec
8
+ super
9
+ end
10
+
11
+ def []=(key, value)
12
+ super
13
+ @spec.instance_variable_set(:@current_config, nil)
14
+ @spec.instance_variable_get(:@config)[key] = value
15
+ end
16
+
17
+ def delete(key)
18
+ super
19
+ @spec.instance_variable_set(:@current_config, nil)
20
+ @spec.instance_variable_get(:@config).delete(key)
21
+ end
22
+
23
+ def dup
24
+ Hash[self]
25
+ end
26
+
27
+ # in rails 4.2, active support tries to create a copy of the original object's class
28
+ # instead of making a new Hash object, so it fails since initialize expects an argument
29
+ def transform_keys(&block)
30
+ dup.transform_keys(&block)
31
+ end
32
+ end
33
+
34
+ def initialize(name, config, adapter_method)
35
+ super(name, config.deep_symbolize_keys, adapter_method)
36
+ end
37
+
38
+ def initialize_dup(original)
39
+ @current_config = nil
40
+ super
41
+ end
42
+
43
+ def config
44
+ @current_config = nil if GuardRail.environment != @current_config_environment || GuardRail.global_config_sequence != @current_config_sequence
45
+ return @current_config if @current_config
46
+
47
+ @current_config_environment = GuardRail.environment
48
+ @current_config_sequence = GuardRail.global_config_sequence
49
+ config = @config.dup
50
+ if @config.has_key?(GuardRail.environment)
51
+ env_config = @config[GuardRail.environment]
52
+ # an array of databases for this environment; for now, just choose the first non-nil element
53
+ if env_config.is_a?(Array)
54
+ env_config = env_config.detect { |individual_config| !individual_config.nil? }
55
+ end
56
+ config.merge!(env_config.symbolize_keys)
57
+ end
58
+
59
+ config.keys.each do |key|
60
+ next unless config[key].is_a?(String)
61
+ config[key] = config[key] % config
62
+ end
63
+
64
+ config.merge!(GuardRail.global_config)
65
+
66
+ @current_config = CacheCoherentHash.new(self)
67
+ @current_config.replace(config)
68
+
69
+ @current_config
70
+ end
71
+
72
+ def config=(value)
73
+ @config = value
74
+ @current_config = nil
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,38 @@
1
+ module GuardRail
2
+ module HelperMethods
3
+ def self.included(base)
4
+ base.singleton_class.include(ClassMethods)
5
+ # call guard_rail_class_method on the class itself, which then calls guard_rail_method on the singleton_class
6
+ base.singleton_class.singleton_class.include(ClassMethods)
7
+ end
8
+
9
+ # see readme for example usage
10
+ module ClassMethods
11
+ def guard_rail_class_methods(*methods, opts)
12
+ methods.each { |m| guard_rail_class_method(m, opts) }
13
+ end
14
+
15
+ def guard_rail_class_method(method, opts)
16
+ self.singleton_class.guard_rail_method(method, opts)
17
+ end
18
+
19
+ def guard_rail_methods(*methods, opts)
20
+ methods.each { |m| guard_rail_method(m, opts) }
21
+ end
22
+
23
+ def guard_rail_method(method, opts)
24
+ @guard_rail_module ||= begin
25
+ m = Module.new
26
+ self.prepend m
27
+ m
28
+ end
29
+
30
+ @guard_rail_module.class_eval <<-RUBY, __FILE__, __LINE__ + 1
31
+ def #{method}(*args)
32
+ GuardRail.activate(#{opts[:environment].inspect}) { super }
33
+ end
34
+ RUBY
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,9 @@
1
+ module GuardRail
2
+ class Railtie < Rails::Railtie
3
+ initializer "guard_rail.extend_ar", :before => "active_record.initialize_database" do
4
+ ActiveSupport.on_load(:active_record) do
5
+ GuardRail.initialize!
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,3 @@
1
+ module GuardRail
2
+ VERSION = "2.0.0"
3
+ end
@@ -0,0 +1 @@
1
+ require 'guard_rail'
@@ -0,0 +1,167 @@
1
+ require 'active_record'
2
+ require 'byebug'
3
+ require 'rails'
4
+ require 'guard_rail'
5
+
6
+ # we're not actually bringing up ActiveRecord, so we need to initialize our stuff
7
+ GuardRail.initialize!
8
+
9
+ RSpec.configure do |config|
10
+ config.mock_framework = :mocha
11
+ end
12
+
13
+ describe GuardRail do
14
+ ConnectionSpecification = ActiveRecord::ConnectionAdapters::ConnectionSpecification
15
+
16
+ def spec_args(conf, adapter)
17
+ ['dummy', conf, adapter]
18
+ end
19
+
20
+ it "should allow changing environments" do
21
+ conf = {
22
+ :adapter => 'postgresql',
23
+ :database => 'primary',
24
+ :username => 'canvas',
25
+ :deploy => {
26
+ :username => 'deploy'
27
+ },
28
+ :replica => {
29
+ :database => 'replica'
30
+ }
31
+ }
32
+ spec = ConnectionSpecification.new(*spec_args(conf, 'adapter'))
33
+ expect(spec.config[:username]).to eq('canvas')
34
+ expect(spec.config[:database]).to eq('primary')
35
+ GuardRail.activate(:deploy) do
36
+ expect(spec.config[:username]).to eq('deploy')
37
+ expect(spec.config[:database]).to eq('primary')
38
+ end
39
+ expect(spec.config[:username]).to eq('canvas')
40
+ expect(spec.config[:database]).to eq('primary')
41
+ GuardRail.activate(:replica) do
42
+ expect(spec.config[:username]).to eq('canvas')
43
+ expect(spec.config[:database]).to eq('replica')
44
+ end
45
+ expect(spec.config[:username]).to eq('canvas')
46
+ expect(spec.config[:database]).to eq('primary')
47
+ end
48
+
49
+ it "should allow using hash insertions" do
50
+ conf = {
51
+ :adapter => 'postgresql',
52
+ :database => 'primary',
53
+ :username => '%{schema_search_path}',
54
+ :schema_search_path => 'canvas',
55
+ :deploy => {
56
+ :username => 'deploy'
57
+ }
58
+ }
59
+ spec = ConnectionSpecification.new(*spec_args(conf, 'adapter'))
60
+ expect(spec.config[:username]).to eq('canvas')
61
+ GuardRail.activate(:deploy) do
62
+ expect(spec.config[:username]).to eq('deploy')
63
+ end
64
+ expect(spec.config[:username]).to eq('canvas')
65
+ end
66
+
67
+ it "should be cache coherent with modifying the config" do
68
+ conf = {
69
+ :adapter => 'postgresql',
70
+ :database => 'primary',
71
+ :username => '%{schema_search_path}',
72
+ :schema_search_path => 'canvas',
73
+ :deploy => {
74
+ :username => 'deploy'
75
+ }
76
+ }
77
+ spec = ConnectionSpecification.new(*spec_args(conf.dup, 'adapter'))
78
+ expect(spec.config[:username]).to eq('canvas')
79
+ spec.config[:schema_search_path] = 'bob'
80
+ expect(spec.config[:schema_search_path]).to eq('bob')
81
+ expect(spec.config[:username]).to eq('bob')
82
+ GuardRail.activate(:deploy) do
83
+ expect(spec.config[:schema_search_path]).to eq('bob')
84
+ expect(spec.config[:username]).to eq('deploy')
85
+ end
86
+ external_config = spec.config.dup
87
+ expect(external_config.class).to eq(Hash)
88
+ expect(external_config).to eq(spec.config)
89
+
90
+ spec.config = conf.dup
91
+ expect(spec.config[:username]).to eq('canvas')
92
+ end
93
+
94
+ it "does not share config objects when dup'ing specs" do
95
+ conf = {
96
+ :adapter => 'postgresql',
97
+ :database => 'primary',
98
+ :username => '%{schema_search_path}',
99
+ :schema_search_path => 'canvas',
100
+ :deploy => {
101
+ :username => 'deploy'
102
+ }
103
+ }
104
+ spec = ConnectionSpecification.new(*spec_args(conf.dup, 'adapter'))
105
+ expect(spec.config.object_id).not_to eq spec.dup.config.object_id
106
+ end
107
+
108
+ describe "activate" do
109
+ before do
110
+ #!!! trick it in to actually switching envs
111
+ Rails.env.stubs(:test?).returns(false)
112
+
113
+ # be sure to test bugs where the current env isn't yet included in this hash
114
+ GuardRail.connection_handlers.clear
115
+
116
+ ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => ':memory:')
117
+ end
118
+
119
+ it "should call ensure_handler when switching envs" do
120
+ old_handler = ActiveRecord::Base.connection_handler
121
+ GuardRail.expects(:ensure_handler).returns(old_handler).twice
122
+ GuardRail.activate(:replica) {}
123
+ end
124
+
125
+ it "should not close connections when switching envs" do
126
+ conn = ActiveRecord::Base.connection
127
+ replica_conn = GuardRail.activate(:replica) { ActiveRecord::Base.connection }
128
+ expect(conn).not_to eq(replica_conn)
129
+ expect(ActiveRecord::Base.connection).to eq(conn)
130
+ end
131
+
132
+ it "should track all activated environments" do
133
+ GuardRail.activate(:replica) {}
134
+ GuardRail.activate(:custom) {}
135
+ expected = Set.new([:primary, :replica, :custom])
136
+ expect(GuardRail.activated_environments & expected).to eq(expected)
137
+ end
138
+
139
+ context "non-transactional" do
140
+ it "should really disconnect all envs" do
141
+ ActiveRecord::Base.connection
142
+ expect(ActiveRecord::Base.connection_pool).to be_connected
143
+
144
+ GuardRail.activate(:replica) do
145
+ ActiveRecord::Base.connection
146
+ expect(ActiveRecord::Base.connection_pool).to be_connected
147
+ end
148
+
149
+ ActiveRecord::Base.clear_all_connections!
150
+ expect(ActiveRecord::Base.connection_pool).not_to be_connected
151
+ GuardRail.activate(:replica) do
152
+ expect(ActiveRecord::Base.connection_pool).not_to be_connected
153
+ end
154
+ end
155
+ end
156
+
157
+ it 'is thread safe' do
158
+ GuardRail.activate(:replica) do
159
+ Thread.new do
160
+ GuardRail.activate!(:deploy)
161
+ expect(GuardRail.environment).to eq :deploy
162
+ end.join
163
+ expect(GuardRail.environment).to eq :replica
164
+ end
165
+ end
166
+ end
167
+ end
metadata ADDED
@@ -0,0 +1,164 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: guardrail
3
+ version: !ruby/object:Gem::Version
4
+ version: 2.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Cody Cutrer
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-10-05 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activerecord
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '5.1'
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '6.1'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: '5.1'
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '6.1'
33
+ - !ruby/object:Gem::Dependency
34
+ name: railties
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '5.1'
40
+ - - "<"
41
+ - !ruby/object:Gem::Version
42
+ version: '6.1'
43
+ type: :runtime
44
+ prerelease: false
45
+ version_requirements: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: '5.1'
50
+ - - "<"
51
+ - !ruby/object:Gem::Version
52
+ version: '6.1'
53
+ - !ruby/object:Gem::Dependency
54
+ name: appraisal
55
+ requirement: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: '0'
60
+ type: :development
61
+ prerelease: false
62
+ version_requirements: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: '0'
67
+ - !ruby/object:Gem::Dependency
68
+ name: byebug
69
+ requirement: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: '0'
74
+ type: :development
75
+ prerelease: false
76
+ version_requirements: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ version: '0'
81
+ - !ruby/object:Gem::Dependency
82
+ name: mocha
83
+ requirement: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ type: :development
89
+ prerelease: false
90
+ version_requirements: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
95
+ - !ruby/object:Gem::Dependency
96
+ name: rspec
97
+ requirement: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - "~>"
100
+ - !ruby/object:Gem::Version
101
+ version: '3.0'
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ requirements:
106
+ - - "~>"
107
+ - !ruby/object:Gem::Version
108
+ version: '3.0'
109
+ - !ruby/object:Gem::Dependency
110
+ name: sqlite3
111
+ requirement: !ruby/object:Gem::Requirement
112
+ requirements:
113
+ - - ">="
114
+ - !ruby/object:Gem::Version
115
+ version: '0'
116
+ type: :development
117
+ prerelease: false
118
+ version_requirements: !ruby/object:Gem::Requirement
119
+ requirements:
120
+ - - ">="
121
+ - !ruby/object:Gem::Version
122
+ version: '0'
123
+ description: Allows multiple environments in database.yml, and dynamically switching
124
+ them.
125
+ email: cody@instructure.com
126
+ executables: []
127
+ extensions: []
128
+ extra_rdoc_files: []
129
+ files:
130
+ - LICENSE
131
+ - README.md
132
+ - lib/guard_rail.rb
133
+ - lib/guard_rail/connection_handler.rb
134
+ - lib/guard_rail/connection_specification.rb
135
+ - lib/guard_rail/helper_methods.rb
136
+ - lib/guard_rail/railtie.rb
137
+ - lib/guard_rail/version.rb
138
+ - lib/guardrail.rb
139
+ - spec/guard_rail_spec.rb
140
+ homepage: http://github.com/instructure/guardrail
141
+ licenses:
142
+ - MIT
143
+ metadata: {}
144
+ post_install_message:
145
+ rdoc_options: []
146
+ require_paths:
147
+ - lib
148
+ required_ruby_version: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '2.5'
153
+ required_rubygems_version: !ruby/object:Gem::Requirement
154
+ requirements:
155
+ - - ">="
156
+ - !ruby/object:Gem::Version
157
+ version: '0'
158
+ requirements: []
159
+ rubygems_version: 3.1.2
160
+ signing_key:
161
+ specification_version: 4
162
+ summary: ActiveRecord database environment switching for secondaries and least-privilege
163
+ test_files:
164
+ - spec/guard_rail_spec.rb