mirror of
https://github.com/we-promise/sure.git
synced 2026-05-29 23:39:03 +00:00
Exploration spike following expert-panel synthesis. Codifies the 3-tier gating model (instance / per-user / per-family) without adding a framework. Investments is the first family-scoped module. - Family#disabled_modules: string[] column, opt-out semantics. No per-module DB column. Existing families default to [] = enabled. - Family::AVAILABLE_MODULES = %w[investments] (the registry). - ModuleGateable concern (auto-included in ApplicationController): require_module! class macro + module_enabled? helper. - Api::V1::BaseController#require_module!: 403 feature_disabled JSON, mirrors require_ai_enabled. - NavigationHelper extracts mobile/desktop nav into a single source with module: key support; mobile shrinks via justify-around, never auto-fills empty slots. - Settings → Preferences gains a family-scoped module toggle card. - 4 HTML controllers (Investments, Holdings, Trades, Securities) + 3 API controllers gated. Investment/Crypto account types hidden in the new-account modal when off. - docs/feature-gating.md codifies the rule for future modules. Background-job layer not wired (no Investments-specific scheduled job to gate; flagged as TODO in docs). Run db:migrate before bin/rails test. No PR yet — awaiting decisions in open-questions list.
45 lines
1.5 KiB
Ruby
45 lines
1.5 KiB
Ruby
class Settings::PreferencesController < ApplicationController
|
|
layout "settings"
|
|
|
|
def show
|
|
@user = Current.user
|
|
@family = Current.family
|
|
end
|
|
|
|
# Writes per-user boolean preferences stored in the JSONB `users.preferences`
|
|
# column, plus per-family module toggles in `families.disabled_modules`. The
|
|
# auto-submit pattern matches Settings::AppearancesController#update.
|
|
def update
|
|
@user = Current.user
|
|
user_params = params.permit(user: [ :preview_features_enabled ]).fetch(:user, {})
|
|
module_params = params.permit(family: { modules: {} }).dig(:family, :modules)
|
|
|
|
ActiveRecord::Base.transaction do
|
|
@user.lock!
|
|
updated_prefs = (@user.preferences || {}).deep_dup
|
|
if user_params.key?(:preview_features_enabled)
|
|
updated_prefs["preview_features_enabled"] =
|
|
ActiveModel::Type::Boolean.new.cast(user_params[:preview_features_enabled])
|
|
end
|
|
@user.update!(preferences: updated_prefs)
|
|
|
|
if module_params.present?
|
|
family = Current.family
|
|
family.lock!
|
|
disabled = Array(family.disabled_modules).map(&:to_s)
|
|
module_params.each do |name, enabled|
|
|
name = name.to_s
|
|
next unless Family::AVAILABLE_MODULES.include?(name)
|
|
if ActiveModel::Type::Boolean.new.cast(enabled)
|
|
disabled.delete(name)
|
|
else
|
|
disabled << name unless disabled.include?(name)
|
|
end
|
|
end
|
|
family.update!(disabled_modules: disabled)
|
|
end
|
|
end
|
|
redirect_to settings_preferences_path
|
|
end
|
|
end
|