What is wrong with this code? A normal user still being able to see all relatos, when he should only see his own.
My view code:
<% if can? :read, Relato %>
<td><%= relato.id %></td>
<td><%= relato.cliente.name %></td>
<td><%= relato.projeto.name %></td>
<td><%= relato.local.logra %></td>
<td><%= relato.time %></td>
<td><%= relato.comment %></td>
<% end %>
My Ability class:
can :manage, :all if user.role == "admin"
if user.role == "normal"
can :read, Relato , :user_id => user.id
can :manage, Relato, :user_id => user.id
end
You need to authorize the user for a particular instance:
<%= if can? :read, relato %>
When you attempt to authorize a user for an entire class, as you do above, CanCanCan ignores any conditions defined in the Ability because it can't determine a user_id field for the entire Relato model; it can only do so for a single relato instance.
Related
I'm setting up cancancan to implement this function:
if the user is admin, it can destroy every user but himself.
This is my ability.rb
class Ability
include CanCan::Ability
def initialize(user)
if user.role == 'admin'
cannot :destroy, User, id: user.id
end
end
end
And here's my view
<h1>Listing Users</h1>
<table>
<thead>
<tr>
<th>E-Mail</th>
<th>Name</th>
<th>Role</th>
<th colspan="3"></th>
</tr>
</thead>
<tbody>
<% #users.each do |user| %>
<tr>
<td><%= user.email %></td>
<td><%= user.name %></td>
<td><%= user.role %></td>
<% if can? :destroy, #user %>
<%= link_to 'Destroy', user, method: :delete, data: { confirm: 'Are you sure?' } %>
<% end %>
</tr>
<% end %>
</tbody>
</table>
And even with this setup there's no destroy link at the end of every user now. What I want is that there's a destroy link behind every users but the admin himself. What should I do? Thanks!
Your main issue is that you're calling if can? :destroy, #user when #user doesn't exist:
<% #users.each do |user| %>
<% if can? :destroy, user %>
<% end %>
If the original answer does not work, perhaps you'd be better using a block to evaluate the user object:
#app/models/ability.rb
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new
case user.role
when "admin"
cannot :destroy, User do |x|
x.id == user.id
end
end
end
end
end
This will allow you to use:
<%= link_to 'Destroy', user, method: :delete, data: { confirm: 'Are you sure?' } if can? :destroy, user %>
... although fixing the first issue, I think, will resolve your issue.
Just because an admin cannot destroy itself, does not give it permission to destroy other users. You could try giving it destroy permissions.
class Ability
include CanCan::Ability
def initialize(user)
if user.role == 'admin'
cannot :destroy, User, id: user.id
else
can :destroy, User
end
end
end
I made a simple role based auth with Sorcery and Cancan, which database holds a column named ROLE that when registering a user can be admin or normal,
Relato is a scaffold where you can create "Reports" i wanted to normal user can only see those created by himself and do other stuff(update,destroy) as well.
my ability.rb
def initialize(user)
if user.role == 'admin'
can :manage, :all
elsif user.role == 'normal'
can :create, Relato
can :manage, Relato, :user_id => user.id
can [:read, :update, :destroy], User, :id => user.id
end
no need for control protection
in my view index.html.erb where it lists all "reports" i put
<% if can? :index, Relato %>
<tbody>
<% #relatos.each do |relato| %>
<tr class="alt">
<td><%= relato.cliente.name %></td>
<td><%= relato.projeto.name %></td>
<td><%= relato.local_id %></td>
<td><%= relato.task_id %></td>
<td><%= relato.time %></td>
<td><%= relato.comment %></td>
<td><%= relato.isdoe %></td>
<td><%= link_to 'Editar', edit_relato_path(relato) %></td>
<td><%= link_to 'Deletar', relato, method: :delete, data: { confirm: 'Are you sure?' } %>
</tr>
<% end %>
<% end %>
But it doesn't work, the user can't see his reports, using admin account everything is fine.
Since you have a collection of #relatos, you should not rely on a instance of Relato to check the ability.
Consider using something like can?(:index, Relato). Notice that I'm using the class.
Now you can setup the ability, but since the class is being used, you cannot check check attributes like user_id.
Since you have can :manage, :all for admin, they should be able to read the #relatos.
Let me know if you were trying to achive something else.
<% if can? :read, Relato %>
Not #Relatio.
Also you might want to consider using cascading abilities. Simply put an admin gets all the abilities of a normal user. Plus he gets some special admin abilities. To show why this is a good idea imagine if realize that you also need a editor role:
def initialize(user)
if user.role == 'admin'
can :manage, :all
elsif user.role == 'normal'
can :create, Relato
can :manage, Relato, :user_id => user.id
can [:show, :update, :destroy], User, :id => user.id
elsif user.role == 'editor'
can :manage, Relato
can [:show, :update, :destroy], User, :id => user.id
end
end
Thats alot of duplication. Instead:
def initialize(user)
# Anybody can change their profile
can [:show, :update, :destroy], User, :id => user.id
# Anybody can edit Relatos they own.
can :manage, Relato, :user_id => user.id
if user.role == 'admin'
can :manage, :all
end
if user.role == 'editor'
can :manage, Relato
end
end
I am working on something that should be simple, but I am having trouble because I am new to rails.
I have a table called TimeSheet and table called Entry.
I am working in my time_sheet show view and want to iterate through the related entries on my time_sheet.
MY models are setup like this. A user has_many :time_sheet. A time sheet belongs_to :user and has_many :entries. And entries belong_to: time_sheet
My time sheet view looks like:
<% #current.each do |t| %>
<td><%= t.entries :customer_name %></td>
<td><%= t.entries :order_number %></td>
<td><%= t.entries :time_in %></td>
<td><%= t.entries :time_out %></td>
<% end %>
My controller for the time_sheet show is:
def show
if current_user
#current = current_user.time_sheets.entries
else
redirect_to new_user_session_path, notice: 'You are not logged in.'
end
I am getting strange output that looks like this for each iteration:
#<ActiveRecord::Associations::CollectionProxy::ActiveRecord_Associations_CollectionProxy_Entry:0xb59dfcc8>
Any help will be greatly appreciated.
Because you already
#current = current_user.time_sheets.entries
and
<% #current.each do |t| %>
So the "t" is each entry of your related time_sheets. Are you sure there is another entries? Even assume the statement don't have syntax error.
<td><%= t.entries :customer_name %></td>
How about like this.
def show
if current_user
#time_sheets = current_user.time_sheets
else
redirect_to new_user_session_path, notice: 'You are not logged in.'
end
end
In view:
<% #time_sheets.each do |sheet| %>
<% sheet.entries.each do |t| %>
<td><%= t.customer_name %></td>
<td><%= t.order_number %></td>
<td><%= t.time_in %></td>
<td><%= t.time_out %></td>
<% end %>
<% end %>
I assume customer_name, order_name are the fields name in your entry table.
I'm trying to allow a user to read, update, or destroy a form that they themselves have created. I'm having some trouble.
In my ability.rb, I have
can [:update, :read, :destroy], Form.where(:user => user)
given that user is an attribute of the form. I can get the forms (and the responses to the forms) to display when I don't have this line in my ability class, but I obviously do not want people to be able to delete the forms of other users. I have already called
load_and_authorize_resource
to invoke CanCan in the controller. Currently I have this error:
The can? and cannot? call cannot be used with a raw sql 'can' definition. The checking code cannot be determined for :index Form(id: integer, user_id: integer, policy: boolean, two_adult: boolean, training: boolean, attribute_name: boolean, attribute: boolean, agree_to_form: boolean, user_signature: string, signature_date: date, printed_date: datetime, created_at: datetime, updated_at: datetime)
(Changed some names for privacy. I'm also using Devise, by the way, if this changes anything)
Any help would be greatly appreciated. Thanks!
Edit: Would a solution such as...
can :read, Form do |form|
form.user == user
end
be a possibility?
Edit2: My view...
<% #forms.each do |form| %>
<tr>
<td><%= form.user %></td>
<td><%= form.policy %></td>
<td><%= form.two_adult %></td>
<td><%= form.training %></td>
<td><%= form.other_attribute %></td>
<td><%= form.attribute %></td>
<td><%= form.agree_to_covenant %></td>
<td><%= form.user_signature %></td>
<td><%= form.signature_date %></td>
<td><%= form.printed_date %></td>
<% if can? :read, #form %>
<td><%= link_to 'Show', form %></td>
<% end %>
<% if can? :update, #form %>
<td><%= link_to 'Edit', edit_form_path(form) %></td>
<% end %>
<% if can? :destroy, #form %>
<td><%= link_to 'Destroy', form, method: :delete, data: { confirm: 'Are you sure?' } %></td>
<% end %>
</tr>
<% end %>
I also noticed upon refreshing the page that the user hash changes each time. This is problematic.
Edit3: Pertinent Ability.rb info...
if user.is_camper?
can :read, Camp
can :read, Payment, Payment.where(:user => user)
can :create, Payment
can :read, Form, :user => user.id
Here is a visual of what happens too.
Your last variant (defined through block) should work.
See examples here.
In your case you can define through hash - it is simpler
can :read, Form, :user_id => user.id
And according to this point you can separately define :read, :create, :update and :destroy
Code on the page:
You iterate by form, so you should check ability on each of them (NOT for one #form)
<% #forms.each do |form| %>
...
<% if can? :read, form %>
...
<% end %>
<% if can? :update, form %>
...
<% end %>
<% end %>
Changes in ability:
can :read, Form # user can read all forms (not only own)
can [:create, :update, :destroy], Form, :user_id => user.id # it is better to use `user_id` instead of `user`
It is just one variant that should work properly but it is not tested by me :)
Easy for anyone but this newbie, I'm sure, but I can't find the answer anywhere. I have a User model and a Role model, with role_id in the users table; I want to show the actual role (Admin, Visitor, etc) which resides in the roles table, on my users index page.
The pertinent section of the index.html.erb:
<% #users.each do |user| %>
<tr>
<td><%= user.username %></td>
<td><%= user.email %></td>
<td><%= user.role_id %></td>
<td><%= link_to 'Show', user %></td>
<td><%= link_to 'Edit', edit_user_path(user) %></td>
<td><%= link_to 'Destroy', user, :confirm => 'Are you sure?', :method => :delete %></td>
</tr>
<% end %>
So what do I put in place of user.role.id?
role.rb:
class Role < ActiveRecord::Base
end
user.rb:
class User < ActiveRecord::Base
has_one :role
end
I'm using Rails 3, fwiw.
TIA
Your models are indeed configured incorrectly, but from what I can understand of the question you want the following.
class User < ActiveRecord::Base
belongs_to :role
end
class Role < ActiveRecord::Base
has_many :users
end
And then you can do the following:
<%= user.role.name_field %>
This will allow multiple Users to all have the same role. Instead of enforcing a one to one relationship. No schema change is needed.