ten_years_rails 0.2.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: cb75bc7f573b543ca6491b72f0289b4f107b93b8
4
+ data.tar.gz: ede4f3a2877aee2dbb3d0034b6b99d3b570b0cc4
5
+ SHA512:
6
+ metadata.gz: 0d98fba5b66b37ad5ba174f234ae2670c5908687c97db16339a61364a8b3ab343dfb698d90b25c9d75a8e5faec2d7eeab1fcea9045ee716b6b85c24ff9ca9625
7
+ data.tar.gz: 1c230f4d09bd81f2fb89e346f32304b05c26be4a4c715a8d93e5d7b83135156ef3ec5cfce648805fc6c1bab91409d4eb91ae5b8b95bca8a0b1ce5c9c0ac41f6c
@@ -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
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.3.3
5
+ before_install: gem install bundler -v 1.16.1
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in ten_years_rails.gemspec
6
+ gemspec
@@ -0,0 +1,87 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ ten_years_rails (0.1.0)
5
+ actionview
6
+ activesupport
7
+ colorize (>= 0.8.1)
8
+ rest-client (>= 2.0.2)
9
+
10
+ GEM
11
+ remote: https://rubygems.org/
12
+ specs:
13
+ actionview (5.2.0)
14
+ activesupport (= 5.2.0)
15
+ builder (~> 3.1)
16
+ erubi (~> 1.4)
17
+ rails-dom-testing (~> 2.0)
18
+ rails-html-sanitizer (~> 1.0, >= 1.0.3)
19
+ activesupport (5.2.0)
20
+ concurrent-ruby (~> 1.0, >= 1.0.2)
21
+ i18n (>= 0.7, < 2)
22
+ minitest (~> 5.1)
23
+ tzinfo (~> 1.1)
24
+ builder (3.2.3)
25
+ colorize (0.8.1)
26
+ concurrent-ruby (1.0.5)
27
+ crass (1.0.4)
28
+ diff-lcs (1.3)
29
+ domain_name (0.5.20180417)
30
+ unf (>= 0.0.5, < 1.0.0)
31
+ erubi (1.7.1)
32
+ http-cookie (1.0.3)
33
+ domain_name (~> 0.5)
34
+ i18n (1.0.1)
35
+ concurrent-ruby (~> 1.0)
36
+ loofah (2.2.2)
37
+ crass (~> 1.0.2)
38
+ nokogiri (>= 1.5.9)
39
+ mime-types (3.1)
40
+ mime-types-data (~> 3.2015)
41
+ mime-types-data (3.2016.0521)
42
+ mini_portile2 (2.3.0)
43
+ minitest (5.11.3)
44
+ netrc (0.11.0)
45
+ nokogiri (1.8.2)
46
+ mini_portile2 (~> 2.3.0)
47
+ rails-dom-testing (2.0.3)
48
+ activesupport (>= 4.2.0)
49
+ nokogiri (>= 1.6)
50
+ rails-html-sanitizer (1.0.4)
51
+ loofah (~> 2.2, >= 2.2.2)
52
+ rake (10.4.2)
53
+ rest-client (2.0.2)
54
+ http-cookie (>= 1.0.2, < 2.0)
55
+ mime-types (>= 1.16, < 4.0)
56
+ netrc (~> 0.8)
57
+ rspec (3.7.0)
58
+ rspec-core (~> 3.7.0)
59
+ rspec-expectations (~> 3.7.0)
60
+ rspec-mocks (~> 3.7.0)
61
+ rspec-core (3.7.1)
62
+ rspec-support (~> 3.7.0)
63
+ rspec-expectations (3.7.0)
64
+ diff-lcs (>= 1.2.0, < 2.0)
65
+ rspec-support (~> 3.7.0)
66
+ rspec-mocks (3.7.0)
67
+ diff-lcs (>= 1.2.0, < 2.0)
68
+ rspec-support (~> 3.7.0)
69
+ rspec-support (3.7.1)
70
+ thread_safe (0.3.6)
71
+ tzinfo (1.2.5)
72
+ thread_safe (~> 0.1)
73
+ unf (0.1.4)
74
+ unf_ext
75
+ unf_ext (0.0.7.5)
76
+
77
+ PLATFORMS
78
+ ruby
79
+
80
+ DEPENDENCIES
81
+ bundler (~> 1.16)
82
+ rake (~> 10.0)
83
+ rspec (~> 3.0)
84
+ ten_years_rails!
85
+
86
+ BUNDLED WITH
87
+ 1.16.1
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2018 Jordan Raine
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.
@@ -0,0 +1,96 @@
1
+ # Ten Years of Rails Upgrades
2
+
3
+ This is a companion to the "Ten Years of Rails Upgrades" conference talk. You'll find various utilities that we use at Clio to help us prepare for and complete Rails upgrades.
4
+
5
+ These scripts are still early days and may not work in every environment or app.
6
+
7
+ I wouldn't recommend adding this to your Gemfile long-term. Rather, try out the scripts and use them as a point of reference. Feel free to tweak them to better fit your environment.
8
+
9
+ ## Usage
10
+
11
+ ### `bundle_report`
12
+
13
+ Learn about your Gemfile and see what needs updating.
14
+
15
+ ```bash
16
+ # Show all out-of-date gems
17
+ bundle_report outdated
18
+ # Show five oldest, out-of-date gems
19
+ bundle_report outdated | head -n 5
20
+ # Show gems that don't work with Rails 5.2.0
21
+ bundle_report compatibility --rails-version=5.2.0
22
+ bundle_report --help
23
+ ```
24
+
25
+ ### Deprecation tracking
26
+
27
+ If you're using RSpec, add this snippet to `rails_helper.rb` or `spec_helper.rb` (whichever loads Rails).
28
+
29
+ ```ruby
30
+ RSpec.configure do |config|
31
+ # Tracker deprecation messages in each file
32
+ if ENV["DEPRECATION_TRACKER"]
33
+ DeprecationTracker.track_rspec(
34
+ config,
35
+ shitlist_path: "spec/support/deprecation_warning.shitlist.json",
36
+ mode: ENV["DEPRECATION_TRACKER"],
37
+ transform_message: -> (message) { message.gsub("#{Rails.root}/", "") }
38
+ )
39
+ end
40
+ end
41
+ ```
42
+
43
+ We don't use MiniTest, so there isn't a prebuilt config for it but I suspect it's pretty similar to `DeprecationTracker.track_rspec`.
44
+
45
+ Once you have that, you can start using deprecation tracking in your tests:
46
+
47
+ ```bash
48
+ # Run your tests and save the deprecations to the shitlist
49
+ DEPRECATION_TRACKER=save rspec
50
+ # Run your tests and raise an error when the deprecations change
51
+ DEPRECATION_TRACKER=compare rspec
52
+ ```
53
+
54
+ #### `deprecations` command
55
+
56
+ Once you have stored your deprecations, you can use `deprecations` to display common warnings, run specs, or update the shitlist file.
57
+
58
+ ```bash
59
+ deprecations info
60
+ deprecations info --pattern "ActiveRecord::Base"
61
+ deprecations run
62
+ deprecations --help # For more options and examples
63
+ ```
64
+
65
+ Right now, the path to the shitlist is hardcoded so make sure you store yours at `spec/support/deprecations.shitlist.json`.
66
+
67
+ ### Dual-boot Rails next
68
+
69
+ This command helps you dual-boot your application.
70
+
71
+ ```bash
72
+ next --init # Create Gemfile.next
73
+ vim Gemfile # Tweak your dependencies conditionally using `next?`
74
+ next bundle install # Install new gems
75
+ next rails s # Start server using Gemfile.next
76
+ ```
77
+
78
+ ## Installation
79
+
80
+ Add this line to your application's Gemfile:
81
+
82
+ ```ruby
83
+ gem 'ten_years_rails'
84
+ ```
85
+
86
+ And then execute:
87
+
88
+ $ bundle
89
+
90
+ Or install it yourself as:
91
+
92
+ $ gem install ten_years_rails
93
+
94
+ ## License
95
+
96
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "ten_years_rails"
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(__FILE__)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,120 @@
1
+ # Deprecation Tracker
2
+
3
+ In order to control the deprecation warnings that occur during a test run, we create a shitlist of deprecation warnings that we expect to see for each spec file. If code run by a spec file changes how often or what deprecation warning occurs, an error is raised after the RSpec run is complete.
4
+
5
+ It looks something like this:
6
+
7
+ ```
8
+ An error occurred in an `after(:suite)` hook.
9
+ Failure/Error: raise UnexpectedDeprecations, message
10
+
11
+ DeprecationTracker::UnexpectedDeprecations:
12
+ ⚠️ Deprecation warnings have changed!
13
+
14
+ Code called by the following spec files is now generating different deprecation warnings:
15
+
16
+ ./spec/deprecation_spec.rb
17
+
18
+ Here is a diff between what is expected and what was generated by this process:
19
+
20
+ diff --git a/spec/support/deprecation_tracker.json b/var/folders/mv/x81dlrp92w5053_m9bgqbkmm0000gp/T/test-deprecations20180328-33449-xgor20
21
+ index 76d2118f9f2..257be30d46c 100644
22
+ --- a/spec/support/deprecation_tracker.json
23
+ +++ b/var/folders/mv/x81dlrp92w5053_m9bgqbkmm0000gp/T/test-deprecations20180328-33449-xgor20
24
+ @@ -1,7 +1,8 @@
25
+ {
26
+ "./spec/deprecation_spec.rb": [
27
+ "DEPRECATION WARNING: `ActiveRecord::Base.symbolized_base_class` is deprecated and will be removed without replacement. (called from block (2 levels) in <top (required)> at /Users/Jordan/projects/themis3/spec/deprecation_spec.rb:5)",
28
+ - "DEPRECATION WARNING: `ActiveRecord::Base.symbolized_base_class` is deprecated and will be removed without replacement. (called from block (2 levels) in <top (required)> at /Users/Jordan/projects/themis3/spec/deprecation_spec.rb:6)"
29
+ + "DEPRECATION WARNING: `ActiveRecord::Base.symbolized_base_class` is deprecated and will be removed without replacement. (called from block (2 levels) in <top (required)> at /Users/Jordan/projects/themis3/spec/deprecation_spec.rb:6)",
30
+ + "DEPRECATION WARNING: `ActiveRecord::Base.symbolized_base_class` is deprecated and will be removed without replacement. (called from block (2 levels) in <top (required)> at /Users/Jordan/projects/themis3/spec/deprecation_spec.rb:7)"
31
+ ],
32
+ "./spec/models/refund_spec.rb": [
33
+
34
+ # ./spec/support/deprecation_tracker.rb:65:in `compare'
35
+ # ./spec/support/deprecation_tracker.rb:29:in `block in track_rspec'
36
+ # /Users/Jordan/.rbenv/versions/2.4.3/bin/rspec:23:in `load'
37
+ # /Users/Jordan/.rbenv/versions/2.4.3/bin/rspec:23:in `<top (required)>'
38
+ # /Users/Jordan/.rbenv/versions/2.4.3/bin/bundle:23:in `load'
39
+ # /Users/Jordan/.rbenv/versions/2.4.3/bin/bundle:23:in `<main>'
40
+
41
+ Finished in 1.83 seconds (files took 9.09 seconds to load)
42
+ 1 example, 0 failures, 1 error occurred outside of examples
43
+ ```
44
+
45
+ When the diff shows a line was removed, it means we expected to see that deprecation message but didn't.
46
+ When the diff shows a line was added, it means didn't expect to see that deprecation message.
47
+
48
+ > **Protip**: If you find the diff difficult to read in your terminal, copy/paste it into your text editor and set syntax highlighting to "diff" for a colorized view.
49
+
50
+ Keeping a list of all deprecation warnings has two primary benefits:
51
+
52
+ - We can fail CI when new deprecation warnings are added (i.e., "stop the bleeding")
53
+ - We can more easily find and eliminate deprecation warnings
54
+
55
+ ## Modes
56
+
57
+ The deprecation tracker has three mode: `compare`, `save`, and off.
58
+
59
+ - `DEPRECATION_TRACKER=compare rspec foo_spec.rb`: in `compare` mode, changes to the deprecation warnings output by a test file will trigger an error.
60
+ - `DEPRECATION_TRACKER=save rspec foo_spec.rb`: in `save` mode, changes to the deprecation warnings output by a test file will update the shitlist.
61
+ - `rspec foo_spec.rb`: when turned off, changes to the deprecation warnings output by a test file won't trigger a warning or update the shitlist.
62
+
63
+
64
+ ## What does it track?
65
+
66
+ This tracks deprecations from Rails and `Kernel#warn`, the latter often used by gems that don't use ActiveSupport.
67
+
68
+ It only tracks deprecation warnings that occur during a test and not before or after. This means that some deprecations, for example the ones you might see while the app boots, won't be tracked.
69
+
70
+ It also doesn't track constant deprecations (`warning: constant Foo is deprecated`) because those [happen in C code](http://ruby-doc.org/core-2.3.0/Module.html#method-i-deprecate_constant). (If you can think of a way to capture these, do it!)
71
+
72
+ ## How do I get my pull request green on CI?
73
+
74
+ ### There are _added_ deprecation warnings
75
+
76
+ If the diff shows added deprecation warnings, you'll need to go back and change code until those messages don't happen. Under normal circumstances, we shouldn't increase the number of deprecation warnings.
77
+
78
+ You can check if your test file has changed deprecation warnings by running this command:
79
+
80
+ ```bash
81
+ DEPRECATION_TRACKER=compare bundle exec rspec spec/foo_spec.rb
82
+ ```
83
+
84
+ In this case, an error will be raised if `spec/foo_spec.rb` triggers deprecation warnings that are different than we expect.
85
+
86
+ > An example of when it would be OK to increase the number of deprecation warnings is during a Rails upgrade. We're not introducing code that adds deprecation warnings but rather change libraries so what used to be safe to call is now deprecated.
87
+
88
+ ### There are _removed_ deprecation warnings
89
+
90
+ If the diff shows removed deprecation warnings, congratulations! Pat yourself on the back and then run this command:
91
+
92
+ ```ruby
93
+ DEPRECATION_TRACKER=save bundle exec rspec <spec files mentioned in the error>
94
+ ```
95
+
96
+ This will rerun the tests that reduces the number of deprecation warnings and save that to the log.
97
+
98
+ Be sure to commit your changes, then push and wait for CI to go ✅.
99
+
100
+ ### The deprecation warnings are _different_
101
+
102
+ If the diff shows added and removed lines, the deprecation messages we expected to see may have changed. Most deprecation warnings contain the file and line of app code that caused it. If the line number changes, the deprecation warning message will also change. In this case, you can follow the same steps as you would if you removed deprecation warnings to update the stale deprecation messages:
103
+
104
+ ```ruby
105
+ DEPRECATION_TRACKER=save bundle exec rspec <spec files mentioned in the error>
106
+ ```
107
+
108
+ This will update the deprecation warning shitlist. Be sure to commit your changes, then push and wait for CI to go ✅.
109
+
110
+ ## Where is the shitlist stored?
111
+
112
+ ```
113
+ spec/support/deprecation_warnings.shitlist.json
114
+ ```
115
+
116
+ ## How does it work?
117
+
118
+ While running tests, we keep track of what test file is being run and record each deprecation warning as it happens. After the test run is complete, we compare the deprecation messages we saw with the deprecation messages we expected to see and raise an error if the two differ.
119
+
120
+ See `spec/support/deprecation_tracker.rb` for implementation details.
@@ -0,0 +1,264 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Print a report on our Gemfile
4
+ # Why not just use `bundle outdated`? It doesn't give us the information we care about (and it fails).
5
+ #
6
+ at_exit do
7
+ require "optparse"
8
+
9
+ options = {}
10
+ option_parser = OptionParser.new do |opts|
11
+ opts.banner = <<~EOS
12
+ Usage: #{$0} [report-type] [options]
13
+
14
+ report-type There are two report types available: `outdated` and `compatibility`
15
+
16
+ Examples:
17
+ #{$0} compatibility --rails-version 5.0
18
+ #{$0} outdated
19
+ EOS
20
+
21
+ opts.separator ""
22
+ opts.separator "Options:"
23
+
24
+ opts.on("--rails-version [STRING]", "Rails version to check compatibility against (defaults to 5.0)") do |rails_version|
25
+ options[:rails_version] = rails_version
26
+ end
27
+
28
+ opts.on("--include-rails-gems", "Include Rails gems in compatibility report (defaults to false)") do
29
+ options[:include_rails_gems] = true
30
+ end
31
+
32
+ opts.on_tail("-h", "--help", "Show this message") do
33
+ puts opts
34
+ exit
35
+ end
36
+ end
37
+
38
+ begin
39
+ option_parser.parse!
40
+ rescue OptionParser::ParseError => e
41
+ STDERR.puts e.message.red
42
+ puts option_parser
43
+ exit 1
44
+ end
45
+
46
+ report_type = ARGV.first
47
+
48
+ case report_type
49
+ when "outdated" then BundleReport.outdated
50
+ else
51
+ BundleReport.compatibility(rails_version: options.fetch(:rails_version, "5.0"), include_rails_gems: options.fetch(:include_rails_gems, false))
52
+ end
53
+ end
54
+
55
+ # Needs to happen first
56
+ require "bundler/setup"
57
+
58
+ require "action_view"
59
+ require "active_support/core_ext/object/acts_like"
60
+ require "colorize"
61
+ require "cgi"
62
+ require "erb"
63
+ require "json"
64
+ require "rest-client"
65
+
66
+ class BundleReport
67
+ def self.compatibility(rails_version:, include_rails_gems:)
68
+ incompatible_gems = BundleReport::GemInfo.all.reject do |gem|
69
+ gem.compatible_with_rails?(rails_version: rails_version) || (!include_rails_gems && gem.from_rails?)
70
+ end.sort_by do |gem|
71
+ [
72
+ gem.latest_version.compatible_with_rails?(rails_version: rails_version) ? 0 : 1,
73
+ gem.name
74
+ ].join("-")
75
+ end
76
+
77
+ incompatible_gems_by_state = incompatible_gems.group_by { |gem| gem.state(rails_version) }
78
+
79
+ template = <<~ERB
80
+ <% if incompatible_gems_by_state[:latest_compatible] -%>
81
+ <%= "=> Incompatible with Rails #{rails_version} (with new versions that are compatible):".white.bold %>
82
+ <%= "These gems will need to be upgraded before upgrading to Rails #{rails_version}.".italic %>
83
+
84
+ <% incompatible_gems_by_state[:latest_compatible].each do |gem| -%>
85
+ <%= gem_header(gem) %> - upgrade to <%= gem.latest_version.version %>
86
+ <% end -%>
87
+
88
+ <% end -%>
89
+ <% if incompatible_gems_by_state[:incompatible] -%>
90
+ <%= "=> Incompatible with Rails #{rails_version} (with no new compatible versions):".white.bold %>
91
+ <%= "These gems will need to be removed or replaced before upgrading to Rails #{rails_version}.".italic %>
92
+
93
+ <% incompatible_gems_by_state[:incompatible].each do |gem| -%>
94
+ <%= gem_header(gem) %> - new version, <%= gem.latest_version.version %>, is not compatible with Rails #{rails_version}
95
+ <% end -%>
96
+
97
+ <% end -%>
98
+ <% if incompatible_gems_by_state[:no_new_version] -%>
99
+ <%= "=> Incompatible with Rails #{rails_version} (with no new versions):".white.bold %>
100
+ <%= "These gems will need to be upgraded by us or removed before upgrading to Rails #{rails_version}.".italic %>
101
+ <%= "This list is likely to contain internal gems, like Cuddlefish.".italic %>
102
+
103
+ <% incompatible_gems_by_state[:no_new_version].each do |gem| -%>
104
+ <%= gem_header(gem) %> - new version not found
105
+ <% end -%>
106
+
107
+ <% end -%>
108
+ <%= incompatible_gems.length.to_s.red %> gems incompatible with Rails <%= rails_version %>
109
+ ERB
110
+
111
+ puts ERB.new(template, nil, "-").result(binding)
112
+ end
113
+
114
+ def self.gem_header(_gem)
115
+ header = "#{_gem.name} #{_gem.version}".bold
116
+ header << " (loaded from git)".magenta if _gem.sourced_from_git?
117
+ header
118
+ end
119
+
120
+ def self.outdated
121
+ gems = BundleReport::GemInfo.all
122
+ out_of_date_gems = gems.reject(&:up_to_date?).sort_by(&:created_at)
123
+ percentage_out_of_date = ((out_of_date_gems.count / gems.count.to_f) * 100).round
124
+ sourced_from_git = gems.select(&:sourced_from_git?)
125
+
126
+ out_of_date_gems.each do |_gem|
127
+ header = "#{_gem.name} #{_gem.version}"
128
+
129
+ puts <<~MESSAGE
130
+ #{header.bold.white}: released #{_gem.age} (latest version, #{_gem.latest_version.version}, released #{_gem.latest_version.age})
131
+ MESSAGE
132
+ end
133
+
134
+ puts ""
135
+ puts <<~MESSAGE
136
+ #{"#{sourced_from_git.count}".yellow} gems are sourced from git
137
+ #{"#{out_of_date_gems.length}".red} of the #{gems.count} gems are out-of-date (#{percentage_out_of_date}%)
138
+ MESSAGE
139
+ end
140
+
141
+ class GemInfo
142
+ include ActionView::Helpers::DateHelper
143
+
144
+ class NullGemInfo < GemInfo
145
+ def initialize; end
146
+
147
+ def age
148
+ "-"
149
+ end
150
+
151
+ def time_to_latest_version
152
+ "-"
153
+ end
154
+
155
+ def created_at
156
+ Time.now
157
+ end
158
+
159
+ def up_to_date?
160
+ false
161
+ end
162
+
163
+ def version
164
+ "NOT FOUND"
165
+ end
166
+
167
+ def unsatisfied_rails_dependencies(*)
168
+ ["unknown"]
169
+ end
170
+
171
+ def state(_)
172
+ :null
173
+ end
174
+ end
175
+
176
+ def self.all
177
+ Gem::Specification.each.map do |gem_specification|
178
+ new(gem_specification)
179
+ end
180
+ end
181
+
182
+ attr_reader :gem_specification, :version, :name
183
+ def initialize(gem_specification)
184
+ @gem_specification = gem_specification
185
+ @version = gem_specification.version
186
+ @name = gem_specification.name
187
+ end
188
+
189
+ def age
190
+ "#{time_ago_in_words(created_at)} ago"
191
+ end
192
+
193
+ def sourced_from_git?
194
+ !!gem_specification.git_version
195
+ end
196
+
197
+ def time_to_latest_version
198
+ distance_of_time_in_words(created_at, latest_version.created_at)
199
+ end
200
+
201
+ def created_at
202
+ @created_at ||= gem_specification.date
203
+ end
204
+
205
+ def up_to_date?
206
+ version == latest_version.version
207
+ end
208
+
209
+ def state(rails_version)
210
+ if compatible_with_rails?(rails_version: rails_version)
211
+ :compatible
212
+ elsif latest_version.compatible_with_rails?(rails_version: rails_version)
213
+ :latest_compatible
214
+ elsif latest_version.version == "NOT FOUND"
215
+ :no_new_version
216
+ else
217
+ :incompatible
218
+ end
219
+ end
220
+
221
+ def latest_version
222
+ @latest_version ||= begin
223
+ latest_gem_specification = Gem.latest_spec_for(name)
224
+ if latest_gem_specification
225
+ GemInfo.new(latest_gem_specification)
226
+ else
227
+ NullGemInfo.new
228
+ end
229
+ end
230
+ end
231
+
232
+ def compatible_with_rails?(rails_version: Gem::Version.new("5.0"))
233
+ unsatisfied_rails_dependencies(rails_version: rails_version).empty?
234
+ end
235
+
236
+ def unsatisfied_rails_dependencies(rails_version:)
237
+ rails_dependencies = gem_specification.runtime_dependencies.select {|dependency| rails_gems.include?(dependency.name) }
238
+
239
+ rails_dependencies.reject do |rails_dependency|
240
+ rails_dependency.requirement.satisfied_by?(Gem::Version.new(rails_version))
241
+ end
242
+ end
243
+
244
+ def from_rails?
245
+ rails_gems.include?(name)
246
+ end
247
+
248
+ private def rails_gems
249
+ [
250
+ "rails",
251
+ "activemodel",
252
+ "activerecord",
253
+ "actionmailer",
254
+ "actioncable",
255
+ "actionpack",
256
+ "actionview",
257
+ "activejob",
258
+ "activestorage",
259
+ "activesupport",
260
+ "railties",
261
+ ]
262
+ end
263
+ end
264
+ end
@@ -0,0 +1,110 @@
1
+ #!/usr/bin/env ruby
2
+ require "json"
3
+ require "colorize"
4
+ require "optparse"
5
+ require "set"
6
+
7
+ def run_tests(deprecation_warnings, tracker_mode:, next_mode:)
8
+ rspec_command = if next_mode
9
+ "bin/next rspec"
10
+ else
11
+ "bundle exec rspec"
12
+ end
13
+
14
+ command = "DEPRECATION_TRACKER=#{tracker_mode} #{rspec_command} #{deprecation_warnings.keys.join(" ")}"
15
+ puts command
16
+ exec command
17
+ end
18
+
19
+ def print_info(deprecation_warnings, verbose: false)
20
+ frequency_by_message = deprecation_warnings.each_with_object({}) do |(test_file, messages), hash|
21
+ messages.each do |message|
22
+ hash[message] ||= { test_files: Set.new, occurrences: 0 }
23
+ hash[message][:test_files] << test_file
24
+ hash[message][:occurrences] += 1
25
+ end
26
+ end.sort_by {|message, data| data[:occurrences] }.reverse.to_h
27
+
28
+ puts "Ten most common deprecation warnings:".underline
29
+ frequency_by_message.take(10).each do |message, data|
30
+ puts "Occurrences: #{data.fetch(:occurrences)}".bold
31
+ puts "Test files: #{data.fetch(:test_files).to_a.join(" ")}" if verbose
32
+ puts message.red
33
+ puts "----------"
34
+ end
35
+ end
36
+
37
+ options = {}
38
+ option_parser = OptionParser.new do |opts|
39
+ opts.banner = <<~MESSAGE
40
+ Usage: #{__FILE__.to_s} [options] [mode]
41
+
42
+ Parses the deprecation warning shitlist and show info or run tests.
43
+
44
+ Examples:
45
+ bin/deprecations info # Show top ten deprecations
46
+ bin/deprecations --next info # Show top ten deprecations for Rails 5
47
+ bin/deprecations --pattern "ActiveRecord::Base" --verbose info # Show full details on deprecations matching pattern
48
+ bin/deprecations --tracker-mode save --pattern "pass" run # Run tests that output deprecations matching pattern and update shitlist
49
+
50
+ Modes:
51
+ info
52
+ Show information on the ten most frequent deprceation warnings.
53
+
54
+ run
55
+ Run tests that are known to cause deprecation warnings. Use --pattern to filter what tests are run.
56
+
57
+ Options:
58
+ MESSAGE
59
+
60
+ opts.on("--next", "Run against the next shitlist") do |next_mode|
61
+ options[:next] = next_mode
62
+ end
63
+
64
+ opts.on("--tracker-mode MODE", "Set DEPRECATION_TRACKER in test mode. Options: save or compare") do |tracker_mode|
65
+ options[:tracker_mode] = tracker_mode
66
+ end
67
+
68
+ opts.on("--pattern RUBY_REGEX", "Filter deprecation warnings with a pattern.") do |pattern|
69
+ options[:pattern] = pattern
70
+ end
71
+
72
+ opts.on("--verbose", "show more information") do
73
+ options[:verbose] = true
74
+ end
75
+
76
+ opts.on_tail("-h", "--help", "Prints this help") do
77
+ puts opts
78
+ exit
79
+ end
80
+ end
81
+
82
+ option_parser.parse!
83
+
84
+ options[:mode] = ARGV.last
85
+ path = options[:next] ? "spec/support/deprecation_warning.next.shitlist.json" : "spec/support/deprecation_warning.shitlist.json"
86
+
87
+ pattern_string = options.fetch(:pattern, ".+")
88
+ pattern = /#{pattern_string}/
89
+
90
+ deprecation_warnings = JSON.parse(File.read(path)).each_with_object({}) do |(test_file, messages), hash|
91
+ filtered_messages = messages.select {|message| message.match(pattern) }
92
+ hash[test_file] = filtered_messages if !filtered_messages.empty?
93
+ end
94
+
95
+ if deprecation_warnings.empty?
96
+ abort "No test files with deprecations matching #{pattern.inspect}."
97
+ exit 2
98
+ end
99
+
100
+ case options.fetch(:mode, "info")
101
+ when "run" then run_tests(deprecation_warnings, next_mode: options[:next], tracker_mode: options[:tracker_mode])
102
+ when "info" then print_info(deprecation_warnings, verbose: options[:verbose])
103
+ when nil
104
+ STDERR.puts "Must pass a mode: run or info".red
105
+ puts option_parser
106
+ exit 1
107
+ else
108
+ STDERR.puts "Unknown mode: #{options[:mode]}".red
109
+ exit 1
110
+ end
@@ -0,0 +1,46 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Output a markdown table containing the version differences between the Gemfile and Gemfile.next.
4
+ # This can be used as a TODO list to bring the two Gemfiles as close to parity as possible,
5
+ # either by upgrading gems within `Gemfile` or downgrading gems within `Gemfile.next`.
6
+ #
7
+
8
+ def parse_gem_versions(&block)
9
+ bundler_output = block.call
10
+ bundler_output.split("\n").each_with_object({}) do |line, hash|
11
+ next unless line.start_with?("Using")
12
+
13
+ gem_name, version = line.split(/\s+/)[1..2]
14
+ hash[gem_name] = version
15
+ end
16
+ end
17
+
18
+ rails_gems = [
19
+ "rails",
20
+ "activemodel",
21
+ "activerecord",
22
+ "actionmailer",
23
+ "actioncable",
24
+ "actionpack",
25
+ "actionview",
26
+ "activejob",
27
+ "activestorage",
28
+ "activesupport",
29
+ "railties",
30
+ ]
31
+
32
+ gems = parse_gem_versions { `BUNDLE_CACHE_PATH=vendor/cache BUNDLE_GEMFILE=Gemfile bundle install` }
33
+ gems_next = parse_gem_versions { `BUNDLE_CACHE_PATH=vendor/cache.next BUNDLE_GEMFILE=Gemfile.next bundle install` }
34
+ all_gem_names = (gems.keys + gems_next.keys).uniq.sort
35
+
36
+ puts "| Gem | `Gemfile` version | `Gemfile.next` version | Rails internals |"
37
+ puts "|-----|-------------------|------------------------|-----------------| "
38
+ all_gem_names.each do |gem_name|
39
+ gem_version = gems[gem_name] || "-"
40
+ gem_next_version = gems_next[gem_name] || "-"
41
+ rails_internal = rails_gems.include?(gem_name) ? "✅" : ""
42
+
43
+ if gem_version != gem_next_version
44
+ puts "| **#{gem_name}** | #{gem_version} | #{gem_next_version} | #{rails_internal} |"
45
+ end
46
+ end
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ # Ruby wrapper around next.sh so it works with Rubygems
3
+ exe_dir = File.expand_path(File.dirname(__FILE__))
4
+ exec(File.join(exe_dir, 'next.sh'), *ARGV)
@@ -0,0 +1,36 @@
1
+ #!/bin/bash
2
+ if [[ "${@}" == "--init" ]]; then
3
+ echo "doing a thing"
4
+ # Add next? top of Gemfile
5
+ cat <<-STRING > Gemfile.tmp
6
+ def next?
7
+ File.basename(__FILE__) == "Gemfile.next"
8
+ end
9
+ STRING
10
+ cat Gemfile >> Gemfile.tmp
11
+ mv Gemfile.tmp Gemfile
12
+
13
+ ln -s Gemfile Gemfile.next
14
+ exit $?
15
+ fi
16
+
17
+ if [[ "${@}" =~ ^bundle ]]; then
18
+ BUNDLE_GEMFILE=Gemfile.next BUNDLE_CACHE_PATH=vendor/cache.next $@
19
+ else
20
+ BUNDLE_GEMFILE=Gemfile.next BUNDLE_CACHE_PATH=vendor/cache.next bundle exec $@
21
+ fi
22
+
23
+ COMMAND_EXIT=$?
24
+
25
+ GEM_NOT_FOUND=7 # https://github.com/bundler/bundler/blob/master/lib/bundler/errors.rb#L35
26
+ EXECUTABLE_NOT_FOUND=127 # https://github.com/bundler/bundler/blob/master/lib/bundler/cli/exec.rb#L62
27
+ if [[ $COMMAND_EXIT -eq $GEM_NOT_FOUND || $COMMAND_EXIT -eq $EXECUTABLE_NOT_FOUND ]]; then
28
+ BLUE='\033[0;34m'
29
+ UNDERLINE_WHITE='\033[37m'
30
+ NO_COLOR='\033[0m'
31
+
32
+ echo -e "${BLUE}Having trouble running commands with ${UNDERLINE_WHITE}bin/next${BLUE}?"
33
+ echo -e "Try running ${UNDERLINE_WHITE}bin/next bundle install${BLUE}, then try your command again.${NO_COLOR}"
34
+ fi
35
+
36
+ exit $COMMAND_EXIT
@@ -0,0 +1,147 @@
1
+ require "json"
2
+
3
+ # A shitlist for deprecation warnings during test runs. It has two modes: "save" and "compare"
4
+ #
5
+ # DEPRECATION_TRACKER=save
6
+ # Record deprecation warnings, grouped by spec file. After the test run, save to a file.
7
+ #
8
+ # DEPRECATION_TRACKER=compare
9
+ # Tracks deprecation warnings, grouped by spec file. After the test run, compare against shitlist of expected
10
+ # deprecation warnings. If anything is added or removed, raise an error with a diff of the changes.
11
+ #
12
+ class DeprecationTracker
13
+ UnexpectedDeprecations = Class.new(StandardError)
14
+
15
+ module KernelWarnTracker
16
+ def self.callbacks
17
+ @callbacks ||= []
18
+ end
19
+
20
+ def warn(*messages)
21
+ KernelWarnTracker.callbacks.each do |callback|
22
+ messages.each { |message| callback.(message) }
23
+ end
24
+
25
+ super
26
+ end
27
+ end
28
+
29
+ # There are two forms of the `warn` method: one for class Kernel and one for instances of Kernel (i.e., every Object)
30
+ Object.prepend(KernelWarnTracker)
31
+ Kernel.singleton_class.prepend(KernelWarnTracker)
32
+
33
+ def self.track_rspec(rspec_config, shitlist_path:, mode:, transform_message: nil)
34
+ deprecation_tracker = DeprecationTracker.new(shitlist_path, transform_message)
35
+ ActiveSupport::Deprecation.behavior << -> (message, _callstack) { deprecation_tracker.add(message) }
36
+ KernelWarnTracker.callbacks << -> (message) { deprecation_tracker.add(message) }
37
+
38
+ rspec_config.around do |example|
39
+ deprecation_tracker.bucket = example.metadata.fetch(:rerun_file_path)
40
+
41
+ begin
42
+ example.run
43
+ ensure
44
+ deprecation_tracker.bucket = nil
45
+ end
46
+ end
47
+
48
+ rspec_config.after(:suite) do
49
+ if mode == "save"
50
+ deprecation_tracker.save
51
+ elsif mode == "compare"
52
+ deprecation_tracker.compare
53
+ end
54
+ end
55
+ end
56
+
57
+ attr_reader :deprecation_messages, :shitlist_path, :transform_message
58
+ attr_reader :bucket
59
+
60
+ def initialize(shitlist_path, transform_message = nil)
61
+ @shitlist_path = shitlist_path
62
+ @transform_message = transform_message || -> (message) { message }
63
+ @deprecation_messages = {}
64
+ end
65
+
66
+ def add(message)
67
+ return if bucket.nil?
68
+
69
+ @deprecation_messages[bucket] << transform_message.(message)
70
+ end
71
+
72
+ def bucket=(value)
73
+ @bucket = value
74
+ @deprecation_messages[value] ||= [] unless value.nil?
75
+ end
76
+
77
+ def compare
78
+ shitlist = read_shitlist
79
+
80
+ changed_buckets = []
81
+ normalized_deprecation_messages.each do |bucket, messages|
82
+ if shitlist[bucket] != messages
83
+ changed_buckets << bucket
84
+ end
85
+ end
86
+
87
+ if changed_buckets.length > 0
88
+ message = <<~MESSAGE.red
89
+ ⚠️ Deprecation warnings have changed!
90
+
91
+ Code called by the following spec files is now generating different deprecation warnings:
92
+
93
+ #{changed_buckets.join("\n")}
94
+
95
+ To check your failures locally, you can run:
96
+
97
+ DEPRECATION_TRACKER=compare bundle exec rspec #{changed_buckets.join(" ")}
98
+
99
+ Here is a diff between what is expected and what was generated by this process:
100
+
101
+ #{diff}
102
+
103
+ See \e[4;37mdev-docs/testing/deprecation_tracker.md\e[0;31m for more information.
104
+ MESSAGE
105
+
106
+ raise UnexpectedDeprecations, message
107
+ end
108
+ end
109
+
110
+ def diff
111
+ new_shitlist = create_temp_shitlist
112
+ `git diff --no-index #{shitlist_path} #{new_shitlist.path}`
113
+ ensure
114
+ new_shitlist.delete
115
+ end
116
+
117
+ def save
118
+ new_shitlist = create_temp_shitlist
119
+ FileUtils.cp(new_shitlist.path, shitlist_path)
120
+ ensure
121
+ new_shitlist&.delete
122
+ end
123
+
124
+ def create_temp_shitlist
125
+ temp_file = Tempfile.new("temp-deprecation-tracker-shitlist")
126
+ temp_file.write(JSON.pretty_generate(normalized_deprecation_messages))
127
+ temp_file.flush
128
+
129
+ temp_file
130
+ end
131
+
132
+ # Normalize deprecation messages to reduce noise from file output and test files to be tracked with separate test runs
133
+ def normalized_deprecation_messages
134
+ normalized = read_shitlist.merge(deprecation_messages).each_with_object({}) do |(bucket, messages), hash|
135
+ hash[bucket] = messages.sort
136
+ end
137
+
138
+ normalized.reject {|_key, value| value.empty? }.sort_by {|key, _value| key }.to_h
139
+ end
140
+
141
+ def read_shitlist
142
+ return {} unless File.exist?(shitlist_path)
143
+ JSON.parse(File.read(shitlist_path))
144
+ rescue JSON::ParserError => e
145
+ raise "#{shitlist_path} is not valid JSON: #{e.message}"
146
+ end
147
+ end
@@ -0,0 +1,6 @@
1
+ require "ten_years_rails/version"
2
+ require "deprecation_tracker"
3
+
4
+ module TenYearsRails
5
+ # Your code goes here...
6
+ end
@@ -0,0 +1,3 @@
1
+ module TenYearsRails
2
+ VERSION = "0.2.0"
3
+ end
@@ -0,0 +1,30 @@
1
+
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "ten_years_rails/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "ten_years_rails"
8
+ spec.version = TenYearsRails::VERSION
9
+ spec.authors = ["Jordan Raine"]
10
+ spec.email = ["jnraine@gmail.com"]
11
+
12
+ spec.summary = %q{Companion code to Ten Years of Rails Upgrades}
13
+ spec.homepage = "https://github.com/clio/ten_years_rails"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
17
+ f.match(%r{^(test|spec|features)/})
18
+ end
19
+ spec.bindir = "exe"
20
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
21
+ spec.require_paths = ["lib"]
22
+
23
+ spec.add_dependency "actionview"
24
+ spec.add_dependency "activesupport"
25
+ spec.add_dependency "colorize", ">= 0.8.1"
26
+ spec.add_dependency "rest-client", ">= 2.0.2"
27
+ spec.add_development_dependency "bundler", "~> 1.16"
28
+ spec.add_development_dependency "rake", "~> 10.0"
29
+ spec.add_development_dependency "rspec", "~> 3.0"
30
+ end
metadata ADDED
@@ -0,0 +1,167 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ten_years_rails
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Jordan Raine
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2018-10-21 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: actionview
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: activesupport
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: colorize
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: 0.8.1
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: 0.8.1
55
+ - !ruby/object:Gem::Dependency
56
+ name: rest-client
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: 2.0.2
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: 2.0.2
69
+ - !ruby/object:Gem::Dependency
70
+ name: bundler
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '1.16'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '1.16'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rake
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '10.0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '10.0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rspec
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '3.0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '3.0'
111
+ description:
112
+ email:
113
+ - jnraine@gmail.com
114
+ executables:
115
+ - bundle_report
116
+ - deprecations
117
+ - gem-next-diff
118
+ - next
119
+ - next.sh
120
+ extensions: []
121
+ extra_rdoc_files: []
122
+ files:
123
+ - ".gitignore"
124
+ - ".rspec"
125
+ - ".travis.yml"
126
+ - Gemfile
127
+ - Gemfile.lock
128
+ - LICENSE.txt
129
+ - README.md
130
+ - Rakefile
131
+ - bin/console
132
+ - bin/setup
133
+ - deprecation_tracker.md
134
+ - exe/bundle_report
135
+ - exe/deprecations
136
+ - exe/gem-next-diff
137
+ - exe/next
138
+ - exe/next.sh
139
+ - lib/deprecation_tracker.rb
140
+ - lib/ten_years_rails.rb
141
+ - lib/ten_years_rails/version.rb
142
+ - ten_years_rails.gemspec
143
+ homepage: https://github.com/clio/ten_years_rails
144
+ licenses:
145
+ - MIT
146
+ metadata: {}
147
+ post_install_message:
148
+ rdoc_options: []
149
+ require_paths:
150
+ - lib
151
+ required_ruby_version: !ruby/object:Gem::Requirement
152
+ requirements:
153
+ - - ">="
154
+ - !ruby/object:Gem::Version
155
+ version: '0'
156
+ required_rubygems_version: !ruby/object:Gem::Requirement
157
+ requirements:
158
+ - - ">="
159
+ - !ruby/object:Gem::Version
160
+ version: '0'
161
+ requirements: []
162
+ rubyforge_project:
163
+ rubygems_version: 2.6.14
164
+ signing_key:
165
+ specification_version: 4
166
+ summary: Companion code to Ten Years of Rails Upgrades
167
+ test_files: []