Module: Seams::Generators::HostInjector

Overview

Idempotent host-file edits used by every canonical generator. Mix into a Rails::Generators::Base subclass; the methods below delegate to Thor’s inject_into_file / append_to_file primitives but skip the edit if the host already contains the snippet.

Every method is safe to call when the target file is missing —it prints a yellow ‘skip` line so the user knows to do the edit themselves later.

Instance Method Summary collapse

Instance Method Details

#host_inject_gem(name, *args, group: nil) ⇒ Object



16
17
18
19
20
21
22
23
24
25
# File 'lib/seams/generators/host_injector.rb', line 16

def host_inject_gem(name, *args, group: nil)
  gemfile = host_path("Gemfile")
  return host_skip("Gemfile not found — add `gem #{name.inspect}` yourself") unless File.exist?(gemfile)

  return if File.read(gemfile).match?(/^\s*gem\s+["']#{Regexp.escape(name)}["']/)

  line = build_gem_line(name, args, group)
  say "  inject  Gemfile (gem \"#{name}\")", :green
  append_to_file(gemfile, "\n#{line}\n")
end

#host_inject_include_in_application_controller(concern_name) ⇒ Object



56
57
58
59
60
61
# File 'lib/seams/generators/host_injector.rb', line 56

def host_inject_include_in_application_controller(concern_name)
  host_inject_include(
    "app/controllers/application_controller.rb", "ApplicationController",
    concern_name, label: "ApplicationController"
  )
end

#host_inject_include_in_user(concern_name) ⇒ Object



52
53
54
# File 'lib/seams/generators/host_injector.rb', line 52

def host_inject_include_in_user(concern_name)
  host_inject_include("app/models/user.rb", "User", concern_name, label: "User model")
end

#host_inject_mount(engine_class:, at:) ⇒ Object



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/seams/generators/host_injector.rb', line 27

def host_inject_mount(engine_class:, at:)
  routes = host_path("config/routes.rb")
  unless File.exist?(routes)
    return host_skip("config/routes.rb not found — add `mount #{engine_class}, at: \"#{at}\"` yourself")
  end

  # Word-boundary match on the engine class name so a sibling
  # `mount Auth::EngineExtras` doesn't trick us into thinking
  # `Auth::Engine` is already mounted. The boundary is "anything
  # that isn't a constant-name character" (`[\w:]` excluded).
  return if File.read(routes).match?(/\bmount\s+#{Regexp.escape(engine_class)}(?![\w:])/)

  say "  inject  config/routes.rb (mount #{engine_class})", :green
  inject_into_file(routes, after: routes_draw_anchor) do
    "  mount #{engine_class}, at: \"#{at}\"\n"
  end
end

#host_uninject_gem(name) ⇒ Object

Reverse of host_inject_gem — used by the remove generator. Removes the ‘gem “<name>”` line if present.



65
66
67
68
69
70
71
72
# File 'lib/seams/generators/host_injector.rb', line 65

def host_uninject_gem(name)
  gemfile = host_path("Gemfile")
  return unless File.exist?(gemfile)

  new_content = File.read(gemfile).gsub(/^\s*gem\s+["']#{Regexp.escape(name)}["'][^\n]*\n/, "")
  File.write(gemfile, new_content)
  say "  remove  Gemfile (gem \"#{name}\")", :red
end

#host_uninject_include(file_relative, concern_name) ⇒ Object



87
88
89
90
91
92
93
94
# File 'lib/seams/generators/host_injector.rb', line 87

def host_uninject_include(file_relative, concern_name)
  full = host_path(file_relative)
  return unless File.exist?(full)

  new_content = File.read(full).gsub(/^\s*include\s+#{Regexp.escape(concern_name)}\s*\n/, "")
  File.write(full, new_content)
  say "  remove  #{file_relative} (include #{concern_name})", :red
end

#host_uninject_mount(engine_class:) ⇒ Object



74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/seams/generators/host_injector.rb', line 74

def host_uninject_mount(engine_class:)
  routes = host_path("config/routes.rb")
  return unless File.exist?(routes)

  # Word-boundary match — see host_inject_mount. Without it, an
  # unrelated `mount Auth::EngineExtras` would match a remove of
  # `mount Auth::Engine` and silently delete the wrong line.
  pattern     = /^\s*mount\s+#{Regexp.escape(engine_class)}(?![\w:])[^\n]*\n/
  new_content = File.read(routes).gsub(pattern, "")
  File.write(routes, new_content)
  say "  remove  config/routes.rb (mount #{engine_class})", :red
end

#routes_draw_anchorObject

Matches the common ‘Rails.application.routes.draw do` forms: plain `do`, `do |routes|` block-arg, `do # comment`, and the rare `Rails::Application.routes.draw`.



48
49
50
# File 'lib/seams/generators/host_injector.rb', line 48

def routes_draw_anchor
  /Rails(?:\.application|::Application)\.routes\.draw\s+do(?:\s*\|[^|]+\|)?[^\n]*\n/
end