Class: Studio::ProfilesController
- Inherits:
-
ApplicationController
- Object
- ApplicationController
- Studio::ProfilesController
- Defined in:
- app/controllers/studio/profiles_controller.rb
Overview
/profile — the shared account page every Studio app gets.
A plain host-inherited controller in the same shape as StyleController and Studio::EmailsController: its view is a bare content wrapper, so it renders inside each host's application layout and picks up that app's navbar, theme and flash. The engine supplies the page; the app supplies the frame.
WHY /profile AND NOT /account (2026-08-14, operator's call). turf-monster owns
AccountsController and the account_path helper today. Drawing a shared
/account route would raise Invalid route name, already in use while turf's
own routes.rb loads, which takes down EVERY route in that app — the exact
failure that forced draw_admin_emails_routes and draw_onboarding_routes to be
opt-in. profile is unclaimed in all five consumers, so this page can be
drawn by default, which is what makes a brand-new app correct on day one.
It also buys the migration path: turf keeps /account working untouched while its rows move to /profile one at a time, and /account is deleted only once it is empty. Two pages briefly coexisting is the point, not an accident.
WHAT ROWS RENDER is Studio.profile_sections — see lib/studio/profile_sections.rb. Iteration one ships two: the avatar and the first name.
Constant Summary collapse
- MAX_FIRST_NAME =
THE shared cap, not a copy of it — see Studio::FIRST_NAME_MAX_LENGTH. The onboarding step writes this same column and reads this same constant, so the two surfaces cannot drift apart.
Studio::FIRST_NAME_MAX_LENGTH
Instance Method Summary collapse
-
#avatar ⇒ Object
PATCH /profile/avatar — the picture, on its own route.
- #show ⇒ Object
-
#unlink_google ⇒ Object
DELETE /profile/google — drop the linked Google identity.
-
#update ⇒ Object
PATCH /profile — the scalar fields.
Instance Method Details
#avatar ⇒ Object
PATCH /profile/avatar — the picture, on its own route.
SEPARATE FROM #update deliberately: an attachment param submitted empty PURGES the attachment, so a combined form that carried both would delete someone's avatar every time they edited their name. turf-monster learned this and branched inside its own #update; a separate route is the same lesson expressed so the trap cannot be reintroduced.
87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 |
# File 'app/controllers/studio/profiles_controller.rb', line 87 def avatar return unsupported(:avatar) unless serves?(:avatar) file = params.dig(:profile, :avatar) if file.blank? return redirect_to profile_path, alert: "Choose an image first.", status: :see_other end unless Studio::ProfileImage.acceptable?(file) return redirect_to profile_path, alert: Studio::ProfileImage::MESSAGE, status: :see_other end rescue_and_log(target: current_user) do current_user.avatar.attach(file) redirect_to profile_path, notice: "Photo updated." end end |
#show ⇒ Object
35 36 37 |
# File 'app/controllers/studio/profiles_controller.rb', line 35 def show @profile_sections = Studio.profile_sections_for(view_context) end |
#unlink_google ⇒ Object
DELETE /profile/google — drop the linked Google identity.
REFUSES WHEN IT WOULD ORPHAN THE ACCOUNT. turf-monster's version is an
unconditional update!(provider: nil, uid: nil); for an account whose only
sign-in is Google (no email, so no magic link; no wallet; no password) that
locks someone out of their own account behind a button labelled "Unlink".
It is safe in turf only because turf's users happen to carry an email —
a property of that app's data, not of the code.
Studio::OauthIdentity gates on Studio.auth_methods, not merely on the column: an app with an email column that does not offer magic-link sign-in cannot use it to get back in.
118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 |
# File 'app/controllers/studio/profiles_controller.rb', line 118 def unlink_google return unsupported(:google_account) unless serves?(:provider) && serves?(:uid) unless Studio::OauthIdentity.google_linked?(current_user) return redirect_to profile_path, alert: "No Google account is linked.", status: :see_other end if Studio::OauthIdentity.unlink_orphans_account?(current_user) return redirect_to profile_path, status: :see_other, alert: "Google is the only way to sign in to this account. " \ "Add an email address first, then unlink." end rescue_and_log(target: current_user) do current_user.update!(provider: nil, uid: nil) redirect_to profile_path, notice: "Google account unlinked." end end |
#update ⇒ Object
PATCH /profile — the scalar fields. Today that is the first name.
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
# File 'app/controllers/studio/profiles_controller.rb', line 40 def update return unsupported(:first_name) unless serves?(:first_name) value = normalized_first_name if value.blank? return redirect_to profile_path, alert: "Enter your first name.", status: :see_other end rescue_and_log(target: current_user) do attrs = { first_name: value } # Backfill `name` when it is blank so the display-name chain has # something better than an email prefix to show. Same rule as the # onboarding step, which writes this column from the other direction. attrs[:name] = value if current_user.respond_to?(:name) && current_user.name.blank? if current_user.update(attrs) # Read back rather than trusting the write. A host whose before_save # DERIVES first_name from name (turf-monster's set_name_parts does # exactly that) would silently discard the value, and a flash saying # "Saved" over a discarded write is worse than a plain failure. # Reporting what actually persisted keeps the page honest on a host the # engine has not met yet. persisted = current_user.reload.first_name.to_s if persisted == value redirect_to profile_path, notice: "Name updated." else redirect_to profile_path, alert: "This app derives your name from another field — it saved as #{persisted.presence || "blank"}.", status: :see_other end else redirect_to profile_path, alert: current_user.errors..to_sentence.presence || "Could not save that name.", status: :see_other end end end |