good_migrations 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 957989ed1010171743f14af193d62b5bfbb820e59ad79b42ca16d6b8b51ca948
4
- data.tar.gz: 89dc256beca58029d4de2c142834a3069b7b165959055bc4f7ab1721ffea6c82
3
+ metadata.gz: 9dee0175ec6d3a50dfbf5b22591311fb571d718c5f7d5fa56ddaa3e07a95860a
4
+ data.tar.gz: 0c483eb30524bbe3a426f197d40dbb7de049db96b8db30d7835ad97f36e23c86
5
5
  SHA512:
6
- metadata.gz: 5a2e09ff0b2bf6473ececfa8ca0905eae9a974cd81ff5ea76626e7b5e56693169040472a577551aa44976e2d412e957043a31d47e0ca12b405c8b30a16f506cc
7
- data.tar.gz: f2b50907f8826e6c76c03b933a1571b5b523ebb7087ab4854fddb300c27d5ba64d5f75e53f2d5639234bd8de07adff2702ceb0ae61d37744fcd7737ccb0c3323
6
+ metadata.gz: 91d8a48741dd67c0163e5ad85958f6f623be07d36273aa8df78d94cc55a419bd72498dfb34501b7ecbdfba931ce1a20e3ce16c85c84ddb2b2905e5850152a3a5
7
+ data.tar.gz: 55774f660e0aa7b9671dbcf4d734a1266515e37718045ac561ac076d0c9f8ca8a722b0e1c03e88498f848e3db980a7821bef31c298f14bcece1d300b29484b2a
data/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 0.2.0
4
+
5
+ * Add `permit_autoloading_before` configuration
6
+
3
7
  ## 0.1.0
4
8
 
5
9
  * Add support for zeitwerk@2.5 & higher
data/Gemfile.lock CHANGED
@@ -1,32 +1,32 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- good_migrations (0.1.0)
4
+ good_migrations (0.2.0)
5
5
  activerecord (>= 3.1)
6
6
  railties (>= 3.1)
7
7
 
8
8
  GEM
9
9
  remote: https://rubygems.org/
10
10
  specs:
11
- actionpack (6.1.4)
12
- actionview (= 6.1.4)
13
- activesupport (= 6.1.4)
11
+ actionpack (6.1.4.1)
12
+ actionview (= 6.1.4.1)
13
+ activesupport (= 6.1.4.1)
14
14
  rack (~> 2.0, >= 2.0.9)
15
15
  rack-test (>= 0.6.3)
16
16
  rails-dom-testing (~> 2.0)
17
17
  rails-html-sanitizer (~> 1.0, >= 1.2.0)
18
- actionview (6.1.4)
19
- activesupport (= 6.1.4)
18
+ actionview (6.1.4.1)
19
+ activesupport (= 6.1.4.1)
20
20
  builder (~> 3.1)
21
21
  erubi (~> 1.4)
22
22
  rails-dom-testing (~> 2.0)
23
23
  rails-html-sanitizer (~> 1.1, >= 1.2.0)
24
- activemodel (6.1.4)
25
- activesupport (= 6.1.4)
26
- activerecord (6.1.4)
27
- activemodel (= 6.1.4)
28
- activesupport (= 6.1.4)
29
- activesupport (6.1.4)
24
+ activemodel (6.1.4.1)
25
+ activesupport (= 6.1.4.1)
26
+ activerecord (6.1.4.1)
27
+ activemodel (= 6.1.4.1)
28
+ activesupport (= 6.1.4.1)
29
+ activesupport (6.1.4.1)
30
30
  concurrent-ruby (~> 1.0, >= 1.0.2)
31
31
  i18n (>= 1.6, < 2)
32
32
  minitest (>= 5.1)
@@ -38,16 +38,16 @@ GEM
38
38
  concurrent-ruby (1.1.9)
39
39
  crass (1.0.6)
40
40
  erubi (1.10.0)
41
- i18n (1.8.10)
41
+ i18n (1.8.11)
42
42
  concurrent-ruby (~> 1.0)
43
- loofah (2.10.0)
43
+ loofah (2.12.0)
44
44
  crass (~> 1.0.2)
45
45
  nokogiri (>= 1.5.9)
46
46
  method_source (1.0.0)
47
- mini_portile2 (2.5.3)
47
+ mini_portile2 (2.6.1)
48
48
  minitest (5.14.4)
