Rails Blog In VS Code — Active Admin 3.2

How To Create A Blog in VS Code — Part XIV — Add Admin To Your Rails App — RailsSeries#Episode 16

J3
Jungletronics
7 min readMay 8, 2024

--

Welcome back! In this episode, building upon our previous experiments with Active Admin, we’re diving deeper into its functionalities with more confidence.

This time, we’ll integrate Active Admin into our website.

Instead of manually implementing views, controllers, routes, and actions, we’ll leverage the power of the Active Admin gem, striving to achieve more with less code. Join us as we explore the alternatives in this post.

Let’s compare our approach to Deanin’s manual implementation. On the left, you see Deanin’s work, while on the right, our result shines through with a much cleaner design. Remember, there’s no one-size-fits-all solution. What matters most are personal preferences. Personally, I’m a big fan of Admin Action. How about you?

I favor Active Admin for its simplicity and efficiency.

Prepare to be amazed by how far we can go!

Let’s Get Started!

Note: if you get stuck, please see my repo.

To integrate Active Admin into your Rails 7 website, you can follow these general steps:

0#step — Download the last version (v8) post here and prepare your vscode environment.

Let’s open a new Feature Branch: git checkout -b add_admin_dash .

GoTo Gemfile:

gem 'activeadmin', '~> 3.2.0'
gem 'sassc'

[On terminal]
bundle install

1#step — Now the fun begins!

rails g active_admin:install

2#step — GoTo:

app/models/application_record.rb

def self.ransackable_attributes(_auth_object = nil)
%w[id email created_at updated_at]
end

4#step —Save this:

db/seeds.rb

user1 = User.create(email: 'test1@test.com', name: 'test1', password: 'password', password_confirmation: 'password')
user2 = User.create(email: 'test2@test.com', name: 'test2', password: 'password', password_confirmation: 'password')

3.times do |i|
post1 = Post.create(title: "Title #{i}", body: "Body #{i} words goes here idk...", user_id: user1.id)
post2 = Post.create(title: "Title #{i}", body: "Body #{i} words goes here idk...", user_id: user2.id)

3.times do |j|
Comment.create(post_id: post1.id, user_id: user2.id, body: "Comment body for Post #{post1.id} by User #{user2.id}")
Comment.create(post_id: post2.id, user_id: user1.id, body: "Comment body for Post #{post2.id} by User #{user1.id}")
end
end

AdminUser.create!(email: 'admin@example.com', password: 'password', password_confirmation: 'password') if Rails.env.development?

5#step — Run:

rails generate migration AddPublishedAtToPosts published_at:datetime

This will generate a migration file in the db/migrate directory. Open this file and you should see something like:

class AddPublishedAtToPosts < ActiveRecord::Migration[6.0]
def change
add_column :posts, :published_at, :datetime
end
end

6#step — Run the app:

rails db:migrate
rails db:seed
rails s

7#step — Let’s register our models. Run:

rails g active_admin:resource Post
rails g active_admin:resource User
rails g active_admin:resource Comment

8#step — Let’s customize each file created by the gem:

GoTo:

app/admin/posts.rb

ActiveAdmin.register Post do
permit_params :title, :body, :comment, :published_at, :user_id

scope :all
scope :published
scope :unpublished

filter :title_cont, label: 'Title' # Add a filter for the name attribute
filter :body_cont, label: 'Body' # Add a filter for the email attribute
filter :published_at, label: 'Published_at'
# filter :user_id, with: 'user_id'

index do
selectable_column
id_column
column :title
column :body

actions
end

form do |f|
f.inputs do
f.input :user
f.input :title
f.input :body
end
f.actions
end

action_item :publish, only: :show do
link_to 'Publish', publish_admin_post_path(post), method: :put unless post.published_at?
end

action_item :unpublish, only: :show do
link_to 'Unpublish', unpublish_admin_post_path(post), method: :put if post.published_at?
end

member_action :publish, method: :put do
post = Post.find(params[:id])
post.update(published_at: Time.zone.now)
redirect_to admin_post_path(post)
end

member_action :unpublish, method: :put do
post = Post.find(params[:id])
post.update(published_at: nil)
redirect_to admin_post_path(post)
end

end

9#step — GoTo:

app/admin/users.rb

ActiveAdmin.register User do
permit_params :name, :email # Ensure name and email are permitted parameters

