Module: YiffSpace::FixTracker
- Defined in:
- lib/yiffspace/fix_tracker.rb
Overview
Tracks which db/fixes/.rb scripts a host app has applied, in a fixes table (see
db/migrate/_create_fixes.rb) the same way ActiveRecord's own schema_migrations table
tracks migrations. Used by the fixes:* rake tasks (lib/tasks/fixes.rake).
Class Method Summary collapse
-
.all_fixes ⇒ Object
Only numbered one-time fixes are tracked/auto-applied - a host app's db/fixes may also hold reusable on-demand maintenance scripts that aren't meant to run automatically as part of
fixes:migrate. - .applied ⇒ Object
- .applied?(name) ⇒ Boolean
-
.dump_structure_sql! ⇒ Object
Mirrors how ActiveRecord appends
INSERT INTO "schema_migrations"to structure.sql after a schema dump (see Connection#dump_schema_versions) - appends the currently-applied fixes the same way, so loading structure.sql into a fresh database marks them applied too instead of leavingfixes:migrateto re-run every historical fix. - .fix_path(name) ⇒ Object
-
.key_for(name) ⇒ Object
db/fixes filenames are "
_description.rb", occasionally " _ _description.rb" (e.g. 1_1_..., 1_2_...) for a handful of fixes split into ordered steps - the fixestable mirrors this as an (id, index) pair, with index null for fixes with no subtype. - .pending ⇒ Object
- .record!(name) ⇒ Object
- .run!(path) ⇒ Object
-
.sort_key(name) ⇒ Object
Sort on the (id, index) parts, not the filename string, so 12 doesn't sort before 2_2.
Class Method Details
.all_fixes ⇒ Object
Only numbered one-time fixes are tracked/auto-applied - a host app's db/fixes may also hold
reusable on-demand maintenance scripts that aren't meant to run automatically as part of
fixes:migrate. Those stay reachable via fixes:run[name].
32 33 34 |
# File 'lib/yiffspace/fix_tracker.rb', line 32 def all_fixes Rails.root.glob("db/fixes/*.rb").map { |path| File.basename(path, ".rb") }.grep(/\A\d/).sort_by { |name| sort_key(name) } end |
.applied ⇒ Object
36 37 38 |
# File 'lib/yiffspace/fix_tracker.rb', line 36 def applied ActiveRecord::Base.connection.select_rows('SELECT id, "index" FROM fixes').to_set end |
.applied?(name) ⇒ Boolean
40 41 42 43 44 45 |
# File 'lib/yiffspace/fix_tracker.rb', line 40 def applied?(name) id, index = key_for(name) conn = ActiveRecord::Base.connection index_clause = index.nil? ? '"index" IS NULL' : %("index" = #{index}) conn.select_value("SELECT 1 FROM fixes WHERE id = #{id} AND #{index_clause}").present? end |
.dump_structure_sql! ⇒ Object
Mirrors how ActiveRecord appends INSERT INTO "schema_migrations" to structure.sql after
a schema dump (see Connection#dump_schema_versions) - appends the currently-applied fixes
the same way, so loading structure.sql into a fresh database marks them applied too instead
of leaving fixes:migrate to re-run every historical fix. Hooked into db:schema:dump in
lib/tasks/fixes.rake, so it runs on every schema dump, not just fixes:migrate.
73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
# File 'lib/yiffspace/fix_tracker.rb', line 73 def dump_structure_sql! db_config = ActiveRecord::Base.connection_db_config return unless db_config.schema_format == :sql filename = ActiveRecord::Tasks::DatabaseTasks.schema_dump_path(db_config) return unless filename && File.exist?(filename) conn = ActiveRecord::Base.connection return unless conn.table_exists?(:fixes) rows = conn.select_rows('SELECT id, "index" FROM fixes ORDER BY id DESC, "index" DESC NULLS LAST') return if rows.empty? values = rows.map { |id, index| "(#{id}, #{index || 'NULL'})" }.join(",\n") File.open(filename, "a") do |f| f.puts(<<~TEXT) INSERT INTO "fixes" (id, "index") VALUES #{values}; TEXT end end |
.fix_path(name) ⇒ Object
10 11 12 |
# File 'lib/yiffspace/fix_tracker.rb', line 10 def fix_path(name) Rails.root.glob("db/fixes/#{name}.rb").first end |
.key_for(name) ⇒ Object
db/fixes filenames are "fixes
table mirrors this as an (id, index) pair, with index null for fixes with no subtype.
17 18 19 20 21 |
# File 'lib/yiffspace/fix_tracker.rb', line 17 def key_for(name) id, maybe_index = name.split("_", 3) index = maybe_index =~ /\A\d+\z/ ? maybe_index.to_i : nil [id.to_i, index] end |
.pending ⇒ Object
47 48 49 50 |
# File 'lib/yiffspace/fix_tracker.rb', line 47 def pending applied_keys = applied all_fixes.reject { |name| applied_keys.include?(key_for(name)) } end |
.record!(name) ⇒ Object
52 53 54 55 56 57 58 59 |
# File 'lib/yiffspace/fix_tracker.rb', line 52 def record!(name) id, index = key_for(name) conn = ActiveRecord::Base.connection conn.execute(<<~SQL.squish) INSERT INTO fixes (id, "index") VALUES (#{id}, #{index || 'NULL'}) ON CONFLICT (id, "index") DO NOTHING SQL end |
.run!(path) ⇒ Object
61 62 63 64 65 66 |
# File 'lib/yiffspace/fix_tracker.rb', line 61 def run!(path) name = File.basename(path, ".rb") load(File.(path)) record!(name) name end |
.sort_key(name) ⇒ Object
Sort on the (id, index) parts, not the filename string, so 12 doesn't sort before 2_2.
24 25 26 27 |
# File 'lib/yiffspace/fix_tracker.rb', line 24 def sort_key(name) id, index = key_for(name) [id, index || 0] end |