Implement FeatureFlag model, rake task, super admin UI, and helpers #130

Closed
opened 2026-06-06 21:29:10 +00:00 by ldraney · 3 comments
Owner

Type

Feature

Lineage

Implementation ticket from spike #129 (feature flag strategy). The architecture doc (docs/feature-flags.md) defines the approach; this ticket builds it.

Repo

ldraney/landscaping-assistant

User Story

As a platform operator
I want to toggle features on/off in production without redeploying
So that I can safely roll out Stripe, client requests, and future integrations with instant rollback

Context

No mechanism exists to toggle features in production without redeploying. Upcoming integrations (Stripe #125) and UI features (client requests #123) need runtime gating. The architecture doc (docs/feature-flags.md) defines a database-backed FeatureFlag model with cached lookups, a deploy-time rake task, and a super admin UI.

Current auth state (PR #136): current_user is a session hash with :username, :email, :roles keys. Use current_user_has_role?("super_admin") for role checks — there is no ActiveRecord User model. super_admin Keycloak realm role already exists (lucas-super-admin test user has both admin and super_admin).

Acceptance Criteria

Model + Migration:

  • Migration creates feature_flags table: name (string, unique, not null), enabled (boolean, default false, not null), description (string), timestamps
  • FeatureFlag model with validations, enabled?(name), enable(name), disable(name) class methods
  • enabled? uses Rails.cache.fetch with 1-minute TTL; enable/disable bust cache immediately
  • Uses pick(:enabled) for minimal DB overhead on hot path

Helpers:

  • feature_enabled?(name) helper in ApplicationController, exposed to views via helper_method

Rake Task (NOT seeds):

  • lib/tasks/feature_flags.rake with feature_flags:sync task
  • Two initial flags: stripe, client_requests (all disabled by default)
  • Task is idempotent — find_or_create_by! creates missing flags without overwriting existing enabled state
  • Important: db:prepare only seeds on first DB creation. The rake task runs on every deploy.

Super Admin UI:

  • Super admin UI at /platform/feature_flags — table of flags with toggle buttons
  • Platform::FeatureFlagsController with index and update actions
  • require_super_admin before_action using current_user_has_role?("super_admin") — returns 404 for non-super-admin users (NOT 403 — the route doesn't exist for them)
  • Person icon shows "Platform" link for super admin users only

Tests:

  • spec/support/feature_flags.rb test helper with with_feature_disabled(name) and with_feature_enabled(name) block helpers
  • RSpec before(:each) hook enables all flags and clears cache (guard with table_exists? check)
  • Model spec covering enabled?, enable, disable, cache behavior
  • Request spec for super admin UI (access, toggle, 404 for non-super-admin, 404 for plain admin)
  • Request spec demonstrating controller-level flag gating

Technical Notes

  • Rails.cache defaults to :file_store in production (SolidCache gem is in Gemfile but not configured — cache_store is commented out, no solid_cache.yml). Rails.cache.fetch works with any cache store, so no additional config is needed for V1. SolidCache can be wired up later (#2).
  • current_user is a hash, NOT an ActiveRecord model. Use current_user_has_role?("super_admin") not current_user&.super_admin?
  • super_admin Keycloak realm role already exists — no Keycloak changes needed
  • Person icon already renders in nav slot 3 — Platform is a view accessible from there for super admin only
  • Emergency Turbo Stream broadcast (turbo_stream_from "application") requires Solid Cable (#2) — not in scope here
  • Deploy initContainer update is tracked separately in #140 (cross-repo, pal-e-deployments)
  • See docs/feature-flags.md for full architecture rationale

Out of Scope

  • Per-business flag scoping (Phase 4 when Business model lands)
  • Turbo Stream broadcast on flag toggle (needs Solid Cable #2)
  • Per-user or percentage rollout granularity
  • Deploy initContainer change (tracked in #140, blocked on this ticket)
  • Keycloak role changes (super_admin already exists)

File Targets

Files to create:

  • db/migrate/YYYYMMDD_create_feature_flags.rb
  • app/models/feature_flag.rb
  • app/controllers/platform/feature_flags_controller.rb
  • app/views/platform/feature_flags/index.html.erb
  • lib/tasks/feature_flags.rake
  • spec/support/feature_flags.rb
  • spec/models/feature_flag_spec.rb
  • spec/requests/platform/feature_flags_spec.rb
  • spec/requests/feature_flag_gating_spec.rb

Files to modify:

  • app/controllers/application_controller.rb — add feature_enabled? helper
  • config/routes.rb — add platform namespace
  • app/views/layouts/application.html.erb — Platform link under Person icon for super admin
  • app/assets/stylesheets/application.css — styles for flags table and platform view

Constraints

  • CSS must follow ~/ror-css-guide (design tokens, component comments, no Tailwind, no inline styles)
  • No !important, no #id selectors in CSS

Enforcement

After #130 lands, the workflow for adding a new feature flag is:

  1. Add an entry to lib/tasks/feature_flags.rake in the feature_flags:sync task (name, enabled: false, description with ticket #)
  2. Gate the feature code with feature_enabled?(:flag_name) checks in controllers/views
  3. The PR template Review Checklist now includes: "Feature flag needed?" — this forces the question at review time

What gets a flag: new user-visible workflow, external service integration, or role-gated UI surface.
What does NOT: bug fixes, refactors, CSS/layout, test infra, internal model changes, database indexes.

The template-pr-body in pal-e-docs has been updated with this checklist item.

Checklist

  • PR opened
  • Tests pass
  • No unrelated changes
  • Spike: #129
  • Architecture doc: docs/feature-flags.md
  • Deploy initContainer: #140 (blocked on this)
  • Stripe integration: #125
  • Client request UI: #123
  • Auth/roles: #107
  • Solid Cable: #2 (prerequisite for Turbo Stream broadcast)
### Type Feature ### Lineage Implementation ticket from spike #129 (feature flag strategy). The architecture doc (`docs/feature-flags.md`) defines the approach; this ticket builds it. ### Repo `ldraney/landscaping-assistant` ### User Story As a platform operator I want to toggle features on/off in production without redeploying So that I can safely roll out Stripe, client requests, and future integrations with instant rollback ### Context No mechanism exists to toggle features in production without redeploying. Upcoming integrations (Stripe #125) and UI features (client requests #123) need runtime gating. The architecture doc (`docs/feature-flags.md`) defines a database-backed `FeatureFlag` model with cached lookups, a deploy-time rake task, and a super admin UI. **Current auth state (PR #136):** `current_user` is a session hash with `:username`, `:email`, `:roles` keys. Use `current_user_has_role?("super_admin")` for role checks — there is no ActiveRecord User model. `super_admin` Keycloak realm role already exists (`lucas-super-admin` test user has both `admin` and `super_admin`). ### Acceptance Criteria **Model + Migration:** - [ ] Migration creates `feature_flags` table: `name` (string, unique, not null), `enabled` (boolean, default false, not null), `description` (string), `timestamps` - [ ] `FeatureFlag` model with validations, `enabled?(name)`, `enable(name)`, `disable(name)` class methods - [ ] `enabled?` uses `Rails.cache.fetch` with 1-minute TTL; `enable`/`disable` bust cache immediately - [ ] Uses `pick(:enabled)` for minimal DB overhead on hot path **Helpers:** - [ ] `feature_enabled?(name)` helper in `ApplicationController`, exposed to views via `helper_method` **Rake Task (NOT seeds):** - [ ] `lib/tasks/feature_flags.rake` with `feature_flags:sync` task - [ ] Two initial flags: `stripe`, `client_requests` (all disabled by default) - [ ] Task is idempotent — `find_or_create_by!` creates missing flags without overwriting existing `enabled` state - [ ] **Important**: `db:prepare` only seeds on first DB creation. The rake task runs on every deploy. **Super Admin UI:** - [ ] Super admin UI at `/platform/feature_flags` — table of flags with toggle buttons - [ ] `Platform::FeatureFlagsController` with `index` and `update` actions - [ ] `require_super_admin` before_action using `current_user_has_role?("super_admin")` — returns 404 for non-super-admin users (NOT 403 — the route doesn't exist for them) - [ ] Person icon shows "Platform" link for super admin users only **Tests:** - [ ] `spec/support/feature_flags.rb` test helper with `with_feature_disabled(name)` and `with_feature_enabled(name)` block helpers - [ ] RSpec `before(:each)` hook enables all flags and clears cache (guard with `table_exists?` check) - [ ] Model spec covering `enabled?`, `enable`, `disable`, cache behavior - [ ] Request spec for super admin UI (access, toggle, 404 for non-super-admin, 404 for plain admin) - [ ] Request spec demonstrating controller-level flag gating ### Technical Notes - Rails.cache defaults to `:file_store` in production (SolidCache gem is in Gemfile but not configured — `cache_store` is commented out, no `solid_cache.yml`). `Rails.cache.fetch` works with any cache store, so no additional config is needed for V1. SolidCache can be wired up later (#2). - `current_user` is a hash, NOT an ActiveRecord model. Use `current_user_has_role?("super_admin")` not `current_user&.super_admin?` - `super_admin` Keycloak realm role already exists — no Keycloak changes needed - Person icon already renders in nav slot 3 — Platform is a view accessible from there for super admin only - Emergency Turbo Stream broadcast (`turbo_stream_from "application"`) requires Solid Cable (#2) — not in scope here - Deploy initContainer update is tracked separately in #140 (cross-repo, pal-e-deployments) - See `docs/feature-flags.md` for full architecture rationale ### Out of Scope - Per-business flag scoping (Phase 4 when Business model lands) - Turbo Stream broadcast on flag toggle (needs Solid Cable #2) - Per-user or percentage rollout granularity - Deploy initContainer change (tracked in #140, blocked on this ticket) - Keycloak role changes (super_admin already exists) ### File Targets Files to create: - `db/migrate/YYYYMMDD_create_feature_flags.rb` - `app/models/feature_flag.rb` - `app/controllers/platform/feature_flags_controller.rb` - `app/views/platform/feature_flags/index.html.erb` - `lib/tasks/feature_flags.rake` - `spec/support/feature_flags.rb` - `spec/models/feature_flag_spec.rb` - `spec/requests/platform/feature_flags_spec.rb` - `spec/requests/feature_flag_gating_spec.rb` Files to modify: - `app/controllers/application_controller.rb` — add `feature_enabled?` helper - `config/routes.rb` — add platform namespace - `app/views/layouts/application.html.erb` — Platform link under Person icon for super admin - `app/assets/stylesheets/application.css` — styles for flags table and platform view ### Constraints - CSS must follow `~/ror-css-guide` (design tokens, component comments, no Tailwind, no inline styles) - No `!important`, no `#id` selectors in CSS ### Enforcement After #130 lands, the workflow for adding a new feature flag is: 1. Add an entry to `lib/tasks/feature_flags.rake` in the `feature_flags:sync` task (name, enabled: false, description with ticket #) 2. Gate the feature code with `feature_enabled?(:flag_name)` checks in controllers/views 3. The PR template Review Checklist now includes: "Feature flag needed?" — this forces the question at review time **What gets a flag**: new user-visible workflow, external service integration, or role-gated UI surface. **What does NOT**: bug fixes, refactors, CSS/layout, test infra, internal model changes, database indexes. The `template-pr-body` in pal-e-docs has been updated with this checklist item. ### Checklist - [ ] PR opened - [ ] Tests pass - [ ] No unrelated changes ### Related - Spike: #129 - Architecture doc: `docs/feature-flags.md` - Deploy initContainer: #140 (blocked on this) - Stripe integration: #125 - Client request UI: #123 - Auth/roles: #107 - Solid Cable: #2 (prerequisite for Turbo Stream broadcast)
ldraney changed title from Implement FeatureFlag model, migration, and helpers to Implement FeatureFlag model, rake task, super admin UI, and helpers 2026-06-06 21:51:23 +00:00
Author
Owner

Scope Review: NEEDS_REFINEMENT

Review note: review-1354-2026-06-06

Well-documented ticket with comprehensive architecture doc backing. All 13 file targets verified -- creates are confirmed absent, modifies are confirmed present with expected content. Dependencies properly tracked, no decomposition needed despite large scope (architecture doc provides complete implementation guide).

Issues found:

  • [SCOPE] No "auth" user story entry in project-landscaping-assistant user-stories table (story:auth label is used across 10+ board items but has no backing story)
  • [SCOPE] No arch-rails-app architecture note in pal-e-docs (arch:rails-app label used on majority of board items but has no backing note)
  • [BODY] Technical Notes claim "SolidCache is already configured in production" but production config has cache_store commented out and no solid_cache.yml exists. Gem is in Gemfile but not wired up.
  • [BODY] This issue is currently closed -- reopen before moving to next_up if work has not been merged
## Scope Review: NEEDS_REFINEMENT Review note: `review-1354-2026-06-06` Well-documented ticket with comprehensive architecture doc backing. All 13 file targets verified -- creates are confirmed absent, modifies are confirmed present with expected content. Dependencies properly tracked, no decomposition needed despite large scope (architecture doc provides complete implementation guide). **Issues found:** - **[SCOPE]** No "auth" user story entry in project-landscaping-assistant user-stories table (story:auth label is used across 10+ board items but has no backing story) - **[SCOPE]** No arch-rails-app architecture note in pal-e-docs (arch:rails-app label used on majority of board items but has no backing note) - **[BODY]** Technical Notes claim "SolidCache is already configured in production" but production config has cache_store commented out and no solid_cache.yml exists. Gem is in Gemfile but not wired up. - **[BODY]** This issue is currently closed -- reopen before moving to next_up if work has not been merged
ldraney reopened this issue 2026-06-07 02:14:33 +00:00
Author
Owner

Scope review refinements (review-1354-2026-06-06):

  1. Reopened — was auto-closed by a stale "Closes #130" in PR #141 body (since fixed)
  2. Fixed SolidCache claim — "SolidCache is already configured in production" was wrong. cache_store is commented out in config/environments/production.rb and no solid_cache.yml exists. Updated to note Rails.cache defaults to :file_store and works fine for V1. SolidCache wiring is tracked in #2.
  3. Added Enforcement section — documents the developer workflow for adding feature flags and references the updated PR template checklist item.

Two systemic gaps flagged (missing story:auth user story and arch:rails-app architecture note) are not #130-specific — will be tracked separately.

**Scope review refinements (review-1354-2026-06-06):** 1. **Reopened** — was auto-closed by a stale "Closes #130" in PR #141 body (since fixed) 2. **Fixed SolidCache claim** — "SolidCache is already configured in production" was wrong. `cache_store` is commented out in `config/environments/production.rb` and no `solid_cache.yml` exists. Updated to note Rails.cache defaults to `:file_store` and works fine for V1. SolidCache wiring is tracked in #2. 3. **Added Enforcement section** — documents the developer workflow for adding feature flags and references the updated PR template checklist item. Two systemic gaps flagged (missing `story:auth` user story and `arch:rails-app` architecture note) are not #130-specific — will be tracked separately.
Author
Owner

Scope Review: READY (re-review)

Review note: review-1354-2026-06-06 (rev 2)

Re-review after refinements. Both fixable items from previous review are resolved:

  • SolidCache claim corrected -- Technical Notes now accurately describe cache store defaults
  • Issue reopened -- state is open

Deferred items (missing story:auth user story entry, missing arch-rails-app note) are systemic gaps tracked separately -- not blocking this ticket.

No new issues found. Ticket is ready to advance.

## Scope Review: READY (re-review) Review note: `review-1354-2026-06-06` (rev 2) Re-review after refinements. Both fixable items from previous review are resolved: - SolidCache claim corrected -- Technical Notes now accurately describe cache store defaults - Issue reopened -- state is `open` Deferred items (missing story:auth user story entry, missing arch-rails-app note) are systemic gaps tracked separately -- not blocking this ticket. No new issues found. Ticket is ready to advance.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
ldraney/landscaping-assistant#130
No description provided.