Any website with customer support will eventually face an issue that is difficult for the customer to explain or difficult to understand without seeing what they are seeing. With the pretender gem we can log in to the users account and see exactly what they are seeing.
gem 'devise'
gem 'pundit'
gem 'pretender'
class ApplicationController < ActionController::Base
before_action :authenticate_user!
impersonates :user
end
class CustomerImpersonationsController < ApplicationController
before_action :authenticate_customer_support_agent!
def create
user = User.find!(params[:id])
impersonate_user(user)
redirect_to root_path
end
def destroy
# current_user is the impersonated user before we call
# stop_impersonating_user so we should build the notice here or store the
# current_user in a variable so we can fetch values from it later
notice = "You are logged out as #{current_user.email}"
stop_impersonating_user
redirect_to root_path, notice: notice
end
private def authenticate_customer_support_agent!
# If we're already impersonating a user, true_user will be the customer
# support agent
#
# If we're not currently impersonating a user, current_user will be the
# customer support agent
return if (true_user || current_user)&.customer_support_agent?
raise ActionController::RoutingError, 'Not Found'
end
end
Rails.application.routes.draw do
resource :customer_impersonation, only: %i[create destroy]
end
<table>
<thead>
<tr>
<td>Name</td>
<td>Email</td>
<td></td>
</tr>
</thead>
<tbody>
<% @users.each do |user| %>
<tr>
<td><%= user.name %></td>
<td><%= user.email %></td>
<td>
<%= link_to('Show', user) %>
<%= link_to(
'Impersonate',
customer_impersonation_path(id: user.id),
method: :post
) %>
</td>
</tr>
<% end %>
</tbody>
</table>
<% if user_signed_in? %>
<%= link_to(
'Log out',
true_user ? customer_impersonation_path : destroy_user_session_path,
method: :delete
) %>
<% end %>
bin/rails generate model AuditLog data:jsonb
class AuditLog < ApplicationModel
belongs_to :user
end
class User
has_many :audit_logs
end
class ApplicationController < ActionController::Base
before_action :authenticate_user!
after_action :log_impersonated_activity, if: :true_user
impersonates :user
private def log_impersonated_activity
return unless true_user
true_user.audit_logs.create!(
data: {
controller: params[:controller],
action: params[:action],
method: request.method,
headers: headers,
params: params,
customer_id: current_user.id,
customer_email: current_user.email,
admin_id: true_user.id,
admin_email: true_user.email
}
)
end
end
class ApplicationController
private def pundit_user
{ current_user: current_user, true_user: true_user }
end
end
class ApplicationPolicy
attr_reader :user, :true_user, :record
def initialize(current_and_true_user, record)
@user = current_and_true_user[:current_user]
@true_user = current_and_true_user[:true_user]
@record = record
end
protected def impersonating_customer?
user.present? && user != true_user
end
end
class PaymentPolicy < ApplicationPolicy
def create?
!impersonating_customer?
end
end
<% if true_user.present? %>
<div class='banner danger warning'>
You are signed in as <%= current_user.name %>.
<%= link_to 'Stop impersonating', cutomer_impersonations_path(id: current_user.id), method: :delete %>
</div>
<% end %>
<%= f.submit 'Save', data: { confirm: "Are you sure you want to save this item for #{current_user.name}'s account" } %>
<% if true_user %>
<span class='disabled_impersonation'>Link account</span>
<% else %>
<%= link_to 'Link account', link_account_path %>
<% end %>
Ready to start your project? Contact Us
From us to your inbox weekly.
`true_user` is only blank when not signed in
It should be current_user != true_user. I've updated the article.
Thanks