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. - Profile needs
pu:profile:conn— without it, no route, noprofile_urlhelper, no user-menu link. - Every user needs a profile row — add an
after_createcallback (orfind_or_create_by). Without it,current_user.profileis nil and the profile route errors.
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:migrate
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.
The generated definition injects a custom ShowPage that renders the SecuritySection component.
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.
Required: every user gets a profile
class User < ApplicationRecord
after_create :create_profile!
private
def create_profile! = create_profile
endWithout this, current_user.profile returns nil and the profile route errors. For existing users at migration time, run a one-off backfill:
rails runner "User.find_each(&:create_profile)"Connecting to a portal
pu:profile:conn registers the profile as a singular resource — /profile (no :id), and exposes 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.
Linking to the profile
link_to("Profile", profile_url) if respond_to?(:profile_url)The respond_to? guard is defensive — only portals that ran pu:profile:conn have the helper.
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
current_user.profileis nil — every user needs a profile row. Addafter_create :create_profile!to the user model.profile_urlis undefined — 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
