Logo

dev-resources.site

for different kinds of informations.

Devise: User + Profile

Published at
8/21/2021
Categories
devise
rails
ruby
Author
skrix
Categories
3 categories in total
devise
open
rails
open
ruby
open
Author
5 person written this
skrix
open
Devise: User + Profile

Why?

Students of the #pivorak Ruby Summer Courses 2021 have been working on their practical part project "HoldMyDog" (a dog sitting service) and there was a registration form for users. Since we have split the information about user between User and Profile models we need to configure devise to save data from one form to both of them.

image

What we had?

Database structure

Users migration

db/migrations/20210810072523_devise_create_users.rb
class DeviseCreateUsers < ActiveRecord::Migration[6.1]
  def change
    create_table :users do |t|
      ## Database authenticatable
      t.string :email,              null: false, default: ""
      t.string :encrypted_password, null: false, default: ""

      ## Recoverable
      t.string   :reset_password_token
      t.datetime :reset_password_sent_at

      ## Rememberable
      t.datetime :remember_created_at

      t.string :role

      t.timestamps null: false
    end

    add_index :users, :email,                unique: true
    add_index :users, :reset_password_token, unique: true
  end
end
Enter fullscreen mode Exit fullscreen mode

User model

app/models/user.rb
class User < ApplicationRecord
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable

  has_one :profile
end
Enter fullscreen mode Exit fullscreen mode

Profiles migration

db/migrations/20210810073644_create_profiles.rb
class CreateProfiles < ActiveRecord::Migration[6.1]
  def change
    create_table :profiles do |t|
      t.string     :first_name
      t.string     :last_name
      t.string     :phone
      t.text       :description
      t.references :user, null: false, foreign_key: true

      t.timestamps
    end
  end
end
Enter fullscreen mode Exit fullscreen mode

Profile model

app/models/profile.rb
class Profile < ApplicationRecord
  belongs_to :user

  validates :first_name, presence: true
  validates :last_name,  presence: true
  validates :phone,      presence: true
  validates :description, length: { maximum: 300 }
end
Enter fullscreen mode Exit fullscreen mode

What to do?

First we need to generate devise views and controllers for registration and then modify them accordingly to allow form params for profile to pass.

Devise generators

We can use devise generators:

  1. rails generate devise:views - to generate all devise views
  2. rails generate devise:controllers - to generate all devise controllers

1. Generating devise views for registration

bundle exec rails g devise:controllers users -c registrations
Enter fullscreen mode Exit fullscreen mode

This will generate only registrations controller for us.

2. Editing routes.rb to use our customised controller

config/routes.rb
Rails.application.routes.draw do
  devise_for :users, controllers: {
    registrations: 'users/registrations'
  }
end
Enter fullscreen mode Exit fullscreen mode

This part is important because without explicit routing you will end up using default devise controller.

3. Generating views

bundle exec rails g devise:views users
Enter fullscreen mode Exit fullscreen mode

This will generate all devise views in scope of users.

4. Editing form view

app/views/users/registrations/new.html.erb
<div class="container">
  <h2 class="form-header">Sign up now!</h2>
  <%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
    <%= render "users/shared/error_messages", resource: resource %>

    <%= f.fields_for :profile do |pf| %>
      <div>
        <%= pf.text_field :first_name, placeholder: 'First name *' %>
      </div>
      <div>
        <%= pf.text_field :last_name, placeholder: 'Last name *' %>
      </div>
      <div>
        <%= pf.text_field :phone, placeholder: 'Phone' %>
      </div>
    <% end %>

    <div>
      <%= f.email_field :email, autocomplete: "email", placeholder: 'Email *' %>
    </div>
    <div>
      <%= f.password_field :password, autocomplete: "new-password", placeholder: 'Password *' %>
    </div>
    <div>
      <%= f.password_field :password_confirmation, autocomplete: "new-password", placeholder: 'Repeat password *' %>
    </div>

    <%= f.fields_for :profile do |profile_form| %>
      <%= profile_form.text_area :description, cols: 40, rows: 3, placeholder: 'Tell us about yourself ;)' %>
    <% end %>

    <div>* How would you like to use the service?</div>
    <div>
      <%= f.radio_button :role, 'sitter', css: 'form-check-input' %>
      <%= label :role_sitter, 'I want to hold someone`s pet', css: 'form-check-label' %>
    </div>

    <div>
      <%= f.radio_button :role, 'owner', css: 'form-check-input' %>
      <%= label :role_owner, 'I want to give my pet to sitter', css: 'form-check-label' %>
    </div>

      <%= f.submit "Sign up", class: "btn btn-light sign-up-btn mt-4" %>
  <% end %>
