Continuing on from the first part of the Rails Blog tutorial we’re going to flesh out our blog concept even further. Now we want to create an administrator role who has permissions to edit/delete other’s posts in case they contain unsuitable material or are outdated and the connected account is deleted. There’s a few possible ways of doing this, but what we’re going to do is add a boolean attribute to the user model that signifies whether a user is an administrator.
Firstly, run rake db:reset
to reset the database, which we’ll need to do because we’re going to set the first registered user of the blog/new-site to be an administrator and they can promote other admins afterwards. Next run rails g migration AddAdminToUsers admin:boolean
then open the newly created migration, it should be in db/migrate/YYYYMMDDHHMMSS_add_admin_to_users.rb with it’s own timestamp in the title. Change the change
method to look like the below (which gives a default value of false for administrator):
def change
add_column :users, :admin, :boolean, :default => false
end
Then run rake db:migrate
to assign the changes to the schema and database. Next, go into app/controllers/posts_controller.rb and in the index
method add the following before the end
keyword:
if current_user == User.first && !current_user.try(:admin?) && User.count == 1
current_user.update_attribute :admin, true
end
This checks if the user is the first user in the database, that they’re not already an admin and that they’re the only user in the database and if all three conditions are met (ie: the blog has just been set up) it sets the current user as an admin. Now we can test for if the current user is an admin with this: current_user.try(:admin?)
, which returns true if the user is an admin and false if the current user is not an admin or there is no current user. If you’re certain that a value contains a user, you can also user @user.admin?
. So now we need to add some logic to the controller to allow the admins to do their work:
# GET /posts/1/edit
def edit
if author_exists = User.where(:id => @post.user_id).first
if current_user == author_exists || current_user.try(:admin?)
else
render :show
end
else
if current_user.try(:admin?)
else
render :show
end
end
end
# PATCH/PUT /posts/1
# PATCH/PUT /posts/1.json
def update
if author_exists = User.where(:id => @post.user_id).first
if current_user == author_exists || current_user.try(:admin?)
respond_to do |format|
if @post.update(post_params)
format.html { redirect_to @post, notice: 'Post was successfully updated.' }
format.json { render :show, status: :ok, location: @post }
else
format.html { render :edit }
format.json { render json: @post.errors, status: :unprocessable_entity }
end
end
else
render :show
end
else
if current_user.try(:admin?)
if @post.update(post_params)
redirect_to @post, notice: 'Post was successfully updated.'
else
render :edit
end
else
render :show
end
end
end
# DELETE /posts/1
# DELETE /posts/1.json
def destroy
if author_exists = User.where(:id => @post.user_id).first
if current_user == author_exists || current_user.try(:admin?)
@post.destroy
respond_to do |format|
format.html { redirect_to posts_url, notice: 'Post was successfully destroyed.' }
format.json { head :no_content }
end
else
render :show
end
else
if current_user.try(:admin?)
@post.destroy
redirect_to posts_url, notice: 'Post was successfully destroyed.'
else
render :show
end
end
end
For each method, it checks if the author of the post exists and if so, performs an action if the current user is the author OR the current user is an admin; in the case that there is no existing author an admin can still perform the appropriate actions on the post.
Next we’ll need to open up app/views/posts/index.html and edit the section with the Edit/Destroy links to look like so:
<% if (current_user == post.user && post.user != nil) || current_user.try(:admin?) %>
<td><%= link_to 'Edit', edit_post_path(post) %></td>
<td><%= link_to 'Destroy', post, method: :delete, data: { confirm: 'Are you sure?' } %></td>
<% end %>
This makes it so if the current user is the post owner OR an administrator they can edit and destroy the posts through the view.
We’ll also need a way of promoting new admins and to do this we’ll need to create a User controller. Now Devise performs some controls and gives some routes, but we need to create our own controller to add some actions. So run rails g controller users
, then open up config/routes.rb and add resources :users
after the other resource routes. Next open up the new app/controllers/users_controller.rb and make it look like:
class UsersController < ApplicationController
before_filter :authenticate_user!
def index
@users = User.all
end
def show
@user = User.find(params[:id])
end
def update
@user = User.find(params[:id])
if current_user.try(:admin?)
if current_user == @user && params[:user][:admin] == '0' && User.where(:admin => true).count == 1
render :show
else
if @user.update(user_params)
redirect_to user_path(@user), notice: 'User was successfully updated.'
else
render :show
end
end
else
render :show
end
end
def destroy
if current_user.try(:admin?)
@user = User.find(params[:id])
if current_user == @user && User.where(:admin => true).count == 1
render :show
else
@user.destroy
redirect_to users_path
end
else
render :show
end
end
private
def user_params
params.require(:user).permit(:admin)
end
end
The obvious classes (index
, show
) simply show all or a select User. update
checks if the current_user is an admin and if they are, updates the selected user unless they happen to be the selected user, they’re disabling admin and there’s only one admin currently available (to prevent a “no admin on blog” situation occurring). destroy
checks if the user is an admin and if so, deletes the selected user unless they are the selected user and there’s only one admin on the board.
Next we’ll need to create some views to see this, so create app/views/users/index.html.erb and fill it with:
<h1>Listing users</h1>
<table width="100%">
<tr>
<td width="30%"><strong>Email</strong></td>
<td width="10%"><strong>Admin</strong></td>
<td colspan="2" width="20%"></td>
</tr>
<% @users.each do |user| %>
<tr>
<td><%= link_to user.email, user_path(user) %></td>
<td>
<% if user.admin? %>
Yes
<% end %>
</td>
<% if current_user.try(:admin?) %>
<td><%= link_to 'Destroy', user, method: :delete, data: { confirm: 'Are you sure?' } %></td>
<% else %>
<td></td>
<% end %>
</tr>
<% end %>
</table>
<p>
<%= link_to 'Index', posts_path %>
</p>
This iterates through the users and displays them all, also showing if they’re admins and allowing admins to delete users (under the constraints set out in the users controller). Also create app/views/users/show.html.erb and set it’s contents to be:
<h1><%= @user.email %></h1>
<p>
<% if current_user.try(:admin?) %>
<%= form_for @user, url: user_path(@user) do |f| %>
<p>
<%= f.label :admin %>
<%= f.check_box(:admin) %>
</p>
<%= f.submit %>
<% end %>
<% end %>
</p>
<p>
<%= link_to 'Users List', users_path %>
</p>
This is where an admin can mark other users as admins or not, simple really. Lastly we’ll need to open up app/views/posts/index.html.erb and change the last few lines to the following:
<p>
<%= link_to 'New Post', new_post_path %>
</p>
<p>
<%= link_to 'Users', users_path %>
</p>
This gives us a link to the users section that people can use to view current users and admins can use to admin!
Now you should have a working administrator model with protections/capabilities all set out. You can apply the same ideas here to things such as the Rails Forum Skeleton project (Part 1, Part 2, Part 3, Thoughts) and also expand it to create other roles including moderator or banned or whatever else you need.