Class: Seams::Generators::RemoveGenerator
- Inherits:
-
Rails::Generators::NamedBase
- Object
- Rails::Generators::NamedBase
- Seams::Generators::RemoveGenerator
- Includes:
- HostInjector
- Defined in:
- lib/generators/seams/remove/remove_generator.rb
Overview
Removes an engine generated by ‘seams:engine`. Prompts for confirmation unless –force is passed. Cleans up the surviving engines’ .rubocop.yml so OtherEngines no longer references the engine that was just removed. Reverses host file edits made by the canonical generators (mount line, includes) — leaves the Gemfile alone since other engines may share gem deps.
Run with: bin/rails generate seams:remove billing [–force]
Constant Summary collapse
- NAME_PATTERN =
Same constraints as EngineGenerator — keeps ‘seams:remove ../../etc` from being interpreted as a relative path the destructive FileUtils.rm_rf at line 80 would happily follow with –force.
/\A[a-z][a-z0-9_]*\z/- CANONICAL_HOST_EDITS =
Maps engine name -> { mount: <Class>, includes: { user: […], application_controller: […] } } Lets remove know what to undo for the canonical engines. Generic engines aren’t in this table and just get the directory deleted.
{ "auth" => { mount: "Auth::Engine", user_includes: %w[Auth::Authenticatable], application_controller_includes: %w[Auth::Authentication] }, "notifications" => { mount: "Notifications::Engine", user_includes: %w[Notifications::Notifiable] }, # Post-Wave-9: billing no longer injects Billing::Billable # into the host User — the engine includes it into the # configured tenant class (default Accounts::Account) at # boot via Billing.configuration.billable_class. The only # host-side edit billing makes is the mount line, so unmount # is the only reverse-edit we need. "billing" => { mount: "Billing::Engine", user_includes: %w[] }, "teams" => { # Wave 9 removed Teams::Teamable — there's no canonical host # User concern to remove. The `mount Teams::Engine` line is # the only host edit the teams generator makes, so unmount # is the only reverse-edit we need here. mount: "Teams::Engine", user_includes: %w[] }, # Wave 9 — accounts engine. Like billing/teams post-Wave-9, the # accounts generator does NOT inject anything into the host User # (the canonical demo doesn't have one). Mount is the only # host-side edit, so unmount is the only reverse-edit we need. "accounts" => { mount: "Accounts::Engine", user_includes: %w[] }, # Wave 11A — admin engine. Mounts under Seams::Admin::Engine. # The generator injects `gem "administrate"` and `gem "pundit"` # into the host Gemfile but the remover does NOT prune them # (the host may have other dashboards depending on either). # Unmounting + dropping the engine dir is enough. "admin" => { mount: "Seams::Admin::Engine", user_includes: %w[] } }.freeze
Instance Method Summary collapse
-
#capture_engine_tables ⇒ Object
Read the engine’s create_table calls before its directory is deleted.
- #remove_engine_directory ⇒ Object
-
#run_bundle_install ⇒ Object
Phase 1.7 — re-run ‘bundle install` so the host’s lockfile no longer references the removed engine’s gem deps (the canonical generators each inject their own — bcrypt, faraday, etc; the remover doesn’t touch the Gemfile because deps are usually shared, but a fresh ‘bundle install` keeps lockfile + Gemfile in sync if the host did prune anything by hand).
- #unwire_from_host ⇒ Object
- #update_sibling_engines ⇒ Object
- #validate_name ⇒ Object
-
#write_drop_table_migration ⇒ Object
Phase 1.7 — generate a drop-table migration in the host’s db/migrate so the host can run ‘bin/rails db:migrate` to reclaim the engine’s tables.
Methods included from HostInjector
#host_inject_gem, #host_inject_include_in_application_controller, #host_inject_include_in_user, #host_inject_mount, #host_uninject_gem, #host_uninject_include, #host_uninject_mount, #routes_draw_anchor
Instance Method Details
#capture_engine_tables ⇒ Object
Read the engine’s create_table calls before its directory is deleted. Used by write_drop_table_migration to generate the reversal. Pattern-matches the literal ActiveRecord::Migration call so we drop exactly what the engine created — never host-side tables that happen to share the prefix.
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 |
# File 'lib/generators/seams/remove/remove_generator.rb', line 93 def capture_engine_tables engine_path = File.join(destination_root, "engines", name) return @engine_tables = [] unless File.directory?(engine_path) migrate_dir = File.join(engine_path, "db/migrate") return @engine_tables = [] unless File.directory?(migrate_dir) @engine_tables = Dir.glob(File.join(migrate_dir, "*.rb")).flat_map do |file| # Strip line comments before scanning so a stray # `# create_table :backup_table do |t|` in a migration # doesn't end up in the drop-table list. source = File.read(file).gsub(/^\s*#.*$/, "") source.scan(/^\s*create_table\s+:(\w+)/).flatten end.uniq end |
#remove_engine_directory ⇒ Object
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 |
# File 'lib/generators/seams/remove/remove_generator.rb', line 109 def remove_engine_directory engine_path = File.join(destination_root, "engines", name) unless File.directory?(engine_path) @engine_was_present = false say " skip engines/#{name}/ (not found)", :yellow return end return unless [:force] || confirm_removal? FileUtils.rm_rf(engine_path) @engine_was_present = true say " remove engines/#{name}/", :red end |
#run_bundle_install ⇒ Object
Phase 1.7 — re-run ‘bundle install` so the host’s lockfile no longer references the removed engine’s gem deps (the canonical generators each inject their own — bcrypt, faraday, etc; the remover doesn’t touch the Gemfile because deps are usually shared, but a fresh ‘bundle install` keeps lockfile + Gemfile in sync if the host did prune anything by hand). Skipped when the engine was never present (no-op remove) or when there’s no Gemfile to bundle against.
152 153 154 155 156 157 158 159 160 |
# File 'lib/generators/seams/remove/remove_generator.rb', line 152 def run_bundle_install return unless @engine_was_present return unless File.exist?(File.join(destination_root, "Gemfile")) say " run bundle install (post-remove sync)", :green Dir.chdir(destination_root) do system("bundle", "install", "--quiet") || say(" → bundle install failed; resolve manually.", :red) end end |
#unwire_from_host ⇒ Object
179 180 181 182 183 184 |
# File 'lib/generators/seams/remove/remove_generator.rb', line 179 def unwire_from_host return unless @engine_was_present unwire_generic_host_edits unwire_canonical_host_edits(CANONICAL_HOST_EDITS[name]) end |
#update_sibling_engines ⇒ Object
162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 |
# File 'lib/generators/seams/remove/remove_generator.rb', line 162 def update_sibling_engines return unless @engine_was_present engines_root = File.join(destination_root, "engines") return unless Dir.exist?(engines_root) survivors = Dir.children(engines_root) .select { |c| File.directory?(File.join(engines_root, c)) } .reject { |c| c.start_with?(".") } .sort return if survivors.empty? Seams::Generators::SiblingRubocopWriter.rewrite!(engines_root: engines_root, dirs: survivors) say " update .rubocop.yml of #{survivors.size} sibling engine(s)", :green end |
#validate_name ⇒ Object
30 31 32 33 34 35 36 |
# File 'lib/generators/seams/remove/remove_generator.rb', line 30 def validate_name return if NAME_PATTERN.match?(name) raise Seams::GeneratorError, "Engine name #{name.inspect} must be lowercase letters, digits, " \ "and underscores, starting with a letter." end |
#write_drop_table_migration ⇒ Object
Phase 1.7 — generate a drop-table migration in the host’s db/migrate so the host can run ‘bin/rails db:migrate` to reclaim the engine’s tables. Idempotent if-table-exists check so re-running doesn’t blow up on already-dropped tables.
129 130 131 132 133 134 135 136 137 138 139 140 141 142 |
# File 'lib/generators/seams/remove/remove_generator.rb', line 129 def write_drop_table_migration return unless @engine_was_present return if @engine_tables.nil? || @engine_tables.empty? migrate_dir = File.join(destination_root, "db/migrate") FileUtils.mkdir_p(migrate_dir) filename = "#{}_drop_#{name}_tables.rb" migration_path = File.join(migrate_dir, filename) File.write(migration_path, drop_table_migration_body) say " create db/migrate/#{filename}", :green say " → run `bin/rails db:migrate` to drop #{@engine_tables.size} table(s).", :yellow end |