activerecord-forbid_implicit_connection_checkout 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: a9786df26344782e9e8a5ca2607ba495cc57030c
4
+ data.tar.gz: ab51e4fde2ab702654bde3dacab719c13208b8b2
5
+ SHA512:
6
+ metadata.gz: afb3b97d2012c8551e627f7caeab6b40f1daa65cab09b74ed6b63719c3103de1f608535f83fed5c334e8a3210fcb4729bc1344fefcd5aea1da4bcf232ba29e1c
7
+ data.tar.gz: 7e7594613c1c1c7dd01682cbc66900e454eccd417450c8fc0891b37dbb6777701d4279bb986e9b0aa3c8e468f132e1e27d8ae4a5c38304af37b8c8c98f834075
@@ -0,0 +1,10 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /log/
10
+ /tmp/
@@ -0,0 +1,13 @@
1
+ PreCommit:
2
+ RuboCop:
3
+ enabled: true
4
+ required: false
5
+ on_warn: fail
6
+
7
+ HardTabs:
8
+ enabled: true
9
+ required: false
10
+
11
+ CommitMsg:
12
+ TrailingPeriod:
13
+ enabled: false
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
@@ -0,0 +1,6 @@
1
+ inherit_gem:
2
+ salsify_rubocop: conf/rubocop.yml
3
+
4
+ Style/FileName:
5
+ Exclude:
6
+ - lib/active_record-forbid_implicit_connection_checkout.rb
@@ -0,0 +1 @@
1
+ activerecord-forbid_implicit_checkout
@@ -0,0 +1 @@
1
+ ruby-2.4.3
@@ -0,0 +1,13 @@
1
+ language: ruby
2
+ env:
3
+ matrix:
4
+ - RAILS_VERSION="~> 5.0.6"
5
+ - RAILS_VERSION="~> 5.1.4"
6
+ rvm:
7
+ - 2.2.8
8
+ - 2.3.5
9
+ - 2.4.3
10
+ before_install: gem install bundler -v 1.12.4
11
+ script:
12
+ - bundle exec rubocop
13
+ - bundle exec rspec
@@ -0,0 +1,4 @@
1
+ # activerecord-forbid_implicit_checkout
2
+
3
+ ## v0.1.0
4
+ - Initial version
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+
2
+ source 'https://rubygems.org'
3
+
4
+
5
+ # override the :github shortcut to be secure by using HTTPS
6
+ git_source(:github) { |repo_name| "https://github.com/#{repo_name}.git" }
7
+
8
+ # Specify your gem's dependencies in activerecord-forbid_implicit_checkout.gemspec
9
+ gemspec
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2018 Salsify, Inc
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
22
+
@@ -0,0 +1,126 @@
1
+ # ActiveRecord-ForbidImplicitCheckout
2
+
3
+ This gem allows a `Thread` to prevent itself from checking out out an ActiveRecord connection. This can be useful
4
+ in preventing your application from accidentally checking out more connections than the database can handle.
5
+
6
+ Inspired by this blog post: https://bibwild.wordpress.com/2014/07/17/activerecord-concurrency-in-rails4-avoid-leaked-connections/
7
+
8
+ ## Installation
9
+
10
+ Add this line to your application's Gemfile:
11
+
12
+ ```ruby
13
+ gem 'activerecord-forbid_implicit_checkout'
14
+ ```
15
+
16
+ And then execute:
17
+
18
+ $ bundle
19
+
20
+ Or install it yourself as:
21
+
22
+ $ gem install activerecord-forbid_implicit_checkout
23
+
24
+ ## Usage
25
+
26
+ ```ruby
27
+ require 'active_record-forbid_implicit_connection_checkout'
28
+
29
+ Thread.new do
30
+ ActiveRecord::Base.forbid_implicit_connection_checkout_for_thread!
31
+ # Code that doesn't require ActiveRecord
32
+ end
33
+ ```
34
+
35
+ If your thread needs a connection at some point
36
+ ```ruby
37
+ Thread.new do
38
+ ActiveRecord::Base.forbid_implicit_connection_checkout_for_thread!
39
+ # Code that doesn't require ActiveRecord
40
+
41
+ ActiveRecord::Base.connection_pool.with_connection do
42
+ # Allow the thread to take a connection within this block
43
+ ActiveRecord::Base.connection
44
+ end
45
+
46
+ # Code that doesn't require ActiveRecord
47
+ end
48
+ ```
49
+
50
+ ## Why
51
+ Consider the following initially:
52
+
53
+ ```ruby
54
+ require 'active_record-forbid_implicit_connection_checkout'
55
+
56
+ class Foo
57
+ def self.download(id)
58
+ Net::HTTP.get(URI("http://example.com/products/#{URI.encode(id)}"))
59
+ end
60
+ end
61
+
62
+ class Downloader
63
+ def download_all(ids)
64
+ threads = ids.map do |id|
65
+ Thread.new do
66
+ ActiveRecord::Base.forbid_implicit_connection_checkout_for_thread!
67
+ Foo.download
68
+ end
69
+ end
70
+ threads.each(&:join)
71
+ end
72
+ end
73
+
74
+ Downloader.download_all([1,2,3,4,5])
75
+ ```
76
+
77
+ The developer initially designed this parallel downloading to not be dependent on the database, so the developer feels confident in running
78
+ many parallel processes of `Downloader.download_all`.
79
+
80
+ However, `Foo.download` might be subject to change.
81
+
82
+ ```ruby
83
+ class SomeModel < ApplicationRecord
84
+ # ...
85
+ end
86
+
87
+ class Foo
88
+ def self.download(id)
89
+ id = SomeModel.find(id).alias_id
90
+ Net::HTTP.get(URI("http://example.com/products/#{URI.encode(id)}"))
91
+ end
92
+ end
93
+ ```
94
+
95
+ After this modification to `Foo.download`, each thread will checkout a new connection to the database. Which could
96
+ overwhelm the database if there are many parallel processes executing `Downloader.download_all`. While this violates,
97
+ the initial assumption about `Downloader.download_all` using the database, it would have been useful to have a safeguard to prevent
98
+ this situation from occurring.
99
+
100
+ The error generated by setting `ActiveRecord::Base.forbid_implicit_connection_checkout_for_thread!` could have detected
101
+ this situation during testing which should have failed with `ActiveRecord::ImplicitConnectionForbiddenError`. In the
102
+ worst case, the production code running this would have failed with `ActiveRecord::ImplicitConnectionForbiddenError`,
103
+ but it would have prevented the database from being overwhelmed and have protected the rest of the application.
104
+
105
+ ## Development
106
+
107
+ After checking out the repo, run `bin/setup` to install dependencies. Then,
108
+ run `rake spec` to run the tests. You can also run `bin/console` for an
109
+ interactive prompt that will allow you to experiment.
110
+
111
+ To install this gem onto your local machine, run `bundle exec rake install`.
112
+
113
+ To release a new version, update the version number in `version.rb`, and then
114
+ run `bundle exec rake release`, which will create a git tag for the version,
115
+ push git commits and tags, and push the `.gem` file to
116
+ [rubygems.org](https://rubygems.org)
117
+ .
118
+
119
+ ## Contributing
120
+
121
+ Bug reports and pull requests are welcome on GitHub at
122
+ https://github.com/salsify/activerecord-forbid_implicit_checkout.## License
123
+
124
+ The gem is available as open source under the terms of the
125
+ [MIT License](http://opensource.org/licenses/MIT).
126
+
@@ -0,0 +1,8 @@
1
+ require 'bundler/gem_tasks'
2
+
3
+
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
@@ -0,0 +1,44 @@
1
+ # coding: utf-8
2
+
3
+ lib = File.expand_path('../lib', __FILE__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'active_record/forbid_implicit_connection_checkout/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = 'activerecord-forbid_implicit_connection_checkout'
9
+ spec.version = ActiveRecord::ForbidImplicitConnectionCheckout::VERSION
10
+ spec.authors = ['Salsify, Inc']
11
+ spec.email = ['engineering@salsify.com']
12
+
13
+ spec.summary = 'Optionally prevents threads from checking out activerecord connections.'
14
+ spec.description = spec.summary
15
+ spec.homepage = 'https://github.com/salsify/activerecord-forbid_implicit_connection_checkout'
16
+
17
+ spec.license = 'MIT'
18
+
19
+
20
+ # Set 'allowed_push_post' to control where this gem can be published.
21
+ if spec.respond_to?(:metadata)
22
+ spec.metadata['allowed_push_host'] = 'https://rubygems.org'
23
+
24
+ else
25
+ raise 'RubyGems 2.0 or newer is required to protect against public gem pushes.'
26
+ end
27
+
28
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
29
+ spec.bindir = 'bin'
30
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
31
+ spec.require_paths = ['lib']
32
+ spec.add_dependency 'activerecord', ['>= 5', '< 5.1']
33
+ spec.add_dependency 'activemodel', ['>= 5', '< 5.1']
34
+ spec.add_dependency 'activesupport', ['>= 5', '< 5.1']
35
+
36
+ spec.add_development_dependency 'bundler', '~> 1.12'
37
+ spec.add_development_dependency 'rake', '~> 10.0'
38
+ spec.add_development_dependency 'rspec', '~> 3.4'
39
+ spec.add_development_dependency 'salsify_rubocop', '~> 0.48.1'
40
+ spec.add_development_dependency 'overcommit'
41
+ spec.add_development_dependency 'database_cleaner'
42
+ spec.add_development_dependency 'pg', '~> 0.18'
43
+ spec.add_development_dependency 'with_model'
44
+ end
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'activerecord-forbid_implicit_checkout'
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require 'irb'
14
+ IRB.start
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -v
5
+
6
+ bundle update
7
+
8
+ overcommit --install
@@ -0,0 +1,9 @@
1
+ require 'active_record'
2
+ require 'active_record/forbid_implicit_connection_checkout/version'
3
+ require 'active_record/implicit_connection_forbidden_error'
4
+
5
+ require 'active_record/forbid_implicit_connection_checkout/prevent_connection_checkout'
6
+ require 'active_record/forbid_implicit_connection_checkout/connection_override'
7
+
8
+ ActiveRecord::Base.include(ActiveRecord::ForbidImplicitConnectionCheckout::PreventConnectionCheckout)
9
+ ActiveRecord::Base.singleton_class.prepend(ActiveRecord::ForbidImplicitConnectionCheckout::ConnectionOverride)
@@ -0,0 +1,15 @@
1
+ require 'active_record/implicit_connection_forbidden_error'
2
+
3
+ module ActiveRecord
4
+ module ForbidImplicitConnectionCheckout
5
+ module ConnectionOverride
6
+ def connection(*args, &block)
7
+ if Thread.current[:active_record_forbid_implicit_connections] &&
8
+ !connection_handler.retrieve_connection_pool(connection_specification_name).active_connection?
9
+ raise ActiveRecord::ImplicitConnectionForbiddenError.new
10
+ end
11
+ super
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,13 @@
1
+ module ActiveRecord
2
+ module ForbidImplicitConnectionCheckout
3
+ module PreventConnectionCheckout
4
+ extend ActiveSupport::Concern
5
+
6
+ module ClassMethods
7
+ def forbid_implicit_connection_checkout_for_thread!
8
+ Thread.current[:active_record_forbid_implicit_connections] = true
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,5 @@
1
+ module ActiveRecord
2
+ module ForbidImplicitConnectionCheckout
3
+ VERSION = '0.1.0'.freeze
4
+ end
5
+ end
@@ -0,0 +1,13 @@
1
+ # This is needed because of https://github.com/rails/rails/blob/5-0-stable/activerecord/lib/active_record/errors.rb#L221
2
+ require 'active_model/errors'
3
+ require 'active_record/errors'
4
+
5
+ module ActiveRecord
6
+ class ImplicitConnectionForbiddenError < ActiveRecord::ConnectionNotEstablished
7
+ MESSAGE = 'Implicit ActiveRecord checkout attempted when Thread :active_record_forbid_implicit_connections set!'.freeze
8
+
9
+ def initialize
10
+ super(MESSAGE)
11
+ end
12
+ end
13
+ end
metadata ADDED
@@ -0,0 +1,239 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: activerecord-forbid_implicit_connection_checkout
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Salsify, Inc
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-01-19 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'
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '5.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'
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '5.1'
33
+ - !ruby/object:Gem::Dependency
34
+ name: activemodel
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '5'
40
+ - - "<"
41
+ - !ruby/object:Gem::Version
42
+ version: '5.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'
50
+ - - "<"
51
+ - !ruby/object:Gem::Version
52
+ version: '5.1'
53
+ - !ruby/object:Gem::Dependency
54
+ name: activesupport
55
+ requirement: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: '5'
60
+ - - "<"
61
+ - !ruby/object:Gem::Version
62
+ version: '5.1'
63
+ type: :runtime
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '5'
70
+ - - "<"
71
+ - !ruby/object:Gem::Version
72
+ version: '5.1'
73
+ - !ruby/object:Gem::Dependency
74
+ name: bundler
75
+ requirement: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - "~>"
78
+ - !ruby/object:Gem::Version
79
+ version: '1.12'
80
+ type: :development
81
+ prerelease: false
82
+ version_requirements: !ruby/object:Gem::Requirement
83
+ requirements:
84
+ - - "~>"
85
+ - !ruby/object:Gem::Version
86
+ version: '1.12'
87
+ - !ruby/object:Gem::Dependency
88
+ name: rake
89
+ requirement: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - "~>"
92
+ - !ruby/object:Gem::Version
93
+ version: '10.0'
94
+ type: :development
95
+ prerelease: false
96
+ version_requirements: !ruby/object:Gem::Requirement
97
+ requirements:
98
+ - - "~>"
99
+ - !ruby/object:Gem::Version
100
+ version: '10.0'
101
+ - !ruby/object:Gem::Dependency
102
+ name: rspec
103
+ requirement: !ruby/object:Gem::Requirement
104
+ requirements:
105
+ - - "~>"
106
+ - !ruby/object:Gem::Version
107
+ version: '3.4'
108
+ type: :development
109
+ prerelease: false
110
+ version_requirements: !ruby/object:Gem::Requirement
111
+ requirements:
112
+ - - "~>"
113
+ - !ruby/object:Gem::Version
114
+ version: '3.4'
115
+ - !ruby/object:Gem::Dependency
116
+ name: salsify_rubocop
117
+ requirement: !ruby/object:Gem::Requirement
118
+ requirements:
119
+ - - "~>"
120
+ - !ruby/object:Gem::Version
121
+ version: 0.48.1
122
+ type: :development
123
+ prerelease: false
124
+ version_requirements: !ruby/object:Gem::Requirement
125
+ requirements:
126
+ - - "~>"
127
+ - !ruby/object:Gem::Version
128
+ version: 0.48.1
129
+ - !ruby/object:Gem::Dependency
130
+ name: overcommit
131
+ requirement: !ruby/object:Gem::Requirement
132
+ requirements:
133
+ - - ">="
134
+ - !ruby/object:Gem::Version
135
+ version: '0'
136
+ type: :development
137
+ prerelease: false
138
+ version_requirements: !ruby/object:Gem::Requirement
139
+ requirements:
140
+ - - ">="
141
+ - !ruby/object:Gem::Version
142
+ version: '0'
143
+ - !ruby/object:Gem::Dependency
144
+ name: database_cleaner
145
+ requirement: !ruby/object:Gem::Requirement
146
+ requirements:
147
+ - - ">="
148
+ - !ruby/object:Gem::Version
149
+ version: '0'
150
+ type: :development
151
+ prerelease: false
152
+ version_requirements: !ruby/object:Gem::Requirement
153
+ requirements:
154
+ - - ">="
155
+ - !ruby/object:Gem::Version
156
+ version: '0'
157
+ - !ruby/object:Gem::Dependency
158
+ name: pg
159
+ requirement: !ruby/object:Gem::Requirement
160
+ requirements:
161
+ - - "~>"
162
+ - !ruby/object:Gem::Version
163
+ version: '0.18'
164
+ type: :development
165
+ prerelease: false
166
+ version_requirements: !ruby/object:Gem::Requirement
167
+ requirements:
168
+ - - "~>"
169
+ - !ruby/object:Gem::Version
170
+ version: '0.18'
171
+ - !ruby/object:Gem::Dependency
172
+ name: with_model
173
+ requirement: !ruby/object:Gem::Requirement
174
+ requirements:
175
+ - - ">="
176
+ - !ruby/object:Gem::Version
177
+ version: '0'
178
+ type: :development
179
+ prerelease: false
180
+ version_requirements: !ruby/object:Gem::Requirement
181
+ requirements:
182
+ - - ">="
183
+ - !ruby/object:Gem::Version
184
+ version: '0'
185
+ description: Optionally prevents threads from checking out activerecord connections.
186
+ email:
187
+ - engineering@salsify.com
188
+ executables:
189
+ - console
190
+ - setup
191
+ extensions: []
192
+ extra_rdoc_files: []
193
+ files:
194
+ - ".gitignore"
195
+ - ".overcommit.yml"
196
+ - ".rspec"
197
+ - ".rubocop.yml"
198
+ - ".ruby-gemset"
199
+ - ".ruby-version"
200
+ - ".travis.yml"
201
+ - CHANGELOG.md
202
+ - Gemfile
203
+ - LICENSE.txt
204
+ - README.md
205
+ - Rakefile
206
+ - activerecord-forbid_implicit_connection_checkout.gemspec
207
+ - bin/console
208
+ - bin/setup
209
+ - lib/active_record-forbid_implicit_connection_checkout.rb
210
+ - lib/active_record/forbid_implicit_connection_checkout/connection_override.rb
211
+ - lib/active_record/forbid_implicit_connection_checkout/prevent_connection_checkout.rb
212
+ - lib/active_record/forbid_implicit_connection_checkout/version.rb
213
+ - lib/active_record/implicit_connection_forbidden_error.rb
214
+ homepage: https://github.com/salsify/activerecord-forbid_implicit_connection_checkout
215
+ licenses:
216
+ - MIT
217
+ metadata:
218
+ allowed_push_host: https://rubygems.org
219
+ post_install_message:
220
+ rdoc_options: []
221
+ require_paths:
222
+ - lib
223
+ required_ruby_version: !ruby/object:Gem::Requirement
224
+ requirements:
225
+ - - ">="
226
+ - !ruby/object:Gem::Version
227
+ version: '0'
228
+ required_rubygems_version: !ruby/object:Gem::Requirement
229
+ requirements:
230
+ - - ">="
231
+ - !ruby/object:Gem::Version
232
+ version: '0'
233
+ requirements: []
234
+ rubyforge_project:
235
+ rubygems_version: 2.6.14
236
+ signing_key:
237
+ specification_version: 4
238
+ summary: Optionally prevents threads from checking out activerecord connections.
239
+ test_files: []