'Rails require user's profile setup after sign_up

How can I require user to create profile after he has signed up with Devise? I am thinking about using Wicked Wizard gem, but it is possible for user to skip all steps and just access the website.

User must setup their profile first, only then they should have access to website.

I am thinking about this implementation:

SomeController.rb
  before_action :authenticate_user!
  before_action :check_if_profile_created?

  private
    def check_if_profile_created?
      current_user.profile
    end
end

But this solution will do this check on all requests made by user, which I think is not good. Is there any other ways to do this? Or how can it be implemented with Wicked Wizard gem? I haven't found how to make it redirect from all pages to current step in wizard.



Solution 1:[1]

I have a solution that I just implemented two days ago. There is no much documentation on the Internet about this matter. There might be many ways to do this. This is just the way I did it. So, without more delay, let's get to it.

The Concept:

The whole idea, at least how I conceive it, is to "force" the recently signed up user, after he/she also confirms its email, to additionally confirm his profile. As simple as that. That's because in my case many of the profile fields are obviously not mandatory, except the first_name, last_name, email and obviously the password. The rest (job_title, company, affiliation, etc, etc, etc), are not mandatory. But the thing is that at the same time, I would like to enforce that every single User has a profile as complete as possible. This is absolutely necessary for purposes that are not necessary to mention here.

Note:

This is based in that you already have a SettingsController, that allows to any logged user to update his own profile and to update his password as well (in a separated action method).

Step # 1: Add the related boolean field

Modify your users table by adding a boolean field.

class AddProfileWasConfirmedToUsersTable < ActiveRecord::Migration[6.1]
  def change
    add_column :users, :profile_was_confirmed, :boolean, null: false, default: false
  end
end

The boolean field profile_was_confirmed will ensure that the User's profile has been confirmed. It's false by default, right from creation, and it will be updated once and only once to true, when the user confirms his profile. The system won't enforce a 100% complete profile after that, because in order to do such thing it would have to be mandatory on the User model, and that would mess up with the initial sign up process (using the usual devise).

Step # 2: Add the necessary routes.

This one is for the view where we will include a form (inn our case reuse the settings profile form):

get :confirm_profile, to: 'settings#confirm_profile'

And this one is for the updating of the incoming fields from the previously mentioned form:

patch :update_confirmed_profile, to: 'settings#update_confirmed_profile'

So at the end you would have something like this (just an example):

get :confirm_profile, to: 'settings#confirm_profile'
resource :settings, only: [:edit, :update] do
  patch :update_confirmed_profile, to: 'settings#update_confirmed_profile'
  patch :change_password, to: "settings#change_password"
  resource :site_terms, only: [:edit, :update]
end

Note:

Remember to place the additional get route before the resources declaration. Otherwise the framework will confuse your route with the show action method.

Step # 3: Modify your ApplicationsController

before_action :check_profile_was_confirmed
.
.
.
  def check_profile_was_confirmed
    return unless current_user.present?
    unless current_user.profile_was_confirmed? || devise_controller?
      redirect_to confirm_profile_path
    end
  end

Step # 3: Modify your SettingsController

skip_before_action :check_profile_was_confirmed


  def confirm_profile
    if current_user.profile_was_confirmed?
      flash["notice"] = "Your profile has been already confirmed"
      redirect_to dashboard_path
    end
  end

  def update_confirmed_profile
    if @user.update(update_params) && confirm_params_ready?
      @user.update({ profile_was_confirmed: true })
      flash["notice"] = "Profile confirmed"
      redirect_to dashboard_path
    else
      flash["alert"] = "All the fields must be filled"
      redirect_to confirm_profile_path
    end
  end


private

  def confirm_params_ready?
    params_base = params.require(:user).permit(:first_name, :last_name, :email, :company, :job_title)
    params_base.values.all?(&:present?)
  end

Step 4: Create your view with the form or reuse the same one you are using for updating the profile.

File: app/views/settings/confirm_profile.html.erb

<% content_for(:title) { " | Profile Confirmation" } %>
<% content_for(:view_header) do %>
  <%= render 'shared/view_header', view_title: 'Profile Confirmation' %>
<% end %>
<%#= render partial: 'nav' %>

<div class="w-prose mx-auto space-y-8">
  <div class="card">
    <div class="card-header">
      <h4 class="mb-0">Fill all the fields</h4>
    </div>

    <div class="card-body">
      <%= render partial: "settings/profile_form", locals: { form_url: update_confirmed_profile_settings_path } %>
    </div>
  </div>
</div> 

Step 5: Update your User model's factory

Step 6: Update all your integration tests.

Solution 2:[2]

there is no way you can do it without a validation on every request, because you always have to be sure they fill the profile, no matter on what page you enter.

maybe you can add that validation to the application controller with a before action so you don't have to do it in every controller. and just validate if user is logged in and user.profile...

Solution 3:[3]

What do you mean ugly? If it's a matter of "the code will be duplicated everywhere", then you can create a controller that inherits from ActionController, and add before_action to that controller specifying that the profile needs to be filled up. Or you can create a concern which you can then plop in

class SomethingController < ApplicationController
  before_action :ensure_profile_filled

  def ensure_profile_filled
    redirect_to 'wherever'
  end
end

class ChildController < SomethingController
end

There are also route constraints but I think you want to do this on the controller layer.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 xploshioOn
Solution 3 Daryll Santos