</div>
Enter fullscreen mode Exit fullscreen mode

image

You may notice that the form looks different and we don't have all the fields that we described in our view, that's because we need to modify the new action for registration and build profile object for form before rendering.

5. Editing new action: building profile

app/controllers/users/registrations_controller.rb
class Users::RegistrationsController < Devise::RegistrationsController
  def new
    build_resource({})
    resource.build_profile
    respond_with resource
  end
end
Enter fullscreen mode Exit fullscreen mode

After we built a profile in new action, after reload form will look like this:
image
It may seem that we're done, but we need to save the data from the form to the database.

6. Permitting profile saving

app/controllers/users/registrations_controller.rb
class Users::RegistrationsController < Devise::RegistrationsController
  before_action :configure_sign_up_params, only: [:create]

  def new
    build_resource({})
    resource.build_profile
    respond_with resource
  end

  protected

  def sign_up_params
    devise_parameter_sanitizer.sanitize(:sign_up) { |user| user.permit(permitted_attributes) }
  end

  def configure_sign_up_params
    devise_parameter_sanitizer.permit(:sign_up, keys: permitted_attributes)
  end

  def permitted_attributes
    [
      :email,
      :password,
      :password_confirmation,
      :remember_me,
      :role,
      profile_attributes: %i[first_name last_name phone description]
    ]
  end
end
Enter fullscreen mode Exit fullscreen mode

This way we allow a user to pass params from form to database, but there is one more step we need to do - since we are passing all params together and we haven't modified the create action for Users::RegistrationsController we need to allow User model to accept attributes for Profile.

app/models/user.rb

class User < ApplicationRecord
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable

  has_one :profile

  accepts_nested_attributes_for :profile
end
Enter fullscreen mode Exit fullscreen mode

That's all folks!

devise Article's
30 articles in total
Favicon
Devise not accepting JSON Token
Favicon
Reset password mailer implementation in rails 7 api, devise_token_auth, and sendgrid-ruby.
Favicon
Ruby on Rails: Autenticação utilizando Devise + Keycloak
Favicon
How to Install Devise
Favicon
Warden of Hanami - hanami.rb basic authentication
Favicon
Devise raise validations error when new and old passwords are same
Favicon
Hooking in to Devise controller actions
Favicon
Rails 基礎 Part 06 -- devise でログインをした上で、API UT を叩く
Favicon
Using Devise and SendGrid to send confirmation email on rails app
Favicon
Omniauth without Devise
Favicon
Setting Up User Auth With React and Rails Minus The JWT Headache
Favicon
How to Backup Android Contacts to Mac Devices?
Favicon
Signout Users
Favicon
Rails 7 + Devise + Log out
Favicon
How to Add ToS Agreement Checkbox to Your Rails App using Devise?
Favicon
Multi-Factor Authentication for Rails with WebAuthn and Devise
Favicon
Omniauth + Devise + Rails Tutorial
Favicon
Rails 7.0.0alpha2, esbuild, tailwind and devise
Favicon
Devise: User + Profile
Favicon
Rails redirect user to the previous page after signup or login
Favicon
Devise-ing A Backend...
Favicon
Devise Cheat Sheet
Favicon
Rails Authentication with Devise
Favicon
Extending the default user devise
Favicon
Using Devise for User Auth
Favicon
install gem invisible_captcha with devise
Favicon
Adding a field to your sign-up form with Devise
Favicon
Declaring multiple sets of scopes for the same provider with Devise and OmniAuth in Rails
Favicon
Devise and JWT in Rails
Favicon
Customize Devise’s flash messages

Featured ones: