Class: RubyCms::Generators::InstallGenerator

Inherits:
Rails::Generators::Base
  • Object
show all
Defined in:
lib/generators/ruby_cms/install_generator.rb

Constant Summary collapse

NEXT_STEPS_MESSAGE =
<<~TEXT

  ✓ RubyCMS install complete.

  Next steps (if not already done):
  - rails db:migrate
  - rails ruby_cms:seed_permissions (includes manage_visitor_errors and manage_analytics)
  - rails ruby_cms:setup_admin (or: rails ruby_cms:grant_manage_admin email=you@example.com)
  - To seed content blocks from YAML: add content under content_blocks in config/locales/<locale>.yml, then run rails ruby_cms:content_blocks:seed (or call it from db/seeds.rb).

  Notes:
  - If the host uses /admin already, remove or change those routes.
  - Avoid root to: redirect("/admin") — use a real root or ruby_cms.unauthorized_redirect_path.
  - Review config/initializers/ruby_cms.rb (session, CSP).
  - RubyCMS admin styles are compiled once on install to app/assets/stylesheets/ruby_cms/admin.css.
  - Visit /admin (sign in as the admin you configured).

  Tracking:
  - Visitor errors: Automatically captured via ApplicationController (see /admin/visitor_errors)
  - Page views (Ahoy): Include RubyCms::PageTracking in your public controllers to track page views
    Example: class PagesController < ApplicationController; include RubyCms::PageTracking; end
  - Conversions: Call ahoy.track RubyCms::Analytics::Report::EVENT_CONVERSION, goal: "contact_form"
    from any controller action (e.g. after a successful form submit). Goal names are free-form strings.
  - Bot filtering: Set config.ruby_cms.analytics_visit_scope to exclude IPs/bots (see config/initializers/ruby_cms.rb)
  - Analytics: View visit/event data in Ahoy tables (ahoy_visits, ahoy_events)
TEXT
SKIP_VIEW_DIRS =

Directories to skip when scanning for page templates

%w[layouts shared mailers components admin].freeze

Instance Method Summary collapse

Instance Method Details

#add_catch_all_routeObject



99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/generators/ruby_cms/install_generator.rb', line 99

def add_catch_all_route
  routes_path = Rails.root.join("config/routes.rb")
  return unless routes_path.exist?

  content = File.read(routes_path)
  return if content.include?("ruby_cms/errors#not_found")

  # Add catch-all route at the end of the routes block (before final 'end')
  catch_all = <<~ROUTE

    # RubyCMS: Catch-all route for 404 error tracking (must be LAST)
    match "*path", to: "ruby_cms/errors#not_found", via: :all,
          constraints: ->(req) { !req.path.start_with?("/rails/", "/assets/") }
  ROUTE

  # Insert before the last 'end' in the file
  gsub_file routes_path, /(\nend)\s*\z/ do
    "#{catch_all}end\n"
  end
  say "✓ Catch-all route: Added for 404 error tracking", :green
rescue StandardError => e
  say "⚠ Catch-all route: Could not add automatically: #{e.message}. " \
      "Add manually at the END of routes.rb:\n  " \
      'match "*path", to: "ruby_cms/errors#not_found", via: :all, ' \
      'constraints: ->(req) { !req.path.start_with?("/rails/", "/assets/") }',
      :yellow
end

#add_current_user_to_authenticationObject



141
142
143
144
145
146
147
148
149
150
151
# File 'lib/generators/ruby_cms/install_generator.rb', line 141

def add_current_user_to_authentication
  auth_path = Rails.root.join("app/controllers/concerns/authentication.rb")
  return unless auth_path.exist?
  return if File.read(auth_path).include?("def current_user")

  gsub_file auth_path, "    helper_method :authenticated?\n",
            "    helper_method :authenticated?, :current_user\n"
  inject_into_file auth_path, after: "  private\n" do
    "    def current_user\n      Current.user\n    end\n\n"
  end
end

#add_page_tracking_to_home_controllerObject



174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
# File 'lib/generators/ruby_cms/install_generator.rb', line 174

def add_page_tracking_to_home_controller
  home_path = Rails.root.join("app/controllers/home_controller.rb")
  return unless home_path.exist?

  content = File.read(home_path)
  return if content.include?("RubyCms::PageTracking")

  inject_into_file home_path, after: /class HomeController.*\n/ do
    "  include RubyCms::PageTracking\n"
  end

  say "✓ Page tracking: Added RubyCms::PageTracking to HomeController", :green
rescue StandardError => e
  say "⚠ Page tracking: Could not add to HomeController: #{e.message}. " \
      "Add manually: include RubyCms::PageTracking",
      :yellow
end

#add_permittable_to_userObject



127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/generators/ruby_cms/install_generator.rb', line 127

