Class: Sessions::Generators::InstallGenerator
- Inherits:
-
Rails::Generators::Base
- Object
- Rails::Generators::Base
- Sessions::Generators::InstallGenerator
- Includes:
- ActiveRecord::Generators::Migration
- Defined in:
- lib/generators/sessions/install_generator.rb
Overview
‘rails generate sessions:install` — detects the app’s auth stack and writes the right pieces:
Rails 8 omakase auth → ONE migration extending the existing
`sessions` table (the Devise-extends-`users` precedent) + the
events table. The generated Session model stays untouched.
Devise → the Rails-8-shaped `sessions` table (plus our columns,
with token_digest populated by the Warden adapter) + the events
table + a 3-line app-owned Session shell model. The app converges
on the omakase shape: a future Devise→Rails-auth migration finds
its table already waiting.
Neither → aborts with guidance. The gem decorates a session of
record; it never creates one.
Plus, in every mode: the annotated initializer, the SessionsSweepJob (host-scheduled — the trackdown/nondisposable pattern), and the post-install steps.
Class Method Summary collapse
Instance Method Summary collapse
-
#check_devise_auth_model_fit! ⇒ Object
Default (non-polymorphic) mode assumes a ‘User` class — the same assumption `rails generate authentication` makes: `belongs_to :user` and a foreign key to `users`.
- #check_for_conflicting_sessions_table! ⇒ Object
- #create_initializer ⇒ Object
- #create_migration_files ⇒ Object
-
#create_session_model ⇒ Object
Devise mode only: the app-owned 3-line shell.
- #create_sweep_job ⇒ Object
- #detect_auth_stack! ⇒ Object
- #display_post_install_message ⇒ Object
Class Method Details
.next_migration_number(dir) ⇒ Object
38 39 40 |
# File 'lib/generators/sessions/install_generator.rb', line 38 def self.next_migration_number(dir) ActiveRecord::Generators::Base.next_migration_number(dir) end |
Instance Method Details
#check_devise_auth_model_fit! ⇒ Object
Default (non-polymorphic) mode assumes a ‘User` class — the same assumption `rails generate authentication` makes: `belongs_to :user` and a foreign key to `users`. A Devise app whose auth model is Member/Account would pass detection and then break at db:migrate (FK to a missing `users` table) or at runtime (`belongs_to :user` constantizing a class that doesn’t exist) — catch it HERE, with the fix in the error message. ‘–polymorphic` works with any model(s).
88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 |
# File 'lib/generators/sessions/install_generator.rb', line 88 def check_devise_auth_model_fit! return if polymorphic? return unless devise_detected? # An adopted omakase table already proves whatever owner shape the # host made work — nothing for us to second-guess. return if adopt_existing_table? classes = devise_auth_class_names return if classes.empty? # mappings unreadable — stay permissive return if classes == ["User"] # the default assumption holds if classes.include?("User") # User plus other scopes: default mode tracks User and SILENTLY # skips the rest (the runtime adapter's row_accepts? guard) — # surface that tradeoff at install time, but proceed. say "⚠️ Multiple Devise models detected (#{classes.join(", ")}).", :yellow say " The default install tracks only User — sessions for the other models", :yellow say " stay untracked. Re-run with --polymorphic to track them all.", :yellow return end raise Thor::Error, <<~MSG ❌ Your Devise model is #{classes.join(", ")} — not User. The default install assumes a `User` class (`belongs_to :user`, foreign key to `users`, the same assumption `rails generate authentication` makes) and would break at migrate/runtime. Re-run with the polymorphic owner, which works with any model(s): rails generate sessions:install --polymorphic …then declare `has_sessions` on #{classes.join(" and ")}. MSG end |
#check_for_conflicting_sessions_table! ⇒ Object
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
# File 'lib/generators/sessions/install_generator.rb', line 65 def check_for_conflicting_sessions_table! return unless conflicting_sessions_table? raise Thor::Error, <<~MSG ❌ A `#{table_name}` table exists but doesn't look like the Rails 8 auth shape (no user reference + ip_address + user_agent columns) — most likely a legacy table (activerecord-session_store?). Two ways forward: 1. Re-run with a different model: rails g sessions:install --model=SessionRecord (and set `config.session_class = "SessionRecord"` in the initializer) 2. Migrate/rename the legacy table first, then re-run. MSG end |
#create_initializer ⇒ Object
144 145 146 |
# File 'lib/generators/sessions/install_generator.rb', line 144 def create_initializer template "initializer.rb", "config/initializers/sessions.rb" end |
#create_migration_files ⇒ Object
123 124 125 126 127 128 129 130 131 132 133 134 |
# File 'lib/generators/sessions/install_generator.rb', line 123 def create_migration_files if adopt_existing_table? migration_template "add_sessions_columns.rb.erb", File.join(db_migrate_path, "add_sessions_columns_to_#{table_name}.rb") else migration_template "create_sessions.rb.erb", File.join(db_migrate_path, "create_#{table_name}.rb") end migration_template "create_sessions_events.rb.erb", File.join(db_migrate_path, "create_sessions_events.rb") end |
#create_session_model ⇒ Object
Devise mode only: the app-owned 3-line shell. All gem logic lives in the Sessions::Model concern, so this file never goes stale.
138 139 140 141 142 |
# File 'lib/generators/sessions/install_generator.rb', line 138 def create_session_model return if adopt_existing_table? || session_model_file? template "session.rb.erb", "app/models/#{model_name.underscore}.rb" end |
#create_sweep_job ⇒ Object
148 149 150 |
# File 'lib/generators/sessions/install_generator.rb', line 148 def create_sweep_job template "sessions_sweep_job.rb", "app/jobs/sessions_sweep_job.rb" end |
#detect_auth_stack! ⇒ Object
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
# File 'lib/generators/sessions/install_generator.rb', line 42 def detect_auth_stack! # Detection is MEMOIZED here, before anything is generated: # create_session_model writes app/models/session.rb a few steps # below, which would otherwise flip omakase_detected? mid-run and # make the post-install message claim the wrong stack. adopt_existing_table? detected_stack return if omakase_detected? || devise_detected? raise Thor::Error, <<~MSG ❌ sessions couldn't detect an authentication system to decorate. The gem tracks the session of record your app already has — it never creates one. Set one up first: • Rails 8+ omakase auth: bin/rails generate authentication • or Devise: https://github.com/heartcombo/devise …then run `rails generate sessions:install` again. MSG end |
#display_post_install_message ⇒ Object
152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 |
# File 'lib/generators/sessions/install_generator.rb', line 152 def say "\n🔐 The `sessions` gem has been installed#{" (#{detected_stack} detected)" if detected_stack}.", :green say "\nTo complete the setup:" migrate_verb = adopt_existing_table? ? "enrich your sessions table" : "create the sessions tables" say " 1. Run 'rails db:migrate' to #{migrate_verb}." say " ⚠️ You must run migrations before starting your app!", :yellow say " 2. Add the macro to your auth model:" say " class User < ApplicationRecord" say " has_sessions" say " end" say " 3. Mount the \"Your devices\" page wherever you want it to live:" say " # config/routes.rb" if devise_detected? && !omakase_detected? say " authenticate :user do" say " mount Sessions::Engine => \"/settings/sessions\"" say " end" else say " mount Sessions::Engine => \"/settings/sessions\"" end say " 4. Schedule the sweep (retention purge + cap + opt-in expiry):" say " # config/recurring.yml (Solid Queue)" say " production:" say " sessions_sweep:" say " class: SessionsSweepJob" say " schedule: every day at 4am" say "\nEvery login now lands on the devices page and in the trail:" say " current_user.sessions.active # live devices, revocable" say " current_user.session_history # the trail — logins, failures, revocations" say "\nEvery session, every device, every login — tracked. 🔐✨\n", :green end |