DEV Community

Cover image for Devise Profile Usernames
Chuck
Chuck

Posted on

Devise Profile Usernames

I have worked on several projects recently with user accounts managed by Devise, and I have been working at changing the way user profile URL's are set up and presented to the user. In this article I will address this task and use a few of my recent favorite gems.

TLTR: If you just want the code go grab it, and post questions or responses if you like.

Goals

Here are the project parameters:

  • Set up user accounts with Devise
  • Set up an URL for a User's profile page as: /users/:username
  • Generate a unique username which is not requested on the sign-up form

name_of_person - We will use the name_of_person gem by Basecamp. This gem creates a pseudo-field for full name (requires first_name and last_name in the User table). It has many other abstractions, but this is the only feature we will use.

friendly_id - We will use the friendly_id gem, which created slugs that we can map to a predetermined route. This is a method you can use throughout an application, not just with the User models.

Basic set up

We will start by setting up a basic Rails app: rails new awesome_app, and set up a static controller for a home route: rails g controller static home.

Configure the routes to load the basic home.html.erb, in config/routes.rb:

Rails.application.routes.draw do

  root "static#home"

end
Enter fullscreen mode Exit fullscreen mode

Add to follow to the application.html.erb, to add a rudimentary navbar we can use later:

<!DOCTYPE html>
<html>
<head>
  <title>ArticleDeviseUsernames</title>
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <%= csrf_meta_tags %>
  <%= csp_meta_tag %>

  <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
  <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
</head>

<body>
<div style="margin-top: 20px">
  <% if user_signed_in? %>
    <%= link_to "User Profile", user_path(current_user.slug) %>
    <%= link_to "Logout", destroy_user_session_path(current_user.slug), method: :delete %>
  <% else %>
    <%= link_to "Log in", new_user_session_path %>
  <% end %>
  <%= yield %>

</div>
</body>
</html>

Enter fullscreen mode Exit fullscreen mode

Set up Devise

Add the Devise gem to the Gemfile and bundle install, then install Devise: rails generate devise:install. You will need to add to config/environments/development.rb the following line:

config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
Enter fullscreen mode Exit fullscreen mode

We are going to generate a devise User model: rails generate devise User.

To use name_of_person gem, we need to add two columns to the created Devise migration. Add first_name and last_name somewhere within the database migration, then migrate with rails db:migrate:

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: ""

      ...

      t.string :first_name
      t.string :last_name

      t.timestamps null: false
    end

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

Devise will automatically add the appropriate routes, but it always a good idea to check, so make sure that in config/routes.rb the route is found: devise_for: users.

Add to the User model to use the name_of_person gem, by adding has_person_name:

class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable

  has_person_name
end
Enter fullscreen mode Exit fullscreen mode

This is the basic configure for the gem, but we are going to have to tell Devise to allow the new name field. So, in app/controllers/application_controller.rb add the following:

class ApplicationController < ActionController::Base
  before_action :configure_permitted_parameters, if: :devise_controller?

  protected

  def configure_permitted_parameters
    devise_parameter_sanitizer.permit(:sign_up, keys: [:name])
    devise_parameter_sanitizer.permit(:account_update, keys: [:name])
  end
end
Enter fullscreen mode Exit fullscreen mode

We will need to add the name field to the sign-in and sign-up forms, so we need to generate the views: rails generate devise:views. In app/views/devise/registrations/new.html.erb, .../registrations/edit.html.erb, and .../sessions/new.html.erb add the following for the name field:

<h2>Sign up</h2>

<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
  <%= render "devise/shared/error_messages", resource: resource %>

  <div class="field">
    <%= f.label :name %><br />
    <%= f.text_field :name, autofocus: true %>
  </div>

  <div class="field">
    <%= f.label :email %><br />
    <%= f.email_field :email, autofocus: false, autocomplete: "email" %>
  </div>

  ... 
Enter fullscreen mode Exit fullscreen mode

Almost there ... We need to create a page to contain the User Profile, so we need to create a User controller:

rails g controller User show
Enter fullscreen mode Exit fullscreen mode

Edit controller:

class UsersController < ApplicationController

  def show
    @user = User.find(params[:id])
  end

end
Enter fullscreen mode Exit fullscreen mode

Lastly, update the routes, so for the route to the profile page:

Rails.application.routes.draw do
  devise_for :users

  resources :users, only: [:show]

  root "static#home"

end
Enter fullscreen mode Exit fullscreen mode

All done with Devise. You can restart your rails server, open the development site, and create a User Account. When you are redirected to the root_path, click the "User Profile" link in the navbar. You will be redirected to a path, something like http://localhost:3000/users/1. This is not the goal, so lets move on.

Friendly ID

Add the friendly_id gem to the Gemfile and bundle install, then create a migration:

rails g migration AddSlugToUsers slug:uniq
Enter fullscreen mode Exit fullscreen mode

This will create a unique slug. In our case we are going to use the first and last name fields, and it will create a unique username. So in my case /users/chuck-smith. If this is not unique, maybe there is another "Chuck Smith" in the user table, it will make it unique: /users/chuck-s.

Next we need to generate friendly_id: rails generate friendly_id, and migrate the database: rails db:migrate.

We use Friendly_Id by extending the User model, and define the :name column, from name_of_person as the slug field:

class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable

  has_person_name

  extend FriendlyId
  friendly_id :name, use: :slugged
end

Enter fullscreen mode Exit fullscreen mode

Now, edit the show action in the UsersController to use the :slug param from Friendly:

class UsersController < ApplicationController

  def show
    @user = User.friendly.find(params[:slug])
  end

end
Enter fullscreen mode Exit fullscreen mode

Lastly, update the routes, for the new param:

Rails.application.routes.draw do
  devise_for :users

  resources :users, only: [:show], param: :slug

  root "static#home"

end
Enter fullscreen mode Exit fullscreen mode

If you already have Users created, from the rails console execute: User.find_each(&:save), which will update the new slug column.

Now, where you log in and browse to your User Profile the URL is friendlier (forgive the pun): /users/chuck-smith.

One additional optional configuration. If you want to change the user profile path you can edit the routes file:

Rails.application.routes.draw do
  devise_for :users

  resources :users, only: [:show], param: :slug, path: ""

  root "static#home"

end
Enter fullscreen mode Exit fullscreen mode

Notice I have added a path key to the :users resource which is empty. So, now if you browse to the user profile page the path will be (in this example): /chuck-smith.

Footnote

This has been fun. Leave a comment or send me a DM on Twitter.

Shameless Plug: If you work at a great company, and you are in the market for a Software Developer with a varied skill set and life experiences, send me a message on Twitter and check out my LinkedIn.

Top comments (1)

Collapse
 
kgilpin profile image
Kevin Gilpin

Nice post! Devise is fairly complex, and this is a nice walk through of getting it setup with a few interesting twists.