I have a Rails app that is supposed to send a POST request, but for some reason is sending a GET.
View:
<% if #competition.users.exclude?(#user) %>
<%= link_to 'Attend Competition', attend_competition_path(#competition.id), :method => :post %>
<% else %>
<%= link_to 'Withdraw', withdraw_competition_path(#competition.id), :method => :post %>
<% end %>
Controller:
def attend
p current_user.daily
#competition = Competition.find(params[:id])
if #competition.users.include?(current_user)
flash[:error] = "You're already attending this competition."
elsif current_user.daily == []
flash[:error] = "You must have a working device to compete."
else
current_user.competitions << #competition
flash[:success] = "Attending competition!"
end
redirect_to #competition
end
def withdraw
p "WITHDRAWING"
#competition = Competition.find(params[:id])
p #competition
attendee = Attendee.find_by_user_id_and_competition_id(current_user.id, #competition.id)
if attendee.blank?
flash[:error] = "No current attendees"
else
attendee.delete
flash[:success] = 'You are no longer attending this competition.'
end
p attendee
redirect_to #competition
end
Routes:
resources :competitions do
post 'attend', on: :member
end
resources :competitions do
member do
post 'withdraw'
end
end
So I click on the button and go to the page, but get an error that there is no route for a GET request. There shouldn't be a route for get requests, but it should be sending a post.
ActionController::RoutingError (No route matches [GET] "/competitions/1/withdraw")
One thing you can do is to run:
rake routes
That will show you all your available routes and their methods. I believe since you are doing a post to a method other then create that it is not understanding what you are trying to do. I am looking to see if I can find the proper way to do this, but I did find that the Rails documentation says:
If you are relying on the POST behavior, you should check for it in your controller's action by using the request object's methods for post?, delete?, :patch, or put?.
So you may need to check for a post in your controller action. I looked for an example of how to do this but couldn't find anything.
Looking at your routes, it should work the way you have it. One other thing to try is to use "put" instead of "post."
One other option that you may want to consider is to make it a form and style the button like a link if that is the look you are going for.
Mike Riley
Related
I have this setup where it destroys all users but i want it not to destroy current user which is admin.
controller.
def remove_all
User.destroy_all
redirect_to(admin_users_path, { flash: { success: 'You have wiped all the data on the website!' } })
end
navigation.html
<%= link_to "Nuke Button", remove_all_profiles_path, :method => :get %>
route:
resources :profiles do
member do
get :delete
end
collection do
get 'remove_all'
end
end
I know I have to add something to the controller just don't know what to add
I'm assuming you have access to the current_user in your controller (or if not that, you know the id of the current user somehow
User.where.not(id: current_user.id).destroy_all
Note: In Rails 7 you will also be able to do
User.excluding(current_user).destroy_all
Which is a bit nicer maybe, but this doesn't work yet.
https://blog.saeloun.com/2021/03/08/rails-6-1-adds-excluding-to-active-record-relation.html
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 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'm getting
ActionView::Template::Error (No route matches {:action=>"to_approve", :controller=>"microposts", :id=>nil} missing required keys: [:id]):
No route matches {:action=>"to_approve", :controller=>"microposts",
:id=>nil} missing required keys: [:id]
But it make no sense because I'm routing to different route
route.rb
match '/microposts/:id/approve', to: 'microposts#to_approve' , via: [:get, :post], as: 'approve_micropost'
match '/microposts/to_approve', to: 'microposts#approve' , via: :get
controller.rb
def show
#tag = Tag.find(params[:id])
#microposts = #tag.microposts
end
show.html.rb
<%= render #microposts %>
_micropost.html.rb - Here is the line it shows the error on
<% if is_an_admin? %>
<%= link_to "Approve", approve_micropost_path(micropost.id) %>
<% end %>
micropost_controller.rb
def approve
#microposts = Micropost.unapproved
end
def to_approve
micropost = Micropost.unapproved_by_id(params[:id])
if micropost.update_attributes(approved: true)
flash[:success] = "Approved!"
else
flash[:error] = "Not approved!"
end
redirect_back_or microposts_to_approve_path
end
micropost.rb
default_scope { where(approved: true).order('microposts.created_at DESC')}
def self.unapproved
self.unscoped.all.where(approved: false).order('microposts.created_at DESC')
end
def self.unapproved_by_id(id = nil)
self.unscoped.all.where(id: id)
end
You can see it tries to create microposts_to_approve_path with :id which obviously not exists, but I wrote approve_micropost_path.
What am I missing?
Plus, in route for microposts_to_approve_path I permitted [:get, :post] although I only want to allow access to to_approve method through on_click events (post?) and there is no view for it.. How should I rewrite this?
rake routes:
microposts POST /microposts(.:format) microposts#create
micropost DELETE /microposts/:id(.:format) microposts#destroy
approve_micropost GET|POST /microposts/:id/approve(.:format) microposts#to_approve
microposts_to_approve GET /microposts/to_approve(.:format) microposts#approve
On the error page, the parameters:
Request
Parameters:
{"id"=>"4",
"name"=>"tag name"}
Solution
The problem was because I use default_scope and than the object the I was working with wasn't OK.
Before fix
#microposts = #tag.microposts ##microposts is CollectionProxy
After
#microposts = #tag.microposts.all ##microposts is AssociationRelation
Once I've change to .all the problem was solved.
BTW, is it a bug? In my prespective default_scope shouldn't change the default behavior..
If you want it to only respond to a post, then try changing your link to use the method 'post'.
<% if is_an_admin? %>
<%= link_to "Approve", approve_micropost_path(micropost.id), method: :post %>
<% end %>
Conversely, if you want it to route to microposts#to_approve on a get, then make your link explicitly call get.
<% if is_an_admin? %>
<%= link_to "Approve", approve_micropost_path(micropost.id), method: :get %>
<% end %>
Then you should be routed to microposts#to_approve using POST. However, since you re allowing either action to take place, make sure in your to_approve action, you'll have to check for request type. Such as:
#microposts controller
def to_approve
if request.post?
# Do post related things
else
# Do get related things
end
end
Sidenote *** using the to_approve action on the approve url, and using the approve action on the to_approve url is confusing, things you might forget why-you-did-what-you-did when you look at the code 6 months from now.
Edit
An alternative might be to break apart the routes so you aren't calling an if statement in your controller.
route.rb
post '/microposts/:id/approve', to: 'microposts#approve', as: 'approve_micropost'
get '/microposts/:id/to_approval', to: 'microposts#to_approve', as: 'micropost_approvals'
resources :microposts
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