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 :)
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
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.
I have made my form:
<tbody>
<% #player.bases.each do |basis| %>
<td><%= basis.id %></td>
<td><%= image_tag(basis.image_url(:thumb), class: 'thumbnail') %></td>
<td><%= link_to basis.name, basis %></td>
<td><%= basis.cost %></td>
<td><%= basis.short_info %></td>
<td>
<%= form_for #player, url: {:controller => 'players', :action => :remove_detail} do |f| %>
<%= f.hidden_field :type, :value => 'basis' %>
<%= f.hidden_field :detail_id, :value => basis.id %>
<%= f.submit 'Remove',class: 'btn btn-danger' %>
<% end %>
</td>
<% end %>
</tbody>
In my routes, I have added this:
resources :players do
collection do
get 'search'
post 'remove_detail'
end
end
I have remove_detail in my players_controller.rb, and I have added this action to before_action to get current player. However when I press on my Remove button, it throws me error and tries to run update action of my controller. Why?
My before_action:
before_action :set_player, only: [:show, :edit, :update, :destroy, :remove_detail]
My remove_detail:
def remove_detail
type = params['type']
id = params['detail_id']
if type == 'basis'
basis = Basis.find(id)
name = basis.name
#player.bases.delete(basis)
end
redirect_to #player, notice: "#{name} detail is removed"
end
To fix that, try as follows:
First of all, I'd redefine your routes as follows:
resources :players do
member do
delete 'remove_detail'
end
collection do
get 'search'
end
end
This will generate proper url for deleting a detail for a "single Player":
/players/:id/remove_detail
Because of REST-y nature of Rails, we defined the url to be accessible by performing delete request.
Your form change accordingly:
<%= form_for #player, { url: { action: "remove_detail" }, method: :delete } do |f| %>
Changing your routes to use delete method is more to keep the convention of Rails. Post would make your application work too, but - its just Rails-y way.
Good luck!
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 display something like this on posts/index.html.erb
Post #1
Comment #1 for Post #1
Comment #2
Post #2
Comment #1 for Post #2
etc.
It works fine if I go to /posts/1/comments/, /posts/2/comments/ etc
Since it's using the index file, there is no :post_id in the URL and it throws a nil error. The models use the appropriate have_many and belongs_to.
Here's part of routes.rb
resources :posts do
resources :comments
end
resources :posts
Here's part of my posts_controller.rb
def index
#posts = Post.all
#comments = params[:post_id][:desc]
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => #posts }
end
end
Here's part of index.html.erb
<% #posts.each do |post| %>
<tr>
<td><%= post.title %></td>
<td><%= link_to 'Show', post %></td>
<td><%= link_to 'Edit', edit_post_path(post) %></td>
<td><%= link_to 'Destroy', post, :confirm => 'Are you sure?', :method => :delete %></td>
</tr>
<tr><td><%= #comments %></td></tr>
<% end %>
</table>
Thanks!
Well, since comments belongs_to a given post, you just need to have a separate loop in the view to iterate over each of the comments for a given post.
So take the #comments var out of your controller and index view, and do this in the index view instead where you currently have #comments:
<% for comment in post.comments %>
<tr><td><%= comment.user_name %><%= comment.text %></td></tr>
<% end %>
I made the user_name and text attrs up of course you would use whatever is in your comment model.
ian.