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.
Related
Lets start off with i am new to programming in rails and trying to learn by building a project. I am creating a project that has following and follower capability similar to twitter... I have implemented the option to delete a post. However, it seems that i can delete other people post as well that i am following etc. How can i implement the delete of my own post and have other user have the ability to edit modify and delete their own post.
post.rb
class Post < ActiveRecord::Base
belongs_to :user
validates :user_id, presence: true
validates :content, presence: true, length: { maximum: 140 } #
default_scope -> { order(created_at: :desc) } # newest tweets / posts first
end
post controller
def destroy
#status_update = Post.find(params[:id])
if #status_update.present?
#status_update.destroy
end
redirect_to root_url
end
home
<%= link_to('Delete', post_path(#p), :method => :delete,data: { confirm: "Are you sure?" } ) %>
i was also looking at something like this:
def owned_post
unless current_user == #post.user
flash[:alert] = "That post doesn't belong to you!"
redirect_to root_path
end
end
Lets say you have a Post model and views all set up:
In your views/posts/show you can set up something like this:
<% if #post.user.id == current_user.id %>
<%= link_to "Edit", edit_post_path(#post), class:"btn btn-success btn-sm" %>
<% end %>
You will still have a small issue, users can still access the form to edit, so now in your views/posts/edit it renders a form so put a condition on it:
<% if user_signed_in? %>
<% if #post.user.id == current_user.id %>
<%= render 'form', tutorial: #post %>
<% end %>
<% else %>
<h1>stop trying to edit others post</h1>
<% end %>
Good question, though there isn't one single answer I can give you. The question of "authorization" of actions in your app is a big one, and there are gems like Pundit that you could look into for a full-fledged solution.
However, it's always good to start with the basics on your own and build up to a bigger solution. What you have already isn't wrong -- just add before_action :owned_post, only: [:delete] (perhaps rename to ensure_post_owner or such) to your controller to enforce your rule.
Another option is to scope your ActiveRecord queries to the set of objects your current user is allowed to operate on. So instead of #post = Post.find(params[:id]), you could do, #post = current_user.posts.find(params[:id]). If the user tries to modify a post they don't own, they'll get back a 404 as if that post simply doesn't exist. This can be a good strategy to avoid letting attackers enumerate which posts are and aren't in your database.
On view specify something like this:
<% if user_signed_in? && current_user.id == #post.user_id %>
# something like edit. links... delete links..
<% end %>
or you can also use gem like: cancancan
The following Rails 4 link_to is wrong, and thus I'm unable to delete and not sure why. In this project, "bookmarks" is a nested resource under "users" so rake routes gives me:
DELETE /users/:user_id/bookmarks/:id(.:format) bookmarks#destroy
View:
<% #bookmarks.each do |bookmark| %>
<%= link_to "delete", user_bookmarks_path(#user, bookmark.id), method: :delete %>
<% end %>
Controller:
def destroy
#user.bookmarks.find(params[:id]).destroy
redirect_to root_path
end
private
def bookmark_params
params.require(:bookmark).permit(:title, :bookmark_url)
end
def get_user
#user = User.friendly.find(params[:user_id])
end
The result is a link that looks like http://www.example.com/users/jane-doe/bookmarks.6 where 6 is the correct ID of the bookmark to be deleted. But I don't understand why it's not creating /bookmarks/6, which I think would then work fine with destroy in my controller. It feels like there's some big conceptual piece I'm just not understanding. Any tips are appreciated.
Your view should look something like this. It appears your path name is incorrect:
<% #bookmarks.each do |bookmark| %>
<%= link_to "delete", user_bookmark_path(#user, bookmark.id), method: :delete %>
<% end %>
For my projects I have the following relevant code
routes:
resources :lists do
resources :items
end
I now included a loop on list/show page in which I want to show the item and provide the users with the possibility to delete the item.
So i got code like this:
<% #items.each do |item|%>
<p>
Item: <%= item.name %>
<%= time_ago_in_words(item.created_at) %> ago.
</p>
<%= link_to "Delete", [#list.item, item], method: :delete %>
<% end %>
But when I try to run it I get the error:
undefined method 'item' for #<List:0x007fba7be6fe28>
While I did define the variables in my controller:
Items-controller:
def destroy
#list = current_user.list
#item = #list.items
end
Could anyone explain whats causing this error?
you have a typo in your controller - it should be #items = #list.items that's why the iteration doesn't work properly.
edit: after formatting your original question, I see that the error was raised on #list object, so you have to fix path to delete action:
<%= link_to "Delete", [#list, item], method: :delete %>
you build the path by providing the parent object (#list) and then the object itself (item) - Rails will translate it to list_item_path.
change your link_to
= link_to("Delete", lists_items_url(#list, #item), method: :delete)
You have defined resources :lists and resources :items.
Basically what you want to do is send a DELETE request to the collection with the IDs to fetch and delete the items.
Run a rake routes to check the exact naming, but I think it's written like that to be sure. Although you don't seem to be needing the IDs based on your controller's action, you still need to supply them for the helper.
You're using #items.each and in your controller have only #item.
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.
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 %>