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
Related
I'm creating rails product controller to hide the product without deleting user grid panel using Boolean values, but I'm trying so many times, it's not working. I'm just show my code..please help where i'm missing...
I'm using ruby 2.4.1, rails 5.3.2 please help me.
rails route is,
post 'products/hide', to: 'products#hide'
products controller function is,
def hide
#product = Product.find(params[:id])
#product.hidden = true
flash[:notice] = 'you have successfully hide your product'
redirect_to suppliers_index_path
end
view form is,
<%= link_to 'delete', product, method: :hide, data: { confirm:
"Are you sure hide this #{product.Product_name} ?" } %>
products table migration are
class AddHiddenToProducts < ActiveRecord::Migration[5.2]
def change
add_column :products, :hidden, :boolean, :default => false
end
end
please help to resolve this issue.
There's a handful of things wrong with your code.
The most important issue is that you aren't saving your changes.
Your controller code which reads #product.hidden = true only assigns the value to the ruby object in memory. It does not save that change to the database. I believe this is the code you want in your controller action.
def hide
#product = Product.find(params[:id])
#product.update_attribute(:hidden, true)
flash[:notice] = 'you have successfully hide your product'
redirect_to suppliers_index_path
end
Your link_to is attempting to use a non-standard HTTP method ("hide"). I can tell that you're interpreting this as the controller method you want to hit, but that's not what method: means here. The method: parameter in link_to allows you to specify which HTTP method you want to send along with the request. HTTP methods are a standard that function as "verbs" that tell the server what you want to do to the resource you're requesting. Since you're only updating one attribute on the object, the Mozilla Foundation would instruct you to use the PATCH method for your request here.
NOTE: We'll be updating the route to match this in the next point.
COMMENT: Whoever named the product's "name" column products.Product_name needs to have a code review meeting with someone. But anyway...
<%= link_to 'delete', product, method: :patch, data: { confirm:
"Are you sure hide this #{product.Product_name} ?" } %>
Your routes need to be mapped to respond to a PATCH method request. You could use the format you use in your example, but most of the time you want to use "resourceful" routes.
resources :products do
member do
patch :hide
end
end
I will add that you don't have to use the PATCH method at all. You could also use put :hide + method: :put or post :hide + method: :post. They just need to match.
The key method passed to link_to call doesn't define controller method, but HTTP method, so it should be :post actually. You specify controller method by setting correct path/http method. You can achieve your goal by:
<%= link_to 'delete', { controller: products, action: :hide, id: product }, method: :post, data: { confirm: "Your confirmation" } %>
But I would go a little bit further and provide correct named route, assuming you have resources :products somewhere in your routes:
resources :products do
member do
post :hide
end
end
and in your view:
<%= link_to 'delete', [:hide, product], method: :post, data: { confirm: "Confirmation text" } %>
I am working with rails I have a controller name books and has a user defined method in it .I need to call this method so that i can see the output on console.And I dont want to call this method in helpers.
def approve
#user=current_user.users.find params[:id]
puts '#{#usery}'
end
Also I Have a link
<%= link_to 'approve',users_path,data: { :confirm => 'Are you sure to delete the folder and all of its contents?'} %>
.When i click on this link I want to call the above method on it .
You'll just need to define a route and call it through that:
#config/routes.rb
resources :users do
get :approve, on: :member
end
<%= link_to "Approve", users_approve_path(#user) %>
As #Rich suggested that, you can achieve it by member. Please note that when you'll create a member route in member block
resources :users do
member do
get 'approve'
end
end
then you'll get the params[:id]. Like
def approve
#user = User.find params[:id]
puts '#{#user}'
end
and when create a member route using :on then you'll get params[:user_id]. Like
def approve
#user = User.find params[:user_id]
puts '#{#user}'
end
Path will be same in both cases that is
<%= link_to "Approve", users_approve_path(#user) %>
Source Rails - Adding More RESTful Actions
Happy coding !!!
I have something like this in view:
<% if actions.include? "delete" %>
<%= link_to 'UsuĊ', asset_path(asset), :method => :delete, :data => { :confirm => "Want to delete it?" }, :role => "link" %>
<% end %>
and this in assetcontroller:
def destroy
#asset = current_user.assets.find(params[:id])
#asset.destroy
redirect_to assets_path
end
the question is, why it "use" destroy method when action in view is "delete" ?
delete is method of HTTP protocol. destroy is the action of your controller.
Route with delete HTTP method leads to destroy action.
To edit this routes and make delete HTTP method lead to delete action (for example), you should edit config/routes.rb file.
This is because in your routes.rb file you have defined the model as a resource (or a generator like scaffold did). This means that the default CRUD routes are generated. If you want to do it another way, use your own routes instead of generating them.
http://guides.rubyonrails.org/routing.html#crud-verbs-and-actions
I have a name route:
map.up_vote 'up_vote', :controller => 'rep', :action => 'up_vote
But up_vote requires two arguments to be passed in, postID and posterID and I can't seem to figure how to do that in a partial, but in an integration test I have no issues.
Partial:
link_to 'Up Vote', up_vote_path, {:postID => session[:user_post_id], :postersID => session[:poster_id]}
Integration test:
post up_vote_path,{:postID => #latest.id,:postersID => users(:bob).id} (this works ok)
1) What is going on the in the partial?
2) What changes can I make to my tests to catch this?
A question: why are you passing your session variables in a link? You can get them directly from the session...
I don't know if there are any special reasons to put :user_post_id and :poster_id in the session but I recommend you two things:
1) Pass your variables in urls, sessions can be evil (try hitting back, refresh and forward on your browser)
2) Use resources in your URLs / controller actions logic.
Example (valid only if I got it right and you're voting an user's post):
routes:
map.resources :users do |user|
user.resources :posts do |post|
post.resource :vote
end
end
So you can have this url:
/users/:id/posts/:post_id/vote
And the link path:
link_to "Up", user_post_vote_path(#user, #post), :method => :create
I putting #user and #post instead of the integers because path methods accept them and you can build a shorter version with:
link_to "Up", [#user, #post, :vote] # or [:vote, #post, #user]
Implementing:
class VoteController ....
def create
# do your stuff here
end
end
This way it will be easier and RESTful.
Ryan Bates got a great episode on resources, it definately worths a look.
You want to pass your params in the ..._path
link_to "Up Vote", up_vote_path(:postID => session[:user_post_id], :postersID => session[:poster_id])
The integration test is written out differently than the link_to since your testing the act.
post "to" up_vote_path, "with these" {params}
Also since your doing a POST, you will want to add the appropriate :method option to the link_to
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!