Profile โ
Manages Rodauth account settings as a Plutonium resource: users view/edit personal fields and access Rodauth security features (change password, 2FA, etc.) on one page.
๐จ Critical โ
- Profile association is always
:profileregardless of the model class:current_user.profile,build_profile,params.require(:profile)always work.pu:profile:connhardcodescurrent_user.profilein the code it generates. - Profile needs
pu:profile:conn. Without it: no route, noprofile_urloverride, no user-menu link. - Check an existing profile policy for
update?. Policies generated by older versions lackdef update? = true, which leaves the profile read-only once it exists. See Whatpu:profile:conngenerates.
Quick setup โ
rails g pu:profile:setup date_of_birth:date bio:text \
--dest=competition \
--portal=competition_portalMeta-generator: runs pu:profile:install + pu:profile:conn in one shot.
Step-by-step โ
rails generate pu:profile:install bio:text avatar:attachment 'timezone:string?' \
--dest=customer
rails db:prepare
rails generate pu:profile:conn --dest=customer_portal| Option | Default | Description |
|---|---|---|
--dest=DEST | (prompts) | Target package or main_app |
--user-model=NAME | User | Rodauth user model |
Custom resource name (first positional argument):
rails g pu:profile:install AccountSettings bio:text --dest=main_appWhat gets created โ
By default the model is {UserModel}Profile (UserProfile, StaffUserProfile, etc.), derived from --user-model.
app/models/[package/]user_profile.rb
db/migrate/xxx_create_user_profiles.rb
app/controllers/[package/]user_profiles_controller.rb
app/policies/[package/]user_profile_policy.rb
app/definitions/[package/]user_profile_definition.rbThe generator modifies the user model:
has_one :profile, class_name: "UserProfile", dependent: :destroyAssociation name is fixed
Even when the class is StaffUserProfile, the association is :profile. Don't rename it: current_user.profile, build_profile, params.require(:profile) all assume this.
If you reuse a profile model whose user association has a different name (e.g. has_one :user_profile), add has_one :profile, class_name: "UserProfile" to the user model rather than editing current_user.profile out of the generated policy and profile_url. Then connect it with pu:profile:conn UserProfile --dest=<portal>.
The SecuritySection component โ
Dynamically lists Rodauth security links based on which features are enabled on the account.
| Feature enabled | Link rendered |
|---|---|
change_password | Change Password |
change_login | Change Email |
otp | Two-Factor Authentication |
recovery_codes | Recovery Codes |
webauthn | Security Keys |
active_sessions | Active Sessions |
close_account | Close Account |
If a feature isn't enabled on the account, its link doesn't render; no configuration needed.
To customize (e.g. add chrome, reorder), override ShowPage#render_after_content:
class UserProfileDefinition < Plutonium::Resource::Definition
class ShowPage < ShowPage
private
def render_after_content
render Plutonium::Profile::SecuritySection.new
end
end
endSee UI โบ Pages for page-class customization.
Connecting to a portal โ
pu:profile:conn registers the profile as a singular resource: /profile (no :id), and overrides the profile_url helper:
rails g pu:profile:conn --dest=customer_portalThis is what makes the profile visible. Without it, the model exists but has no route in any portal.
What pu:profile:conn generates โ
It runs pu:res:conn with --singular --policy --definition, then injects:
- Policy:
relation_scope { skip_default_relation_scope!; relation.where(user: user) }(a profile belongs to the user, not the portal's entity),create? = user.profile.nil?,update? = true,destroy? = false, andpermitted_attributes_for_createminus:user. - Controller:
resource_paramsmerged withuser: current_user, so the owner is set server-side. - Definition: a
ShowPagewhoserender_after_contentrendersPlutonium::Profile::SecuritySection. - Portal controller concern:
profile_url, which points at the profile or, when the user has none yet, at the new-profile form.
The explicit update? matters: the base policy's update? delegates to create?, which is false once the profile exists. Policies generated by older versions lack it and leave the profile read-only, so add it if it's missing:
def update?
true
endUsers without a profile row โ
The generated profile_url sends a user with no profile to the new-profile form, and create? allows exactly one, so a missing row does not break the route. It does mean the first visit is a "create profile" form, and the SecuritySection only appears after it is saved. If you want the show page from the first click, create the row up front:
class User < ApplicationRecord
after_create :create_profile
endand backfill existing users:
rails runner "User.find_each { |u| u.profile || u.create_profile }"Skip this when the profile has required fields the user must fill in, or when existing code creates profiles itself (the unique user_id index makes a second insert fail).
Linking to the profile โ
Nothing to add. Plutonium::Auth::Rodauth defines profile_url returning nil, and the topbar avatar menu renders a "Profile" entry whenever it returns a URL. pu:profile:conn overrides it in the portal's controller concern, which is what makes the link appear. Elsewhere:
link_to("Profile", profile_url) if profile_urlAccount features on the profile page โ
Per-user settings that aren't part of the profile row (API tokens, connected accounts, notification preferences) are discovered from the profile page. Link them from the same ShowPage hook that renders SecuritySection, instead of adding custom items to the sidebar or overriding the topbar partial:
class ShowPage < ShowPage
private
def render_after_content
render Plutonium::Profile::SecuritySection.new
div(class: "mt-8") do
a(href: resource_url_for(ApiToken), class: "font-medium text-[var(--pu-text)] hover:underline") { "API tokens" }
end
end
endA resource like ApiToken belongs to the user, not the portal's entity, so connect it with pu:res:conn ApiToken --dest=<portal> --policy and give it the same shape the profile has: skip_default_relation_scope! plus relation.where(user: user) in the policy, and resource_params merged with user: current_user in the controller.
Customizing the definition โ
The generated definition is a normal Plutonium resource definition. Customize like any other:
class UserProfileDefinition < Plutonium::Resource::Definition
field :bio, as: :markdown
input :avatar, as: :uppy
field :timezone, as: :select, choices: ActiveSupport::TimeZone.all.map(&:name)
metadata :created_at, :updated_at
class ShowPage < ShowPage
private
def render_after_content
render Plutonium::Profile::SecuritySection.new
end
end
endSee Resource โบ Definition for the full definition surface.
Multiple account types โ
If your app has both User and StaffUser accounts, run pu:profile:install once per:
rails g pu:profile:install --user-model=User --dest=main_app
rails g pu:profile:install --user-model=StaffUser --dest=main_appEach gets its own *Profile model with :profile association on the respective user. Connect each to the appropriate portal:
rails g pu:profile:conn UserProfile --dest=customer_portal
rails g pu:profile:conn StaffUserProfile --dest=admin_portalGotchas โ
- Profile is read-only after creation. The policy predates the generated
update?, soupdate?falls back tocreate?(user.profile.nil?). Adddef update? = true. - First visit shows a "create profile" form. The user has no profile row yet. Not an error; add
after_create :create_profileplus a backfill if they should land on the show page. - No Profile link in the user menu.
profile_urlstill returns the defaultnilbecause the profile isn't connected to this portal. Runpu:profile:conn --dest=<portal>. - Custom resource name: pass it as the first positional argument to
pu:profile:install. The association is still:profile. - SecuritySection shows nothing: none of the relevant Rodauth features are enabled on the account. Enable
change_password,otp, etc. on the Rodauth plugin.
Related โ
- Accounts: Rodauth feature flags that gate SecuritySection links
- Resource โบ Definition: customizing the profile definition
- App โบ Generators โบ Profile generators