def add_permittable_to_user
  user_path = Rails.root.join("app/models/user.rb")
  unless File.exist?(user_path)
    say "Skipping User: app/models/user.rb not found.", :yellow
    return
  end

  return if File.read(user_path).include?("RubyCms::Permittable")

  inject_into_file user_path, after: /class User .*\n/ do
    "  include RubyCms::Permittable\n"
  end
end

#add_visitor_error_captureObject



153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
# File 'lib/generators/ruby_cms/install_generator.rb', line 153

def add_visitor_error_capture
  ac_path = Rails.root.join("app/controllers/application_controller.rb")
  return unless ac_path.exist?

  content = File.read(ac_path)
  return if content.include?("RubyCms::VisitorErrorCapture")

  to_inject = "  include RubyCms::VisitorErrorCapture\n"
  to_inject += "  rescue_from StandardError, with: :handle_visitor_error\n" \
    unless content.include?("rescue_from StandardError")

  inject_into_file ac_path, after: /class ApplicationController.*\n/ do
    to_inject
  end
  say "✓ Visitor error capture: Added to ApplicationController", :green
rescue StandardError => e
  say "⚠ Visitor error capture: Could not add to ApplicationController: #{e.message}. " \
      "Add manually: include RubyCms::VisitorErrorCapture and rescue_from StandardError, with: :handle_visitor_error",
      :yellow
end

#copy_fallback_cssObject



192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
# File 'lib/generators/ruby_cms/install_generator.rb', line 192

def copy_fallback_css
  src_dir = RubyCms::Engine.root.join("app/assets/stylesheets/ruby_cms")
  dest_dir = Rails.root.join("app/assets/stylesheets/ruby_cms")

  return unless src_dir.exist?

  FileUtils.mkdir_p(dest_dir)
  copy_admin_css(dest_dir)
  # Don't copy component files - only the compiled admin.css is needed
  # copy_components_css(src_dir, dest_dir)
  say "✓ Task css/copy: Combined component CSS into " \
      "app/assets/stylesheets/ruby_cms/admin.css", :green
rescue StandardError => e
  say "⚠ Task css/copy: Could not copy CSS files: #{e.message}.", :yellow
end

#create_admin_layoutObject



208
209
210
211
212
213
214
215
216
217
# File 'lib/generators/ruby_cms/install_generator.rb', line 208

def create_admin_layout
  layout_path = Rails.root.join("app/views/layouts/admin.html.erb")
  return if File.exist?(layout_path)

  template "admin.html.erb", layout_path.to_s
  say "✓ Layout admin: Created app/views/layouts/admin.html.erb", :green
rescue StandardError => e
  say "⚠ Layout admin: Could not create admin.html.erb: #{e.message}. " \
      "Create it manually using the RubyCMS template.", :yellow
end

#create_initializerObject



90
91
92
93
# File 'lib/generators/ruby_cms/install_generator.rb', line 90

def create_initializer
  @detected_pages = detect_page_templates
  template "ruby_cms.rb", "config/initializers/ruby_cms.rb"
end

#install_action_textObject



274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
# File 'lib/generators/ruby_cms/install_generator.rb', line 274

def install_action_text
  migrate_dir = Rails.root.join("db/migrate")
  return unless migrate_dir.directory?

  if action_text_already_installed?(migrate_dir)
    say "ℹ Task action_text: Existing Action Text setup detected. Skipping action_text:install.",
        :cyan
  else
    say "ℹ Task action_text: Installing Action Text for rich text/image content blocks.", :cyan
    run "bin/rails action_text:install"
    say "✓ Task action_text: Installed Action Text", :green
  end

  configure_action_text_assets
rescue StandardError => e
  say "⚠ Task action_text: Could not install: #{e.message}. Rich text will be disabled.",
      :yellow
end

#install_ahoyObject



255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
# File 'lib/generators/ruby_cms/install_generator.rb', line 255

def install_ahoy
  if ahoy_already_installed?
    say "ℹ Task ahoy: Existing Ahoy setup detected (tables or migrations). Skipping ahoy:install.",
        :cyan
    configure_ahoy_server_side_only
    return
  end

  say "ℹ Task ahoy: Installing Ahoy for visit/event tracking.", :cyan
  run "bin/rails generate ahoy:install"
  add_ahoy_security_fields_migration
  configure_ahoy_server_side_only
  say "✓ Task ahoy: Installed Ahoy (visits, events, tracking)", :green
rescue StandardError => e
  say "⚠ Task ahoy: Could not install: #{e.message}. " \
      "Run 'rails g ahoy:install' manually.",
      :yellow
end

#install_ruby_uiObject



503
504
505
506
507
508
509
510
511
512
513
514
# File 'lib/generators/ruby_cms/install_generator.rb', line 503

def install_ruby_ui
  gemfile = Rails.root.join("Gemfile")
  gemfile_content = File.read(gemfile)
  return if ruby_ui_in_gemfile?(gemfile_content)

  add_ruby_ui_gem
