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.
Related
I have a database with people and nicknames, nicknames are nested resources to people.
All works fine except one thing:
I am displaying a list of nicknames on the "show" page for people - next to them, I have a little link that says "delete". However I cannot delete them - I get an error as follows:
Couldn't find Person with 'id'=44
def load_person
#person = Person.find(params[:person_id])
end
The load_person method is defined in the nicknames_controller as
before_action :load_person
def load_person
#person = Person.find(params[:person_id])
end
Here's the destroy method from nicknames_controller.rb:
def destroy
#nickname.destroy
respond_to do |format|
format.html { redirect_to #person, notice: "Nickname was successfully destroyed." }
format.json { head :no_content }
end
end
Here's the view snippet of views/persons/show.html.erb:
<% #person.nicknames.each do |aka| %>
<li>
<%= link_to nickname.display_name, edit_person_nickname_path(#person, aka) %>
<%= link_to '', person_nickname_path(aka), method: :delete, data: { confirm: 'Are you sure?' }, class: "far fa-trash-alt text-error", title: "Remove" %>
</li>
<% end %>
So the route to "delete" is something like
http://127.0.0.1:3000/persons/44/nicknames/15
WHen I display the nickname (show method) and delete from there (same route as above), everything works fine.
What am I overlooking?
ok - so as simple as it was stupid. the delete link had the wrong path - it should be
<%= link_to '', person_nickname_path(#person, aka) .... %>
but I had
..person_nickname_path(aka) ...
In a view (index.html.erb), I have a list of all my articles:
<% #articles.each do |article| %>
...
<%= link_to 'Show', article_path(article) %>
<%= link_to 'Edit', edit_article_path(article) %>
<%= link_to 'Delete', article_path(article), method: :delete, data: { confirm: 'Are you sure?' } %>
...
<% end %>
And I would like to make a link (that only admins see), where the admin can set the article-attribute 'illegal' to be true/false. Something along these lines:
<% if current_user.try(:admin?) %>
<%= link_to "Mark as illegal", article, :method => "set_article_as_illegal" %>
<% end %>
This obviously doesn't work. But I don't know how to do this, in the best way. The only way I can think of, is really clumsy (Option B from below).
The way, that I think it should be done (or at least something along those lines).
A) Making two helper-functions as such:
def set_article_as_illegal(article)
article.update_attribute :illegal, true
end
def set_article_as_legal(article)
article.update_attribute :illegal, false
end
And somehow calling them like such:
<%= link_to "Mark as illegal", article, :method => "set_article_as_illegal" %>
... But I don't know how to make a link (or a button) that performs a method and takes an input (which should be the article in this case).
B) Making a new controller (or a bunch of them) to do it.
This just seems so wrong, but it's the only way, where I would be able to achieve what I want to achieve at this given point, which is why I mention it. So it would mean:
Making a route: get '/make_article_illegal', to: 'article#make_illegal'
Making another route: get '/make_article_legal', to: 'article#make_legal'
Making a controller make_illegal that handles making it illegal.
Making another controller make_legal that handles making it legal.
But this seems sooo clumsy.
There must be an obvious way to do this, that I'm missing.
There are endless (almost) ways of doing this of course. One way is to.
# routes.rb
get '/update_article_legality/:id/:illegal' => 'article#update_legality', as: :update_article_legality
# in the view
= link_to 'Make legal', update_article_legality_path(id: article.id, illegal: 'false')
= link_to 'Make illegal', update_article_legality_path(id: article.id, illegal: 'true')
# in controller
def update_legality
article = Article.find(params[:id])
illegal = params[:illegal] == 'false' ? false : true
article.update_attribute :illegal, illegal
end
Another way and more of an MVC one is to actually create two controllers with just one method each to update the legality. Maybe this is overkill for your situation but this is the theory at least.
# app/controllers/article/illegal_controller.rb
class Article::IllegalController < ApplicationController
def update
article = Article.find(params[:id])
article.update_attribute :illegal, false
end
end
# app/controllers/article/legal_controller.rb
class Article::LegalController < ApplicationController
def update
article = Article.find(params[:id])
article.update_attribute :illegal, true
end
end
# routes.rb
get '/make_article_legal' => 'article/legal#update', as: :make_article_legal
get '/make_article_illegal' => 'article/illegal#update', as: :make_article_illegal
# in the view
= link_to 'Make legal', make_article_legal_path(id: article.id)
= link_to 'Make illegal', make_article_illegal_path(id: article.id)
PS: I haven't tested this code, so there might be the odd spelling error, but it works theoretically at least.
There is an interesting article on this matter here.
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 %>
I'm trying to figure out how to allow a user to click on a link or button on the index page to clear out all of the objects from the app's database, and then redirect to a newly cleared index page. So, with the example model Article, I expect it should have something to do with an Article.destroy_all method, and I'm expecting it would be a simple solution, but I've tried some variations and am just not sure of how to actually implement it.
So it would be another action in your controller. If we're dealing with Articles then the controller would be:
class ArticlesController < ApplicationController
def indef
#articles = Article.all
end
def destroy_them_all
Article.destroy_all
redirect_to articles_path
end
end
And in the view where you want the user to click on a button to destroy all articles:
<%= link_to 'Destroy them all', destroy_them_all_path, method: :delete, confirm: 'Are you crazy?' %>
Don't forget to add a named route in your routes file:
match '/articles/destroy_them_all', to: 'Articles#destroy_them_all', via: :delete
That should work. Though you might have to check rake routes to make sure I got the destroy_them_all_path correct.
try this:
Article controller:
def destroy_all
#Articles = Article.all
#Articles.each do |a|
a.destroy
end
redirect_to articless_path, notice: "Delted"
end
routes:
post "articles/destroy_all"
view:
<%= form_tag ({ :controller => 'articles', :action => 'destroy_all' }) do%>
<%= submit_tag 'destroy all'%>
<% end %>
I have a List object, with nested Tasks. I have created a page that displays individual tasks, and also a page that allows a user to edit individual tasks. I now want to add the ability to delete a task from a list on the tasks edit page. Using the following code
<%= link_to 'Delete this task',#task, confirm: 'Are you sure?', method: :delete %>
yields
undefined task_path method
This code is on the show.html.erb page, where I call #task to display all of the data stored within the task, so I believe that this issue may be a routing error of some kind, however I cannot seem to figure it out.
The related controller method is
def destroy
#task = Task.find(params[:id])
#task.destroy
respond_to do |format|
format.html { redirect_to list_tasks_path(#task) }
format.json { head :ok }
end
end
I thought that with the delete method the #task I supplied would just be sent to the destroy method via params, but this error seems to be showing that this isn't exactly how it works. So how can I properly destroy a nested resource in Rails?
edit:
Here is the route file with nested resources:
MyApp::Application.routes.draw do
resources :lists do
resources :tasks
end
get "home/index"
root :to => 'home#index'
end
Thank you for your help!
You should have #list setup, or use #task.list (assuming you have a belong to relationship), and you could do the following:
<%= link_to "Delete this task", list_task_path(#task.list, #task), confirm: "Are you sure?", method: :delete %>
Cheers!
Try this:
<%= link_to 'Delete this task', list_task_path(#list, #task), confirm: 'Are you sure?', method: :delete %>
Or if you want it more compact (like you've written it):
<%= link_to 'Delete this task', [#list, #task], confirm: 'Are you sure?', method: :delete %>
Either way, since it's a nested resource, you must pass in both the #list and #task objects.