everythingfunctional
everythingfunctional

Reputation: 871

Rails Allow Admins to Make Other Users Admins

I'm a little new to it, but I'm building a new web app using rails. Most of what I've got so far is based on railstutorial.org. I've only got a few possible user "roles" (basic user, excom, and admin), so I'm just modeling it using a couple boolean fields in the user model.

I'd like my admin users to be able to make other users admin or excom, without having to resort to some full blown user role modeling system.

I don't want admins to be able to modify other user data (like name, email, etc.) or of course allow users to make themselves admin, so adding something like that to the users_controller update method seems cumbersome and error prone. But it also seems like a whole new controller and routes is overkill.

I just want a button for admins to click to "Make user admin" and have it work, but I'm not sure of the "right" way to implement that.

Edit:

The only exposure an admin has at this point, is checking whether a user is an admin in some before_action. I.e.

def admin_user
    redirect_to(root_url) unless current_user.admin?
end

or

def correct_user_or_excom_or_admin
    @user = User.find(params[:id])
    redirect_to(root_url) unless current_user?(@user) || current_user.admin? || current_user.excom?
end

I think what I want is how to define a route such that I can write the following method in the users_controller and include it in the admin_user before_action.

def make_admin
    @user = User.find(params[:id])
    @user.admin = true
    @user.save
    flash[:success] = "#{@user.name} is now an Admin"
end

And then be able to include the following in the appropriate view

<%= link_to "Make Admin", user_admin_path(user), method: :post,
                          data: { confirm: "You sure?" } %>

I think @widjajayd answer is on the right track. Does creating custom routes that way include the user id in the params?

Upvotes: 0

Views: 390

Answers (2)

everythingfunctional
everythingfunctional

Reputation: 871

Here's the solution I came up with, with inspiration taken from @widjalayd.

Create the following custom routes.

post   '/users/:id/make_admin', to: 'users#make_admin', as: :make_admin
delete '/users/:id/remove_admin', to: 'users#remove_admin', as: :remove_admin
post   '/users/:id/make_excom', to: 'users#make_excom', as: :make_excom
delete '/users/:id/remove_excom', to: 'users#remove_excom', as: :remove_excom

Create the corresponding methods in the users_controller being sure they are in the admin_user before_action

def make_admin
    @user = User.find(params[:id])
    @user.admin = true
    @user.save
    flash[:success] = "#{@user.name} is now an Admin"
    redirect_to users_url
end

def remove_admin
    @user = User.find(params[:id])
    @user.admin = false
    @user.save
    flash[:success] = "#{@user.name} is no longer an Admin"
    redirect_to users_url
end

def make_excom
    @user = User.find(params[:id])
    @user.excom = true
    @user.save
    flash[:success] = "#{@user.name} is now an Executive Committee Member"
    redirect_to users_url
end

def remove_excom
    @user = User.find(params[:id])
    @user.excom = false
    @user.save
    flash[:success] = "#{@user.name} is no longer an Executive Committee Member"
    redirect_to users_url
end

And the partial for displaying a user on the index page is then

<li>
    <%= gravatar_for user, size: 50 %>
    <%= link_to user.name, user %>
    <% if current_user.admin? && !current_user?(user) %>
        |
        <%= link_to "Delete", user, method: :delete,
                                      data: { confirm: "You sure?" } %>
        |
        <% if user.admin? %>
            <%= link_to "Remove Admin", remove_admin_path(user), method: :delete,
                                        data: { confirm: "You sure?" } %>
        <% else %>
            <%= link_to "Make Admin", make_admin_path(user), method: :post,
                                      data: { confirm: "You sure?" } %>
        <% end %>
        |
        <% if user.excom? %>
            <%= link_to "Remove Excom", remove_excom_path(user), method: :delete,
                                        data: { confirm: "You sure?" } %>
        <% else %>
            <%= link_to "Make Excom", make_excom_path(user), method: :post,
                                      data: { confirm: "You sure?" } %>
        <% end %>
    <% end %>
</li>

And then write some tests to be sure.

test "admins should be able to make and remove new admins" do
    log_in_as(@user)
    post make_admin_path(@other_user)
    assert @other_user.reload.admin?
    delete remove_admin_path(@other_user)
    assert_not @other_user.reload.admin?
end

test "non admins can't make or remove admins" do
    log_in_as(@other_user)
    delete remove_admin_path(@user)
    assert @user.reload.admin?
    post make_admin_path(@another_user)
    assert_not @another_user.reload.admin?
end

test "admins should be able to make and remove executive committee" do
    log_in_as(@user)
    post make_excom_path(@another_user)
    assert @another_user.reload.excom?
    delete remove_excom_path(@another_user)
    assert_not @another_user.reload.excom?
end

test "non admins can't make or remove executive committee" do
    log_in_as(@another_user)
    post make_excom_path(@user)
    assert_not @user.reload.excom?
    delete remove_excom_path(@other_user)
    assert @other_user.reload.excom?
end

Edit:

This probably is pushing the limit of "good/maintainable" code and the "rails-way", which is why I asked the question. But since this works, and took a lot less time than learning and setting up a full blown roles system like devise I'll stick with it for now. If I need to make any significant changes then I will probably switch to devise.

Upvotes: 0

widjajayd
widjajayd

Reputation: 6253

you can create custom route with custom method for admin

inside routes.rb, create 2 routes for new and create just for admin

resources users do
   collection { 
     get :new_admin  
     put :create_admin
   }
end

inside user_controllers.rb, create 2 methods

  def new_admin
    @user = User.new
    # this depending with what system you use devise/bcryt/others
  end

  def create_admin
    @user = User.new(user_params)
    @user.role = "Admin" 
    # this depending with what system you use devise/bcryt/others
  end 

create view file inside app/users/new_admin.html.erb

<%= form_for @user, url: create_admin_users_path, do |f| %>
  # your fields name, password, etc
<% end %>

button availability just for admin user

<% if user.role == admin %>
   <%= link_to 'Make User Admin', new_admin_users_path, :class => 'form-control btn btn-info' %>
<% end %>

Edit with additional code if you want to make some user to be an admin

below usually you list user in index.html.erb

<% if @users.any? %>
  <table id="table-user" class="table table-striped">
  <thead>
    <tr>
      <th>email</th>
      <th>name</th>
      <th>Role</th>
      <th class="edit"></th>
      <th class="destroy"></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <% @user.each do |user| %>
        <td><%= user.email %></td>
        <td><%= user.username %></td>
        <td><%= user.role %></td>
        <td><%= link_to "Make Admin", create_admin_users_path(user_id: user.id), method: :post,
                          data: { confirm: "You sure?" } %> </td>
      <% end %>
  </tbody>
  </table>
<% end %>

from form you pass params with hash user_id (it can be any name you want) then inside create controller you get the params with sample below

  def create_admin
    @user = User.find(params[:user_id])
    @user.admin = true
    @user.save
    flash[:success] = "#{@user.name} is now an Admin"
  end

Upvotes: 0

Related Questions