yiffspace 0.1.7 → 0.1.8
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 +4 -4
- data/lib/generators/yiffspace/fixer/USAGE +40 -0
- data/lib/generators/yiffspace/fixer/fixer_generator.rb +86 -0
- data/lib/generators/yiffspace/fixer/templates/fixer.rb +4 -0
- data/lib/generators/yiffspace/install/fixes_generator.rb +21 -0
- data/lib/generators/yiffspace/install/templates/create_fixes.rb.erb +18 -0
- data/lib/tasks/fixes.rake +66 -0
- data/lib/yiffspace/configuration/fixer_templates.rb +87 -0
- data/lib/yiffspace/configuration.rb +14 -0
- data/lib/yiffspace/fix_tracker.rb +95 -0
- data/lib/yiffspace/fixer_template.rb +44 -0
- data/lib/yiffspace/version.rb +1 -1
- data/lib/yiffspace.rb +1 -0
- metadata +11 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b8d498d64c506f7f2c008b8c03aca70d0d51f371805cbd1f6c5c4d9fdb89a453
|
|
4
|
+
data.tar.gz: c311ba586f4365538d8a0802ad47ab000ea90a76f94d6b9e0642aef233bfcba2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d463a33bee7f0760dc314e832ad7776a894185ee46590b87f3c49f540a89c521732d28f7b126b6606f03204f013b8230d05d512043130e3b101e2e33e0cf2d68
|
|
7
|
+
data.tar.gz: 561db6abdedb892c69defa2bc2f2baebf4cdf1f79d1ab194ed45ebc3929eaef6121a70dd0cfec2811f10dadc85688b14ea78e95aece7a59352d04ce3d05d2659
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
Description:
|
|
2
|
+
Generates a new one-time fix script tracked by YiffSpace::FixTracker.
|
|
3
|
+
|
|
4
|
+
Example:
|
|
5
|
+
bin/rails generate yiffspace:fixer fixer_name
|
|
6
|
+
|
|
7
|
+
This will create:
|
|
8
|
+
db/fixes/<id>_fixer_name.rb
|
|
9
|
+
|
|
10
|
+
bin/rails generate yiffspace:fixer fixer_name --steps 2
|
|
11
|
+
|
|
12
|
+
This will create:
|
|
13
|
+
db/fixes/<id>_1_fixer_name.rb
|
|
14
|
+
db/fixes/<id>_2_fixer_name.rb
|
|
15
|
+
|
|
16
|
+
A host app can define named templates, auto-discovered from db/fixer_templates/*.rb (see
|
|
17
|
+
YiffSpace::FixerTemplate and YiffSpace.config.fixer_templates_path) - no registration needed,
|
|
18
|
+
just define a subclass. Each `step` block's return value becomes that step's file content:
|
|
19
|
+
|
|
20
|
+
# db/fixer_templates/elasticsearch_template.rb
|
|
21
|
+
class ElasticsearchTemplate < YiffSpace::FixerTemplate
|
|
22
|
+
short("e")
|
|
23
|
+
|
|
24
|
+
step { "# add the elasticsearch index\n" }
|
|
25
|
+
step { "# backfill the elasticsearch data\n" }
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
bin/rails generate yiffspace:fixer fixer_name --template elasticsearch
|
|
29
|
+
bin/rails generate yiffspace:fixer fixer_name -t elasticsearch
|
|
30
|
+
bin/rails generate yiffspace:fixer fixer_name -e
|
|
31
|
+
|
|
32
|
+
All three create the 2 steps above; `short` is optional - a template is always reachable via
|
|
33
|
+
--template/-t, the shortname flag is just a convenience.
|
|
34
|
+
|
|
35
|
+
A host app can also register a preset with just a step count (no per-step content - every
|
|
36
|
+
step falls back to the generic fixer.rb template) directly in an initializer instead:
|
|
37
|
+
|
|
38
|
+
YiffSpace.configure do |config|
|
|
39
|
+
config.fixer_templates.register(:legacy, steps: 3)
|
|
40
|
+
end
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yiffspace
|
|
4
|
+
# Scaffolds a new db/fixes/*.rb script for YiffSpace::FixTracker (see `rake fixes:list`).
|
|
5
|
+
#
|
|
6
|
+
# `--steps N` splits the fix into N ordered steps sharing an id, each one a copy of the generic
|
|
7
|
+
# fixer.rb template. A host app can instead define a YiffSpace::FixerTemplate subclass (see that
|
|
8
|
+
# class) to control both the step count and each step's file content, auto-discovered from
|
|
9
|
+
# YiffSpace.config.fixer_templates_path - reachable here with `--template <name>`/`-t <name>`,
|
|
10
|
+
# or with the template's own shortname flag if it registered one (e.g. `-e`).
|
|
11
|
+
class FixerGenerator < Rails::Generators::NamedBase
|
|
12
|
+
source_root(File.expand_path("templates", __dir__))
|
|
13
|
+
# No short alias - -s/-f/-p/-q are already claimed by Rails::Generators::Base's own runtime
|
|
14
|
+
# options (--skip/--force/--pretend/--quiet), and Thor silently lets the last-defined
|
|
15
|
+
# class_option win a collision instead of erroring.
|
|
16
|
+
class_option(:steps, type: :numeric, default: 1,
|
|
17
|
+
desc: "Split the fix into N ordered steps sharing an id, e.g. 12_1_name.rb, 12_2_name.rb")
|
|
18
|
+
class_option(:template, type: :string, default: nil, aliases: ["-t"],
|
|
19
|
+
desc: "Use a fixer template registered with YiffSpace.config.fixer_templates")
|
|
20
|
+
|
|
21
|
+
YiffSpace.config.fixer_templates.each do |template|
|
|
22
|
+
next unless template.short
|
|
23
|
+
|
|
24
|
+
class_option(template.name.to_sym, type: :boolean, default: false, aliases: ["-#{template.short}"],
|
|
25
|
+
desc: "Shortcut for --template #{template.name}")
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def create_fixer
|
|
29
|
+
content_blocks = resolve_content_blocks
|
|
30
|
+
|
|
31
|
+
id = Dir[File.join(destination_root, "db/fixes/*.rb")].map { |f| File.basename(f, ".rb").split("_").first.to_i }.max.to_i + 1
|
|
32
|
+
|
|
33
|
+
if content_blocks.size == 1
|
|
34
|
+
create_fixer_file("db/fixes/#{id}_#{file_name}.rb", content_blocks.first)
|
|
35
|
+
else
|
|
36
|
+
content_blocks.each_with_index { |block, index| create_fixer_file("db/fixes/#{id}_#{index + 1}_#{file_name}.rb", block) }
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
# One entry per step - a block returning that step's file content, or nil to fall back to
|
|
43
|
+
# copying the generic fixer.rb template (a plain --steps N, or a manually
|
|
44
|
+
# YiffSpace.config.fixer_templates.register'd template, has no per-step content).
|
|
45
|
+
def resolve_content_blocks
|
|
46
|
+
template = resolve_template
|
|
47
|
+
return template.content_blocks if template
|
|
48
|
+
|
|
49
|
+
Array.new(steps_from_flag)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def resolve_template
|
|
53
|
+
name = template_name
|
|
54
|
+
return nil if name.nil?
|
|
55
|
+
|
|
56
|
+
template = YiffSpace.config.fixer_templates[name]
|
|
57
|
+
raise(Thor::Error, "no fixer template named #{name.inspect} is registered") unless template
|
|
58
|
+
|
|
59
|
+
template
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# The template chosen via `--template <name>`, or via a registered template's own shortname
|
|
63
|
+
# flag (e.g. `-e` for a template registered with `short: "e"`) - nil if neither was passed.
|
|
64
|
+
def template_name
|
|
65
|
+
return options["template"] if options["template"]
|
|
66
|
+
|
|
67
|
+
YiffSpace.config.fixer_templates.find { |template| template.short && options[template.name] }&.name
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def steps_from_flag
|
|
71
|
+
steps = options["steps"].to_i
|
|
72
|
+
raise(Thor::Error, "--steps must be at least 1") if steps < 1
|
|
73
|
+
|
|
74
|
+
steps
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def create_fixer_file(path, content_block)
|
|
78
|
+
if content_block
|
|
79
|
+
create_file(path, content_block.call)
|
|
80
|
+
chmod(path, "+x")
|
|
81
|
+
else
|
|
82
|
+
copy_file("fixer.rb", path, mode: :preserve)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yiffspace
|
|
4
|
+
module Install
|
|
5
|
+
# Installs YiffSpace::FixTracker into a host app: `bin/rails generate yiffspace:install:fixes`
|
|
6
|
+
# copies the `fixes` tracking table migration. Run `bin/rails db:migrate` afterwards.
|
|
7
|
+
class FixesGenerator < Rails::Generators::Base
|
|
8
|
+
include(Rails::Generators::Migration)
|
|
9
|
+
|
|
10
|
+
source_root(File.expand_path("templates", __dir__))
|
|
11
|
+
|
|
12
|
+
def self.next_migration_number(dirname)
|
|
13
|
+
ActiveRecord::Migration.next_migration_number(current_migration_number(dirname) + 1)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def create_migration_file
|
|
17
|
+
migration_template("create_fixes.rb.erb", "db/migrate/create_fixes.rb")
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Tracks which db/fixes/*.rb scripts have been applied, the same way schema_migrations tracks
|
|
4
|
+
# migrations - see YiffSpace::FixTracker and `rake fixes:migrate`/`rake fixes:list`.
|
|
5
|
+
#
|
|
6
|
+
# `id` is the fix's own leading number (e.g. 12, 23), not a surrogate key, so it isn't unique on
|
|
7
|
+
# its own - a fix can be split into ordered sub-steps (12_1, 12_2, ...) that share an id and are
|
|
8
|
+
# distinguished by `index`. `index` is null for fixes with no subtype.
|
|
9
|
+
class CreateFixes < ActiveRecord::Migration[<%= ActiveRecord::VERSION::STRING.to_f %>]
|
|
10
|
+
def change
|
|
11
|
+
create_table(:fixes, id: false) do |t|
|
|
12
|
+
t.integer(:id, null: false) # not a surrogate key, this is the fix's own leading number
|
|
13
|
+
t.integer(:index)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
add_index(:fixes, %i[id index], unique: true, nulls_not_distinct: true)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Keep structure.sql's fixes rows in sync with the database on every schema dump, the same way
|
|
4
|
+
# ActiveRecord keeps schema_migrations rows in sync - not just when `fixes:migrate` runs.
|
|
5
|
+
Rake::Task["db:schema:dump"].enhance do
|
|
6
|
+
YiffSpace::FixTracker.dump_structure_sql!
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
namespace(:fixes) do
|
|
10
|
+
desc("List available fix scripts in db/fixes, marking which have already been applied")
|
|
11
|
+
task(list: :environment) do
|
|
12
|
+
applied = YiffSpace::FixTracker.applied
|
|
13
|
+
YiffSpace::FixTracker.all_fixes.each do |name|
|
|
14
|
+
puts("#{applied.include?(YiffSpace::FixTracker.key_for(name)) ? '[x]' : '[ ]'} #{name}")
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
desc("Run a fix script from db/fixes by id or name, e.g. `rake fixes:run[1]` - re-runs even if already recorded as applied")
|
|
19
|
+
task(:run, [:name] => :environment) do |_task, args|
|
|
20
|
+
name = args[:name].to_s.delete_suffix(".rb")
|
|
21
|
+
abort("Usage: rake fixes:run[id_or_name] (see `rake fixes:list`)") if name.blank?
|
|
22
|
+
|
|
23
|
+
candidates = Dir["db/fixes/*.rb"]
|
|
24
|
+
exact = candidates.find { |path| File.basename(path, ".rb") == name }
|
|
25
|
+
matches = exact ? [exact] : candidates.select { |path| File.basename(path, ".rb").start_with?("#{name}_") || File.basename(path, ".rb").include?(name) }
|
|
26
|
+
|
|
27
|
+
if matches.empty?
|
|
28
|
+
abort("No fix matches \"#{name}\". Run `rake fixes:list` to see available fixes.")
|
|
29
|
+
elsif matches.size > 1
|
|
30
|
+
abort("Multiple fixes match \"#{name}\", be more specific:\n#{matches.map { |m| " #{File.basename(m, '.rb')}" }.join("\n")}")
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
puts("Running #{File.basename(matches.first, '.rb')}...")
|
|
34
|
+
name = YiffSpace::FixTracker.run!(matches.first)
|
|
35
|
+
puts("Recorded #{name} as applied")
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
desc("Apply all fix scripts not yet recorded as applied, in order, then dump the schema like db:migrate does")
|
|
39
|
+
task(migrate: :environment) do
|
|
40
|
+
pending = YiffSpace::FixTracker.pending
|
|
41
|
+
|
|
42
|
+
if pending.empty?
|
|
43
|
+
puts("No pending fixes.")
|
|
44
|
+
else
|
|
45
|
+
pending.each do |name|
|
|
46
|
+
puts("Running #{name}...")
|
|
47
|
+
YiffSpace::FixTracker.run!(YiffSpace::FixTracker.fix_path(name))
|
|
48
|
+
puts("Recorded #{name} as applied")
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
Rake::Task["db:_dump"].invoke
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
desc("Record all of the current fixes as having been applied")
|
|
56
|
+
task(load: :environment) do
|
|
57
|
+
list = YiffSpace::FixTracker.all_fixes.map { YiffSpace::FixTracker.key_for(it) }
|
|
58
|
+
existing = ActiveRecord::Base.connection.select_all('SELECT id, "index" FROM fixes ORDER BY id, "index"').rows
|
|
59
|
+
list.reject! { |id, index| existing.any? { |e| e[0] == id && e[1] == index } }
|
|
60
|
+
unless list.empty?
|
|
61
|
+
values = list.map { |id, index| "(#{id}, #{index.nil? ? 'NULL' : index})" }.join(", ")
|
|
62
|
+
ActiveRecord::Base.connection.execute(%(INSERT INTO fixes (id, "index") VALUES #{values}))
|
|
63
|
+
end
|
|
64
|
+
puts("Loaded #{list.length} #{'fix'.pluralize(list.length)}")
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module YiffSpace
|
|
4
|
+
class Configuration
|
|
5
|
+
# Named presets for `bin/rails generate yiffspace:fixer`, so `--template <name>` (or a
|
|
6
|
+
# registered shortname flag) can stand in for `--steps N`. Populated two ways:
|
|
7
|
+
#
|
|
8
|
+
# - Automatically, from YiffSpace::FixerTemplate subclasses found in
|
|
9
|
+
# YiffSpace.config.fixer_templates_path (the common case - see FixerTemplate).
|
|
10
|
+
# - Manually, via #register, for a preset that just needs a step count and no per-step
|
|
11
|
+
# content (every step falls back to the generic fixer.rb template).
|
|
12
|
+
#
|
|
13
|
+
# See YiffSpace::Configuration#fixer_templates and Yiffspace::FixerGenerator.
|
|
14
|
+
class FixerTemplates
|
|
15
|
+
include(Enumerable)
|
|
16
|
+
|
|
17
|
+
Template = Struct.new(:name, :short, :content_blocks, keyword_init: true) do
|
|
18
|
+
def steps
|
|
19
|
+
content_blocks.size
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# -t is yiffspace:fixer's own --template flag; -f/-p/-q/-s are Rails::Generators::Base's
|
|
24
|
+
# built-in runtime options (--force/--pretend/--quiet/--skip).
|
|
25
|
+
RESERVED_SHORTS = %w[t f p q s].freeze
|
|
26
|
+
|
|
27
|
+
def initialize
|
|
28
|
+
@templates = {}
|
|
29
|
+
@discovered = false
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# short is optional - a template is always reachable via `--template <name>`, a shortname
|
|
33
|
+
# flag (e.g. `-e`) is just a convenient alias for it.
|
|
34
|
+
def register(name, steps:, short: nil)
|
|
35
|
+
raise(ArgumentError, "steps must be a positive integer, got #{steps.inspect}") unless steps.is_a?(Integer) && steps.positive?
|
|
36
|
+
|
|
37
|
+
add(name.to_s, short: short, content_blocks: Array.new(steps))
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def [](name)
|
|
41
|
+
discover!
|
|
42
|
+
@templates[name.to_s]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def each(&)
|
|
46
|
+
discover!
|
|
47
|
+
@templates.each_value(&)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
def add(name, short:, content_blocks:)
|
|
53
|
+
raise(ArgumentError, "a fixer template named #{name.inspect} is already registered") if @templates.key?(name)
|
|
54
|
+
|
|
55
|
+
short = short.to_s.delete_prefix("-") if short
|
|
56
|
+
if short
|
|
57
|
+
raise(ArgumentError, "\"-#{short}\" is reserved by yiffspace:fixer's own options") if RESERVED_SHORTS.include?(short)
|
|
58
|
+
|
|
59
|
+
existing = @templates.values.find { |template| template.short == short }
|
|
60
|
+
raise(ArgumentError, "\"-#{short}\" is already registered to the #{existing.name.inspect} fixer template") if existing
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
@templates[name] = Template.new(name: name, short: short, content_blocks: content_blocks)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Scans YiffSpace.config.fixer_templates_path for YiffSpace::FixerTemplate subclasses and
|
|
67
|
+
# registers each one - runs once, the first time a template is looked up (#[] or #each),
|
|
68
|
+
# so it sees whatever the host app registered manually beforehand.
|
|
69
|
+
def discover!
|
|
70
|
+
return if @discovered
|
|
71
|
+
|
|
72
|
+
@discovered = true
|
|
73
|
+
path = YiffSpace.config.fixer_templates_path
|
|
74
|
+
return unless path && Dir.exist?(path)
|
|
75
|
+
|
|
76
|
+
before = FixerTemplate.subclasses
|
|
77
|
+
Dir[File.join(path, "*.rb")].each { |file| require(file) }
|
|
78
|
+
|
|
79
|
+
(FixerTemplate.subclasses - before).each do |klass|
|
|
80
|
+
raise(ArgumentError, "#{klass.name} has no step blocks - add at least one `step { ... }`") if klass.steps.empty?
|
|
81
|
+
|
|
82
|
+
add(klass.template_name, short: klass.short, content_blocks: klass.steps)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -112,5 +112,19 @@ module YiffSpace
|
|
|
112
112
|
def images
|
|
113
113
|
@images ||= Images.new
|
|
114
114
|
end
|
|
115
|
+
|
|
116
|
+
# Named `--steps` presets for `bin/rails generate yiffspace:fixer`. See
|
|
117
|
+
# YiffSpace::Configuration::FixerTemplates.
|
|
118
|
+
def fixer_templates
|
|
119
|
+
@fixer_templates ||= FixerTemplates.new
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Directory scanned for YiffSpace::FixerTemplate subclasses (see FixerTemplate) - defaults to
|
|
123
|
+
# db/fixer_templates in the host app.
|
|
124
|
+
attr_writer(:fixer_templates_path)
|
|
125
|
+
|
|
126
|
+
def fixer_templates_path
|
|
127
|
+
@fixer_templates_path ||= Rails.root.join("db/fixer_templates")
|
|
128
|
+
end
|
|
115
129
|
end
|
|
116
130
|
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module YiffSpace
|
|
4
|
+
# Tracks which db/fixes/*.rb scripts a host app has applied, in a `fixes` table (see
|
|
5
|
+
# db/migrate/*_create_fixes.rb) the same way ActiveRecord's own schema_migrations table
|
|
6
|
+
# tracks migrations. Used by the `fixes:*` rake tasks (lib/tasks/fixes.rake).
|
|
7
|
+
module FixTracker
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
def fix_path(name)
|
|
11
|
+
Rails.root.glob("db/fixes/#{name}.rb").first
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# db/fixes filenames are "<id>_description.rb", occasionally "<id>_<index>_description.rb"
|
|
15
|
+
# (e.g. 1_1_..., 1_2_...) for a handful of fixes split into ordered steps - the `fixes`
|
|
16
|
+
# table mirrors this as an (id, index) pair, with index null for fixes with no subtype.
|
|
17
|
+
def key_for(name)
|
|
18
|
+
id, maybe_index = name.split("_", 3)
|
|
19
|
+
index = maybe_index =~ /\A\d+\z/ ? maybe_index.to_i : nil
|
|
20
|
+
[id.to_i, index]
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Sort on the (id, index) parts, not the filename string, so 12 doesn't sort before 2_2.
|
|
24
|
+
def sort_key(name)
|
|
25
|
+
id, index = key_for(name)
|
|
26
|
+
[id, index || 0]
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Only numbered one-time fixes are tracked/auto-applied - a host app's db/fixes may also hold
|
|
30
|
+
# reusable on-demand maintenance scripts that aren't meant to run automatically as part of
|
|
31
|
+
# `fixes:migrate`. Those stay reachable via `fixes:run[name]`.
|
|
32
|
+
def all_fixes
|
|
33
|
+
Rails.root.glob("db/fixes/*.rb").map { |path| File.basename(path, ".rb") }.grep(/\A\d/).sort_by { |name| sort_key(name) }
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def applied
|
|
37
|
+
ActiveRecord::Base.connection.select_rows('SELECT id, "index" FROM fixes').to_set
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def applied?(name)
|
|
41
|
+
id, index = key_for(name)
|
|
42
|
+
conn = ActiveRecord::Base.connection
|
|
43
|
+
index_clause = index.nil? ? '"index" IS NULL' : %("index" = #{index})
|
|
44
|
+
conn.select_value("SELECT 1 FROM fixes WHERE id = #{id} AND #{index_clause}").present?
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def pending
|
|
48
|
+
applied_keys = applied
|
|
49
|
+
all_fixes.reject { |name| applied_keys.include?(key_for(name)) }
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def record!(name)
|
|
53
|
+
id, index = key_for(name)
|
|
54
|
+
conn = ActiveRecord::Base.connection
|
|
55
|
+
conn.execute(<<~SQL.squish)
|
|
56
|
+
INSERT INTO fixes (id, "index") VALUES (#{id}, #{index || 'NULL'})
|
|
57
|
+
ON CONFLICT (id, "index") DO NOTHING
|
|
58
|
+
SQL
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def run!(path)
|
|
62
|
+
name = File.basename(path, ".rb")
|
|
63
|
+
load(File.expand_path(path))
|
|
64
|
+
record!(name)
|
|
65
|
+
name
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Mirrors how ActiveRecord appends `INSERT INTO "schema_migrations"` to structure.sql after
|
|
69
|
+
# a schema dump (see Connection#dump_schema_versions) - appends the currently-applied fixes
|
|
70
|
+
# the same way, so loading structure.sql into a fresh database marks them applied too instead
|
|
71
|
+
# of leaving `fixes:migrate` to re-run every historical fix. Hooked into `db:schema:dump` in
|
|
72
|
+
# lib/tasks/fixes.rake, so it runs on every schema dump, not just `fixes:migrate`.
|
|
73
|
+
def dump_structure_sql!
|
|
74
|
+
db_config = ActiveRecord::Base.connection_db_config
|
|
75
|
+
return unless db_config.schema_format == :sql
|
|
76
|
+
|
|
77
|
+
filename = ActiveRecord::Tasks::DatabaseTasks.schema_dump_path(db_config)
|
|
78
|
+
return unless filename && File.exist?(filename)
|
|
79
|
+
|
|
80
|
+
conn = ActiveRecord::Base.connection
|
|
81
|
+
return unless conn.table_exists?(:fixes)
|
|
82
|
+
|
|
83
|
+
rows = conn.select_rows('SELECT id, "index" FROM fixes ORDER BY id DESC, "index" DESC NULLS LAST')
|
|
84
|
+
return if rows.empty?
|
|
85
|
+
|
|
86
|
+
values = rows.map { |id, index| "(#{id}, #{index || 'NULL'})" }.join(",\n")
|
|
87
|
+
File.open(filename, "a") do |f|
|
|
88
|
+
f.puts(<<~TEXT)
|
|
89
|
+
INSERT INTO "fixes" (id, "index") VALUES
|
|
90
|
+
#{values};
|
|
91
|
+
TEXT
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module YiffSpace
|
|
4
|
+
# Base class for a `bin/rails generate yiffspace:fixer` template, auto-discovered from *.rb
|
|
5
|
+
# files in YiffSpace.config.fixer_templates_path (default db/fixer_templates) - no explicit
|
|
6
|
+
# registration needed, just define a subclass:
|
|
7
|
+
#
|
|
8
|
+
# class ElasticsearchTemplate < YiffSpace::FixerTemplate
|
|
9
|
+
# short("e")
|
|
10
|
+
#
|
|
11
|
+
# step { "..." }
|
|
12
|
+
# step { "..." }
|
|
13
|
+
# end
|
|
14
|
+
#
|
|
15
|
+
# The template's name comes from the class name with a trailing "Template" dropped and
|
|
16
|
+
# kebab-cased (ElasticsearchTemplate -> "elasticsearch", OpenSearchTemplate -> "open-search"),
|
|
17
|
+
# reachable as `--template <name>` (see Yiffspace::FixerGenerator). Its step count is the
|
|
18
|
+
# number of `step` blocks - each one's return value becomes that step's fix file content,
|
|
19
|
+
# instead of the generic fixer.rb template a plain `--steps N` copies for every step.
|
|
20
|
+
class FixerTemplate
|
|
21
|
+
class << self
|
|
22
|
+
# Optional - a template is always reachable via `--template <name>`, a registered
|
|
23
|
+
# shortname flag (e.g. `-e`) is just a convenient alias for it.
|
|
24
|
+
def short(value = nil)
|
|
25
|
+
@short = value.to_s.delete_prefix("-") unless value.nil?
|
|
26
|
+
@short
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def step(&block)
|
|
30
|
+
raise(ArgumentError, "#{name}.step requires a block") unless block
|
|
31
|
+
|
|
32
|
+
steps << block
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def steps
|
|
36
|
+
@steps ||= []
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def template_name
|
|
40
|
+
name.demodulize.delete_suffix("Template").underscore.dasherize
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
data/lib/yiffspace/version.rb
CHANGED
data/lib/yiffspace.rb
CHANGED
|
@@ -21,6 +21,7 @@ loader.inflector.inflect({ "postgresql" => "PostgreSQL", "yiffspace" => "YiffSpa
|
|
|
21
21
|
loader.ignore("#{__dir__}/yiffspace/core_ext")
|
|
22
22
|
loader.ignore("#{__dir__}/yiffspace/include")
|
|
23
23
|
loader.ignore("#{__dir__}/yiffspace/engine.rb")
|
|
24
|
+
loader.ignore("#{__dir__}/generators")
|
|
24
25
|
loader.setup
|
|
25
26
|
|
|
26
27
|
# Require the engine eagerly so it registers with Rails before the host app's
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: yiffspace
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.8
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Donovan_DMC
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-08-
|
|
10
|
+
date: 2026-08-22 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: abbrev
|
|
@@ -97,6 +97,12 @@ files:
|
|
|
97
97
|
- app/models/yiff_space/application_record.rb
|
|
98
98
|
- app/views/layouts/yiff_space/application.html.erb
|
|
99
99
|
- app/views/yiff_space/error.html.erb
|
|
100
|
+
- lib/generators/yiffspace/fixer/USAGE
|
|
101
|
+
- lib/generators/yiffspace/fixer/fixer_generator.rb
|
|
102
|
+
- lib/generators/yiffspace/fixer/templates/fixer.rb
|
|
103
|
+
- lib/generators/yiffspace/install/fixes_generator.rb
|
|
104
|
+
- lib/generators/yiffspace/install/templates/create_fixes.rb.erb
|
|
105
|
+
- lib/tasks/fixes.rake
|
|
100
106
|
- lib/yiffspace.rb
|
|
101
107
|
- lib/yiffspace/concerns/active_record_extensions.rb
|
|
102
108
|
- lib/yiffspace/concerns/api_methods.rb
|
|
@@ -112,6 +118,7 @@ files:
|
|
|
112
118
|
- lib/yiffspace/concerns/user_resolvable_methods.rb
|
|
113
119
|
- lib/yiffspace/config_builder.rb
|
|
114
120
|
- lib/yiffspace/configuration.rb
|
|
121
|
+
- lib/yiffspace/configuration/fixer_templates.rb
|
|
115
122
|
- lib/yiffspace/configuration/images.rb
|
|
116
123
|
- lib/yiffspace/core_ext/active_record/all.rb
|
|
117
124
|
- lib/yiffspace/core_ext/active_record/cross_join_lateral.rb
|
|
@@ -151,6 +158,8 @@ files:
|
|
|
151
158
|
- lib/yiffspace/extensions/object/to_b.rb
|
|
152
159
|
- lib/yiffspace/extensions/object/truthy_falsy.rb
|
|
153
160
|
- lib/yiffspace/extensions/string/sql.rb
|
|
161
|
+
- lib/yiffspace/fix_tracker.rb
|
|
162
|
+
- lib/yiffspace/fixer_template.rb
|
|
154
163
|
- lib/yiffspace/images.rb
|
|
155
164
|
- lib/yiffspace/images/avatar.rb
|
|
156
165
|
- lib/yiffspace/images/avatar/base.rb
|