undefined method `user?' in rails4 - ruby-on-rails

I am using devise gem. My project's purpose user can upload files & delete thier own file but not other user's file. So, I write "user?" method for ensure that correct user can only show the delete button. I also ensure the 'correct_user' method for delete file. but now I'm faceing this problem "NoMethodError at /upload_files.."
"undefined method `user?' "
Here is my upload_files_controller.rb file:
class UploadFilesController < ApplicationController
before_action :logged_in
before_action :correct_user, only: :destroy
def index
#upload_files = UploadFile.all
end
def new
#upload_file = UploadFile.new
end
def create
#upload_file = current_user.upload_files.build(upload_params)
if #upload_file.save
redirect_to upload_files_path, notice: "The file #{#upload_file.name} has been uploaded."
else
render "new"
end
end
def destroy
upload_file = UploadFile.find(params[:id]).destroy
redirect_to upload_files_path, notice: "The file #{upload_file.name} has been deleted."
end
def user?(check_user)
check_user == current_user.id
end
private
def upload_params
params.require(:upload_file).permit(:name, :upload_file)
end
def logged_in
if admin_signed_in?
return true
else
authenticate_user!
end
end
def correct_user
#upload_file = current_user.upload_files.find_by(id: params[:id])
redirect_to root_url if #upload_file.nil?
end
end
Here is my upload_files/index.html.erb file:
<% #upload_files.each do |file| %>
<tr>
<td><%= file.name %></td>
<td><%= link_to "Download File", file.file_name_url %></td>
<%if admin_signed_in? %>
<td><%= button_to "Delete", file, method: :delete, class: "btn btn-danger", confirm: "Are you sure that you wish to delete #{file.name}?" %></td>
<% else user?(file.user_id) %>
<td><%= button_to "Delete", file, method: :delete, class: "btn btn-danger", confirm: "Are you sure that you wish to delete #{file.name}?" %></td>
<% end %>
</tr>
<% end %>
what did I wrong? please show me a way.
Thanks,
Mezbah

You should place your user? method in helper.
About your view code, why don't you use boolean alternative?
<% if admin_signed_in? || user?(file.user_id) %>
<td><%= button_to "Delete", file, method: :delete, class: "btn btn-danger", confirm: "Are you sure that you wish to delete #{file.name}?" %></td>
<% end %>

Marek is right, however, you may wish to use an authorization gem such as CanCanCan for this
There's a great Railscast about authorization here:
To give you a brief synopsis, authorization is the authority that a user has to CRUD an object. Authentication (Devise) is for giving the user "permission" to use various features in the application; authorization is allowing the user to edit / change data depending on their level of access
Your choice of trying to add a button so that users will be able to remove their own object. This is perfect CanCanCan territory:
--
CanCanCan
This gem was originally called "CanCan", but as Ryan Bates has gone on leave, some of the Rails community took it upon themselves to make their own gem, calling it CanCanCan
The way it works is relatively simple:
Have an "ability" model to define user abilities
Call the can? method to determine if a user can partake in a particular action
This means that you'll be able to make an extensible piece of functionality which will grant your users access to specific objects as required. Here's how:
> rails g cancan:ability
This will create the ability model to define all the methods:
#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, :all
else
can :read, :all
end
end
end
This will give you the ability to then call the can? method on your various objects:
<td>
<% if admin_signed_in? || (can? :destroy, file) %>
<%= button_to "Delete", file, method: :delete, class: "btn btn-danger", confirm: "Are you sure that you wish to delete #{file.name}?" %>
<% end %>
</td>

I would add the following method to your application_controller.rb. That makes that method available in all controllers and views within your application.
# application_controller.rb
def current_user?(user)
current_user == user
end
helper_method :current_user?
Use that method like this in your view:
# in view
<% if admin_signed_in? || current_user?(file.user) %>
<td>
<%= button_to('Delete', file,
method: :delete,
class: 'btn btn-danger',
confirm: "Are you sure that you wish to delete #{file.name}?") %>
</td>
<% end %>

Related

Allow Admin to edit other user's pins - Rails

I added an admin boolean column to my user table and toggled my user to yes. I changed the following code so that the "edit" button shows to teh admin for all pins:
<% if current_user == pin.user || current_user.admin? %>
<p>
<%= link_to 'Edit', edit_pin_path(pin) %>
<%= link_to content_tag(:i, "", class:"icon-trash"), pin, method: :delete, data: { confirm: 'Are you sure?' } %>
</p>
<% end %>
When I try to edit a pin, I get an error: Couldn't find Pin with id=37-outer [WHERE "pins"."user_id" = 1]
I believe this is because of the pins controller, but I haven't figured out how to correctly change it to allow admins to edit. Here's my controller:
def edit
#pin = current_user.pins.find(params[:id])
end
I have a feeling this is an easy fix that I'm just missing.
you are scoping pins to the current_user which is the reason why you're getting the error. Try changing the code that finds the pin to
klass = Pin
klass = klass.where(user_id: current_user.id) unless current_user.admin?
#pin = klass.find(params[:id])
alternatively, if this is something you'll be using in a lot of places, it may be good to define a class method instead
# app/models/pin.rb
def self.find_for_user(pin_id, user)
user.admin? ? find(pin_id) : user.pins.find(pin_id)
end
# in your controller
#pin = Pin.find_for_user(params[:id], current_user)

