Cancancan gem usage in rails application - ruby-on-rails

I have a rails application which manages all the registered users. All crud operations can be done on the details of the registered users. I now need to restrict the application in such a way that only the admin user should be allowed to view all the registered users. Any user without the admin privilege should be able to view, edit, and delete his/her post only. I have used devise gem for authentication and plans to use cancancan gem for authorization. I have only a single model class called Users which contains a text attribute (to store "Admin" or "Nonadmin"). Here is my controller code:
class UsersController < ApplicationController
before_filter :authenticate_user!
def index
if current_user.role == "Admin"
#users = User.all
else
#user = User.find(current_user.id)
end
redirect_to new_user_registration_path if current_user.nil?
respond_to do |format|
format.html
end
end
def show
if (User.all.pluck(:id).include?params[:id].to_i) == false
flash[:notice] = "You cannot perform such an action"
else
#user = User.find(params[:id])
end
respond_to do |format|
format.html
end
end
def new
#user = User.new
end
def create
#user = User.new(user_params)
if #user.save
redirect_to :action => 'index'
else
render :action => 'new'
end
end
def user_params
params.require(:user).permit(:first_name, :last_name, :age, :biography, :email)
end
def edit
if (User.all.pluck(:id).include?params[:id].to_i) == false
flash[:notice] = "You cannot perform such an action"
else
#user = User.find(params[:id])
end
respond_to do |format|
format.html
end
end
def update
params.inspect
#user = User.find(params[:id])
if #user.update_attributes(user_params)
redirect_to :action => 'show', :id => #user
else
render :action => 'edit'
end
end
def delete
User.find(params[:id]).destroy
redirect_to :action => 'index'
end
end
My user table has the following fields: id,email,first_name,last_name,biography,email,role. It also contains all the devise-related fields.Below is my User model :
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
after_initialize :default_values
private
def default_values
self.role ||= "Non-admin"
end
end
I have hardcoded my admin credentials in seeds.rb as shown below:
if User.find_by(:role => 'Admin').nil?
User.create([{email:"admin_user#usermanagementsystem.com",password: "topsecret",first_name:"admin",last_name:"ums", biography: "i serve as admin at the ums", age:20, role: "Admin"}])
end
View files:
Index.html.erb:-
<table id="albums" cellspacing="0">
<thead>
<tr>
<th> NAME </th>
<th> EMAIL </th>
<% if current_user.role == "Admin" %>
<th> EDIT </th>
<th> SHOW </th>
<th> DESTROY </th>
<% end %>
<th colspan="15"></th>
</tr>
</thead>
<% if current_user.role == "Admin" %>
<% #users.each do |user| %>
<tr>
<td><%= user.first_name %></td>
<td><%= user.email %></td>
<td><%= link_to 'Edit', users_edit_path(id: user.id), class: "small button"%></td>
<td><%= link_to 'Show', users_show_path(id: user.id), class: "small button"%></td>
<td><%= link_to 'Destroy', users_delete_path(id: user.id), method: :delete, data: { confirm: 'Are you sure?' } , class: "small button"%></td>
</tr>
<% end %>
<% else %>
<tr>
<td><%= #user.first_name %></td>
<td><%= #user.email %></td>
<td><%= link_to 'Edit', users_edit_path(id: #user.id), class: "small button" %></td>
<td><%= link_to 'Destroy', users_delete_path(id: #user.id), method: :delete, data: { confirm: 'Are you sure?' } , class: "small button"%></td>
</tr>
<%end %>
</table>
<%#= link_to 'Change password', edit_user_registration_path, class: "small button" %>
edit.html.erb:-
<% if #user.nil? == false %>
<%= form_for #user , :as => :user, :url => users_update_path(#user), :method => :PUT do |f| %>
<%= f.label :first_name %>:
<%= f.text_field :first_name %><br>
<%= f.label :last_name %>:
<%= f.text_field :last_name %><br>
<%= f.label :age %>:
<%= f.number_field :age %><br>
<%= f.label :biography %>:
<%= f.text_field :biography %><br>
<%= f.label :email %>:
<%= f.text_field :email %><br>
<%= f.submit :class => 'small button' %>
<% end %>
<%else%>
<h3> Such a user record does not exist. Please click on a specific user </p>
<%end%>
show.html.erb
<br>
<br>
<% if #user.nil? == false %>
<h3>User details</h3>
<table id="albums" cellspacing="0">
<thead>
<tr>
<th>FIRSTNAME</th>
<th>LASTNAME</th>
<th>EMAIL</th>
<th>BIOGRAPHY</th>
<th>AGE</th>
<th colspan="20"></th>
</tr>
</thead>
<tr>
<td> <%= #user.first_name %> </td>
<td> <%= #user.last_name %> </td>
<td> <%= #user.email %> </td>
<td> <%= #user.biography %> </td>
<td> <%= #user.age %> </td>
</tr>
</table>
<%= link_to ' Edit ', users_edit_path(id: #user.id) , class: "small button"%>
<%= link_to 'Delete ', users_delete_path(id: #user.id), method: :delete, data: { confirm: 'Are you sure?' }, class: "small button" %>
<%else%>
<h3> Such a user record does not exist. Please click on a specific user </p>
<%= link_to "Logout", destroy_user_session_path, method: :delete, class: "small button" %>
</footer>
<%end%>
Can anyone help me out in using cancancan gem to restrict any user other than admin from viewing, editing, updating and deleting his own posts. Admin should be able to do these operations on everyone's records.
Please help me improve if you find me doing something wrong with the controller code too. Thanks in advance.

Added the following code to the initialize function of ability.rb and got it working correctly.
if user.role == "Admin"
can :manage, :all
else
can [:index], User, id: user.id
can [:show],User, id: user.id
can [:edit],User, id: user.id
can [:update], User,id: user.id
can [:delete],User, id: user.id
end

Related

Rendering 'form' is giving me an ArgumentError

I am trying to create an activity feed, and trust me, it will take a while to create as I am new to ruby on rails. Sorry for a basic question.
However, I am trying to render '_form.html.erb' as I want the user to be able to create a 'story' and for it to be listed on the same page, like any activity feed. Could someone explain to me what the problem is?
My index.html.erb:
<p id="notice"><%= notice %></p>
<h1>This is a list of posts</h1>
<table>
<thead>
<tr>
<th>Name</th>
<th>Description</th>
<th>User</th>
<th colspan="3"></th>
</tr>
</thead>
<tbody>
<% #stories.each do |story| %>
<tr>
<td><%= story.name %></td>
<td><%= story.description %></td>
<td><%= story.user.email %></td>
<td><%= link_to 'Show', story %></td>
<% if user_signed_in? %>
<td><%= link_to 'Edit', edit_story_path(story) %></td>
<td><%= link_to 'Destroy', story, method: :delete, data: { confirm: 'Are you sure?'} %></td>
<% end %>
</tr>
<% end %>
</tbody>
</table>
<%= link_to 'New Story', new_story_path %>
<%= render 'form' %>
_form.html.erb:
<%= form_for #story do |f| %>
<%= render 'shared/errors', object: #story %>
<div class="form-group">
<%= f.label :name %>
<%= f.text_field :name, class: 'form-control', required: true %>
</div>
<div class="form-group">
<%= f.label :description %>
<%= f.text_area :description, class: 'form-control', required: true, cols: 3 %>
</div>
<%= f.submit 'Create Story', class: 'btn btn-primary' %>
<% end %>
Story Controller:
class StoriesController < ApplicationController
before_action only: [:destroy, :show, :edit, :update]
def index
#stories = Story.order('created_at DESC')
end
def new
#story = current_user.stories.build
end
def create
#story = current_user.stories.build(story_params)
if #story.save
flash[:success] = "Your beautiful story has been added!"
redirect_to root_path
else
render 'new'
end
end
def edit
#story = Story.find(params[:id])
end
def update
#story = Story.find(params[:id])
if #story.update_attributes(params.require(:story).permit(:name, :description))
flash[:success] = "More knowledge, more wisdom"
redirect_to root_path
else
render 'edit'
end
end
def destroy
#story = Story.find(params[:id])
if #story.destroy
flash[:success] = "I think you should have more confidence in your storytelling"
redirect_to root_path
else
flash[:error] = "Can't delete this story, sorry"
end
end
def show
#stories = Story.find(params[:id])
end
private
def story_params
params.require(:story).permit(:name, :description)
end
end
My error in terminal:
ActionView::Template::Error (First argument in form cannot contain nil or be empty):
1: <%= form_for #story do |f| %>
2: <%= render 'shared/errors', object: #story %>
3:
4: <div class="form-group">
You should add #store variable to index action.
def index
#stories = Story.order('created_at DESC')
#story = current_user.stories.build
end

Rails Admin Devise - How to edit other users?

I have created a boolean on devise User to determine admins. Now that I have that, I have a list of Users. I want to make other users admins as well, so I put in Edit links, but they just keep linking to my own profile. Do I HAVE to use a gem like CanCan, or is there a way I can do this just with the boolean on a User?
admin_view.html.erb
<div>
<h1>Admin View</h1>
<table>
<thead>
<tr>
<th>First Name</th>
<th>Last Name</th>
<th>Native Language</th>
<th>Learning Language</th>
<th>Admin?</th>
<th colspan="3"></th>
</tr>
</thead>
<tbody>
<% #users.each do |user| %>
<tr>
<td><%= user.first_name %></td>
<td><%= user.last_name %></td>
<td><%#= user.meeting_time %></td>
<td><%= user.admin %></td>
<td><%= %></td>
<td><%= %></td>
<td><%= link_to 'Edit', edit_user_path(user) %></td>
<% end %>
</tr>
</tbody>
</table>
edit view
<h2>Edit <%= resource_name.to_s.humanize %></h2>
<%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %>
<%= devise_error_messages! %>
<div class="field">
<%= f.label :first_name %>
<%= f.text_field :first_name, autofocus: true %>
</div>
<div class="field">
<%= f.label :current_password %> <i>(we need your current password to confirm your changes)</i><br />
<%= f.password_field :current_password, autocomplete: "off" %>
</div>
<div class="form-group">
<%= f.label :admin %>
<%= f.check_box :admin %>
</div>
<div class="actions">
<%= f.submit "Update" %>
</div>
<% end %>
Still very new to rails, so please let me know if I need to post anything else. Thanks!
UPDATE
profiler_controller.rb
def admin_view
#users = User.all
end
This is a case for authorization (whether a user has permission).
As you've rightly stated, you can use CanCanCan (CanCan is no longer maintained) to do this, although there are many similar gems such as Pundit etc.
If using CanCanCan, I would use the following:
#config/routes.rb
resources :users do
post :admin #-> url.com/users/:user_id/admin
end
#app/models/ability.rb
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user (not logged in)
if user.admin?
can :manage, :user
end
end
end
This will allow you to use:
#app/views/admin/users/index.html.erb
<% #users.each do |user| %>
<tr>
<td><%= user.first_name %></td>
<td><%= user.last_name %></td>
<td><%= user.admin %></td>
<td><%= link_to "Admin?", user_admin_path(user), method: :post if can? :manage, user %></td>
<td><%= link_to 'Edit', edit_user_path(user) %></td>
</tr>
<% end %>
#app/controllers/users_controller.rb
class UsersController < ApplicationController
def admin
#user = User.find params[:user_id]
#user.toggle :admin if can? :manage, #user
redirect_to #user
end
end
I can provide more if required; this should answer your question though.

Multiple submit buttons rails

I am trying to make multiple submit buttons for a form, but I keep getting this error:
Couldn't find User with id=edit_individual
Found in:
# Use callbacks to share common setup or constraints between actions.
def set_user
#user = User.find(params[:id])
end
My controller page:
def edit_individual
if User.find_by_id (params[:user_ids])
if params[:Delete_multiple]
#users = User.find(params[:user_ids])
#users.each do |user|
user.destroy
end
redirect_to :back, notice: 'All selected users were successfuly deleted'
else
#users = User.find(params[:user_ids])
end
else
redirect_to :back, alert: 'No users were selected for editing'
end
end
def update_individual
#users = User.update(params[:users].keys, params[:users].values).reject { |p| p.errors.empty? }
if #users.empty?
redirect_to :back, notice: "Users updated"
else
render :action => "edit_individual"
end
end
My index page:
<%= form_tag edit_individual_users_path do %>
<table class="table table-hover">
<thead>
<tr>
<th></th>
<th><%= sort_link #q, :firstName, "First name" %></th>
<th><%= sort_link #q, :lastName, "Last name" %></th>
<th><%= sort_link #q, :registrationNumber, "Registration number" %></th>
<th><%= sort_link #q, :email, "Email" %></th>
<th></th>
</tr>
</thead>
<tbody>
<% #users.each do |user| %>
<% if (user.archive == false) %>
<tr>
<td><%= check_box_tag "user_ids[]", user.id %></td>
<td><%= user.firstName %></td>
<td><%= user.lastName %></td>
<td><%= user.registrationNumber %></td>
<td><%= user.email %></td>
<td><%= link_to 'View', user, class: 'btn btn-info btn-mini', title: 'View users\'s information' %></td>
<td><%= link_to 'Edit', edit_user_path(user), class: 'btn btn-warning btn-mini', title: 'Edit users\'s information', method: 'get' %></td>
<td><%= link_to 'Delete', user, class: 'btn btn-danger btn-mini', title: 'Delete user', method: :delete, data: { confirm: 'Are you sure?' } %></td>
</tr>
<% end %>
<% end %>
</tbody>
</table>
<p><%= select_tag :field, options_for_select([["All Fields", ""], ["First name", "firstName"], ["Last name", "price"], ["Registration number", "registrationNumber"], ["Email", "email"]]) %></p>
<p><%= submit_tag "Edit checked", name: 'Edit_multiple' %></p>
<p><%= submit_tag "Delete checked", name: 'Delete_multiple', data: { confirm: 'Are you sure you want to delete multiple users?' } %></p>
<% end %>
The edit_individual page:
<% form_tag update_individual_users_path, :method => :put do %>
<% for product in #products %>
<% fields_for "users[]", users do |f| %>
<h2><%=h user.name %></h2>
<%= render :partial "fields", :locals => { :f => f, :user => user } %>
<% end %>
<% end %>
<p><%= submit_tag "Submit" %></p>
<% end %>
The routes file:
resources :users do
get 'archive', on: :collection
post 'edit_individual', on: :collection
put 'update_individual', on: :collection
end
The delete part works fine. Also, the edit part actually edits the field, but displays that error (so it does not redirect back to the index page which makes it look bad).
Can anyone help me fix this? Spent a lot hours to have both edit and delete so it would be pretty bad if I had to delete the edit part.
EDIT 1: Also, If I am editing for example 3 users and click the submit button allowing only 2/3 to pass (due to validation), there is no error (I just get redirected back to edit_individual.html to finish the last user) and then if I have click submit again and everything passes, I get an error.
I have actually managed to find what was wrong.
The problem was with:
def update_individual
#users = User.update(params[:users].keys, params[:users].values).reject { |p| p.errors.empty? }
if #users.empty?
redirect_to :back, notice: "Users updated"
else
render :action => "edit_individual"
end
end
Instead of:
redirect_to :back
It should be:
redirect_to users_path
Or any other fixed path I suppose. Is there a way to keep "redirect_to :back", or do I have to create new methods for other views (within the same controller) that will use this?
it should not be run because you has not any params[:id]
if you user before_filter then
before_filter: set_user except: [:update_individual, :edit_individual]
def set_user
#user = User.find(params[:id])
end
and where you call update_individual in edit_individual

Why my form won't submit correctly?

I have User model and Comment model using acts_as_commentable_with_threading
I'm trying to put the comment function on each User's show page with partial.
But When I try to submit a form, it shows routing error.
Why is this?
Error
Routing Error
No route matches [POST] "/user/john/add_comment"
models/user.rb
acts_as_commentable
views/users/show.html.erb
<%= render 'comment', :user => #user %>
views/users/_comment.html.erb
<table>
<tr>
<th>ID</th>
<th>Title</th>
<th>Body</th>
<th>Subject</th>
<th>Posted by</th>
<th>Delete</th>
</tr>
<% #user.comment_threads.each do |comment| %>
<tr>
<td><%= comment.id %></td>
<td><%= comment.title %></td>
<td><%= comment.body %></td>
<td><%= comment.subject %></td>
<td><%= comment.user.user_profile.nickname if comment.user.user_profile %></td>
<td><%= button_to 'destroy', comment, confirm: 'Are you sure?', :disable_with => 'deleting...', method: :delete %></td>
</tr>
<% end %>
</table>
<%= form_for #user, :html => { :class => 'form-horizontal' } do |f| %>
<div class="field">
<%= f.label :body %><br />
<%= f.text_field :body %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
routes.rb
get "user/:id/add_comment" => 'users#add_comment', :as => :add_comment_user
get "user/:id/delete_comment" => 'users#delete_comment', :as => :delete_comment_user
users_controller.rb
def add_comment
#user = User.find_by_username(params[:id])
#user_who_commented = current_user
#comment = Comment.build_from( #user, #user_who_commented.id, params[:users][:body] )
#comment.save
redirect_to :controller => 'users', :action => 'show', :id => params[:users][:id]
flash[:notice] = "comment added!"
end
Because your route is for a GET, and the form is a POST.
It needs to be a post route. Also you are posting to user/:name/add_comment, you should post the the user id. You can use the name but this would need to be unique across all users.
This line is also wrong.
#user = User.find_by_username(params[:id])
You can either pass in the params[:username] of find_by_id

How to setup the actions with considering database design of rails3

Assume you have the model called 'Topic' as a parent, and 'Comment' as a child.
On the url 'topics/show/35' you can see all the comments that belongs to this topic ID#35.
When logged-in user want to post his new comment at this page,
should I write 'comment_create' action in topics_controller.rb?
or just write 'create' action in comments_controller.rb, and call it from this page?
Which one is regular way??
If I call 'create' action in comments_controller, how can I write in view to pass
'Model name' to add comments into
'Models ID#'
'comment body'
or should I just write actions separately like this?
controllers/comments_controller.rb
def create_in_topic
code here! to add new comment record that belongs to topic....
end
def create_in_user
code here! to add new comment record that belongs to user....
end
for your information, comment adding action should be something like this.
def create
#topic = Topic.find(params[:topics][:id] )
#user_who_commented = current_user
#comment = Comment.build_from( #topic, #user_who_commented.id, params[:topics][:body] )
#comment.save
redirect_to :back
flash[:notice] = "comment added!"
end
Example Updated!!!
views/topics/show.html.erb
<table>
<tr>
<th>ID</th>
<th>Title</th>
<th>Body</th>
<th>Subject</th>
<th>Posted by</th>
<th>Delete</th>
</tr>
<% #topic.comment_threads.each do |comment| %>
<tr>
<td><%= comment.id %></td>
<td><%= comment.title %></td>
<td><%= comment.body %></td>
<td><%= comment.subject %></td>
<td><%= comment.user.user_profile.nickname if comment.user.user_profile %></td>
<td> **Comment destroy method needed here!!!** </td>
</tr>
<% end %>
</table>
<%=form_for :topics, url: url_for( :controller => :topics, :action => :add_comment ) do |f| %>
<div class="field">
<%= f.label :'comment' %><br />
<%= f.text_field :body %>
</div>
<%= f.hidden_field :id, :value => #topic.id %>
<div class="actions">
<%= f.submit %>
<% end %>
controllers/topics_controller.rb
def add_comment
#topic = Topic.find(params[:topics][:id] )
#user_who_commented = current_user
#comment = Comment.build_from( #topic, #user_who_commented.id, params[:topics][:body] )
#comment.save
redirect_to :back
flash[:notice] = "comment added!"
end
I think the most straight forward implementation it's going to be an action (ie: add_comment) in your Topic Controller. Once the view call the TopicController#add_comment action you will have all your Topic information and also the comment data so you can easily add the comment to the Topic from there.
Let me know if you need further help.
FedeX
Well I'm not to sure because that gem, but you could try something like this:
<%=form_for #topic, url: url_for( :controller => :topics, :action => :add_comment ) do |f| %>
<table>
<tr>
<th>ID</th>
<th>Title</th>
<th>Body</th>
<th>Subject</th>
<th>Posted by</th>
<th>Delete</th>
</tr>
<% #topic.comment_threads.each do |comment| %>
<%= f.fields_for :comment_threads, comment do |comment_form| %>
<tr>
<td><%= comment.id %></td>
<td><%= comment.title %></td>
<td><%= comment.body %></td>
<td><%= comment.subject %></td>
<td><%= comment.user.user_profile.nickname if comment.user.user_profile %></td>
<td>Delete? <%= comment_form.check_box :_destroy %></td>
</tr>
<% end %>
<% end %>
</table>
<div class="field">
<%= f.label :'comment' %><br />
<%= f.text_field :body %>
</div>
<%= f.hidden_field :id, :value => #topic.id %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
Let me know if it helps you! FedeX

Resources