49
- nokogiri (1.11.7)
50
- mini_portile2 (~> 2.5.0)
49
+ nokogiri (1.12.5)
50
+ mini_portile2 (~> 2.6.1)
51
51
  racc (~> 1.4)
52
52
  parallel (1.20.1)
53
53
  parser (3.0.1.1)
@@ -55,18 +55,18 @@ GEM
55
55
  pry (0.14.1)
56
56
  coderay (~> 1.1)
57
57
  method_source (~> 1.0)
58
- racc (1.5.2)
58
+ racc (1.6.0)
59
59
  rack (2.2.3)
60
60
  rack-test (1.1.0)
61
61
  rack (>= 1.0, < 3)
62
62
  rails-dom-testing (2.0.3)
63
63
  activesupport (>= 4.2.0)
64
64
  nokogiri (>= 1.6)
65
- rails-html-sanitizer (1.3.0)
65
+ rails-html-sanitizer (1.4.2)
66
66
  loofah (~> 2.3)
67
- railties (6.1.4)
68
- actionpack (= 6.1.4)
69
- activesupport (= 6.1.4)
67
+ railties (6.1.4.1)
68
+ actionpack (= 6.1.4.1)
69
+ activesupport (= 6.1.4.1)
70
70
  method_source
71
71
  rake (>= 0.13)
72
72
  thor (~> 1.0)
@@ -96,7 +96,7 @@ GEM
96
96
  tzinfo (2.0.4)
97
97
  concurrent-ruby (~> 1.0)
98
98
  unicode-display_width (2.0.0)
99
- zeitwerk (2.4.2)
99
+ zeitwerk (2.5.1)
100
100
 
101
101
  PLATFORMS
102
102
  ruby
@@ -110,4 +110,4 @@ DEPENDENCIES
110
110
  standard
111
111
 
112
112
  BUNDLED WITH
113
- 2.2.15
113
+ 2.2.30
data/README.md CHANGED
@@ -6,11 +6,7 @@ code.
6
6
 
7
7
  ## Usage
8
8
 
9
- **HEADS UP: zeitwerk 2.5.0 is not yet released, so if you're using zeitwerk at
10
- all, the gem won't be able to ensure your migrations are safe and you'll see a
11
- warning to that effect**
12
-
13
- Add good_migrations to your Gemfile:
9
+ Add `good_migrations` to your Gemfile:
14
10
 
15
11
  ``` ruby
16
12
  gem 'good_migrations'
@@ -24,7 +20,7 @@ This gem requires that your app uses either of these autoloader strategies:
24
20
 
25
21
  * The classic `ActiveSupport::Dependencies` autoloader (e.g. `config.autoloader
26
22
  = :classic`), which is going away with Rails 7
27
- * Version 2.5 or higher of the zeitwerk autoloader (e.g. `config.autoloader =
23
+ * **Version 2.5 or higher** of the Zeitwerk autoloader (e.g. `config.autoloader =
28
24
  :zeitwerk`) If your app uses an earlier version of zeitwerk, you'll see a
29
25
  warning every time `db:migrate` is run
30
26
 