Rails DELETE http method renders page

I am trying to add a delete functionality to my model. This is what I've come up with, but even though I don't need to render the page to delete something, Rails renders and couldn't find the file for "delete.html.erb"
I am using Ruby 2.0dev and Rails 4.0
My delete link:
<%= link_to "Delete", reservation_delete_path(item), :class => "btn btn-small btn-danger", method: :delete, data: {confirm: 'Are you sure?'} %></td>
My routes file:
match 'reservations/delete/:id' => 'reservations#delete', via: :delete, :as => 'reservation_delete'
My Controller:
def delete
#current = Reservations.find(params[:id])
if current_user
if #current.user_id == current_user.id
#current.destroy!
redirect_to reservations_path
else
redirect_to reservations_path
end
else
redirect_to reservations_path
end
end
There is no need to duplicate the redirect 3 times for each condition. You can simplify your delete method:
def delete
#current = Reservations.find(params[:id])
if current_user && #current.user_id == current_user.id
#current.destroy!
end
redirect_to reservations_path
end
In your question, if current_user isn't available, you have no redirect, and so an implicit render is being run.
Your setup is not idiomatic, and there's code you didn't include, so anything could be going wrong. For example, that can't be your whole routes file; there's nothing specifying an index/show/edit/whatever page where your delete button would be. Another example: your action is named delete instead of destroy. Anyway I can show you an example that works and is much more canonical:
models/reservation.rb:
class Reservation < ActiveRecord::Base
end
controllers/reservations_controller.rb:
class ReservationsController < ApplicationController
def index
#reservations = Reservation.all
end
def destroy
#reservation = Reservation.find(params[:id])
#reservation.destroy
redirect_to reservations_url
end
end
views/reservations/index.html.erb:
<% #reservations.each do |reservation| %>
<%= link_to 'Destroy', reservation, method: :delete, data: { confirm: 'Are you sure?' } %>
<% end %>
(this will literally only show links for deleting corresponding reservations... you'll have to stick <%= reservation.name %> or whatever in there if you want to see more info)
config/routes.rb:
Howdy::Application.routes.draw do
resources :reservations, only: [:index, :destroy]
root 'reservations#index'
end
(my app name is howdy)
You have some user auth going on, so add that accordingly. If you're inheriting from a controller that does special user-auth stuff before even hitting the action, that might be why it's trying to render delete.html.erb
Looks like you're missing a return with those redirects, which is actually causing Rails to both execute the redirect and try to render the view.
return redirect_to reservations_path
Two things:
The delete (destroy) action is part of resources when you specify it in the routes file. To do this the "rails" way, you might consider having your routes file look more like:
resources: :reservations, only: [:delete]
... then having the delete link be more like:
<%= link_to 'Delete', delete_reservation_path(item), :class => 'btn btn-small btn-danger', method: :delete, data: {confirm: 'Are you sure?'} %>
... and then in your controller you can:
def destroy
#current = Reservations.find(params[:id])
if current_user
if #current.user_id == current_user.id
#current.destroy!
redirect_to reservations_path
else
redirect_to reservations_path
end
else
redirect_to reservations_path
end
end
... or you could actually create an rjs template for the delete action to do some fancy javascript work, or you could simply render the view for the index action (faster load the redirecting).
My recommendation when you start putting up && gates is to check to see if there is an existing solution. In this case you're probably looking for the functionality that is available in the CanCan gem.
CanCan
basically you load_and_authorize your users before the controller action and check them through an Ability model. You also get view helpers like
if can? :destroy, reservation
... do awesome stuff here ...
This will be a far better solution over the long run.

How to I get the destroy action to work with administrative control in Rails?

I am going through Michael Hartl's book, "Ruby on Rails Tutorial Learn Web Development with Rails". In the section where he explains how to get administrative access to delete users, I can't seem to get this to work. The delete link won't show up on the web app, and I am mystified as to why this is occurring. This is my unfactored code below, which sits in the directory of: app/views/users/index.html.erb
<% provide(:title, 'All users') %>
<h1>All users</h1>
<%= will_paginate %>
<ul class="users">
<% #users.each do |user| %>
<li>
<%= gravatar_for user, size: 52 %>
<%= link_to user.name, user %>
<% if current_user.admin? && !current_user?(user) %>
| <%= link_to "delete", user, method: :delete,
data: { confirm: "Are you sure?" } %>
<% end %>
</li>
<% end %>
</ul>
<%= will_paginate %>
The methods of current_user.admin? && !current_user?(user) are methods located in: app/helpers/sessions_helper.rb
def current_user
remember_token = User.encrypt(cookies[:remember_token])
#current_user ||= User.find_by(remember_token: remember_token)
end
def current_user?(user)
user = current_user
end
I've been stuck on this problem for quite a while and have tried many different things to solve the problem, and none of them have worked. I'm running Rails on Windows 7 using the Rubymine IDE. The tutorial was done on a Mac OS X operating system, and I'm thinking maybe the problem might be associated with my setup.
Also are there known issues with tests failing using the Windows cmd when running RSpec versus running it on Mac OS X?
Any help would be much appreciated.
Thank you.
I just figured out what was wrong!
This method was written incorrectly
def current_user?(user)
user = current_user
end
The correct method to get the corresponding code above to work is this:
def current_user?(user)
user == current_user
end
Thank you for all your input!
Make sure in your controller you're calling the destroy action in your controller. Your code looks fine thus far and I'm assuming you're not using cancan for authorization.
In view file
<% if current_user.admin? && !current_user?(user) %>
<%= link_to "delete", user, method: :delete, data: { confirm: "Are you sure?" } %>
<% end %>
Controller
def destroy
#user = User.find(params[:id])
#user.destroy
respond_to do |format|
format.html { redirect_to users_path }
format.json { head :no_content }
end
end

Denying unauthorized users from editing a profile

I am trying to setup so that users will get a "not authorized" message if they click edit for a profile that is not theirs. This message should of course not appear for admins since admins can edit all profiles. I previously done this on Permission.rb, however I got rid of the file to go with a more basic user roles/authorization.
I don't see how I can implement what I had previously on Permission.rb for my current files. I have tried some solutions but they don't add up. If someone could point me in the right direction that will be great. Also I am doing this all from scratch, user authentication/authorization.
index.html.erb:
<% #users.each do |user| %>
<li>
<% if current_user.admin? || current_user == #user %>
<% end %>
<%= link_to "Edit #{user} profile", user %>
| <%= link_to "delete", user, method: :delete,
data: { confirm: "You sure?"} %>
</li>
<% end %>
Why are you giving the chance for users to edit other peoples profiles?
First, you should have a if statement in your view where you show the link for the edit page. I guess this is showing up on a profile of every user, so i suppose the code in your controller is something like this:
def show
#user = User.find(params[:id])
end
Then in your view you should have something like this:
<% if current_user.admin? || current_user == #user %>
<%= link_to 'Edit Profile' , edit_user_path(#user) %>
<% end %>
There is also a case if someone tries to 'force' their way in, just like trying to type a url www.yourapplication.com/users/6/edit you could write a before_filter method in your controller:
before_filter :check_privileges, only => [:edit, :update]
and then write a method in called check_privileges
def check_privileges
unless current_user.admin? || current_user.id == params[:id]
flash[:warning] = 'not authorized!'
redirect_to root_path
end
end
EDIT: After the questioner edited his code, i'm showing the mistake:
You are putting the end too soon:
<% #users.each do |user| %>
<li>
<%= 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 %>
</li>
<% end %>

Making a user an admin user through a button without having the attribute accessible in Rails

From what I understand it would create a security flaw if I were to let my admin boolean in my users model be listed in attr_accessible. However I want a link on my show page that lets existing admin users grant admin privileges to other users. I was wondering how to go about doing this? My code in my show view for a user at the moment includes:
<% if current_user.admin? && #user.admin == false %>
<%= link_to "Make Administrator", '#',
data: { confirm: "Make this user an admin?" }, class: "btn btn-large btn-primary" %>
<% elsif current_user.admin? && #user.admin%>
<%= link_to "Remove Administrator", '#', class: "btn btn-large btn-danger" %>
<% end %>
I'm not entirely sure what to put instead of the '#'? #user.toggle!(:admin) doesn't seem to work so any pointers would be appreciated. Thank you in advance!
First, since this is an action this should be a button, not an link.
<%= button_to user_path(#user), :method => :put ... %>
controller code
def update
if params[:admin]
user.update_attribute(:admin, true)
redirect_to ...
end
...
end
or if you prefer to keep your logic in the model
def update
...
if params[:admin]
#user.make_admin
end
...
end
model code
def make_admin
self.update_column(:admin, true)
end

Resources