filter :name_cont, label: 'Name' # Add a filter for the name attribute
filter :email_cont, label: 'Email' # Add a filter for the email attribute

index do
selectable_column
id_column
column :name
column :email
actions
end

form do |f|
f.inputs do
f.input :name
f.input :email
end
f.actions
end
end

10#step — GoTo:

app/admin/comments.rb

ActiveAdmin.register Comment, as: "Post_Comment" do
permit_params :post_id, :user_id, :body # Add :body to the permitted parameters

filter :post_id_cont, label: 'Post ID'
filter :user_id_cont, label: 'User ID'

index do
selectable_column
id_column
column :post_id do |comment|
link_to comment.post.title, admin_post_path(comment.post)
end
column :user_id do |comment|
link_to comment.user.name, admin_user_path(comment.user)
end
column "Body" do |comment|
strip_tags(comment.body.to_plain_text)
end
actions
end

form do |f|
f.inputs do
f.input :user_id
f.input :post_id
f.input :body
end
f.actions
end

end

11#Step — GoTo:

app/models/post.rb

class Post < ApplicationRecord
...

validate :no_curse_words

scope :published, -> { where.not(published_at: nil) }
scope :unpublished, -> { where(published_at: nil) }

def self.ransackable_attributes(_auth_object = nil)
%w[title body comments user] # Allow searching by title, body, comments and user
end

def self.ransackable_associations(_auth_object = nil)
[] # We don't have any searchable associations in this case
end

private

def no_curse_words
errors.add(:title, 'contains inappropriate language') if curse_word_found?(title)
return unless body.present? && curse_word_found?(body.to_s)

errors.add(:body, 'contains inappropriate language')
end

def curse_word_found?(text)
CURSE_WORDS.any? { |word| text.match?(Regexp.new("\\b#{Regexp.escape(word)}\\b", Regexp::IGNORECASE)) }
end

end

12#Step — GoTo:

app/models/user.rb

class User < ApplicationRecord
...

def self.ransackable_attributes(_auth_object = nil)
%w[name email] # Allow searching by name and email
end

def self.ransackable_associations(_auth_object = nil)
[] # We don't have any searchable associations in this case
end

end

13#Step — GoTo:

app/models/comment.rb

class Comment < ApplicationRecord
...
validate :no_curse_words

def self.ransackable_attributes(_auth_object = nil)
%w[user_id post_id] # Allow searching by user_id, title and body
end

def self.ransackable_associations(_auth_object = nil)
[] # We don't have any searchable associations in this case
end

private
...

def no_curse_words
plain_text_body = body.to_plain_text if body.present?
return unless plain_text_body.present? && curse_word_found?(plain_text_body)

errors.add(:base, 'Your comment contains inappropriate language and cannot be saved.')
end

def curse_word_found?(text)
CURSE_WORDS.any? { |word| text.match?(Regexp.new(word, Regexp::IGNORECASE)) }
end

end

14#Step — GoTo:

app/controllers/comments_controller.rb

class CommentsController < ApplicationController

...

def create
@comment = @post.comments.new(comment_params)
@comment.user = current_user

if curse_word_found?(@comment.body.to_plain_text)
flash[:alert] = 'Your comment contains inappropriate language and cannot be saved.'
redirect_to post_path(@post)
elsif @comment.save
flash[:notice] = 'Comment has been created.'
redirect_to post_path(@post)
else
flash[:alert] = 'Comment could not be created.'
redirect_to post_path(@post)
end
end

...


def no_curse_words
plain_text_body = body.to_plain_text if body.present?
return unless plain_text_body.present? && curse_word_found?(plain_text_body)

errors.add(:base, 'Your comment contains inappropriate language and cannot be saved.')
end

def curse_word_found?(text)
CURSE_WORDS.any? { |word| text.match?(Regexp.new(word, Regexp::IGNORECASE)) }
end

end

15#Step — GoTo:

app/controllers/posts_controller.rb

class PostsController < ApplicationController
...

def index
# @posts = Post.all.order(created_at: :desc)
@posts = Post.where.not(published_at: nil).order(published_at: :desc)
end

...

def create
@post = Post.new(post_params)
@post.user = current_user