rescue StandardError => e
  say "⚠ Task ruby_ui: Could not add: #{e.message}. " \
      "Run 'bundle add ruby_ui --group development --require false' manually.",
      :yellow
  nil
end

#install_tailwindObject



460
461
462
463
464
465
466
467
468
469
# File 'lib/generators/ruby_cms/install_generator.rb', line 460

def install_tailwind
  gemfile = Rails.root.join("Gemfile")
  tailwind_css = detect_tailwind_entry_css_path

  install_tailwind_if_needed(gemfile, tailwind_css)
  configure_tailwind(tailwind_css)
rescue StandardError => e
  say "⚠ Task tailwind: Could not install: #{e.message}. Add tailwindcss-rails manually.",
      :yellow
end

#mount_engineObject



95
96
97
# File 'lib/generators/ruby_cms/install_generator.rb', line 95

def mount_engine
  route 'mount RubyCms::Engine => "/"'
end

#run_authenticationObject



37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/generators/ruby_cms/install_generator.rb', line 37

def run_authentication
  user_path = Rails.root.join("app/models/user.rb")
  return if File.exist?(user_path)

  say "ℹ Task authentication: User model not found. " \
      "Running 'rails g authentication' (Rails 8+).", :cyan
  @authentication_attempted = true
  run "bin/rails generate authentication"
  run "bundle install"
rescue StandardError => e
  say "⚠ Could not run 'rails g authentication': #{e.message}.", :yellow
  say "   On Rails 8+, run 'rails g authentication' and 'bundle install' manually.", :yellow
end

#run_migrateObject



1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
# File 'lib/generators/ruby_cms/install_generator.rb', line 1004

def run_migrate
  say "ℹ Task db:migrate: Running db:migrate.", :cyan
  success = run("bin/rails db:migrate")
  raise "db:migrate failed" unless success

  say "✓ Task db:migrate: Completed", :green
rescue StandardError => e
  say "⚠ Task db:migrate: Failed: #{e.message}. Run rails db:create db:migrate if needed.",
      :yellow
end

#run_ruby_ui_installObject



516
517
518
519
520
521
522
523
# File 'lib/generators/ruby_cms/install_generator.rb', line 516

def run_ruby_ui_install
  gemfile = Rails.root.join("Gemfile")
  gemfile_content = File.read(gemfile)
  return unless ruby_ui_in_gemfile?(gemfile_content)
  return if ruby_ui_already_installed?

  install_ruby_ui_generator
end

#run_seed_permissionsObject



1015
1016
1017
1018
1019
1020
1021
# File 'lib/generators/ruby_cms/install_generator.rb', line 1015

def run_seed_permissions
  say "ℹ Task permissions: Seeding RubyCMS permissions.", :cyan
  success = seed_permissions_via_open3
  say_seed_permissions_outcome(success)
rescue StandardError => e
  say_seed_permissions_error(e)
end

#run_setup_adminObject



1023
1024
1025
1026
1027
1028
1029
1030
# File 'lib/generators/ruby_cms/install_generator.rb', line 1023

def run_setup_admin
  return if skip_setup_admin_due_to_existing_admin?
  return unless setup_admin_tty?

  run_setup_admin_task
rescue StandardError => e
  say_setup_admin_error(e)
end

#show_next_stepsObject



1154
1155
1156
# File 'lib/generators/ruby_cms/install_generator.rb', line 1154

def show_next_steps
  say NEXT_STEPS_MESSAGE, :green
end

#verify_application_controllerObject



76
77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/generators/ruby_cms/install_generator.rb', line 76

def verify_application_controller
  ac_path = Rails.root.join("app/controllers/application_controller.rb")
  return unless ac_path.exist?

  content = File.read(ac_path)
  return if content.include?("include Authentication")
  return if @authentication_warning_shown

  say "ℹ Task authentication: ApplicationController does not include " \
      "Authentication. Ensure /admin is protected.",
      :yellow
  @authentication_warning_shown = true
end

#verify_authObject



51
52
53
54
55
# File 'lib/generators/ruby_cms/install_generator.rb', line 51

def verify_auth
  verify_user_model
  verify_session_model
  verify_application_controller
end

#verify_session_modelObject



68
69
70
71
72
73
74
# File 'lib/generators/ruby_cms/install_generator.rb', line 68

def verify_session_model
  return if defined?(::Session)

  say "ℹ Task authentication: Session model not found. " \
      "The host app should provide authentication.",
      :yellow
end

#verify_user_modelObject



57
58
59
60
61
62
63
64
65
66
# File 'lib/generators/ruby_cms/install_generator.rb', line 57

def verify_user_model
  return if defined?(::User)

  message = if @authentication_attempted
              "Run 'rails db:migrate' if the authentication generator succeeded."
            else
              "User model not found. Run 'rails g authentication' before using /admin."
            end
  say "ℹ Task authentication: #{message}", :yellow
end