- Fix stale grace warnings after plan upgrades: Grace/blocked flags now auto-clear when usage drops below limit (self-healing state)
- Fix grace triggering at exact limit:
grace_then_blocknow uses>(over limit) not>=(at limit) - Add lazy grace creation: Grace starts on-demand when checking status, even if callbacks were bypassed
- Add
ExceededStateUtilsmodule: DRY extraction for shared exceeded/blocked logic
- Add
has_plan_assignment?helper: Check if a plan owner has a manual assignment without full plan resolution - Add
plan_assignmenthelper: Retrieve the assignment record directly for inspection
- Manual assignments now override subscriptions: Admin overrides take precedence over Pay/Stripe plans (was incorrectly reversed) -- current plan resolution order: manual assignment → Pay subscription → default plan
- Fix N+1 queries when checking status: Request-scoped caching eliminates N+1 queries in
status()calls (~85% query reduction) - Add automatic callbacks:
on_limit_warning,on_limit_exceeded,on_grace_start,on_blocknow fire automatically when limits change - Add useful admin scopes:
within_all_limits,exceeding_any_limit,in_grace_period,blockedfor dashboard queries - EnforcementState uniqueness: Fixed overly strict validation that blocked multi-limit scenarios
- Added a
metadataalias to plans, and documented its usage
- Fix a bug in the pay gem integration that would always return the default pricing plan regardless of the actual Pay subscription
- Add hidden plans, enabling grandfathering, no-free-users use cases, etc.
- Prevent unlimited limits for limits that were undefined
- Add support for Rails 8+
- Fix a bug where
throw :abortwas causingUncaughtThrowErrorexceptions in controller guards, and instead returnfalsefrombefore_actioncallbacks to halt the filter chain, rather than using the uncaught throw
Initial release