Devise - thuy-econsys/rails_app GitHub Wiki
Install
Install Devise. The following command generates Devise configuration files config/initializers/devise.rb
and config/locales/devise.en.yml
.
possible unresolved issue with Spring when trying to install Devise as it hangs on Spring and never completes. hacky way was to remove Spring from Gemfile while installing Devise and then adding it back afterwards.
rails generate devise:install
Generate a Devise Migration file to create your User table with email and password attributes, as well as other columns for some optional Devise modules. Migrate the files after perusing the file and uncommenting the modules that you want to use:
rails generate devise user
rails db:migrate
Customize Views
To expose some views for authenticating your users, run rails generate devise:views
which invokes Devise::Generators::SharedViewsGenerator and generates these view directories:
app/views/devise/
├── confirmations
│ └── new.html.erb
├── passwords
│ ├── edit.html.erb
│ └── new.html.erb
├── registrations
│ ├── edit.html.erb
│ └── new.html.erb
├── sessions
│ └── new.html.erb
├── shared
│ └── _links.html.erb
└── unlocks
└── new.html.erb
But if you only want to customize certain views, you can pass them as options after the -v
flag. For basic authentication, registrations/new.html.erb is the template for signing up users; registrations/edit.html.erb, the template for updating user registration data; sessions/new.html.erb, the template for signing in users.
rails generate devise:views -v registrations sessions
Customize Controllers
Similar to the views, there's also a generator for exposing the controllers for customization. Running rails generate devise:controllers
invokes Devise::Generators::ControllersGenerator, which generates the following controllers:
app/controllers/
├── application_controller.rb
├── concerns
└── users
├── confirmations_controller.rb
├── omniauth_callbacks_controller.rb
├── passwords_controller.rb
├── registrations_controller.rb
├── sessions_controller.rb
└── unlocks_controller.rb
If you only want to expose specific controllers, pass them after the -c
flag, no comma separation
rails generate devise:controllers -c registrations
Methods are included but commented out. If at this point, you're scratching your head about what super
is in all the controllers, perhaps understand how inheritance works in Ruby, first, before trying to override any Devise methods.
Make sure to also direct the router to use your custom controller:
devise_for :users, path: "account", :controllers => { registrations: 'users/registrations' }
Note that path is set to account allowing for there to be a separate User Controller route for Administrators to be able to update from a Dashboard page, to avoid any conflict with Users updating their own accounts.
At the top level of the controllers directory is ApplicationController, which Devise controllers inherit from. Here is where strong_params will be configured for the controllers. Note that there are default permitted parameters that do not need to be added to the whitelist, like email and password. And since :sign_in
already has the permitted parameters that it needs, no need to adjust here:
class ApplicationController < ActionController::Base
before_action :authenticate_user!
before_action :configure_permitted_parameters, if: :devise_controller?
protected
def configure_permitted_parameters
added_attrs = [:full_name, :phone, :account_type]
devise_parameter_sanitizer.permit(:sign_up, keys: added_attrs)
devise_parameter_sanitizer.permit(:account_update, keys: added_attrs)
end
end
Customizing
Model
Run rails generate migration AddApprovedToUsers approved:boolean
to generate a migration class that will add an approved attribute column. Inside the change method, append :default => false
so that Users start out as unapproved, awaiting administrator approval.
db/migrate/YYYYMMDDHHMMSS_add_approved_to_users.rb
class AddApprovedToUsers < ActiveRecord::Migration[5.2]
def change
add_column :users, :approved, :boolean, :default => false, :null => false
end
end
Time to override Devise::Models::Authenticatable methods: active_for_authentication?
, inactive_message
:
app/models/user.rb
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable, :trackable
...
def active_for_authentication?
super && approved?
end
def inactive_message
approved? ? super : :not_approved
end
end
Note that super
is being called as your methods are inheriting from the Authenticatable module and we're technically adding functionality and not completely overriding it. Calling super
in active_for_authentication?
returns a true. But we're also checking for the condition of approved?
. The model is active, returns true, only if approved. If active_for_authentication?
evaluates to false and therefore the model is inactive, inactive_message
is invoked. Normally the message is :inactive
but the ternary operator overrides super
, as approved?
returns false, so the message is :not_approved
.
If at this point, your mind is blown because "How the hell is a module a superclass?", read their explanation.
If you're wondering why Devise::Models::Authenticatable is not included in the User class below like the other devise modules, read this explanation which breaks down include and extend in the context of Ruby. Might help to illuminate the purpose of super
. A little background in understanding class and instance variables in Ruby as well as class and instance methods in Ruby might be helpful.
Controller
Extend Devise::RegistrationsController so that we can override the create method by running, rails generate devise:controllers users
. It's possible to generate only specific controllers with the -c
flag followed by each controller, no commas:
rails generate devise:controllers users -c registrations
Also possible to just create the file that you need like by creating app/controllers/users/registrations_controller.rb
and defining like so:
class Users::RegistrationsController < Devise::RegistrationsController
def create
super
if @user.errors.empty?
AdminMailer.with(user: @user).admin_approval_email.deliver
end
end
end
The error check is to make sure email is only sent out for successful signups.
And then you will also need to override the route for registrations controller to route to your Users::RegistrationsController:
devise_for :users, :controllers => { registrations: 'users/registrations' }