guardrail 2.0.0

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