From 63a16470851f320d9fd790d39ff2c20a4c4b3c9b Mon Sep 17 00:00:00 2001 From: David Silva Date: Mon, 28 Sep 2026 11:01:58 +0100 Subject: [PATCH] docs: deploy databases/keys, admin setup guide, design next steps - DEPLOYING: Rails 8's cache/queue/cable databases need their own URLs (db:prepare fails on a local socket otherwise, seen in Docker); the encryption keys production needs. Kamal template lists the URLs. - New how-to: SETTING_UP_ADMIN.md; linked from the site sidebar, doc index, README, and Getting Started. Every step verified in a generated app, including a custom Project dashboard and the before_admin_action example. - Admin fix found while writing it: with only core+auth installed every admin page 500'd (navigation touched Accounts/Billing models). Missing engines' dashboards are hidden and 404. - Admin README: accurate custom-dashboard recipe; theme_css_path is documented as not applied yet (it is stored but unused). - Design generator: tailwindcss:build is marked required. --- CHANGELOG.md | 23 +++ README.md | 3 +- doc/README.md | 1 + doc/how-to/DEPLOYING.md | 63 +++++++- doc/how-to/SETTING_UP_ADMIN.md | 140 ++++++++++++++++++ doc/tutorials/GETTING_STARTED.md | 3 +- docs-site/astro.config.mjs | 1 + .../seams/admin/templates/README.md.tt | 67 ++++++--- .../admin/application_controller.rb.tt | 30 +++- .../seams/design/design_generator.rb | 8 +- .../seams/install/templates/deploy.yml.tt | 6 + spec/generators/seams/admin_generator_spec.rb | 8 + .../generators/seams/design_generator_spec.rb | 8 + .../seams/install_generator_spec.rb | 1 + 14 files changed, 333 insertions(+), 29 deletions(-) create mode 100644 doc/how-to/SETTING_UP_ADMIN.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ad5e33..19153c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,8 +7,31 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Docs: [Setting up the admin area](doc/how-to/SETTING_UP_ADMIN.md), a + step-by-step guide covering installation, the staff flag, tenant mode, + `before_admin_action`, ejecting, and adding your own dashboards. Every + step was checked in a generated app. +- Docs: the Deploying guide covers Rails 8's four production databases + (`CACHE_/QUEUE_/CABLE_DATABASE_URL`) and the Active Record encryption + keys the auth engine needs. The generated Kamal `deploy.yml` lists the + three extra database URLs. + ### Fixed +- Admin engine: an app without every canonical engine (for example only + `core` and `auth`) got a 500 on every admin page, because the + navigation loaded dashboards whose models don't exist. Those + dashboards are now hidden and their URLs return 404. +- Design generator: the next-steps message now marks + `bin/rails tailwindcss:build` as required and names the error you get + without it. +- Admin README: the "add your own dashboard" recipe now includes + `self.model`, the policies, the leading-slash route, and deleting + Administrate's generated host controller. It no longer claims + `theme_css_path` restyles the admin, since that setting is not + applied yet. - Docs: the onboarding path now works when followed literally. Both tutorials and the README quick start were re-run in fresh apps from the published gem. The 10-minute tutorial claimed SQLite works (the diff --git a/README.md b/README.md index 35b7d50..a2694bb 100644 --- a/README.md +++ b/README.md @@ -162,7 +162,7 @@ With these set: - Requests for another account's records return 404. - A `member` gets a 403. -The generated `engines/admin/README.md` has the full reference. +Step-by-step guide: [Setting up the admin area](doc/how-to/SETTING_UP_ADMIN.md). The generated `engines/admin/README.md` has the full reference. @@ -198,6 +198,7 @@ The **[documentation site](https://davidslv.uk/seams/)** has every guide below, - [Removing an engine](doc/how-to/REMOVING_AN_ENGINE.md) - [Writing an adapter](doc/how-to/WRITING_AN_ADAPTER.md): swap in Mailgun, Twilio, Paddle, and others - [Writing follow-up generators](doc/how-to/WRITING_FOLLOW_UP_GENERATORS.md) +- [Setting up the admin area](doc/how-to/SETTING_UP_ADMIN.md) - [Insertion points](doc/reference/INSERTION_POINTS.md) and the [catalogue of markers](doc/reference/INSERTION_POINTS_CATALOGUE.md) - [Deploying](doc/how-to/DEPLOYING.md) diff --git a/doc/README.md b/doc/README.md index cf37af2..cdb8ebc 100644 --- a/doc/README.md +++ b/doc/README.md @@ -23,6 +23,7 @@ You know what you want; here's how. - [REMOVING_AN_ENGINE.md](how-to/REMOVING_AN_ENGINE.md) — remove an engine cleanly. - [WRITING_AN_ADAPTER.md](how-to/WRITING_AN_ADAPTER.md) — swap in Mailgun, Twilio, Paddle, etc. - [WRITING_FOLLOW_UP_GENERATORS.md](how-to/WRITING_FOLLOW_UP_GENERATORS.md) — extend an installed engine. +- [SETTING_UP_ADMIN.md](how-to/SETTING_UP_ADMIN.md) — add the admin area, let yourself in, tenant mode, your own dashboards. - [DEPLOYING.md](how-to/DEPLOYING.md) — ship a host to production. - [UPGRADING_FROM_WAVE_8.md](how-to/UPGRADING_FROM_WAVE_8.md) — migrate a pre-Wave-9 host. diff --git a/doc/how-to/DEPLOYING.md b/doc/how-to/DEPLOYING.md index 138b780..cbd0884 100644 --- a/doc/how-to/DEPLOYING.md +++ b/doc/how-to/DEPLOYING.md @@ -34,10 +34,63 @@ When the install generator runs again (e.g. after `bin/seams billing`), it doesn't rewrite the Dockerfile — but `bundle install` inside the container picks up the new engine gemspec on the next image build. +## Databases in production (Rails 8) + +A Rails 8 app has **four** production databases in `config/database.yml`: +`primary`, plus `cache`, `queue` and `cable` for Solid Cache, Solid Queue +and Solid Cable. `DATABASE_URL` only configures `primary`. Unless you +configure the other three, `db:prepare` creates the primary database and +then fails, because it tries a local socket for the rest: + +```text +connection to server on socket "/var/run/postgresql/.s.PGSQL.5432" failed +``` + +Give each of them a URL. Rails reads `_DATABASE_URL` for a named +database. They can live on the same Postgres server as long as the +database names differ: + +```bash +DATABASE_URL=postgres://app:secret@db.example.com/app_production +CACHE_DATABASE_URL=postgres://app:secret@db.example.com/app_production_cache +QUEUE_DATABASE_URL=postgres://app:secret@db.example.com/app_production_queue +CABLE_DATABASE_URL=postgres://app:secret@db.example.com/app_production_cable +``` + +Alternatively, edit the `production:` block of `config/database.yml` to +point all four at your server. + +> Verified with a generated app running every engine against +> `postgres:18` in Docker. With all four URLs set, the container ran +> `db:prepare` and served requests. + +## Encryption keys + +The auth engine encrypts personal data (such as email addresses) with +Active Record encryption. Production needs the same three keys as +development: + +```bash +bin/rails db:encryption:init # prints an active_record_encryption: block +bin/rails credentials:edit # paste it in (or credentials:edit --environment production) +``` + +Ship the key that decrypts those credentials as `RAILS_MASTER_KEY`, or +`config/credentials/production.key` if you use per-environment +credentials. Without the encryption keys, the app boots, but the first +sign-up or sign-in raises +`Missing Active Record encryption credential: active_record_encryption.deterministic_key`. + +> [!WARNING] +> Keep these keys stable. Rotating or losing them makes existing +> encrypted rows unreadable. Follow the Rails +> [key rotation guide](https://guides.rubyonrails.org/active_record_encryption.html#rotating-keys) +> before changing them. + ## Migrations -`bin/docker-entrypoint` runs `bundle exec rails db:prepare` on every -boot. That's idempotent — Rails skips already-applied migrations — +`bin/docker-entrypoint` runs `bundle exec rails db:prepare` when the +container starts the web server. Workers skip it. That's idempotent — Rails skips already-applied migrations — but if you'd rather control migrations from a one-off task (Kamal's `pre-deploy` hook, GitHub Actions step, etc.) comment that line out. @@ -59,6 +112,9 @@ env: secret: - RAILS_MASTER_KEY - DATABASE_URL + - CACHE_DATABASE_URL + - QUEUE_DATABASE_URL + - CABLE_DATABASE_URL - REDIS_URL - STRIPE_SECRET_KEY - STRIPE_WEBHOOK_SECRET @@ -84,8 +140,9 @@ worker: bundle exec sidekiq -c 5 -q default -q billing -q notifications | Variable | Set when | | --- | --- | -| `RAILS_MASTER_KEY` | Always. | +| `RAILS_MASTER_KEY` | Always. Decrypts the credentials that hold the Active Record encryption keys. | | `DATABASE_URL` | Always (host-supplied). | +| `CACHE_DATABASE_URL`, `QUEUE_DATABASE_URL`, `CABLE_DATABASE_URL` | Rails 8 apps, unless `config/database.yml` points these databases elsewhere. See [Databases in production](#databases-in-production-rails-8). | | `REDIS_URL` | If using Sidekiq / ActionCable Redis. | | `STRIPE_SECRET_KEY` | If the Billing engine is installed. | | `STRIPE_WEBHOOK_SECRET` | Same. | diff --git a/doc/how-to/SETTING_UP_ADMIN.md b/doc/how-to/SETTING_UP_ADMIN.md new file mode 100644 index 0000000..8414427 --- /dev/null +++ b/doc/how-to/SETTING_UP_ADMIN.md @@ -0,0 +1,140 @@ +# Setting up the admin area + +This guide adds an admin area at `/admin` to your app, signs you in to +it, and shows how to switch it to tenant mode or add your own +dashboards. For why it's built this way, see +[ARCHITECTURE_WAVE_11.md](../explanation/ARCHITECTURE_WAVE_11.md). + +## What you need first + +- The **`core`** and **`auth`** engines. Admin uses `Auth::Identity` for + sign-in and `Core::AuditLog` for its audit trail. +- The other engines are optional. Admin ships dashboards for accounts, + memberships, teams, invitations, notifications, plans, subscriptions, + invoices and lifetime passes. Each appears only when its engine is + installed. The rest are hidden and their URLs return 404. + +## 1. Generate it + +```bash +bin/seams admin +bundle install # admin adds administrate and pundit to your Gemfile +``` + +The generator mounts the engine at `/admin`. It adds no tables, so no +migration is needed. + +## 2. Let yourself in + +Only **staff** can open the admin area by default. Mark your identity as +staff from a console: + +```bash +bin/rails runner 'Auth::Identity.find_by!(email: "you@example.com").update!(staff: true)' +``` + +Start the app, sign in at `/auth/session/new`, then visit `/admin`: + +| You are | You get | +| --- | --- | +| Signed out | Redirected to `/auth/session/new` | +| Signed in, not staff | 403 "Access denied" | +| Signed in, staff | The Identities dashboard, with a sidebar of every installed dashboard | + +Every create, update and delete made through the admin area writes a +`Core::AuditLog` row, with you as the actor. + +## 3. Choose platform or tenant mode + +**Platform mode** is the default. Staff see every account's data. Use it +for your own operators. + +**Tenant mode** lets each customer's `admin` and `owner` members manage +their own account only. It needs three settings. Create +`config/initializers/seams_admin.rb`: + +```ruby +Seams::Admin.configure do |c| + c.tenancy_scope = :tenant + + # Let any signed-in identity past the gate. The tenant policies then + # decide what each role may do. + c.authenticator = ->(ctrl) { ctrl.current_identity.present? } + + # Tell the admin area which membership the request belongs to. The + # admin controllers don't inherit your ApplicationController, so resolve + # it here. This example takes the identity's first active membership. + # Use however your app picks the current account. + c.current_membership_resolver = lambda do |ctrl| + identity = ctrl.current_identity + identity && Accounts::Membership.find_by(identity_id: identity.id, active: true) + end +end +``` + +In tenant mode: +- An `admin` or `owner` sees only their account's rows. +- Another account's records return 404. +- A `member` gets 403. +- Staff still see everything. + +> [!NOTE] +> Tenant mode needs the `accounts` engine. + +## 4. Add a check before every admin request + +`before_admin_action` runs after the gate on every admin request. Use it +for two-factor checks, IP allow-lists, or extra logging: + +```ruby +Seams::Admin.configure do |c| + c.before_admin_action = ->(ctrl) { ctrl.head(:forbidden) unless ctrl.request.remote_ip.start_with?("10.") } +end +``` + +## 5. Change a dashboard + +Generated files are yours to edit. To keep your edits when the generator +runs again, eject the file first: + +```bash +bin/seams resolve --eject admin/app/dashboards/admin/identity_dashboard.rb +``` + +Then edit `engines/admin/app/dashboards/admin/identity_dashboard.rb`. +Administrate's documentation covers fields and attribute lists: +. + +## 6. Add a dashboard for your own model + +Adding a model such as `Project` takes a dashboard, a controller, two +policies, and a route. All of them live in the admin engine. The generated +`engines/admin/README.md` has the full recipe, under +"Customising a dashboard". In short: + +1. **Dashboard.** Run `bin/rails generate administrate:dashboard Project`. + Move the dashboard to + `engines/admin/app/dashboards/admin/project_dashboard.rb`, wrap it in + `module Admin`, and add `def self.model = ::Project`. +2. **Controller.** Delete the controller Administrate generated in your + app's `app/controllers/admin/`, because it skips the admin gate. + Create `Admin::ProjectsController < ::Seams::Admin::ApplicationController` + in the engine instead. +3. **Policies.** Add `Admin::Platform::ProjectPolicy` and + `Admin::Tenant::ProjectPolicy`, each subclassing its namespace's + `ApplicationPolicy`. A dashboard without a policy is denied. +4. **Route.** Add `resources :projects, controller: "/admin/projects"` + inside the `scope as: :admin do` block in + `engines/admin/config/routes.rb`. + +The sidebar picks up the new dashboard automatically. + +## Troubleshooting + +| Symptom | Cause | Fix | +| --- | --- | --- | +| `[seams admin] missing required dependency` at boot | The auth engine, `administrate`, or `pundit` is missing | `bin/seams auth`, then `bundle install` | +| 403 "Access denied" while signed in | The gate said no. By default it requires `staff?` | Set `staff: true` on your identity, or change `authenticator` | +| A dashboard returns 404 | Its engine isn't installed | Generate that engine, or ignore it; it stays hidden | +| Tenant admins get 403 everywhere | `authenticator` or `current_membership_resolver` isn't set | See [step 3](#3-choose-platform-or-tenant-mode) | +| Signing in fails with `Missing Active Record encryption credential` | No encryption keys | `bin/rails db:encryption:init`, then add the keys to credentials | diff --git a/doc/tutorials/GETTING_STARTED.md b/doc/tutorials/GETTING_STARTED.md index 49f19bd..2cb45bb 100644 --- a/doc/tutorials/GETTING_STARTED.md +++ b/doc/tutorials/GETTING_STARTED.md @@ -153,7 +153,8 @@ of them in parallel (one job per engine). - [ENGINE_CATALOGUE.md](../reference/ENGINE_CATALOGUE.md) — The canonical engines in detail. - [PERMISSIONS.md](../reference/PERMISSIONS.md) — Role → ability grant map and `authorize_permission!`. - [DESIGN_SYSTEM.md](../design-system/DESIGN_SYSTEM.md) — The design engine: components, tokens, theming. -- [ARCHITECTURE_WAVE_11.md](../explanation/ARCHITECTURE_WAVE_11.md) — The admin engine. +- [SETTING_UP_ADMIN.md](../how-to/SETTING_UP_ADMIN.md) — Set up the admin area. +- [ARCHITECTURE_WAVE_11.md](../explanation/ARCHITECTURE_WAVE_11.md) — Why the admin engine is built this way. - [CURRENT_ATTRIBUTES.md](../reference/CURRENT_ATTRIBUTES.md) — Per-request namespaces (Auth::Current, Accounts::Current, Teams::Current). - [ARCHITECTURE.md](../explanation/ARCHITECTURE.md) — Why Seams is built this way. - [UPGRADING_FROM_WAVE_8.md](../how-to/UPGRADING_FROM_WAVE_8.md) — If you adopted seams pre-Wave-9. diff --git a/docs-site/astro.config.mjs b/docs-site/astro.config.mjs index 0414905..d9ac6d2 100644 --- a/docs-site/astro.config.mjs +++ b/docs-site/astro.config.mjs @@ -123,6 +123,7 @@ export default defineConfig({ { label: "Remove an engine", slug: "removing_an_engine" }, { label: "Write an adapter", slug: "writing_an_adapter" }, { label: "Write a follow-up generator", slug: "writing_follow_up_generators" }, + { label: "Set up the admin area", slug: "setting_up_admin" }, { label: "Deploy", slug: "deploying" }, { label: "Upgrade from Wave 8", slug: "upgrading_from_wave_8" }, ], diff --git a/lib/generators/seams/admin/templates/README.md.tt b/lib/generators/seams/admin/templates/README.md.tt index ec303ab..586520e 100644 --- a/lib/generators/seams/admin/templates/README.md.tt +++ b/lib/generators/seams/admin/templates/README.md.tt @@ -136,9 +136,8 @@ Seams::Admin.configure do |c| # at request time. See "Two-mode operation" above. c.tenancy_scope = :platform - # 3. theme_css_path — Optional path to a host-supplied CSS file loaded - # on top of Administrate's stock styling. nil = use Administrate's - # defaults. + # 3. theme_css_path — Reserved for a host-supplied CSS file on top of + # Administrate's stock styling. Accepted but not applied yet. c.theme_css_path = nil # 4. before_admin_action — Optional callable that runs before every @@ -192,18 +191,51 @@ bin/seams resolve --eject admin/app/dashboards/admin/identity_dashboard.rb # Now edit engines/admin/app/dashboards/admin/identity_dashboard.rb freely. ``` -For new dashboards covering host-specific models, generate via -Administrate, then move the result under the `Admin::*` namespace: - -```bash -bin/rails generate administrate:dashboard MyHost::Project -# Move app/dashboards/my_host/project_dashboard.rb to -# engines/admin/app/dashboards/admin/project_dashboard.rb -# Move the controller similarly under app/controllers/admin/. -# Add `resources :projects, controller: "/admin/projects"` to -# engines/admin/config/routes.rb (or splice via the -# admin.routes.after_resources insertion point in a follow-up generator). -``` +To add a dashboard for one of your own models, e.g. `Project`: + +1. **Dashboard:** `engines/admin/app/dashboards/admin/project_dashboard.rb`. + Generate a starting point with `bin/rails generate administrate:dashboard Project`, + then move it here, rename it to `Admin::ProjectDashboard`, and declare + its model. Administrate would otherwise guess `Admin::Project`: + + ```ruby + module Admin + class ProjectDashboard < Administrate::BaseDashboard + def self.model + ::Project + end + # ATTRIBUTE_TYPES, COLLECTION_ATTRIBUTES, ... as generated + end + end + ``` + +2. **Controller:** `engines/admin/app/controllers/admin/projects_controller.rb`, + subclassing the gated base controller. Delete the controller + Administrate generated in your app's `app/controllers/admin/`, because + it bypasses the gate: + + ```ruby + module Admin + class ProjectsController < ::Seams::Admin::ApplicationController + def resource_class = ::Project + def dashboard = (@dashboard ||= ProjectDashboard.new) + end + end + ``` + +3. **Policies:** `Admin::Platform::ProjectPolicy` and `Admin::Tenant::ProjectPolicy` + under `engines/admin/app/policies/admin/{platform,tenant}/`. Subclass the + namespace's `ApplicationPolicy`. The controller name picks the policy, and + an action with no policy is denied. + +4. **Route:** add this to `engines/admin/config/routes.rb`, inside the + `scope as: :admin do` block: + + ```ruby + resources :projects, controller: "/admin/projects" + ``` + + The leading slash is required. The sidebar picks the dashboard up automatically. Administrate's full dashboard / field / controller surface is documented at . @@ -222,8 +254,9 @@ at . - **2FA / IP allow-list for `/admin`.** Not shipped. Wire your own via `Seams::Admin.config.before_admin_action`. -- **Branded UI.** Stock Administrate CSS only in v1. Override via - `theme_css_path`. +- **Branded UI.** Stock Administrate CSS only. `theme_css_path` is + accepted but not applied yet. To restyle, override Administrate's + views (see Administrate's documentation). - **Search beyond Administrate's stock.** Administrate ships a basic Ransack-backed search; rich filtering / faceted search is your job. - **Batch actions.** Not in Administrate's surface; not shipped here. diff --git a/lib/generators/seams/admin/templates/app/controllers/admin/application_controller.rb.tt b/lib/generators/seams/admin/templates/app/controllers/admin/application_controller.rb.tt index 75bcec2..cd4ce97 100644 --- a/lib/generators/seams/admin/templates/app/controllers/admin/application_controller.rb.tt +++ b/lib/generators/seams/admin/templates/app/controllers/admin/application_controller.rb.tt @@ -73,11 +73,21 @@ module Seams # declared model so follow-up dashboards are picked up too. def self.admin_route_keys_by_model admin_routes.map(&:first).uniq.each_with_object({}) do |key, map| - dashboard = "Admin::#{key.classify}Dashboard".safe_constantize - map[dashboard.model.name] = key if dashboard.respond_to?(:model) + model = admin_model_for(key) + map[model.name] = key if model end end + # The model behind a dashboard route key, or nil when its engine is + # not installed (a host with auth but no billing has no + # Billing::Plan). Those dashboards are hidden and answer 404. + def self.admin_model_for(key) + dashboard = "Admin::#{key.classify}Dashboard".safe_constantize + dashboard.model if dashboard.respond_to?(:model) + rescue NameError + nil + end + # Accounts::Membership and Teams::Membership both have the route # key `memberships` (isolated-engine model naming), so # Administrate's polymorphic `[:admin, membership]` calls @@ -153,6 +163,10 @@ module Seams helper_method :seams_admin_tenancy_scope helper_method :seams_admin_navigation + # Runs after the authentication gate, so signed-out visitors are + # still redirected to sign-in rather than told what exists. + before_action :ensure_admin_model_installed + def seams_admin_tenancy_scope ::Seams::Admin.config.tenancy_scope end @@ -219,6 +233,12 @@ module Seams t("administrate.controller.#{key}", resource: resource_class.model_name.human) end + def ensure_admin_model_installed + return if self.class.admin_model_for(controller_name) + + raise ActionController::RoutingError, "#{controller_name} is not installed in this app" + end + def membership_route_key(record) MEMBERSHIP_ROUTE_KEYS[record.class.name] || controller_name.singularize end @@ -270,11 +290,11 @@ module Seams # an index route that the current user may list. def seams_admin_navigation self.class.admin_routes.select { |_key, action| action == "index" }.map(&:first).uniq.filter_map do |key| - dashboard = "Admin::#{key.classify}Dashboard".safe_constantize - next unless dashboard.respond_to?(:model) && authorized_action?(dashboard.model, :index) + model = self.class.admin_model_for(key) + next unless model && authorized_action?(model, :index) { key: key, - label: dashboard.model.model_name.human(count: 2), + label: model.model_name.human(count: 2), path: url_for(controller: "/admin/#{key}", action: :index), active: key == controller_name } end diff --git a/lib/generators/seams/design/design_generator.rb b/lib/generators/seams/design/design_generator.rb index f7332f2..3f8343b 100644 --- a/lib/generators/seams/design/design_generator.rb +++ b/lib/generators/seams/design/design_generator.rb @@ -620,8 +620,12 @@ def report_summary_text 1. bundle install (picks up tailwindcss-rails, injected into the host Gemfile) - 2. bin/rails tailwindcss:install (if Tailwind isn't set up yet) - then build it: bin/rails tailwindcss:build + 2. bin/rails tailwindcss:build (required) + Pages using the layout fail with "The asset 'tailwind.css' was + not found" until the CSS is built. Rebuild after style changes, + or keep `bin/rails tailwindcss:watch` running. + Without --shell, if your layout doesn't load Tailwind yet, run + `bin/rails tailwindcss:install` first. 3. Use the components anywhere in the host or another engine's views: <%= ui_button(variant: :primary) { "Save" } %> diff --git a/lib/generators/seams/install/templates/deploy.yml.tt b/lib/generators/seams/install/templates/deploy.yml.tt index ed46839..cf097f4 100644 --- a/lib/generators/seams/install/templates/deploy.yml.tt +++ b/lib/generators/seams/install/templates/deploy.yml.tt @@ -37,6 +37,12 @@ env: secret: - RAILS_MASTER_KEY - DATABASE_URL + # Rails 8 has separate cache/queue/cable databases; DATABASE_URL only + # covers the primary. Point these at the same server with different + # database names, or configure them in config/database.yml and drop them. + - CACHE_DATABASE_URL + - QUEUE_DATABASE_URL + - CABLE_DATABASE_URL - REDIS_URL - STRIPE_SECRET_KEY - STRIPE_WEBHOOK_SECRET diff --git a/spec/generators/seams/admin_generator_spec.rb b/spec/generators/seams/admin_generator_spec.rb index 95e07e0..4b71a66 100644 --- a/spec/generators/seams/admin_generator_spec.rb +++ b/spec/generators/seams/admin_generator_spec.rb @@ -570,6 +570,14 @@ def readme_needles end end + it "hides dashboards whose engine is not installed and answers 404 for them" do + assert_file controller_path do |content| + expect(content).to include("def self.admin_model_for(key)") + expect(content).to include("rescue NameError") + expect(content).to include("before_action :ensure_admin_model_installed") + end + end + it "keeps the created record for the create audit row" do assert_file controller_path do |content| expect(content).to include("super { |resource| @created_resource = resource }") diff --git a/spec/generators/seams/design_generator_spec.rb b/spec/generators/seams/design_generator_spec.rb index d27b240..94ca82e 100644 --- a/spec/generators/seams/design_generator_spec.rb +++ b/spec/generators/seams/design_generator_spec.rb @@ -90,6 +90,14 @@ def host(relative) run_generator end + describe "next-steps summary" do + it "marks the Tailwind build as required and names the error you get without it" do + text = described_class.new([], {}, destination_root: destination_root).send(:report_summary_text) + expect(text).to include("bin/rails tailwindcss:build (required)") + expect(text).to include("The asset 'tailwind.css' was") + end + end + describe "engine entry point" do it "places the engine under the bare Design namespace, NON-isolated" do assert_file "engines/design/lib/design/engine.rb" do |content| diff --git a/spec/generators/seams/install_generator_spec.rb b/spec/generators/seams/install_generator_spec.rb index 7b3f15a..675d043 100644 --- a/spec/generators/seams/install_generator_spec.rb +++ b/spec/generators/seams/install_generator_spec.rb @@ -186,6 +186,7 @@ class Application < Rails::Application assert_file "config/deploy.yml" do |content| expect(content).to include("service:") expect(content).to include("STRIPE_SECRET_KEY") + expect(content).to include("CACHE_DATABASE_URL") end end