@@ -60,24 +56,70 @@ good!
60
56
  For more background, see the last section of this blog post on [healthy migration
61
57
  habits](http://blog.testdouble.com/posts/2014-11-04-healthy-migration-habits.html)
62
58
 
63
- ## Options
59
+ ## Adding to an existing app
60
+
61
+ If you add `good_migrations` to an existing application **and** any of those
62
+ migrations relied on auto-loading code from `app/`, then you'll see errors
63
+ raised whenever those migrations are run.
64
+
65
+ You have several options if this happens:
66
+
67
+ * If you're confident that every long-lasting environment has run the latest
68
+ migrations, you could consider squashing your existing migrations into a
69
+ single migration file that reflects the current state of your schema. This is
70
+ a tricky procedure to pull off in complex apps, and can require extra
71
+ coordination in cases where a high number of contributors are working on the
72
+ application simultaneously. The
73
+ [squasher](https://github.com/jalkoby/squasher) gem may be able to help.
74
+ * You can rewrite those past migrations to inline any application code inside
75
+ the migration's namespace. One way to do this is to run migrations until they
76
+ fail, check out the git ref of the failing migration so the codebase is
77
+ rewound to where it was at the time the migration was written, and finally
78
+ inline the necessary app code to get the migration passing before checking out
79
+ your primary branch. Rewriting any migration introduces risk of the resulting
80
+ schema diverging from production, so this requires significant care and
81
+ attention
82
+ * If neither of the above options are feasible, you can configure the
83
+ `good_migrations` gem to ignore migrations prior to a specified date with the
84
+ [permit_autoloading_before](#permit_autoloading_before-configuration)
85
+ option, which will effectively disable the gem's auto-loading prevention for
86
+ all migrations prior to a specified time
87
+
88
+ ## Configuration
89
+
90
+ To configure the gem, call `GoodMigrations.config` at some point as Rails is
91
+ loading (a good idea would be an initializer like
92
+ `config/initializers/good_migrations.rb`)
93
+
94
+ ```ruby
95
+ GoodMigrations.config do |config|
96
+ # Setting `permit_autoloading_before` will DISABLE good_migrations for
97
+ # any migrations before the given time. Don't set this unless you need to!
98
+ #
99
+ # Accepts parseable time strings as well as `Date` & `Time` objects
100
+ # config.permit_autoloading_before = "20140728132502"
101
+ end
102
+ ```
103
+
104
+ ## Working around good_migrations
64
105
 
65
- There's no public API to this gem. If you want to work around its behavior, you
66
- have a few options:
106
+ The gem only prevents auto-loading, so you can always can explicitly `require`
107
+ the app code that you need in your migration.
67
108
 
68
- 1. Run the command with the env var `GOOD_MIGRATIONS=skip`
69
- 2. Explicitly `require` the app code you need in your migration
70
- 3. Remove the gem from your project
109
+ If needed, it is possible to run a command with `good_migrations` disabled by
110
+ running the command with the env var `GOOD_MIGRATIONS=skip`.
71
111
 
72
112
  ## Acknowledgements
73
113
 
74
114
  Credit for figuring out where to hook into the ActiveSupport autoloader goes
75
115
  to [@tenderlove](https://github.com/tenderlove) for [this
76
- gist](https://gist.github.com/tenderlove/44447d1b1e466a28eb3f).
116
+ gist](https://gist.github.com/tenderlove/44447d1b1e466a28eb3f). And thanks to
117
+ [@fxn](https://github.com/fxn) for implementing the hook necessary for zeitwerk
118
+ support to be possible.
77
119
 
78
120
  ## Caveats
79
121
 
80
- Because this gem works by monkey-patching the ActiveSupport auto-loader, it will
81
- not work if your Rails environment (development, by default) is configured to
82
- eager load your application's classes (see:
122
+ Because this gem works by augmenting the auto-loader, it will not work if your
123
+ Rails environment (development, by default) is configured to eager load your
124
+ application's classes (see:
83
125
  [config.eager_load](http://edgeguides.rubyonrails.org/configuring.html#rails-general-configuration)).
data/example/Gemfile CHANGED
@@ -7,3 +7,5 @@ gem "good_migrations", path: ".."
7
7
  gem "sqlite3"
8
8
 
9
9
  gem "zeitwerk", github: "fxn/zeitwerk"
10
+
11
+ gem "bootsnap", require: false
@@ -0,0 +1,2 @@
1
+ class Shirt < ActiveRecord::Base
2
+ end
@@ -13,3 +13,4 @@ if File.exist?(gemfile)
13
13
  exit!
14
14
  end
15
15
  end
16
+ require "bootsnap/setup"
@@ -0,0 +1,7 @@
1
+ GoodMigrations.config do |config|
2
+ # Setting `permit_autoloading_before` will DISABLE good_migrations for
3
+ # any migrations before the given time. Don't set this unless you need to!
4
+ #
5
+ # Accepts parseable time strings as well as `Date` & `Time` objects
6
+ config.permit_autoloading_before = ENV["PERMIT_AUTOLOADING_BEFORE"]
7
+ end
@@ -0,0 +1,8 @@
1
+ class CreateShirts < ActiveRecord::Migration[4.2]
2
+ def change
3
+ create_table :shirts do |t|
4
+ t.integer :size
5
+ t.integer :color
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,10 @@
1
+ class ChangeShirtsDangerously < ActiveRecord::Migration[4.2]
2
+ def up
3
+ Shirt.find_each do |shirt|
4
+ # uh oh!
5
+ end
6
+ end
7
+
8
+ def down
9
+ end
10
+ end
@@ -0,0 +1,38 @@
1
+ module GoodMigrations
2
+ def self.config(&blk)
3
+ @configuration ||= Configuration.new
4
+
5
+ @configuration.tap do |config|
6
+ blk&.call(config)
7
+ end
8
+ end
9
+
10
+ class Configuration
11
+ # Migrations with timestamps (the numbers at the beginning of the file name) from
12
+ # before this configured time will be allowed to perform autoloading, bypassing the
13
+ # mechanism of this gem. Accepts:
14
+ # nil (default): never permit autoload
15
+ # String accepted by `Time.parse`, such as: 20211103150610 or 20211103_150610
16
+ # object responding to `to_time`, such as Date and Time
17
+ attr_reader :permit_autoloading_before
18
+ def permit_autoloading_before=(value)
19
+ case value
20
+ when nil
21
+ # Stay nil
22
+ when String
23
+ value = Time.parse(value)
24
+ else
25
+ if value.respond_to?(:to_time)
26
+ value = value.to_time
27
+ else
28
+ raise "Received an invalid value for permit_autoloading_before: #{value.inspect}"
29
+ end
30
+ end
31
+ @permit_autoloading_before = value
32
+ end
33
+
34
+ def initialize
35
+ @permit_autoloading_before = nil
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,29 @@
1
+ module GoodMigrations
2
+ class MigrationDetails
3
+ attr_reader :path
4
+ def initialize(path)
5
+ @path = path
6
+ end
7
+
8
+ def self.currently_executing
9
+ migrate_dir_path = Rails.root.join("db/migrate/").to_s
10
+
11
+ loc = caller.detect { |loc| loc.start_with?(migrate_dir_path) }
12
+ return if loc.nil?
13
+ new(loc.partition(":").first)
14
+ end
15
+
16
+ def associated_time
17
+ timestamp_string = File.basename(@path).partition("_").first
18
+ return if timestamp_string.size != 14
19
+ Time.parse(timestamp_string)
20
+ end
21
+
22
+ def considered_before?(time)
23
+ return false if time.nil?
24
+ my_time = associated_time
25
+ return false if my_time.nil?
26
+ my_time < time
27
+ end
28
+ end
29
+ end
@@ -5,11 +5,16 @@ module GoodMigrations
5
5
  end
6
6
 
7
7
  def initialize
8
+ @permits_autoload = PermitsAutoload.new
9
+ @raises_load_error = RaisesLoadError.new
10
+
8
11
  @disabled = false
9
12
  end
10
13
 
11
- def disabled?
12
- @disabled
14
+ def prevent_autoload_if_necessary!(path)
15
+ return if @disabled || @permits_autoload.permit?(path)
16
+
17
+ @raises_load_error.raise!(path)
13
18
  end
14
19
 
15
20
  def patch!
@@ -20,14 +25,11 @@ module GoodMigrations
20
25
  @disabled = false
21
26
  Rails.autoloaders.each do |loader|
22
27
  loader.on_load do |_, _, path|
23
- if GoodMigrations::PreventsAppLoad.app_path?(path) &&
24
- !GoodMigrations::PatchesAutoloader.instance.disabled?
25
- GoodMigrations::PreventsAppLoad.prevent_load!(path)
26
- end
28
+ GoodMigrations::PatchesAutoloader.instance.prevent_autoload_if_necessary!(path)
27
29
  end
28
30
  end
29
31
  else
30
- warn <<~UNSUPPORTED
32
+ warn <<-UNSUPPORTED.strip_heredoc
31
33
  WARNING: good_migrations is unable to ensure that your migrations are
32
34
  not inadvertently loading application code, because your application
33
35
  uses the zeitwerk autoloader (`config.autoloader = :zeitwerk`), but
@@ -44,12 +46,8 @@ module GoodMigrations
44
46
  ActiveSupport::Dependencies.class_eval do
45
47
  extend Module.new {
46
48
  def load_file(path, const_paths = loadable_constants_for_path(path))
47
- if GoodMigrations::PreventsAppLoad.app_path?(path) &&
48
- !GoodMigrations::PatchesAutoloader.instance.disabled?
49
- GoodMigrations::PreventsAppLoad.prevent_load!(path)
50
- else
51
- super
52
- end
49
+ GoodMigrations::PatchesAutoloader.instance.prevent_autoload_if_necessary!(path)
50
+ super
53
51
  end
54
52
  }
55
53
  end
@@ -0,0 +1,19 @@
1
+ module GoodMigrations
2
+ class PermitsAutoload
3
+ def permit?(path)
4
+ !app_path?(path) || permit_autoloading_based_on_migration_time?
5
+ end
6
+
7
+ private
8
+
9
+ def permit_autoloading_based_on_migration_time?
10
+ permit_before_date = GoodMigrations.config.permit_autoloading_before
11
+ migration_details = GoodMigrations::MigrationDetails.currently_executing
12
+ migration_details.considered_before?(permit_before_date)
13
+ end
14
+
15
+ def app_path?(path)
16
+ path.starts_with? File.join(Rails.application.root, "app")
17
+ end
18
+ end
19
+ end
@@ -1,11 +1,7 @@
1
1
  module GoodMigrations
2
- class PreventsAppLoad
3
- def self.app_path?(path)
4
- path.starts_with? File.join(Rails.application.root, "app")
5
- end
6
-
7
- def self.prevent_load!(path)
8
- raise GoodMigrations::LoadError, <<~ERROR
2
+ class RaisesLoadError
3
+ def raise!(path)
4
+ raise GoodMigrations::LoadError, <<-ERROR.strip_heredoc
9
5
  Rails attempted to auto-load:
10
6
 
11
7
  #{path}
@@ -1,3 +1,3 @@
1
1
  module GoodMigrations
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -1,5 +1,10 @@
1
- require "good_migrations/version"
2
- require "good_migrations/load_error"
3
- require "good_migrations/patches_autoloader"
4
- require "good_migrations/prevents_app_load"
5
- require "good_migrations/railtie" if defined?(Rails)
1
+ require "time"
2
+
3
+ require_relative "good_migrations/version"
4
+ require_relative "good_migrations/load_error"
5
+ require_relative "good_migrations/migration_details"
6
+ require_relative "good_migrations/configuration"
7
+ require_relative "good_migrations/patches_autoloader"
8
+ require_relative "good_migrations/permits_autoload"
9
+ require_relative "good_migrations/raises_load_error"
10
+ require_relative "good_migrations/railtie" if defined?(Rails)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: good_migrations
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Searls
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2021-06-30 00:00:00.000000000 Z
12
+ date: 2021-11-17 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: railties
@@ -136,6 +136,7 @@ files:
136
136
  - example/app/controllers/application_controller.rb
137
137
  - example/app/helpers/application_helper.rb
138
138
  - example/app/models/pant.rb
139
+ - example/app/models/shirt.rb
139
140
  - example/app/views/layouts/application.html.erb
140
141
  - example/bin/rails
141
142
  - example/bin/rake
@@ -148,6 +149,7 @@ files:
148
149
  - example/config/environments/production.rb
149
150
  - example/config/environments/test.rb
150
151
  - example/config/initializers/backtrace_silencers.rb
152
+ - example/config/initializers/good_migrations.rb
151
153
  - example/config/initializers/inflections.rb
152
154
  - example/config/initializers/mime_types.rb
153
155
  - example/config/initializers/secret_token.rb
@@ -157,6 +159,8 @@ files:
157
159
  - example/db/migrate/20160202162849_create_pants.rb
158
160
  - example/db/migrate/20160202163803_change_pants.rb
159
161
  - example/db/migrate/20160202182520_change_pants_dangerously.rb
162
+ - example/db/migrate/20170101150000_create_shirts.rb
163
+ - example/db/migrate/20170102150000_change_shirts_dangerously.rb
160
164
  - example/db/seeds.rb
161
165
  - example/doc/README_FOR_APP
162
166
  - example/lib/tasks/.gitkeep
@@ -181,10 +185,13 @@ files:
181
185
  - example/vendor/plugins/.gitkeep
182
186
  - good_migrations.gemspec
183
187
  - lib/good_migrations.rb
188
+ - lib/good_migrations/configuration.rb
184
189
  - lib/good_migrations/load_error.rb
190
+ - lib/good_migrations/migration_details.rb
185
191
  - lib/good_migrations/patches_autoloader.rb
186
- - lib/good_migrations/prevents_app_load.rb
192
+ - lib/good_migrations/permits_autoload.rb
187
193
  - lib/good_migrations/railtie.rb
194
+ - lib/good_migrations/raises_load_error.rb
188
195
  - lib/good_migrations/version.rb
189
196
  - tasks/good_migrations.rake
190
197
  homepage: https://github.com/testdouble/good-migrations
@@ -206,7 +213,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
206
213
  - !ruby/object:Gem::Version
207
214
  version: '0'
208
215
  requirements: []
209
- rubygems_version: 3.2.15
216
+ rubygems_version: 3.2.22
210
217
  signing_key:
211
218
  specification_version: 4
212
219
  summary: Prevents Rails from auto-loading app code in database migrations