yiffspace-fixers 0.0.3 → 0.0.5

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: 63554d772d4091d0d869023320f37c67b15f7e210ca23ef72ac99f2aaaad2fd3
4
- data.tar.gz: f6466feaf844911fd1ddcdedebe1c4d9f55ff225fbce907bf1f37defc3258662
3
+ metadata.gz: acd62a82b8d0b1a4e8b58612b8e1fa68fa80bee2b1d32d0ec9f3dd6566aac75e
4
+ data.tar.gz: 87ec2fcee2b7f9259dc808f8d7a524a3e91db0c4820d7076e5d820988f2593be
5
5
  SHA512:
6
- metadata.gz: 06e9bfd1da3f639d35d8d14f773a6d8d947486c8b2601aa8ac1bed4fc0746efe8f7e4f85a4d334516db6017d6c2fc3046f0c7069dc5145716ed33b1b413d49b4
7
- data.tar.gz: 2b6bde7b6c07121e45cf4f4869ce0274898272fda784d653206551ab378418cb93bb2add97c3a11601b604106a99075e941598282805691cc9b189acc68edf8c
6
+ metadata.gz: c77170c7d1ada931e3101f9637a50b74f9cfa7306f0638642830dd318b06e71aebf31c4d8b122c38326966666511340d9871b1b7519e9807b2f09117a4b12eed
7
+ data.tar.gz: f2c7eae550ce7fe2039bd7c5478346bd1634848050020381ea551882fbdcff083e36eb6b2e3b05b4bec77e043fe3c7860fdaf6ece9758e22d228bfc18fa3a61d
data/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  ## Unreleased
2
2
 
3
+ - Added `run_fix!`, the same as `run_fix` but runs fixes even if they're already applied.
4
+ - Migrations can run fixes themselves with `run_fix(id_or_name)`, which runs and records any of
5
+ them not yet applied, and does nothing on rollback. `fixes:migrate_all` reads these calls out
6
+ of pending migrations and folds those fixes into the migration's step instead of scheduling
7
+ them separately. A fix run this way can `requires_migration!` the migration running it.
3
8
  - Fixes run through `YiffSpace::FixTracker.run!` (so every `fixes:*` task) can call
4
9
  `requires_migration!` unqualified, via `YiffSpace::Fixers::Script` - each fix is now loaded
5
10
  wrapped in its own module including it, rather than straight into the top level, so it isn't
data/README.md CHANGED
@@ -49,6 +49,21 @@ class RemoveLegacyWidgetColumn < ActiveRecord::Migration[8.1]
49
49
  end