respond_to do |format|
if @post.save
format.html { redirect_to post_url(@post), notice: 'Post was successfully created.' }
format.json { render :show, status: :created, location: @post }
else
error_message = @post.errors.full_messages.join('. ')
if @post.errors.any? { |error| error.attribute == 'title' }
error_message += '. Title contains inappropriate language'
end
if @post.errors.any? { |error| error.attribute == 'body' }
error_message += '. Body contains inappropriate language'
end
format.html { redirect_to new_post_url, alert: error_message }
format.json { render json: @post.errors, status: :unprocessable_entity }
end
end
end

...

end

16#Step — GoTo:

app/admin/dashboard.rb

ActiveAdmin.register_page "Dashboard" do
menu priority: 1, label: proc { I18n.t("active_admin.dashboard") }

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 t("dashboard.welcome")
small link_to(t("dashboard.call_to_action"), "https://medium.com/jungletronics/rails-active-admin-3-2-0-85d04f40e066")
end
end
end
end

17#Step — GoTo:

config/locales/en.yml

en:
active_admin:
dashboard: "Dashboard"
dashboard:
welcome: "Welcome to my Active Admin Tutorial"
call_to_action: "Read more on Active Admin 3.2.0"

18#Step — Create a file:

config/initializers/curse_words.rb

# config/initializers/curse_words.rb
CURSE_WORDS = %w[
curse_word_1
curse_word_2
curse_word_3
]

19#Step —On Terminal:

rails db:drop
rails db:migrate
rails db:seed
rails s

20#Step — GoTo:

config/routes.rb

Rails.application.routes.draw do
devise_for :admin_users, ActiveAdmin::Devise.config
ActiveAdmin.routes(self)
get 'users/profile'
devise_for :users, controllers: {
sessions: 'users/sessions',
registrations: 'users/registrations'
}

get 'u/:id', to: 'users#profile', as: 'user'

# /post/1/comments/4
resources :posts do
resources :comments
end

get 'home', to: 'pages#home'
get 'about', to: 'pages#about'
# Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html

# Defines the root path route ("/")
root 'pages#home'
end

We hope you grasp the big picture here!

Goodbye!

👉️GitHub Final result

Credits & References

Rails Admin Interfaces with ActiveAdmin by Chris Oliver (GoRails)

Setting up Strong Parameters by github.com/activeadmin

git tag -a v14 -m "ActiveAdmin 3.2.0 :  Go to  https://medium.com/jungletronics/rails-blog-in-vs-code-active-admin-3-2-f0c1eea95844" -m "0- Setup Environment: > Download the latest version and prepare your VSCode environment; > Create a new feature branch: git checkout -b add_admin_dash;" -m "1- Gemfile Configuration: > Add Active Admin and Sassc gems to the Gemfile; > Run bundle install in the terminal;" -m "2- Active Admin Installation: > Run rails g active_admin:install in the terminal;" -m "3- Seed Data Setup: > Save seed data in db/seeds.rb;" -m "4- Migration Setup: > Generate a migration file to add published_at column to posts; > Run migrations: rails db:migrate and seed data: rails db:seed;" -m "5- Model Registration: > Register models in Active Admin: Post, User, and Comment;" -m "6- Customization: > Customize each Active Admin resource file: posts.rb, users.rb, and comments.rb;" -m "7- Model Modifications: > Modify model files (post.rb, user.rb, comment.rb) to include ransackable attributes;" -m "8- Model Modifications: > Modify model files (post.rb, user.rb, comment.rb) to include ransackable attributes;" -m "9- Dashboard Customization: > Customize the dashboard page in dashboard.rb;" -m "10- Localization: > Add localization for the dashboard in en.yml;" -m "11- Curse Words Configuration: > Create an initializer file to configure curse words;" -m "12- Database Setup: > Drop, migrate, and seed the database;" -m "13- Route Configuration: > Configure routes in config/routes.rb; You're all set!" -m "Thank you for downloading this project 😍️" 

git push origin v14

Related Posts:

11# Noticed V2 — Part 0 — We’ve constructed our solution using this app. Visit us there to see the progress we’ve made with our website implementation;

01# Quick Start — Part I — Active Admin: Get started with a very simple rails 7 frame;

02# Navbar Creation — Part II — Active Admin: Create a Navbar & User Functionalities in Dropdown;

03# Features — Part III — Active Admin: Implements some Cool advanced features.

--

--

J3
Jungletronics

Hi, Guys o/ I am J3! I am just a hobby-dev, playing around with Python, Django, Ruby, Rails, Lego, Arduino, Raspy, PIC, AI… Welcome! Join us!