In any CRM, you should have the ability to give some administrative rights to specific users. That’s the reason why we previously added the admin
attribute to our User
model.
With this attribute, we want to give access to a restricted set of users to a new screen: the Admin Panel. Among other things, this panel will allow administrators to manage other users.
In future chapters, we will extend this panel to list contacts, opportunities and the ability to see what everyone is doing.
Before we move on, let’s switch to a new branch for this chapter:
git checkout -b Chapter-7
First, we need an admin! The easiest way to do that is by running rails console
and updating a user that we have previously created. Run the following commands from the parent app:
rails console
Once the console is loaded, update the only user that we have:
Blast::User.first.update_column :admin, true
We can confirm our successful edit with the below output:
Blast::User Load (0.5ms) SELECT "blast_users".* FROM "blast_users"
ORDER BY "blast_users"."id" ASC LIMIT ? [["LIMIT", 1]]
Blast::User Update (7.1ms) UPDATE "blast_users" SET "admin" = ?
WHERE "blast_users"."id" = ? [["admin", 1], ["id", 2]]
=> true
Now that we have an admin, we can start implementing the views!
To keep the administration logic encapsulated, we’re going to add a new namespace: Admin
. First, create a folder named admin
in core/app/controllers/blast/
. Inside this new folder, create a controller named admin_controller.rb
and add the following content inside. You can run the command below, from the Core
engine, to generate the folder and the file:
mkdir app/controllers/blast/admin && \
touch app/controllers/blast/admin/admin_controller.rb
Listing 1 shows the code for the AdminController
class.
core/app/controllers/blast/admin/admin_controller.rb
module Blast
module Admin
class AdminController < ApplicationController
def index; end
end
end
end
Now, we need some routes!
We want the admin panel to be available under /admin
, and the index
action (created above) should be used as the default. Listing 2 shows us how:
core/config/routes.rb
Blast::Core::Engine.routes.draw do
devise_for :users, class_name: 'Blast::User', module: :devise
namespace :admin do
get '/' => 'admin#index'
end
root to: 'dashboard#index'
end
Now that the route is defined, we can add a link by using the path generated by Rails. Let’s add the link to the admin panel between the ‘My Account’ and ‘Logout’ links, by using the code in Listing 3:
core/app/views/layouts/blast/application.html.erb
<!-- ... -->
<div class="pull-right">
<ul class="navbar-nav mr-auto">
<% if current_user %>
<li class="nav-item <%= active(blast.edit_user_registration_path) %>">
<%= link_to 'My Account', blast.edit_user_registration_path,
class: "nav-link" %>
</li>
<% if current_user.admin? %>
<li class="nav-item <%= active(blast.admin_path) %>">
<%= link_to 'Admin', blast.admin_path, class: "nav-link" %>
</li>
<% end %>
<li class="nav-item">
<%= link_to 'Logout', blast.destroy_user_session_path,
class: "nav-link", method: :delete %>
</li>
<% end %>
</ul>
</div>
<!-- ... -->
Don’t forget to use blast.admin_path
, or we will get a crash when accessing the views.
Figure 1 shows us what it looks like:
index
view for the admin controllerIf you click on the link we just created, you will get an exception saying that we don’t have a view yet. Run the following command from inside the Core
engine to create this file and all the folders above it:
mkdir -p app/views/blast/admin/admin && \
touch app/views/blast/admin/admin/index.html.erb
You should end up with this structure:
core/
app/
views/
blast/
admin/
admin/
index.html.erb
Having two admin/
folders is not a mistake - the first one is for the Admin
namespace while the second one is for our controller (AdminController
).
Now paste the contents of Listing 4 in the index.html.erb
we just created:
core/app/views/blast/admin/admin/index.html.erb
<h2 class='float-left'>Admin Panel</h2>
<ul class="nav nav-pills float-right">
<li class="nav-item">
<%= link_to 'Dashboard', blast.admin_path,
class: "nav-link #{active(blast.admin_path)}" %>
</li>
</ul>
<div class='clearfix'></div>
<hr>
<div class="row">
<div class="col-md-6">
<!--Will show tables with the last changes for each model -->
</div>
</div>
You should now see the following when accessing /admin
.
We’ve just created a new menu for everything related to the administration of the CRM. Let’s see what we can add to our empty admin panel!
The first thing we’re going to be showing inside the dashboard of our admin panel is the list of all users in the system. To do this, we’re going to create a new controller named UsersController
, within our Core
engine folder, and add to it the contents of Listing 5:
touch app/controllers/blast/admin/users_controller.rb
core/app/controllers/blast/admin/users_controller.rb
module Blast
module Admin
class UsersController < AdminController
def index
@users = Blast::User.ordered
end
end
end
end
The index
action will return the list of all users sorted in a specific way (we’ll add the ordered
method very soon).
users
resources to our routesIt’s time to update our routes, and add one for the index
action we just created:
core/config/routes.rb
Blast::Core::Engine.routes.draw do
devise_for :users, class_name: 'Blast::User', module: :devise
namespace :admin do
get '/' => 'admin#index'
resources :users, only: :index
end
root to: 'dashboard#index'
end
/admin/users
Finally, we need to add a link pointing to our new resource, the list of users. However, because we don’t want to copy/paste our admin navigation all over the place, it’s time to extract it into a partial view.
Before we do this, let’s learn about a little trick to avoid having to write long paths when including our partial views (due to our namespacing policy). To do this, we need to add our namespaced views folder to the Rails lookup paths.
We do this by adding one line in the engine.rb
file (inside the Core
module), as shown in Listing 7:
core/lib/blast/core/engine.rb
module Blast
module Core
class Engine < ::Rails::Engine
isolate_namespace Blast
paths['app/views'] << 'app/views/blast'
initializer :append_migrations do |app|
unless app.root.to_s.match?(root.to_s)
config.paths['db/migrate'].expanded.each do |p|
app.config.paths['db/migrate'] << p
end
end
end
end
end
end
Always restart your server after changing this file.
With this change, instead of having to write the following to render a partial:
<%= render 'blast/admin/shared/nav' %>
We can just write:
<%= render 'admin/shared/nav' %>
Now time to work on adding our navigation partial. Let’s create a folder named shared/
in core/app/views/blast/admin/
and a file named _nav.html.erb
(from inside the Core
engine):
mkdir -p app/views/blast/admin/shared && \
touch app/views/blast/admin/shared/_nav.html.erb
Inside this partial, we’re going to move our admin navigation menu:
core/app/views/blast/admin/shared/_nav.html.erb
<ul class="nav nav-pills float-right">
<li class="nav-item">
<%= link_to 'Dashboard', blast.admin_path,
class: "nav-link #{active(admin_path)}" %>
</li>
<li class="nav-item">
<%= link_to blast.admin_users_path,
class: "nav-link #{active(blast.admin_users_path)}" do %>
Users
<span class="badge">(<%= Blast::User.count %>)</span>
<% end %>
</li>
</ul>
In this partial, we can see the link to the admin dashboard as well as a new link for the users list. To make things fancier, we’ve also added a count showing the total number of users.
Let’s replace the old admin menu with a reference to this partial inside the admin dashboard view, as shown in Listing 9:
core/app/views/blast/admin/admin/index.html.erb
<h2 class='float-left'>Admin Panel</h2>
<%= render 'admin/shared/nav' %>
<div class='clearfix'></div>
<hr>
<div class="row">
<div class="col-md-6">
<!--Will show tables with the last changes for each model -->
</div>
</div>
Figure 3 should show our results:
Before we add an HTML table to display the users, we’re going to add a scope to the User
model, so we can have them sorted by creation date.
core/app/models/blast/user.rb
module Blast
class User < ApplicationRecord
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
scope :ordered, -> { order(created_at: :desc) }
end
end
Finally, we can display the list of users. We need to create a new folder named users
in core/app/views/blast/admin/
, and an index.html.erb
file:
mkdir -p app/views/blast/admin/users && \
touch app/views/blast/admin/users/index.html.erb
core/app/views/blast/admin/users/index.html.erb
<h2 class='float-left'>Users</h2>
<%= render 'admin/shared/nav' %>
<div class='clearfix'></div>
<hr/>
<table class="table table-bordered">
<thead class="table-active">
<th>ID</th>
<th>Email</th>
<th>Signed Up</th>
<th>Admin?</th>
</thead>
<tbody>
<%- @users.each do |user| %>
<tr>
<td><%= user.id %></td>
<td><%= user.email %></td>
<td><%= user.created_at.strftime("%d %b. %Y") %></td>
<td><%= user.admin? %></td>
</tr>
<% end %>
</tbody>
</table>
If you click on the Users
link, you should now see a table listing the users in your application, as shown in Figure 4:
The dashboard in the admin panel is supposed to show you a bit of everything so you can know what’s going on quickly. So we’re going to add a listing of the last 3 users created:
core/app/views/blast/admin/admin/index.html.erb
<h2 class='float-left'>Admin Panel</h2>
<%= render 'admin/shared/nav' %>
<div class='clearfix'></div>
<hr>
<div class="row">
<div class="col-md-6">
<div class="card">
<div class="card-header">
Last 3 users
<div class="float-right">
<%= link_to 'See All', blast.admin_users_path %>
</div>
</div>
<table class="table table-bordered mb-0">
<tbody>
<%- Blast::User.ordered.limit(3).each do |user| %>
<tr>
<td><%= user.id %></td>
<td><%= user.email %></td>
<td class="text-right">
<%= user.created_at.strftime("%d %b. %Y") %>
</td>
</tr>
<% end %>
</tbody>
</table>
</div>
</div>
</div>
Figure 5 shows what you should see:
While playing with the app, you may have noticed a small bug. When you’re on the users listing in the admin section, the admin link in the main navigation bar is not shown as active anymore.
We’ll let you find a way to fix it by yourself. By doing this, you will assimilate what we saw and get a better grasp of the (still simple) codebase.
You know the drill now:
git status
git add .
git commit -m "Setting up the Admin Panel"
git push origin Chapter-7
In this chapter, we’ve added an admin panel to BlastCRM. With it, administrators can now see all the users in the system. From here, we can see how users could be managed. We’ve still only worked on one engine, but there’s still one feature to add before we tackle our second engine.
In the next chapter, we’re going to add authorization with the Pundit gem.