50
50
  ```
51
51
 
52
+ A migration can also run a fix itself with `run_fix`, inside its own transaction - skipped if the
53
+ fix is already applied, and when the migration is rolled back. `fixes:migrate_all` leaves those
54
+ fixes to the migration rather than running them on their own, and a fix run this way can
55
+ `requires_migration!` the migration running it. `run_fix!` does the same, but runs the fix even if
56
+ it's already applied:
57
+
58
+ ```ruby
59
+ class AddWidgetType < ActiveRecord::Migration[8.1]
60
+ def change
61
+ add_column(:widgets, :type, :string)
62
+ run_fix(12)
63
+ end
64
+ end
65
+ ```
66
+
52
67
  See `YiffSpace::FixTracker`, `YiffSpace::FixerTemplate`, `YiffSpace::Fixers::RequiresFix`, and
53
68
  `YiffSpace::MigrationSync` for the full API.
54
69
 
@@ -27,7 +27,7 @@ module YiffSpace
27
27
  end
28
28
 
29
29
  def requires_migration!(version)
30
- return if migration_applied?(version)
30
+ return if migration_applied?(version) || migration_running?(version)
31
31
 
32
32
  raise(MigrationRequired, "migration #{version} must be applied before this fix can run")
33
33
  end
@@ -36,6 +36,23 @@ module YiffSpace
36
36
  migration_context.get_all_versions.include?(version.to_i)
37
37
  end
38
38
 
39
+ # A migration isn't recorded as applied until it finishes, so a fix it runs with
40
+ # YiffSpace::Fixers::RequiresFix#run_fix is let through requires_migration! for it via this.
41
+ def running_migration(version)
42
+ running_migrations << version.to_i
43
+ yield
44
+ ensure
45
+ running_migrations.pop
46
+ end
47
+
48
+ def migration_running?(version)
49
+ running_migrations.include?(version.to_i)
50
+ end
51
+
52
+ def running_migrations
53
+ @running_migrations ||= []
54
+ end
55
+
39
56
  # Statically reads a fix script's declared requires_migration! version, if any, without
40
57
  # loading/running it - nil if the fix has no such call.
41
58
  def required_migration_for(name)
@@ -18,10 +18,32 @@ module YiffSpace
18
18
  # out of order), and read back ahead of time by YiffSpace::MigrationSync to order
19
19
  # `fixes:migrate_all` correctly. Included into ActiveRecord::Migration by
20
20
  # YiffSpace::Fixers::Engine.
21
+ #
22
+ # A migration can also run a fix itself, as part of migrating, with `run_fix`:
23
+ #
24
+ # def change
25
+ # add_column(:widgets, :type, :string)
26
+ # run_fix(3)
27
+ # end
28
+ #
29
+ # Each fix it covers is run and recorded the same as YiffSpace::FixTracker.run!, unless it's
30
+ # already applied, and inside the migration's transaction. Fixes can't be undone, so it does
31
+ # nothing when the migration is rolled back. A fix run this way can requires_migration! the
32
+ # migration running it. `run_fix!` does the same, but runs fixes even if already applied.
21
33
  module RequiresFix
22
34
  # Raised when a migration runs before one of its required fixes has been applied.
23
35
  class FixRequired < StandardError; end
24
36
 
37
+ # Same idea as YiffSpace::FixTracker::REQUIRES_MIGRATION_PATTERN - lets
38
+ # YiffSpace::MigrationSync read the fixes a migration runs out of its file, so
39
+ # `fixes:migrate_all` leaves them to the migration instead of running them on their own.
40
+ RUN_FIX_PATTERN = /^\s*run_fix!?\(\s*(?:(\d+)|["']([^"']+)["'])\s*\)/
41
+
42
+ # The id_or_name of every run_fix/run_fix! call in a migration file.
43
+ def self.fixes_run_by(path)
44
+ File.read(path).scan(RUN_FIX_PATTERN).map { |id, name| id ? id.to_i : name }
45
+ end
46
+
25
47
  def self.included(base)
26
48
  base.extend(ClassMethods)
27
49
  base.prepend(InstanceMethods)
@@ -46,8 +68,35 @@ module YiffSpace
46
68
  super
47
69
  end
48
70
 
71
+ # id_or_name: same as requires_fix.
72
+ def run_fix(id_or_name)
73
+ run_fixes(id_or_name, force: false)
74
+ end
75
+
76
+ # Same as run_fix, but runs fixes even if they're already applied.
77
+ def run_fix!(id_or_name)
78
+ run_fixes(id_or_name, force: true)
79
+ end
80
+
49
81
  private
50
82
 
83
+ def run_fixes(id_or_name, force:)
84
+ return if reverting?
85
+
86
+ names = YiffSpace::FixTracker.resolve(id_or_name)
87
+ raise(ArgumentError, "#{self.class.name} runs fix #{id_or_name.inspect}, which does not exist") if names.empty?
88
+
89
+ names.each do |name|
90
+ if !force && YiffSpace::FixTracker.applied?(name)
91
+ say("#{name} already applied, skipping")
92
+ else
93
+ say_with_time("run_fix#{'!' if force}(#{name.inspect})") do
94
+ YiffSpace::FixTracker.running_migration(version) { YiffSpace::FixTracker.run!(YiffSpace::FixTracker.fix_path(name)) }
95
+ end
96
+ end
97
+ end
98
+ end
99
+
51
100
  def check_required_fixes!
52
101
  self.class.required_fixes.each do |id_or_name|
53
102
  names = YiffSpace::FixTracker.resolve(id_or_name)
@@ -2,6 +2,6 @@
2
2
 
3
3
  module YiffSpace
4
4
  module Fixers
5
- VERSION = "0.0.3"
5
+ VERSION = "0.0.5"
6
6
  end
7
7
  end
@@ -4,8 +4,9 @@ module YiffSpace
4
4
  # Computes and applies pending db/migrate/*.rb migrations and db/fixes/*.rb fixes together, in
5
5
  # whichever order their cross requirements demand - a fix can require a migration
6
6
  # (YiffSpace::FixTracker.requires_migration!) and a migration can require a fix
7
- # (YiffSpace::Fixers::RequiresFix#requires_fix). Migrations stay ordered among themselves by
8
- # version, fixes among themselves by YiffSpace::FixTracker.sort_key, and those cross
7
+ # (YiffSpace::Fixers::RequiresFix#requires_fix), or run it itself (RequiresFix#run_fix).
8
+ # Migrations stay ordered among themselves by version, fixes among themselves by
9
+ # YiffSpace::FixTracker.sort_key, and those cross
9
10
  # requirements are woven in via a topological sort. #plan/#apply are used by `rake
