Skip to content

Backend Training: ActiveAdmin

Nelson Lee edited this page Jan 2, 2018 · 2 revisions

🔖 activeadmin, will_paginate, kaminari

Now, we have all the frontend setup, let's move on to create an admin panel for us to manage the website. For this we will use activeadmin which helps us easily come up with a systematic way of building admin panel.

Setup

Let's start by going to our admin panel at localhost:3000/admin, and you should see...

admin#login

We don't have an account for the admin panel yet. Let's create one.

$ rails c
irb(main):003:0> AdminUser.create!(email: '[email protected]', password: 'password', password_confirmation: 'password')
   (0.1ms)  BEGIN
  AdminUser Exists (0.4ms)  SELECT  1 AS one FROM "admin_users" WHERE "admin_users"."email" = $1 LIMIT $2  [["email", "[email protected]"], ["LIMIT", 1]]
  SQL (1.5ms)  INSERT INTO "admin_users" ("email", "encrypted_password", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id"  [["email", "[email protected]"], ["encrypted_password", "$2a$11$Js89SMyeLHkbO2Bpxh/v/Ovt2JzQslfyS5iFsr7Gz.9CL.DhaV4.2"], ["created_at", "2017-12-12 23:28:36.342152"], ["updated_at", "2017-12-12 23:28:36.342152"]]
   (0.7ms)  COMMIT
=> #<AdminUser id: 2, email: "[email protected]", created_at: "2017-12-12 23:28:36", updated_at: "2017-12-12 23:28:36">

Kaminari Pagination Error

Next, if you click on admin_user link you will get an error.

admin#pagination_error

This is caused by will_paginate and kaminari since they are all using the same parameter for pagination.

To fix this we will create a kaminari config file.

# config/initializers/kaminari.rb
Kaminari.configure do |config|
  config.page_method_name = :per_page_kaminari
end

Now, stop your server Control+c on your terminal window and start your server again..

$ rails s

Refresh the browser Command ⌘+r, and your page should display properly without any problems.

Menu Config

Now, let's login with this account, and you should see... admin#login

Great! Our admin panel is working. Now, let's fix the menus a bit to make it more organized.

# config/initializers/active_admin.rb
ActiveAdmin.setup do |config|
  .....

  # == Menu System
  config.namespace :admin do |admin|
    admin.build_menu do |menu|
      menu.add label: proc { menu_label('Collection', icon: 'database') },
               id: 'collections',
               priority: 3

      menu.add label: proc { menu_label('Forms', icon: 'email') },
               id: 'forms',
               priority: 5

      menu.add label: proc { menu_label('Setting', icon: 'settings') },
               id: 'settings',
               priority: 99
    end

    admin.build_menu :utility_navigation do |menu|
      menu.add  label: proc { admin_menu_label(current_admin_user) },
                id: 'current_admin_user',
                priority: 2

      menu.add  label: proc { menu_label('Profile') },
                id: 'profile',
                url: proc { admin_admin_user_path(current_active_admin_user) },
                priority: 1,
                parent: 'current_admin_user'

      menu.add  label: proc { menu_label('Log Out') },
                id: 'logout',
                url: proc { destroy_admin_user_session_path },
                priority: 2,
                parent: 'current_admin_user'
    end
  end

  ......

Now, stop your server Control+c on your terminal window and start your server again..

$ rails s

Refresh the browser Command ⌘+r, and the end result should look like...

admin#menu

Great! We have added some nav menu to the menu. Let's fix dashboard and admin_user panel as well.

# app/admin/dashboard.rb
ActiveAdmin.register_page 'Dashboard' do
  menu priority: 1,
       label: proc { menu_label(I18n.t('active_admin.dashboard'), icon: 'chart-donut') }

  content title: proc { I18n.t('active_admin.dashboard') } do
    div class: 'blank_slate_container', id: 'dashboard_default_message' do
      span class: 'blank_slate' do
        span I18n.t('active_admin.dashboard_welcome.welcome')
        small I18n.t('active_admin.dashboard_welcome.call_to_action')
      end
    end

    # Here is an example of a simple dashboard with columns and panels.
    #
    # columns do
    #   column do
    #     panel "Recent Posts" do
    #       ul do
    #         Post.recent(5).map do |post|
    #           li link_to(post.title, admin_post_path(post))
    #         end
    #       end
    #     end
    #   end

    #   column do
    #     panel "Info" do
    #       para "Welcome to ActiveAdmin."
    #     end
    #   end
    # end
  end # content
end
# app/admin/admin_user.rb
ActiveAdmin.register AdminUser do
  menu parent: 'settings', priority: 1

  .....
end

Refresh the browser Command ⌘+r, and the end result should look like...

admin#menu

Great! Our dashboard looks more organized now. Let's move on to configuring our model resources.

Strong Params

ActiveAdmin strong params work like standard rails controller strong params.

Let's look at the app/admin/admin_user.rb file. Here the strong params are defined with

permit_params :email, :password, :password_confirmation

Index Table

Let's look at the app/admin/admin_user.rb file. Here the index table is defined with

index do
  selectable_column
  id_column
  column :email
  column :current_sign_in_at
  column :sign_in_count
  column :created_at
  actions
end

Index Filter

Let's look at the app/admin/admin_user.rb file. Here the filters are defined with

filter :email
filter :current_sign_in_at
filter :sign_in_count
filter :created_at

Form

Let's look at the app/admin/admin_user.rb file. Here the form fields are defined with

form do |f|
  f.inputs 'Admin Details' do
    f.input :email
    f.input :password
    f.input :password_confirmation
  end
  f.actions
end

Now, we have looked at how to build out the admin pages for a model. Let's go ahead and add our admin pages for faq, contact, and news.

FAQ

# app/models/faq.rb
class Faq < ApplicationRecord

  ....

  def display_name
    question.truncate(20)
  end

end
# app/decorators/faq_decorator.rb
# Decorates Faqs
class FaqDecorator < ApplicationDecorator

  delegate_all

end
# app/decorators/admin/faq_decorator.rb
# Decorates Faq for AA
class Admin::FaqDecorator < FaqDecorator

  decorates :faq

  def question_with_link
    link_to truncate(question, length: 20), [:edit, :admin, object]
  end

end
# app/admin/faq.rb
ActiveAdmin.register Faq do
  decorate_with Admin::FaqDecorator

  menu parent: 'collections',
       priority: 5

  config.sort_order = 'position_asc'

  permit_params :question, :answer, :position

  actions :all, except: %i[show]

  # INDEX
  filter :question
  filter :answer
  filter :position
  filter :created_at
  filter :updated_at

  index do
    selectable_column
    column :position
    column :question, :question_with_link, sortable: 'question'
    column :updated_at
    actions(dropdown: true, dropdown_name: nil)
  end

  # FORM
  form do |f|
    f.semantic_errors(*f.object.errors.keys)
    f.inputs t('.faq.panel.detail', model: Faq.model_name.human) do
      f.input :position
      f.input :question
      f.input :answer,
              input_html: { class: 'tinymce' }
    end

    f.actions
  end
end

Contact

# app/decorators/faq_decorator.rb
# Decorates Faqs
class FaqDecorator < ApplicationDecorator

  delegate_all

end
# app/decorators/admin/faq_decorator.rb
# Decorates Faq for AA
class Admin::FaqDecorator < FaqDecorator

  decorates :faq

  def question_with_link
    link_to truncate(question, length: 20), [:edit, :admin, object]
  end

end
# app/admin/faq.rb
ActiveAdmin.register Faq do
  decorate_with Admin::FaqDecorator

  menu parent: 'collections',
       priority: 5

  config.sort_order = 'position_asc'

  permit_params :question, :answer, :position

  actions :all, except: %i[show]

  # INDEX
  filter :question
  filter :answer
  filter :position
  filter :created_at
  filter :updated_at

  index do
    selectable_column
    column :position
    column :question, :question_with_link, sortable: 'question'
    column :updated_at
    actions(dropdown: true, dropdown_name: nil)
  end

  # FORM
  form do |f|
    f.semantic_errors(*f.object.errors.keys)
    f.inputs t('.faq.panel.detail', model: Faq.model_name.human) do
      f.input :position
      f.input :question
      f.input :answer,
              input_html: { class: 'tinymce' }
    end

    f.actions
  end
end

News

# app/decorators/admin/post_decorator.rb
# Decorates Post for AA
class Admin::PostDecorator < PostDecorator

  decorates :post

  def title_with_link
    link_to title, [:edit, :admin, object]
  end

end
# app/admin/post.rb
ActiveAdmin.register Post do
  decorate_with Admin::PostDecorator

  menu parent: 'collections',
       priority: 1

  # STRONG PARAMS
  permit_params :title, :content, :slug, :published_date, :published,
                category_list: []

  actions :all, except: %i[show]

  # CONTROLLER
  controller do
    defaults finder: :find_by_slug
  end

  # INDEX
  filter :title
  filter :categories, input_html: { class: 'select2' }
  filter :published, input_html: { class: 'select2' }
  filter :published_date
  filter :created_at
  filter :updated_at

  index do
    selectable_column
    column :title, :title_with_link, sortable: 'title'
    column :published_date
    column :updated_at
    actions(dropdown: true, dropdown_name: nil)
  end

  # FORM
  form do |f|
    f.inputs t('.post.panel.detail', model: Post.model_name.human(count: 1)) do
      f.input :title
      f.input :slug
      f.input :category_list,
              as: :select,
              collection: Post.category_counts.map(&:name),
              multiple: true,
              include_blank: false,
              input_html: {
                class: 'select2',
                data: {
                  'select2-search' => true,
                  'select2-other' => true
                }
              }
      f.input :content,
              input_html: { class: 'tinymce' }
      f.input :published_date
      f.input :published
    end

    f.actions
  end
end

Let's commit and move on...

$ git add -A
$ git commit -m 'ActiveAdmin Setup'