good_migrations 0.1.0 → 0.2.0

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