10
11
  # fixes:migrate_all` (see lib/tasks/fixes.rake).
11
12
  module MigrationSync
@@ -24,15 +25,22 @@ module YiffSpace
24
25
  def plan
25
26
  migrations = pending_migrations
26
27
  fixes = FixTracker.pending
28
+ run_by = fixes_run_by(migrations)
27
29
 
28
30
  steps = {}
29
31
  migrations.each { |m| steps[[:migration, m.version]] = Step.new(:migration, m, [:migration, m.version]) }
30
- fixes.each { |f| steps[[:fix, f]] = Step.new(:fix, f, [:fix, f]) }
32
+ fixes.each { |f| steps[[:fix, f]] = Step.new(:fix, f, [:fix, f]) unless run_by.key?([:fix, f]) }
31
33
 
32
34
  edges = Hash.new { |h, k| h[k] = [] }
33
35
  indegree = steps.each_key.index_with(0)
34
36
 
35
37
  link = lambda do |before, after|
38
+ # A fix a migration runs (see #fixes_run_by) has no step of its own, its requirements and
39
+ # ordering carry over to that migration instead. One requiring that same migration links
40
+ # it to itself, which needs nothing - FixTracker.running_migration covers it at runtime.
41
+ before = run_by.fetch(before, before)
42
+ after = run_by.fetch(after, after)
43
+ next if before == after
36
44
  next unless steps.key?(before) && steps.key?(after)
37
45
 
38
46
  edges[before] << after
@@ -70,6 +78,19 @@ module YiffSpace
70
78
  end
71
79
  end
72
80
 
81
+ # Maps [:fix, name] => [:migration, version] for every pending fix a pending migration runs
82
+ # itself with YiffSpace::Fixers::RequiresFix#run_fix.
83
+ def fixes_run_by(migrations)
84
+ migrations.each_with_object({}) do |m, run_by|
85
+ Fixers::RequiresFix.fixes_run_by(m.filename).each do |id_or_name|
86
+ names = FixTracker.resolve(id_or_name)
87
+ raise(UnresolvableOrder, "#{m.name} runs fix #{id_or_name.inspect}, which does not exist") if names.empty?
88
+
89
+ names.each { |name| run_by[[:fix, name]] ||= [:migration, m.version] }
90
+ end
91
+ end
92
+ end
93
+
73
94
  # Kahn's algorithm - ready nodes are processed FIFO in `steps`' insertion order (migrations
74
95
  # before fixes when both are ready at once), so requirements only ever reorder things when
75
96
  # they actually have to.
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: yiffspace-fixers
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Donovan_DMC
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2026-10-03 00:00:00.000000000 Z
10
+ date: 2026-10-16 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: rails