deprecation_helper 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: e69bb4ef93cda5c44fba549cfd072e0965c9fa8f323a44c96a87be315ad021be
4
+ data.tar.gz: 220dc767f5dde12538c03f3be75a9ce88c7321ef2a2ee701f15c2cc49edc5c53
5
+ SHA512:
6
+ metadata.gz: 7fcf096afe79fd910d4d07d39b1770094a7eaa48def28d0cdf030b6843d8c23f7e5ab203431b43c760a0d3ad6e84d380b8e50f6960921c066a09ba74bee2927b
7
+ data.tar.gz: 7bd9162f156db65ede8358265ba9b3820357d799f9bf0f057bc854dd330cfcf0daf2c7bea84d59ccd2a3331a82a2f6d66c5d2ae9eed5d393fee6f5c38442f778
data/.gitignore ADDED
@@ -0,0 +1,11 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in deprecation_helper.gemspec
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,39 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ deprecation_helper (1.0.0.pre.gusto.pre.pre)
5
+ sorbet-runtime (~> 0.5.6293)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ diff-lcs (1.4.4)
11
+ rspec (3.10.0)
12
+ rspec-core (~> 3.10.0)
13
+ rspec-expectations (~> 3.10.0)
14
+ rspec-mocks (~> 3.10.0)
15
+ rspec-core (3.10.1)
16
+ rspec-support (~> 3.10.0)
17
+ rspec-expectations (3.10.1)
18
+ diff-lcs (>= 1.2.0, < 2.0)
19
+ rspec-support (~> 3.10.0)
20
+ rspec-mocks (3.10.1)
21
+ diff-lcs (>= 1.2.0, < 2.0)
22
+ rspec-support (~> 3.10.0)
23
+ rspec-support (3.10.1)
24
+ sorbet (0.5.6293)
25
+ sorbet-static (= 0.5.6293)
26
+ sorbet-runtime (0.5.6293)
27
+ sorbet-static (0.5.6293-universal-darwin-17)
28
+
29
+ PLATFORMS
30
+ ruby
31
+ x86_64-darwin-17
32
+
33
+ DEPENDENCIES
34
+ deprecation_helper!
35
+ rspec (~> 3.0)
36
+ sorbet (~> 0.5.6293)
37
+
38
+ BUNDLED WITH
39
+ 2.2.4
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2021 Gusto
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 all
13
+ 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 THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,211 @@
1
+ # DeprecationHelper
2
+
3
+ [![Build status](https://badge.buildkite.com/ca0732598feeb90c404e6883a2d79ad32610ff356020f11639.svg?branch=main)](https://buildkite.com/gusto/deprecationhelper)
4
+
5
+ The purpose of this gem is to help Ruby developers change code safely. It provides a basic framework for deprecating code. Since Ruby is an untyped language, you can't be sure when ceratin types of changes you're trying to make, such as deleting or renaming a method, will break production. This gem is provides an opinionated roadmap for deprecating code.
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'deprecation_helper'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle install
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install deprecation_helper
22
+
23
+ ## Usage
24
+ ### Configuration
25
+ First, you'll need to configure `DeprecationHelper`.
26
+
27
+ Here is an example configuration:
28
+ ```ruby
29
+ DeprecationHelper.configure do |config|
30
+ if Rails.env.test? || Rails.env.development?
31
+ config.deprecation_strategies = [
32
+ DeprecationHelper::Strategies::LogError.new(logger: Rails.logger),
33
+ DeprecationHelper::Strategies::RaiseError.new,
34
+ ]
35
+ else
36
+ config.deprecation_strategies = [
37
+ DeprecationHelper::Strategies::LogError.new(logger: Rails.logger),
38
+ MyCustomStrategy.new, # See more on this below
39
+ ]
40
+ end
41
+ end
42
+ ```
43
+
44
+ In this configuration, we always log an error in any environment. In the test environment, this allows to generate TODO lists of code that is exercised by your test suite that is using deprecated methods. These TODO lists can then be passed into the allow list (see below). In test and development, an error is raised as well, which in this configuration forces users to either add the deprecations to the TODO list or to address them prior to landing the deprecation.
45
+
46
+ Note that global configuration is optional -- you can also pass in `deprecation_strategies` directly to your `deprecate!` call (see the advanced usage section below).
47
+
48
+ ### Deprecating Code
49
+ There is one main method which is called with a string
50
+ ```ruby
51
+ DeprecationHelper.deprecate!('your message value')
52
+ ```
53
+
54
+ By inlining these into your code, when the deprecation is called, it will apply the strategies that you've configured.
55
+
56
+ ### Allow lists
57
+ The main method accept an `allow_list` argument, as such:
58
+ ```ruby
59
+ DeprecationHelper.deprecate!('your message value', allow_list: [/your/, /allow/, /list/]))
60
+ ```
61
+
62
+ If your callstack (determined by `Kernel#caller`) matches *any* element in the allow list, then no deprecation strategy will be invoked -- the deprecation will be skipped. It is recommended to use `allow_list` primarily as a TODO list.
63
+
64
+ Note also that your allow list entries should be as specific to avoid unintended matches, especially if the entries represent a todo list of specific lines from specific files.
65
+
66
+ **Examples of allow lists**
67
+
68
+ Here are a couple of examples of `allow_list` values that you might want:
69
+ - `bin/rails` - Perhaps you want to permit everything that happens in your rails console
70
+ - `my_method_wrapper` - In some cases, you want to just use this gem to help you find all places that use the code and wrap them in something that makes it safe to use the deprecated method, but easier to grep for. Perhaps you have a class like:
71
+ ```ruby
72
+ class MyDeprecator
73
+ def self.my_method_wrapper
74
+ yield
75
+ end
76
+ end
77
+ ```
78
+ This class doesn't do anything, but now you can call `MyDeprecator.my_method_wrapper { my_deprecated_code }` and it will be allow-listed using `my_method_wrapper` as the allow-list value.
79
+ - `some_exempt_folder` - Perhaps you'd like an entire folder to be exempt from this deprecation for the time being, for whatever reason
80
+ - `factory_bot` - Perhaps a gem like `factory_bot` or another test only piece of code is using your deprecated functionality and you don't mind permitting it to unblock other tests.
81
+
82
+ ### Deprecation Strategies
83
+ There are several strategies, here's what they do:
84
+
85
+ **Strategy: Do nothing on use of deprecated code**
86
+
87
+ Where to find it: Configure `deprecation_strategies` to be an empty list (this is also the default)
88
+ This might be useful if you want to take no action in certain environments.
89
+
90
+ **Strategy: Raising on use of deprecated code**
91
+
92
+ Where to find it: `DeprecationHelper::Strategies::RaiseError`
93
+
94
+ This is useful if you believe you've already addressed all deprecations in the environment that uses this strategy OR perhaps if the use of the deprecated code is more negatively impactful than raising.
95
+
96
+ Note that this strategy will construct a `DeprecationHelper::DeprecationException` with the message equal to the input value to `deprecate!` and raise that error.
97
+
98
+ **Strategy: Logging on use of deprecated code**
99
+
100
+ Where to find it: `DeprecationHelper::Strategies::LogError`
101
+
102
+ This is useful if you want to generate an allow list to stop the bleeding.
103
+
104
+ **Strategy: Report to your bug tracking tool on use of deprecated code**
105
+
106
+ Where to find it: This you'll need to create yourself, since it has a dependency on bugsnag.
107
+ One option is to use the `ThrottledBugsnag` or `Bugsnag` gem, by creating your own deprecation strategy (see advanced usage below).
108
+
109
+ ### Advanced Usage
110
+ **Overriding global configuration**
111
+
112
+ You can pass in an array of `deprecation_strategies` to `deprecate!` if you'd like to override the global configuration for `DeprecationHelper`.
113
+
114
+ **Creating your own deprecation strategies**
115
+
116
+ You can construct your own deprecation strategies by implementing any `StrategyInterface` class, which means including the class and implementing the method(s) it requires.
117
+
118
+ Here are the interfaces you can include to construct your own strategies:
119
+ - `DeprecationHelper::Strategies::ErrorStrategyInterface`
120
+ - `DeprecationHelper::Strategies::BaseStrategyInterface`
121
+
122
+ Here is an example of them being used:
123
+ ```ruby
124
+ class SlackNotifierDeprecationStrategy
125
+ include DeprecationHelper::Strategies::BaseStrategyInterface
126
+ extend T::Sig
127
+
128
+ sig { override.params(message: String, logger: T.nilable(Logger)).void }
129
+ def apply!(message, logger: nil)
130
+ # This takes in an exception that is message equal to the message passed into `deprecate!`
131
+ SlackNotifier.notify(message)
132
+ end
133
+ end
134
+
135
+ class BugsnagDeprecationStrategy
136
+ include DeprecationHelper::Strategies::ErrorStrategyInterface
137
+ extend T::Sig
138
+
139
+ sig { override.params(exception: StandardError, logger: T.nilable(Logger)).void }
140
+ def apply_to_exception!(exception, logger: nil)
141
+ # This takes in an exception that is a `DeprecationHelper::DeprecationException`
142
+ # with a `message` value equal to the message passed into `deprecate!`
143
+ Bugsnag.notify(exception)
144
+ end
145
+ end
146
+ ```
147
+ You can create your own deprecation strategy by including StrategyInterface and implementing the method `apply!` that takes in an `exception` and an optional `logger`.
148
+
149
+ Here is an example:
150
+ ```ruby
151
+ class BugsnagDeprecationStrategy
152
+ include DeprecationHelper::Strategies::BaseStrategyInterface
153
+ extend T::Sig
154
+ sig { override.params(exception: StandardError).void }
155
+ def apply!(exception)
156
+ ThrottledBugsnag.notify(exception) # or Bugsnag.notify(exception)
157
+ end
158
+ end
159
+ ```
160
+
161
+ This is useful to report instances of use of deprecated code in production without actually raising an error in production. The use of ThrottledBugsnag is recommended in case there are high-frequency, untested code paths that use the deprecated code to prevent sampling or throttling issues in your bug tracking tool.
162
+
163
+ ## Types of things to deprecate
164
+ There are endless things you might deprecate, here are some examples
165
+ - Public API change - A method call. You might want to deprecate a method, either deleting it entirely or renaming it. Tossing a `deprecate!` call in there will let clients know when they need to migrate.
166
+ - Public API change - arguments. You might want to change an optional argument to be required, add or remove an argument, or change an argument's type. You can check for future incompatibility and use this gem when a client is invoking deprecated behavior.
167
+ - A product scenario. You might want to deprecate when a query method returns `nil` when the thing you are looking for, such as a `User`, should always exist.
168
+ - A database condition. Perhaps you want to add a `non-nil` column, but aren't positive that `nil` is never persisted into that column (when querying the column is not enough, because `nil` could happen in a non-terminal condition in an ongoing request). If you use something like `ActiveRecord`, you could create a validator for this:
169
+ ```ruby
170
+ # Usage:
171
+ #
172
+ # class User < ApplicationRecord
173
+ # validates_with PresenceOfColumnSoftValidator, columns: [:name]
174
+ # end
175
+ #
176
+ class PresenceOfColumnSoftValidator < ActiveModel::Validator
177
+ extend T::Sig
178
+
179
+ sig { params(model: T.untyped).returns(T::Boolean) }
180
+ def validate(model)
181
+ columns_to_validate = options[:columns]
182
+ columns_to_validate.each do |column|
183
+ next unless model.public_send(column).nil?
184
+ DeprecationHelper.deprecate!(
185
+ "#{model.class.name}##{column} should never be nil",
186
+ )
187
+ end
188
+
189
+ true
190
+ end
191
+ end
192
+ ```
193
+ - A graceful exit. A place in your code might `rescue` some arbitrary condition, and you'd like to later on stop rescuing. You can use `deprecate!` as a safe way to remove that rescue.
194
+ - Any general assumption. You might want to make a simplifying change to your application, but that change relies on a hard-to-statically-verify assumption in your system, such as some other state in the system, or some sequence of operations. You can use `deprecate!` as a general way to verify assumptions, and call `deprecate!` when your assumption turns out to be false.
195
+
196
+ ## Why you might not want to use this gem
197
+ In an ideal world, we could "deprecate" by simply calling `raise "This is deprecated"`, and we've fully covered all supported scenarios in our test suite, and running our test suite would reveal all deprecations before we go to production
198
+
199
+ In another ideal world, we recognize our test suite isn't perfect, but as a forcing function, we continue to `raise` and backfill tests as errors come into production.
200
+
201
+ However if your application is like the one I work in, a lot of supported and critical scenarios are in fact untested. This means applying this approach will potentially have negative customer impact.
202
+
203
+ When using this gem, it is generally recommended to backfill test coverage for any deprecation that isn't caught by your test suite. Ultimately, when this gem is used, it means you cannot fully trust your test suite. As a long-term goal, it might be advantageous to move towards getting to a place where you can confidently just call `raise` in your codebase and rely on your test suite. Another better systematic approach is to statically type your code base, in which case certain types of deprecations, such as removing a method, can be caught before going to production even without a test suite.
204
+
205
+ ## Development
206
+
207
+ Run `bundle exec rspec` to run all tests.
208
+
209
+ ## Contributing
210
+
211
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/deprecation_helper.
@@ -0,0 +1,25 @@
1
+ lib = File.expand_path('lib', __dir__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+
4
+ Gem::Specification.new do |spec|
5
+ spec.name = 'deprecation_helper'
6
+ spec.version = '0.1.0'
7
+ spec.authors = ['Alex Evanczuk']
8
+ spec.email = ['alex.evanczuk@gusto.com']
9
+
10
+ spec.summary = 'This is a simple, low-dependency gem for managing deprecations.'
11
+
12
+ # Specify which files should be added to the gem when it is released.
13
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
14
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
15
+ `git ls-files -z`.split("\u0000").reject { |f| f.match(%r{A(test|spec|features)/}) }
16
+ end
17
+ spec.bindir = 'exe'
18
+ spec.executables = spec.files.grep(%r{Aexe/}) { |f| File.basename(f) }
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.add_dependency 'sorbet-runtime', '~> 0.5.6293'
22
+
23
+ spec.add_development_dependency 'rspec', '~> 3.0'
24
+ spec.add_development_dependency 'sorbet', '~> 0.5.6293'
25
+ end
@@ -0,0 +1,60 @@
1
+ # typed: strict
2
+
3
+ require 'logger'
4
+ require 'sorbet-runtime'
5
+ require 'deprecation_helper/private'
6
+ require 'deprecation_helper/private/configuration'
7
+ require 'deprecation_helper/private/allow_list'
8
+
9
+ require 'deprecation_helper/strategies/base_strategy_interface'
10
+ require 'deprecation_helper/strategies/error_strategy_interface'
11
+ require 'deprecation_helper/strategies/raise_error'
12
+ require 'deprecation_helper/strategies/log_error'
13
+
14
+ require 'deprecation_helper/deprecation_exception'
15
+
16
+ module DeprecationHelper
17
+ extend T::Sig
18
+
19
+ ################################################################################################
20
+ #### PUBLIC API
21
+ ################################################################################################
22
+ sig { params(blk: T.proc.params(arg0: Private::Configuration).void).void }
23
+ def self.configure(&blk)
24
+ blk.call config
25
+ end
26
+
27
+ sig do
28
+ params(
29
+ message: String,
30
+ allow_list: T::Array[Regexp],
31
+ deprecation_strategies: T.nilable(T::Array[Strategies::BaseStrategyInterface]),
32
+ ).void
33
+ end
34
+ def self.deprecate!(message, allow_list: [], deprecation_strategies: nil)
35
+ backtrace = caller
36
+ return if Private::AllowList.allowed?(allow_list, backtrace)
37
+ (deprecation_strategies || config.deprecation_strategies).each do |strategy|
38
+ strategy.apply!(message, backtrace)
39
+ end
40
+ end
41
+
42
+ # This method is exposed as it might be useful for other systems that want to
43
+ # reuse the global configuration more explicitly
44
+ sig { returns(T::Array[Strategies::BaseStrategyInterface]) }
45
+ def self.deprecation_strategies
46
+ config.deprecation_strategies
47
+ end
48
+
49
+ ################################################################################################
50
+ #### PRIVATE API
51
+ ################################################################################################
52
+
53
+ sig { returns(Private::Configuration) }
54
+ def self.config
55
+ @config = T.let(@config, T.nilable(Private::Configuration))
56
+ @config ||= Private::Configuration.new
57
+ end
58
+
59
+ private_class_method :config
60
+ end
@@ -0,0 +1,12 @@
1
+ # typed: strict
2
+
3
+ module DeprecationHelper
4
+ class DeprecationException < StandardError
5
+ extend T::Sig
6
+
7
+ sig { params(message: String).void }
8
+ def initialize(message)
9
+ super message
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,8 @@
1
+ # typed: strong
2
+
3
+ module DeprecationHelper
4
+ module Private
5
+ end
6
+
7
+ private_constant :Private
8
+ end
@@ -0,0 +1,18 @@
1
+ # typed: strict
2
+
3
+ module DeprecationHelper
4
+ module Private
5
+ class AllowList
6
+ extend T::Sig
7
+
8
+ sig { params(allowable_frames: T::Array[Regexp], exception_frames: T::Array[String]).returns(T::Boolean) }
9
+ def self.allowed?(allowable_frames, exception_frames)
10
+ allowable_frames.any? do |allowable_frame|
11
+ exception_frames.any? do |exception_frame|
12
+ exception_frame.match?(allowable_frame)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,22 @@
1
+ # typed: strict
2
+
3
+ module DeprecationHelper
4
+ module Private
5
+ class Configuration
6
+ extend T::Sig
7
+
8
+ sig { params(deprecation_strategies: T::Array[DeprecationHelper::Strategies::BaseStrategyInterface]).void }
9
+ attr_writer :deprecation_strategies
10
+
11
+ sig { void }
12
+ def initialize
13
+ @deprecation_strategies = T.let(@deprecation_strategies, T.nilable(T::Array[DeprecationHelper::Strategies::BaseStrategyInterface]))
14
+ end
15
+
16
+ sig { returns(T::Array[DeprecationHelper::Strategies::BaseStrategyInterface]) }
17
+ def deprecation_strategies
18
+ @deprecation_strategies || []
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,17 @@
1
+ # typed: strict
2
+
3
+ module DeprecationHelper
4
+ module Strategies
5
+ # This is the interface for all strategies
6
+ module BaseStrategyInterface
7
+ extend T::Sig
8
+ extend T::Helpers
9
+
10
+ interface!
11
+
12
+ sig { abstract.params(message: String, backtrace: T::Array[String]).void }
13
+ def apply!(message, backtrace)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,24 @@
1
+ # typed: strict
2
+
3
+ module DeprecationHelper
4
+ module Strategies
5
+ module ErrorStrategyInterface
6
+ extend T::Sig
7
+ extend T::Helpers
8
+ include BaseStrategyInterface
9
+
10
+ abstract!
11
+
12
+ sig { override.params(message: String, backtrace: T::Array[String]).void }
13
+ def apply!(message, backtrace)
14
+ exception = DeprecationException.new(message)
15
+ exception.set_backtrace(backtrace)
16
+ apply_to_exception!(exception)
17
+ end
18
+
19
+ sig { abstract.params(exception: StandardError).void }
20
+ def apply_to_exception!(exception)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,22 @@
1
+ # typed: strict
2
+
3
+ module DeprecationHelper
4
+ module Strategies
5
+ # This strategy does nothing
6
+ class LogError
7
+ include BaseStrategyInterface
8
+
9
+ extend T::Sig
10
+
11
+ sig { params(logger: T.nilable(Logger)).void }
12
+ def initialize(logger: nil)
13
+ @logger = T.let(logger || Logger.new(STDOUT), Logger)
14
+ end
15
+
16
+ sig { override.params(message: String, backtrace: T::Array[String]).void }
17
+ def apply!(message, backtrace) # rubocop:disable Lint/UnusedMethodArgument
18
+ @logger.warn(message)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,19 @@
1
+ # typed: strict
2
+
3
+ module DeprecationHelper
4
+ module Strategies
5
+ # This strategy raises the original error
6
+ class RaiseError
7
+ include BaseStrategyInterface
8
+
9
+ extend T::Sig
10
+
11
+ sig { override.params(message: String, backtrace: T::Array[String]).void }
12
+ def apply!(message, backtrace)
13
+ exception = DeprecationException.new(message)
14
+ exception.set_backtrace(backtrace)
15
+ raise exception
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,129 @@
1
+ RSpec.describe DeprecationHelper do
2
+ describe 'deprecate!' do
3
+ subject { DeprecationHelper.deprecate!(input_parameter) }
4
+
5
+ let(:error_backtrace) do
6
+ ['frame0', 'frame1', 'frame2', 'some_special_complicated_frame']
7
+ end
8
+ let(:expected_exception_class) { DeprecationHelper::DeprecationException }
9
+ let(:message) { 'This thing is deprecated' }
10
+ let(:input_parameter) { message }
11
+
12
+ let(:logger) { sorbet_double(Logger, warn: nil) }
13
+
14
+ before do
15
+ allow(Logger).to receive(:new).with(STDOUT).and_return(logger)
16
+ allow(DeprecationHelper).to receive(:caller).and_return(error_backtrace)
17
+ end
18
+
19
+ shared_examples 'it raises the error' do |expected_message|
20
+ it do
21
+ expect { subject }.to raise_error(expected_exception_class, expected_message)
22
+ end
23
+ end
24
+
25
+ shared_examples 'it does not raise an error' do
26
+ it do
27
+ expect { subject }.to_not raise_error
28
+ end
29
+ end
30
+
31
+ context 'configured to do nothing' do
32
+ before do
33
+ DeprecationHelper.configure do |config|
34
+ config.deprecation_strategies = []
35
+ end
36
+ end
37
+
38
+ it_behaves_like 'it does not raise an error'
39
+ end
40
+
41
+ context 'configured to raise' do
42
+ before do
43
+ DeprecationHelper.configure do |config|
44
+ config.deprecation_strategies = [DeprecationHelper::Strategies::RaiseError.new]
45
+ end
46
+ end
47
+
48
+ context 'allow list is not passed in' do
49
+ it_behaves_like 'it raises the error', 'This thing is deprecated'
50
+ end
51
+
52
+ context 'allow list is passed in' do
53
+ before { allow(DeprecationHelper).to receive(:caller).and_return(['frame0', 'frame1', 'frame2', 'some_special_complicated_frame']) }
54
+
55
+ subject { DeprecationHelper.deprecate!(input_parameter, allow_list: allow_list) }
56
+
57
+ context 'allow list is passed in, but empty' do
58
+ let(:allow_list) { [] }
59
+
60
+ it_behaves_like 'it raises the error', 'This thing is deprecated'
61
+ end
62
+
63
+ context 'allow list is passed in, but does not cover permit this exception' do
64
+ let(:allow_list) { ['frame10'] }
65
+
66
+ it_behaves_like 'it raises the error', 'This thing is deprecated'
67
+ end
68
+
69
+ context 'allow list permits this exception with a simple string match list' do
70
+ let(:allow_list) { ['frame1'] }
71
+
72
+ it_behaves_like 'it does not raise an error'
73
+ end
74
+
75
+ context 'allow list permits this exception with a simple regexp match list' do
76
+ let(:allow_list) { [/frame1/] }
77
+
78
+ it_behaves_like 'it does not raise an error'
79
+ end
80
+
81
+ context 'allow list permits this exception with a more complex regexp match list' do
82
+ let(:allow_list) { [/special.*?frame/] }
83
+
84
+ it_behaves_like 'it does not raise an error'
85
+ end
86
+ end
87
+ end
88
+
89
+ context 'configured to do log' do
90
+ before do
91
+ DeprecationHelper.configure do |config|
92
+ config.deprecation_strategies = [DeprecationHelper::Strategies::LogError.new]
93
+ end
94
+ end
95
+
96
+ context 'logger is not set' do
97
+ before do
98
+ DeprecationHelper.configure do |config|
99
+ config.deprecation_strategies = [DeprecationHelper::Strategies::LogError.new]
100
+ end
101
+ end
102
+
103
+ it_behaves_like 'it does not raise an error'
104
+
105
+ it 'logs an error' do
106
+ expect(logger).to receive(:warn).with('This thing is deprecated')
107
+ subject
108
+ end
109
+ end
110
+ end
111
+
112
+ context 'configured for multiple purposes' do
113
+ let(:logger) { Logger.new(STDOUT) }
114
+
115
+ before do
116
+ DeprecationHelper.configure do |config|
117
+ config.deprecation_strategies = [DeprecationHelper::Strategies::LogError.new(logger: logger), DeprecationHelper::Strategies::RaiseError.new]
118
+ end
119
+ end
120
+
121
+ it_behaves_like 'it raises the error', 'This thing is deprecated'
122
+
123
+ it 'logs an error' do
124
+ expect(logger).to receive(:warn).with('This thing is deprecated')
125
+ expect { subject }.to raise_error(StandardError)
126
+ end
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,27 @@
1
+ require 'bundler/setup'
2
+ require 'deprecation_helper'
3
+
4
+ RSpec.configure do |config|
5
+ # Enable flags like --only-failures and --next-failure
6
+ config.example_status_persistence_file_path = '.rspec_status'
7
+
8
+ # Disable RSpec exposing methods globally on `Module` and `main`
9
+ config.disable_monkey_patching!
10
+
11
+ config.expect_with :rspec do |c|
12
+ c.syntax = :expect
13
+ end
14
+
15
+ config.before do
16
+ # Reset the config to defaults prior to each test
17
+ DeprecationHelper.configure do |helper_config|
18
+ helper_config.deprecation_strategies = []
19
+ end
20
+ end
21
+ end
22
+
23
+ def sorbet_double(stubbed_class, attr_map = {})
24
+ instance_double(stubbed_class, attr_map).tap do |dbl|
25
+ allow(dbl).to receive(:is_a?) { |tested_class| stubbed_class.ancestors.include?(tested_class) }
26
+ end
27
+ end
data/test.sh ADDED
@@ -0,0 +1,11 @@
1
+ #!/bin/bash
2
+
3
+ set -e
4
+
5
+ bundle check || bundle install --retry 1
6
+ bundle exec rspec
7
+ STATUS=$?
8
+
9
+ popd
10
+
11
+ exit $STATUS
metadata ADDED
@@ -0,0 +1,103 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: deprecation_helper
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Alex Evanczuk
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2021-02-22 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: sorbet-runtime
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.5.6293
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.5.6293
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: sorbet
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 0.5.6293
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 0.5.6293
55
+ description:
56
+ email:
57
+ - alex.evanczuk@gusto.com
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - ".gitignore"
63
+ - ".rspec"
64
+ - Gemfile
65
+ - Gemfile.lock
66
+ - LICENSE
67
+ - README.md
68
+ - deprecation_helper.gemspec
69
+ - lib/deprecation_helper.rb
70
+ - lib/deprecation_helper/deprecation_exception.rb
71
+ - lib/deprecation_helper/private.rb
72
+ - lib/deprecation_helper/private/allow_list.rb
73
+ - lib/deprecation_helper/private/configuration.rb
74
+ - lib/deprecation_helper/strategies/base_strategy_interface.rb
75
+ - lib/deprecation_helper/strategies/error_strategy_interface.rb
76
+ - lib/deprecation_helper/strategies/log_error.rb
77
+ - lib/deprecation_helper/strategies/raise_error.rb
78
+ - spec/deprecation_helper_spec.rb
79
+ - spec/spec_helper.rb
80
+ - test.sh
81
+ homepage:
82
+ licenses: []
83
+ metadata: {}
84
+ post_install_message:
85
+ rdoc_options: []
86
+ require_paths:
87
+ - lib
88
+ required_ruby_version: !ruby/object:Gem::Requirement
89
+ requirements:
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ version: '0'
93
+ required_rubygems_version: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ requirements: []
99
+ rubygems_version: 3.0.3
100
+ signing_key:
101
+ specification_version: 4
102
+ summary: This is a simple, low-dependency gem for managing deprecations.
103
+ test_files: []