I have a Review model which is nested resource of Publication model. Review model have accept_nested_attributes_for review_comments. I wonder how could I show delete path to delete review_commment?
<% #review.review_comments.each do |review_comment| %>
<%= link_to "delete", ???, method: :delete %>
<% end %>
review.rb
has_many :review_comments, :dependent => :destroy
accepts_nested_attributes_for :review_comments, :allow_destroy => :true
review_comment.rb
belongs_to :review
publication.rb
has_many :reviews
routes.rb
resources :publications do
resources :reviews
end
resources :review_comments
UPDATE
def create
#review_comment = ReviewComment.new(params[:review_comment])
if #review_comment.save
redirect_to #review_comment, notice: 'Review comment was successfully created.'
....
end
def destroy
#review_comment = ReviewComment.find(params[:id])
#review_comment.destroy
redirect_to :back, notice: "Deleted"
end
UPDATE
review_comments GET /review_comments(.:format) review_comments#index
POST /review_comments(.:format) review_comments#create
new_review_comment GET /review_comments/new(.:format) review_comments#new
edit_review_comment GET /review_comments/:id/edit(.:format) review_comments#edit
review_comment GET /review_comments/:id(.:format) review_comments#show
PUT /review_comments/:id(.:format) review_comments#update
DELETE /review_comments/:id(.:format) review_comments#destroy
link_to with a method anything other than GET is actually a bad idea, as links can be right clicked and opened in a new tab/window, and because this just copies the url (and not the method) it will break for non-get links.
Also, links are clicked on by web page indexing spiders, and even though the links in question are probably only available to logged in users (and therefore not spiders) it's still bad practise.
It's better to use button_to instead, which makes rails generate a mini-form to produce the same result.
From a practical point of view buttons are better (for the above reasons) but they're also better from a conceptual point of view: generally speaking, links should "take you somewhere", whereas buttons should "do something". It's better to keep these two basic functionalities seperate.
Something like this,
button_to t('general.delete'), :review_comment_path(review_comment), :method => :delete, :confirm => t('review_comment.confirm_delete'), :title => t('review_comment.delete_question')
For your routes:
<%= link_to "delete", review_comment, method: :delete %>
class ReviewCommentsController < ApplicationController
def destroy
#review_comment = ReviewComment.find(params[:id])
#review_comment.destroy
redirect_to review_comments_path # Or another path
end
end
Related
I have an Approved column in a database which is false by default and might become true on "Approve" button click.
That's what this button look like at the moment:
<%= link_to('Approve It', #comment_path, method: :update) %>
But it raises an exception:
No route matches [POST] "/books/4/comments/6
# app/controllers/comments_controller.rb
def update
#comment = Comment.find(params[:id])
#comment.approve = true
redirect_to '/dashboard'
end
# config/routes.rb
resources :books do
resources :comments
end
How can I fix it?
link_to has to point to an existing route/action, with a proper method name. There is no :update HTTP method.
FYI: Approve action doesn't seem like it belongs to the #update method/action. You might want to extract it to a separate route like so:
resources :books do
resources :comments do
post :approve, on: :member
end
end
this is more idiomatic/common approach in Ruby because #update is usually preserved for more general object updates.
For this you will need to change :method argument value to :post and update your route/#comment_path.
Rails-ujs event handlers - this link might be useful for understanding how it works behind the scenes.
Controller Namespaces and Routing
Post / Update actions require forms
You're using a link_to. This is good for GET requests, but is no good for POST/PATCH/UPDATE requests. For that you'll have to use a form in HTML. Luckily Rails offers some short cut. You can use something like button_to:
<%= button_to "Approve", { controller: "comments", action: "update" }, remote: false, form: { "id" => #comment.id, "approved" => true } %>
This creates a form for you. Which will come with CSRF protection automatically. You can style the button however you like.
or you could use a link to:
<%= link_to comment_approved_path(#comment), method: :put %>
but then you would need to create a separate "approved" action in your controller, and a separate route to reach it.
(The above code has not been tested).
#html
<%= link_to "Approve It", book_comment_path(#comment), method: 'put' %>
# app/controllers/comments_controller.rb
def update
#comment = Comment.find(params[:id])
#comment.approve = true
#comment.save
redirect_to '/dashboard'
end
I am working on a rails 3 app, and i am trying to link_to a delete path. It seems pretty straightforward, and my routes look like this
namespace :admin do
resources :users, :except => :destroy do
...
delete :delete_contacts, :on => :collection
end
end
and my controller looks like this
class Admin::UsersController < ApplicationController
...
def delete_contacts
user = User.find(params[:user_id])
user.contacts.destroy_all
redirect_to edit_admin_user_path(current_user.id)
flash[:notice] = "Successfully deleted #{user.name} contacts"
end
end
and my current link_to looks like this
<%= link_to delete_contacts_admin_users_path(user_id: #user.id), method: :delete, data: { confirm: 'Are you sure you want to delete this users Contacts?' }, remote: true do %>
<h4 style="color: #BF3430;"><i class="material-icons">delete_forever</i> Delete Contacts</h4>
<% end %>
I have tried the routes both with and without the :on => :collection, and i seem to keep getting the same error:
ERROR
Started DELETE "/admin/users/955/delete_properties/" for 127.0.0.1 at 2018-01-17 17:58:34 -0800
Processing by Admin::UsersController#delete_properties as JS
Parameters: {"id"=>"955"}
But the link that is made, /admin/users/delete_properties/?user_id=955 , seems like just the link needed?
Does anyone see what I'm doing wrong here? this is pretty straightforward, I'm not sure why it's not behaving the way i'm thinking it should. Any help is much appreciated!
Personally, I think I would have done:
namespace :admin do
resources :users, :except => :destroy do
delete :delete_contacts, :on => :member
end
end
Which would give you:
delete_contacts_admin_user DELETE /admin/users/:id/delete_contacts(.:format) admin/users#delete_contacts
Then, you could do:
<%= link_to delete_contacts_admin_user_path(#user) ... %>
My thinking is that you're working with a particular user and so using a member path instead of a collection path, IMO, seems a little more natural.
Of course, you'd have to change your action slightly:
class Admin::UsersController < ApplicationController
...
def delete_contacts
user = User.find(params[:id])
user.contacts.destroy_all
redirect_to edit_admin_user_path(current_user.id)
flash[:notice] = "Successfully deleted #{user.name} contacts"
end
...
end
That edit_admin_user_path may be munged based on this, so you might have to look at the route for that, too (and possibly others, as well).
So I have a simple list of messages that users can submit. I'm trying to put a delete button (that works) for each message. But as you can see here you get an error about an entirely different action if you click the button. I'm not totally sure about where I went wrong. My guess is that I'm out of my depth in the controller area. Here are all the applicable files.
Routes.rb:
Rails.application.routes.draw do
root 'messages#index', as: :home
get '/new' => 'messages#new', as: :edit
resources :messages
post '/new' => 'messages#create', as: :create
delete 'messages/:id' => 'messages#destroy', as: :delete
The relevant controller:
class MessagesController < ApplicationController
def index
#messages=Message.all
end
def new
#messages=Message.new
end
def destroy
#messages=Message.find(params[:id])
#messages.destroy
end
def create
#messages = Message.new(message_params)
if #messages.save
redirect_to '/'
else
render 'new'
end
end
private
def message_params
params.require(:message).permit(:content, :subject)
end
end
The Relevant View:
<div class="main">
<div="messages">
<%#messages.each do |t|%>
<h2 class="subject"><%=t.subject%></h2>
<p class="content"><%=t.content%></p>
<%=link_to "Delete Message", delete_path(t)%>
<% end %>
<%=link_to "Create Message", edit_path%>
</div>
</div>
You need to pass the method DELETE as well, otherwise it will perform the simply GET request. Here's how:
<%=link_to "Delete Message", delete_path(t), :method => 'delete' %>
Remember if you do not mention any method in link_to, the default will be taken as GET. So you have to be explicit about other HTTP methods.
Edit:
Either use resources :messages, or use the routes that you wrote yourself. Using resources :messages is a bit easier, and it is the preferred way.
Using resources :messages, you'd have to write:
<%= link_to "Delete Message", t, :method => 'delete' %>
Edit 2:
You are getting the error Template is missing, because in your destroy method, neither you are rendering anything, nor you are redirect_toing anything. After you destroy the object, you will have to tell where should it go. Like if you want the user to go all messages page after he/she destroys the record, you need to add the following line to the end of the method:
redirect_to messages_path
I this view is currently in the views/projects/show.html.erb file however I want it to use the website controller for deleting this file:
<%= link_to 'Delete', #website, :controller => 'website', :action => 'delete', method: :delete, data: {confirm: "Are you sure you want to delete this asset?"}%>
It returns the error 'Could not find action destroy in the ProjectsController'. Also i don't have #website defined in the projects controller so should I be using something else? Or am I still able to access it because it is defined in the websites controller.
#controllers/websites_controller.rb
class WebsitesController < ApplicationController
def new
#project = Project.find(params[:project_id])
#website = #project.assets.build(:type => 'Website', :project_id => Project.find(params[:project_id]), :asset_number => #project.assets.size + 1)
end
def create
#website = current_user.assets.build(website_params)
#website.update_attributes(:project_id => #project)
if #website.save
flash[:notice] = "Asset successfully added."
redirect_to(:controller => 'projects', :action => 'show', :id => #website.project_id)
else
render(:action => 'new')
end
end
def delete
#website = Asset.find(params[:id])
end
def destroy
asset = Asset.find(params[:id]).destroy
flash[:notice] = "The asset '#{asset.title}' has been destroyed Successfully."
redirect_to(:controller => 'projects', :action => 'index')
end
private
def website_params
params.require(:website).permit(:id, :project_id, :asset_number, :title, :type, :url, :page_rank, :rev_company ,:social_pages)
end
end
If you are using this link on the show page for projects then #website will not be available unless it is defined in the projects controller.
That said, if there is some relationship between the project and the website, you could use that as opposed to defining #website in your projects controller.
Also, as far as your link_to is concerned, I do not believe that you can specify controller and action in the link_to like that. Instead, you should use the path to #website. Which should make your link_to look something more like this:
<%= link_to "Delete", website_path(#website), method: :delete, data: {confirm: "Are you sure you want to delete this asset?" %>
However, the model that your websites_controller appears to handle is actually an Asset. Without seeing your routes it is hard to guess how you have set them up, but assuming that you do something like
map.resources :assets, :controller => 'websites'
in your routes. Then in your link_to instead of using website_path(#website) you would likely use asset_path(#website).
Generally speaking, it is rarely a good idea to defy rails convention by naming things inconsistently from your model in ruby. If your Asset model uses single table inheritance or you are implying something like single table inheritance and are using controllers to separate responsibilities, then this may perhaps be an exception, but you will still need to be careful to ensure you are mapping to the correct place in your routes.
You may want to read up on the rails guide for routing, as it is a very good resource and explains pretty well how destroy gets mapped, which in turn explains why the link_to for it looks the way that it does.
#website available in the show action is the the one defined in the projects controller because it is he one rendering the current html page.
Therefore the one you wish to delete is not available at the moment.
I have frequently run into the situation where I want to update many records at once - like GMail does with setting many messages "read" or "unread".
Rails encourages this with the 'update' method on an ActiveRecord class - Comment.update(keys, values)
Example - http://snippets.dzone.com/posts/show/7495
This is great functionality, but hard to map to a restful route. In a sense, I'd like to see a :put action on a collection. In routes, we might add something like
map.resources :comments, :collection => { :update_many => :put }
And then in the form, you'd do this...
<% form_for #comments do |f| %>
...
This doesn't work on many levels. If you do this: :collection => { :update_many => :put }, rails will submit a post to the index action (CommentsController#index), I want it to go to the 'update_many' action. Instead, you can do a :collection => { :update_many => :post }. This will at least go to the correct action in the controller.
And, instead of <% form for #comments ... you have to do the following:
<% form_for :comments, :url => { :controller => :comments, :action => :update_many } do |f| %>
It will work OK this way
Still not perfect - feels a little like we're not doing it the 'Rails way'. It also seems like :post, and :delete would also make sense on a collection controller.
I'm posting here to see if there's anything I missed on setting this up. Any other thoughts on how to restfully do a collection level :post, :put, :delete?
I've run into a few situations like you describe. The first couple of times I've implemented form almost identical to the one you suggest.
About the third time I hit this problem I realized that every item I'm updating has a common belongs_to relationship with something else. Usually a user. That's exactly the epiphany you need to make sense of this RESTfully. It will also help you clean clean up the form/controller.
Don't think of it as updating a bunch of messages, think of it as updating one user.
Here's some example code I've used in the past to highlight the difference. Assuming that we we want bulk operations on messages that belong to the current_user...
As of rails 2.3 we can add
accepts_nested_attributes_for :messages
to the user model. Ensure messages_attributes is part of attr_accessible, or is not attr_protected.
Then create the route:
map.resources :users, :member => {:bulk_message_update, :method => :put}
Then add the action to the controller. With AJAX capabilities ;)
def bulk_message_update
#user = User.find(params[:id])
#user.update_attributes(params[:user])
if #user.save
respond_to do |format|
format.html {redirect}
format.js {render :update do |page|
...
}
end
else
....
end
Then your form will look like this:
<% form_for current_user, bulk_message_update_user_url(current_user),
:html => {:method => :put} do |f| %>
<% f.fields_for :messages do |message| %>
form for each message
<% end %>
<%= sumbit_tag %>
<% end %>
I often add collection-based update_multiple and destroy_multiple actions to an otherwise RESTful controller.
Check out this Railscast on Updating Through Checkboxes. It should give you a good idea how to approach it, come back and add to this question if you run into troubles!