Skip to content

Replace CoPlan::UserModel concern with engine-owned CoPlan::User model#32

Merged
HamptonMakes merged 3 commits intomainfrom
hampton/coplan-user-model
Feb 27, 2026
Merged

Replace CoPlan::UserModel concern with engine-owned CoPlan::User model#32
HamptonMakes merged 3 commits intomainfrom
hampton/coplan-user-model

Conversation

@HamptonMakes
Copy link
Collaborator

Summary

Refactors the CoPlan engine to own its own CoPlan::User model instead of requiring host apps to provide a User model and include CoPlan::UserModel.

Motivation

The old approach (CoPlan::UserModel concern included in the host's User model) coupled the engine to the host's schema, forced string PKs on the host, and required implementing 3 controller methods + a NotAuthorizedError. This replaces all of that with a single config.authenticate callback.

What changed

Engine

  • New CoPlan::User modelcoplan_users table with external_id, name, admin, metadata
  • config.authenticate callback — receives request, returns user attributes hash (or nil)
  • Auto-provisioningApplicationController calls the callback and does find-or-create on every request
  • All belongs_to associations reference "CoPlan::User" directly (no more CoPlan.user_class_name)
  • Removed: CoPlan::UserModel, config.user_class, CoPlan.user_class, CoPlan.user_class_name, can_admin_coplan?
  • ApplicationPolicy#admin? now reads user.admin? directly

Host app

  • Removed include CoPlan::UserModel and can_admin_coplan? from User model
  • Replaced config.user_class = "User" with session-based config.authenticate callback
  • SlackNotificationJob bridges to host User via external_id for email lookup
  • Migration creates coplan_users, copies existing users (reusing IDs so FK columns stay valid), and updates foreign keys

Specs

  • New :coplan_user factory
  • sign_in_as bridges CoPlan::User → host User for session auth
  • All CoPlan-context specs use :coplan_user

Docs

  • New docs/HOST_APP_GUIDE.md with full integration guide

Breaking change

This is a breaking change for any host app using CoPlan::UserModel. Since we're pre-1.0 and haven't deployed to other hosts, this is acceptable.

The engine now owns its own User model (coplan_users table) instead of
requiring host apps to provide one and include CoPlan::UserModel.

Engine changes:
- Add CoPlan::User model with external_id, name, admin, metadata
- Add config.authenticate callback (receives request, returns user attrs)
- ApplicationController handles auth internally via find-or-create flow
- All belongs_to associations reference CoPlan::User directly
- Remove CoPlan::UserModel concern, config.user_class, CoPlan.user_class_name
- Replace can_admin_coplan? with CoPlan::User#admin
- CommentsHelper uses CoPlan::User and :api_tokens association
- TokensController uses current_user.api_tokens (no coplan_ prefix)
- Layout conditionally renders sign-out link

Host app changes:
- Remove include CoPlan::UserModel from User model
- Replace config.user_class with config.authenticate callback
- SlackNotificationJob bridges to host User via external_id for email
- Migration creates coplan_users, migrates data, updates foreign keys

Specs:
- Add :coplan_user factory for CoPlan::User
- Update sign_in_as to bridge CoPlan::User -> host User for session auth
- All CoPlan-context specs use :coplan_user instead of :user

Docs:
- Add docs/HOST_APP_GUIDE.md with full integration guide

Amp-Thread-ID: https://ampcode.com/threads/T-019c9ba6-3a56-7499-90bf-78e84f8c18cd
Co-authored-by: Amp <amp@ampcode.com>
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4028b12aae

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

- Skip host's authenticate_user! in CoPlan::ApplicationController so the
  overridden current_user doesn't break the parent's auth check
- Use explicit column: args for all remove_foreign_key calls to avoid
  ambiguity when a table has multiple FKs to the same target
- Update api_tokens_spec to expect 401 (CoPlan returns unauthorized
  directly instead of redirecting to host sign_in)
If two concurrent requests try to create the same CoPlan::User (by
external_id), the unique index causes a RecordNotUnique on the loser.
Rescue and re-find the existing record, then sync attributes.
@HamptonMakes
Copy link
Collaborator Author

@codex review

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 740b790e71

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

remove_foreign_key :coplan_plan_collaborators, column: :added_by_user_id
remove_foreign_key :coplan_plans, column: :created_by_user_id

add_foreign_key :coplan_api_tokens, :users, column: :user_id

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Remap CoPlan user IDs before restoring user foreign keys

The down path re-adds FKs to users immediately, but after up runs, newly provisioned coplan_users no longer share IDs with host users (only external_id matches). If any post-migration data references those new coplan_users.id values (e.g., coplan_plans.created_by_user_id), add_foreign_key ... :users fails with FK violations and blocks rollback during an incident. The rollback needs a data remap from CoPlan IDs back to host user IDs (via external_id) or should be marked irreversible.

Useful? React with 👍 / 👎.

@HamptonMakes HamptonMakes merged commit a434d0f into main Feb 27, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant