Rails Allow Admins to Make Other Users Admins - ruby-on-rails

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?

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

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.

Related

Add checkbox to edit bool value of user in devise in Rails

I have created a user using devise gem. I have added a column admin to the User table which has boolean value. Now what I need is to add checkboxes after every user in users_page and give a feature so that when the checkbox is checked the value of admin column changes to true. How can I add the functionality?
users_controller.rb
class UsersController < ApplicationController
def users_page
#users = User.all
end
def change_user_role
#user = User.find(params[:id])
format.html { redirect_to users, notice: 'Role changed successfully' }
end
def destroy
#user = User.find(params[:id])
#user.destroy
if #user.destroy
redirect_to root_url, notice: "User deleted."
end
end
end
users_page.html.erb
<h1>Users</h1>
<% #users.each do |user| %>
<h5><%= user.email %></h5>
<%= user.admin %>
<%= form_tag({controller: "users", action: "change_user_role"}, method: "get") do %>
<%= check_box_tag(:admin, checked: false) %>
<p><%= submit_tag 'Submit Answer' %></p>
<% end %>
<br>
<%= link_to "Destroy", admin_destroy_user_path(user), method: :delete, data: { confirm: "You sure?" } %>
<% end %>
routes.rb
Rails.application.routes.draw do
root to: 'pages#home'
get 'users/users_page'
devise_for :users
match 'users/:id' => 'users#destroy', :via => :delete, :as => :admin_destroy_user
get 'users#change_user_role'
end
Here I should submit the value of the checkbox in users_page to change_user_role and update the value in db and redirect it to users_page. How can I do that?
First of all, change your get method to put in your routes.rb because you'll be updating the resource in database:
Rails.application.routes.draw do
root to: 'pages#home'
get 'users/users_page'
devise_for :users
match 'users/:id' => 'users#destroy', :via => :delete, :as => :admin_destroy_user
// it will require user id in your url
resources :users do
member do
put :change_user_role
end
end
end
More on routes here
Then change your view to something like this:
<h1>Users</h1>
<% #users.each do |user| %>
<h5><%= user.email %></h5>
<%= form_for(user, url: change_user_role_user_path(user)) do |f| %>
<%= f.check_box(:admin) %>
<p><%= f.submit 'Submit Answer' %></p>
<% end %>
<br>
<%= link_to "Destroy", admin_destroy_user_path(user), method: :delete, data: { confirm: "You sure?" } %>
<% end %>
More on forms here
Your controller should look something like this:
class UsersController < ApplicationController
def users_page
#users = User.all
end
def change_user_role
#user = User.find(params[:id])
// if user is updated successfully then redirect
if(#user.update_attributes(user_params)
format.html { redirect_to users, notice: 'Role changed successfully' }
end
end
def destroy
#user = User.find(params[:id])
if #user.destroy
redirect_to root_url, notice: "User deleted."
end
end
// new method added to allow specific attributes only and discarding other malicious attributes that user may send
def user_params
params.require(:user).permit(:admin)
end
end
More on parameters here

Ruby on Rails: Devise + Cancan - How can I edit other users?

I just set up Devise with Cancan for user roles. I think I'm on the right track, but I just ran into a situation where I think I'm missing something small.
I want any user with role :admin to be able to edit the profiles/roles of every other user. I have the routes set up right, but when I click on the links for other users, I get redirected.
_user.html.erb
<% #users.each do |user| %>
<li>
<%= gravatar_for user, size: 52 %>
<%= link_to user.name, user %>
<% if can? :assign_roles, #user %>
| <%= link_to "delete", user, method: :delete, confirm: "Delete user?" %>
| <%= link_to "edit", edit_user_path(user) %>
<% end %>
</li>
<% end %>
users_controller.rb
...
def edit
#user = User.find(params[:id])
end
def update
authorize! :assign_roles, #user if params[:user][:assign_roles]
if #user.update_attributes(params[:user])
flash[:success] = "Profile updated"
sign_in #user
redirect_to #user
else
render 'edit'
end
end
ability.rb
def initialize(user)
can :assign_roles, User if user.admin?
can :manage, :all if user.is? :admin
end
I've been changing this code around all day, I might even be going in circles.
Any help would be greatly appreciated.
I figured it out. Even though I was able to limit the html/css with the logic shown about, I was not able to limit model/controller interaction. I have multiple controllers, and the one I was dealing with had no authentication check. So in other words, I added
before_filter :authenticate_user!
to my users_controller.rb file, and now it knows that I'm an admin, and what that means. I just added this on a whim, but everything I've learned about Devise/Cancan so far is from the wiki:
https://github.com/ryanb/cancan/#wiki-readme

Update a record with a button in Rails?

I'm trying to update a record in my Rails app using a button. I have a User and I want to update its school_id value. I have a School view page where a User can click on a button to add that school's id to the User school_id field. I'm struggling with the implementation. Here's what I have so far:
User controller:
def add_school
#user = User.find(params[:user_id])
#user.update_attributes(:school_id)
respond_to do |format|
flash[:notice] = "School has been added!"
format.html { redirect_to :back }
format.js
end
Button on School show page:
<%= button_to "Add School", add_school_user_path, :method => "put" %>
I tried to do this a different way by just adding code to the update action in the User controller but I couldn't get that to work either:
def update
#user = User.find(params[:id])
if #user.update_attributes(params[:school_id])
flash[:notice] = "School has been added!"
redirect_to #user
end
if #user.update_attributes(params[:user])
flash[:notice] = "Successfully updated account and profile!"
end
end
What's the best way to pass the School's id into the User school_id column?
Thanks!!
EDIT 1: Routes
resources :users do
member do
get :following, :followers
put :add_school
end
Updated controller:
def add_school
#user = current_user
#school = School.find(params[:id])
#user.update_attributes(school_id: params[:school_id])
respond_to do |format|
flash[:notice] = "School has been added!"
redirect_to #user.school
end
end
Updated button link:
<%= button_to "Add School", add_school_user_path(#user, school_id: #school.id), :method => :put %>
Routing error:
No route matches {:action=>"add_school", :controller=>"users", :school_id=>1, :id=>nil}
You need a form for that instead of just abutton
<%= form_tag add_school_user_path(#user), method: put do -%>
<%= hidden_field_tag :school_id, #school.id -%>
<%= submit_tag 'Add school' -%>
<%- end -%>
you didn't provide the context code, maybe #user and #school are not the real variable names but you can get the idea from this
The provided answer is no longer entirely accurate. You can pass data and update a record with a button_to, so long as you aren't attempting to pass arbitrary data.
The button_to syntax would be something like this -- please note, this is my implementation of it, and it is not adjusted to your specific implementation as your controller would need additional adjustments from what you've specificed to accept certain params --:
<%= button_to "Mark Completed", todo_path(todo.id),
params: {:todo => { :id => todo.id, :completed => true, :user_id => current_user.id }},
method: :put, data: { confirm: "Mark this To-Do as being completed? This will hide it from view." } %>
You need to pass the whole hash into the call to .update_attributes.
Preferably, you will put school_id inside of user, so it will look like
# params[:user] = { school_id: 1 }
#user.update_attributes(params[:user])
Or you could code the school id manually
#user.update_attributes(school_id: params[:school_id])
Or, better yet, validate the association
#user.school = School.find(params[:school_id]
The path you would want is
user_add_school_path(#user, school_id: 1)
This is an old Q, but nevertheless for the sake of completeness. You do not necessarily need a form for a simple form-alike submission with a button/link. You can rely on jquery-ujs and data-params.
<%= link_to add_school_user_path(#user), class: "btn btn-xs btn-primary",
title: "Add school",
data: {remote: true, confirm: 'Are you sure?', method: 'put',
params: {user: {schoold_id: #school.id}}} do %>
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
Add school…
<% end %>
Note that you'd want to have school_params method in your controller akin to params.require(:user).permit(:school_id)

deleting users in devise generated model

First of all I am new to rails. I used devise gem for user authentication. Now I want to provide admin a way to delete other users. id of the user is not passing to my destroy action. Here is my code
user_controller.rb
class UsersController < ApplicationController
def destroy
User.find(params[:id]).destroy
redirect_to dashboard_path
end
end
dashboard.html.erb
<% if current_user.admin == true %>
<% #users = User.all %>
<% #users.each do |user| %>
<%= user.email %>
| <%= link_to "delete", destroy_path, method: :delete, data: { confirm: "Are you sure"} %><br>
<% end %>
<% end %>
First of all, You shouldn't assign instance variables directly in your views. This is a Controller responsibility. So, in the above example, the right thing to do is something like this:
# users_controller.rb
class UsersController < ApplicationController
def dashboard
#users = User.all
end
def destroy
User.find(params[:id]).destroy
redirect_to dashboard_path
end
end
And your view should look something like this:
# dashboard.html.erb
<% if current_user.admin == true %>
<% #users.each do |user| %>
<%= user.email %>
| <%= link_to "delete", user, method: :delete, data: { confirm: "Are you sure"} %><br>
<% end %>
<% end %>
And last but not least =P make sure your routes.rb have something like this:
# routes.rb
delete "/users/:id" => "users#destroy", as: :user
Of course it's just an example based on your question, but it should work like a charm =P

not authorized being shown for admin

I have added a before filter and def check priv to the users controller. It is suppose to be setup so that only admin can view/edit all profiles, and that only the created User can view their own profile. As before anyone could view/edit profiles. I have tried a few methods, none work. When I go to view profile as admin or even regular user I get the "not authorized" message.
Any help would be appreciated.
users_controller:
before_filter :check_privileges, :only => [:edit, :update]
def check_privileges
unless current_user.admin? || current_user.id == params[:id]
flash[:warning] = 'not authorized!'
redirect_to root_path
end
end
index.html:
<%= link_to user.name, user %>
<% if (current_user.admin? || current_user) == #user %>
<%= link_to "Edit #{user} profile", user %>
| <%= link_to "delete", user, method: :delete,
data: { confirm: "You sure?"} %>
<% end %>
I have a similar method in my app, try something like this:
def check_privileges
return if current_user.admin? # this user is an admin, if is not the case do:
flash[:warning] = 'not authorized!'
redirect_to root_path
end
UPDATE 1
Again, try to change the if condition as the follow
if (condition || condition)
or
if ((condition) || (condition))
The problem is that Ruby parsers stop at the first condition if not explicited declared.
UPDATE 2
I think that there are an error in the parentheses on your index.html.erb, try the following:
<%= link_to user.name, user %>
<% if (current_user.admin? || (current_user == #user)) %>
<%= link_to "Edit #{user} profile", user %>
| <%= link_to "delete", user, method: :delete,
data: { confirm: "You sure?"} %>
<% end %>
Food for thought maybe you could do something like this:
def correct_user
#user = User.find(params[:id])
if current_user.role? :administrator
#Allow admin to access everyone account
else
access_denied unless current_user?(#user)
end
end
Then inside your view your view do the if statement. Or alternatively my best suggestion is to go with something like CanCan. Something like this will allow you to set up role authentication really easily. If you have a look at it you can set a certain amount of rule in your ability.rb which you can then enforce on the view.
If you WERE to go with the method of CanCan you could do something like this in your ability.rb
def initialize(user)
user ||= User.new # guest user
# raise user.role?(:administrator).inspect
if user.role? :administrator
can :manage, :all
can :manage, User
elsif user.role? :employee
can :read, :all
end
The above is an example.... So that in your views you can enforce this type of rule by doing something like
<%= link_to user.name, user %>
<% if can? :manage, #user %>
<%= link_to "Edit #{user} profile", user %>
| <%= link_to "delete", user, method: :delete,
data: { confirm: "You sure?"} %>
<% end %>
Something like this should work. Your options are there hope this helps :)

Resources