Chapter 7

The Core Module: Admin Panel

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

7.1. Changing an existing user to admin

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!

7.2. Creating the admin controller

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.

Listing 0.1: AdminController contents 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!

7.3. Setting the admin 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:

Listing 0.2: Updated routes.rb for admin 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

7.4. Adding an admin link to the dashboard

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:

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:

https://s3.amazonaws.com/devblast-modr-book/images/figures/02_07/admin_in_navbar
Figure 1

7.5. Creating the index view for the admin controller

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

Listing 0.4: Contents of index.html.erb 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.

https://s3.amazonaws.com/devblast-modr-book/images/figures/02_07/admin
Figure 2

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!

7.6. Creating the admin users controller

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
Listing 0.5: Contents of UsersController for Admin 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).

7.7. Adding the users resources to our routes

It’s time to update our routes, and add one for the index action we just created:

Listing 0.6: routes.rb with new index action 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

7.8. Adding a link to /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:

Listing 0.7: Add view folder to Rails lookup paths 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:

Listing 0.8: 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:

Listing 0.9: Admin navigation menu 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:

7.9. Listing users

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.

Listing 0.10: Adding ordered scope 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
Listing 0.11: 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:

https://s3.amazonaws.com/devblast-modr-book/images/figures/02_07/list_of_users
Figure 4

10 Adding a summary to the dashboard

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:

Listing 0.12: Updated Users index 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:

https://s3.amazonaws.com/devblast-modr-book/images/figures/02_07/admin_users
Figure 5

11 A small bug

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.

12 Pushing Our Changes

You know the drill now:

  1. Check the changes:
    git status
    
  2. Stage them:
    git add .
    
  3. Commit them:
    git commit -m "Setting up the Admin Panel"
    
  4. Push to your GitHub repo if you’ve configured it:
    git push origin Chapter-7
    

13 Wrap Up

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.

13.1 What did we learn?

  • Once everything is in place, working inside an engine is very similar to working on a regular Rails application.
  • It is possible to write short paths when rendering partials by hooking into Ruby on Rails lookup paths.

13.2 Next Step

In the next chapter, we’re going to add authorization with the Pundit gem.