In views file, my code is similar with:
<%= link_to refresh_post_user_post_path(#user,#post), :method => :put%>
In routes.rb:
resources :users do
resources :posts do
member do
put :refresh_post
end
end
end
The interesting thing is when inspecting the request object in controller:
def refresh_post
... ...
p request.method # => POST
p request.request_method # => PUT
... ...
end
I know method and request method are different, but where's POST request from?
Moreover:
$ rake routes
refresh_post_user_post_path PUT /users/:user_id/posts/:id/refresh_post, {:action => "refresh_post", :controller => "posts"}
I am with Rails 3.0.11 and Ruby ree-1.8.7, everything above works with no exception. But any body knows how come the request is a POST?
Rails emulates "advanced" request types (PUT, DELETE, etc) with a POST type. This is because browsers typically support only GET and POST.
So rails accepts a POST request and looks for a :method parameter. If such parameter is found, it updates request type accordingly (so that your routes can work, for example).
The truth is request.method always returns POST, no matter for a PUT or POST request and no matter the controller method is a default 'update' or a custom one. Sergio, you are right.
It is from Rails' doc for request class:
method:
Returns the original value of the environment’s REQUEST_METHOD, even if it was overridden by middleware
request_method:
Returns the HTTP method that the application should see. In the case where the method was overridden by a middleware (for instance, if a HEAD request was converted to a #GET, or if a _method parameter was used to determine the method the application should use), this method returns the overridden value, not the original.
The interesting thing is even if it is a PUT request, in the log file it says like:
Started POST "/users/251/posts/1234" for 127.0.0.1 at Fri Jan 18 21:48:21 +0800 2012
This happens in Rails 3.0.11, and log file doesn't tell it is a PUT request at all. However in later versions, it has been fixed:
https://github.com/rails/rails/commit/17a91a6ef93008170e50c073d1c3794f038a0a33
And the log becomes as friendly as:
Started PUT "/users/251/posts/1234" for 127.0.0.1 at Fri Jan 18 21:48:21 +0800 2012
Related
I started Ruby on Rails few days ago, and I'm struggling with routing.
Indeed, I would like to make a post request through my routes.rb, but I keep having a
No route matches [GET] "/orders/refresh"
error.
Here is my routes.rb :
# frozen_string_literal: true
Rails.application.routes.draw do
# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
get '/orders', to: 'orders#index'
get '/orders/active(/:q)', to: 'orders#active'
post '/orders/refresh', to: 'orders#refresh'
end
and here is my controller (orders_controller.rb) :
# frozen_string_literal: true
class OrdersController < ApplicationController
def index
#orders = Order.order(:departure_date).all
render json: #orders.to_json
end
def active
if !params[:q]
#orders = Order.order(:departure_date).where(active: true)
else
#orders = Order.order(:departure_date).where("reference = ? OR client_name = ? OR departure_city = ? OR arrival_city = ?",
params[:q], params[:q], params[:q], params[:q])
.where(active: true)
end
render json: #orders.to_json
end
def refresh
response = RestClient.get 'https://wakeo-technical-test.s3.eu-west-3.amazonaws.com/api.json'
json = JSON.parse response
if !json.nil?
json.each do |order|
old_order = Order.find_by(reference: order["client_number"])
if !old_order.nil?
old_order.update(departure_date: order["dep_time"])
old_order.update(arrival_date: order["arr_time"])
old_order.update(client_name: order["company"])
old_order.update(departure_city: order["dep_city"])
old_order.update(arrival_city: order["arr_city"])
end
end
else
puts "error seeding external API"
end
end
end
From what I have understood, it seems like RoR will try to find a GET request for that specific URL, and since it won't find any, it will throw that error. How could I make that request be a POST for Rails ?
Also, I would appreciate any suggestion about how I should use ActiveRecord Querying, I'm pretty sure I could do it better here.
Thanks, have a great day !
EDIT : Here is the list of different routes my app seems to be capable of, including my POST.
Routes and error
The most common reason you unexpectly get GET requests instead of PUT, PATCH, POST or DELETE is that you are using link_to 'Something', '/some_path', method: :post and you broke the Rails Unobtrusive Javascript Driver (Rails UJS):
Because submitting forms with HTTP methods other than GET and POST
isn't widely supported across browsers, all other HTTP methods are
actually sent over POST with the intended method indicated in the
_method parameter. Rails automatically detects and compensates for this.
Rails does that with a JavaScript event handler attached to any link with the data-method attribute. But if you broke that functionality the browser will just perform its default action which is sending a GET request when the user clicks a link.
This problem usually boils down to one or more of:
Your javascript is throwing an error which halts script execution (use the browser console to find the error, make it suck less).
Rails UJS is not included in your assets pipeline or webpacker packs and thus not in the page.
The quick and easy solution to sidestep the problem is by using button_to which actually creates a form and does not require any JavaScript trickery. After all forms can send POST requests. And by just passing a _METHOD hidden field Rack will treat the request as any other HTTP verb.
button_to 'Something', '/some_path', method: :post
But in the long run you should probably fix the problem if you want to use any of the features of Rails UJS.
Your routes.rb is expecting a POST request to /orders/refresh routes, but apparently you are testing with a GET request.
Try changing your routes.rb:
Rails.application.routes.draw do
# ...
get '/orders/refresh', to: 'orders#refresh'
end
... or change your request to a POST request. If you are using Rails forms, you must do something like this:
form_with(url: "/orders/refresh", method: "post")
Ok, I think I figured it out.
It might be because when I hit /orders/refresh directly in my web browser, it will try to find a GET corresponding to the request.
I managed to make POST using a client like Postman, and everything works fine.
Thank you for your help !
Update
This was a legacy app I inherited, and I found out that the previous developers had removed the rack code that converted browser POST requests into PUT/PATCH based on the _method param that Rails adds to your forms.
# config/application.rb
# This is the line that caused the problem...
config.middleware.delete ::Rack::MethodOverride
Once I removed that line and restarted the server, things worked as expected.
Original Question
When I post a Rails form using the standard resources in the routes file, it raises a route not found error when I'm trying to update an existing record:
No route matches [POST] "/admin/lookups/record_types/1"
The model is namespaced as app/models/lookups/record_type.rb
# model file
module Lookups
class RecordType < ApplicationRecord
# ...
end
end
# form in view file
<%= form_with model: #record_type, scope: :record_type, url: [:admin, #record_type], local: true do |form| %>
<%= form.text_field :value %>
<% end %>
# Request being sent
POST admin/lookups/record_types/1
{ record_type: { _method: "patch", value: "value" } }
# in routes .rb
namespace :admin do
namespace :lookups do
# Does not work
resources :record_types
# Works when explicitly written out
post "record_types/:id", controller: record_types, action: :update
end
end
When I explicitly write out the POST request in the routes.rb file, it works as expected.
I know that Rails is actually POSTing the request and using the _method hidden attribute to map the routes file. However, something isn't converting that request properly.
It's an application I inherited, and at one point it was exclusively an JSON API (no direct UI), so I'm wondering if there was something removed that converted the Rails _method param to the proper controller? I don't know what that would be, though.
This is the output of my rake routes:
admin_lookups_record_types
GET /admin/lookups/record_types(.:format)
admin/lookups/record_types#index
POST /admin/lookups/record_types(.:format)
admin/lookups/record_types#create
new_admin_lookups_record_type
GET /admin/lookups/record_types/new(.:format)
admin/lookups/record_types#new
edit_admin_lookups_record_type
GET /admin/lookups/record_types/:id/edit(.:format)
admin/lookups/record_types#edit
admin_lookups_record_type
GET /admin/lookups/record_types/:id(.:format)
admin/lookups/record_types#show
PATCH /admin/lookups/record_types/:id(.:format)
admin/lookups/record_types#update
PUT /admin/lookups/record_types/:id(.:format)
admin/lookups/record_types#update
DELETE /admin/lookups/record_types/:id(.:format)
admin/lookups/record_types#destroy
The problem seems to be the use of the scope argument to the form_with method.
If you take a look at the routes you'll note that the route to the update action uses PUT or PATCH whereas the route to the create action uses POST.
As the documentation of the FormHelper module states:
in the options hash. If the verb is not GET or POST, which are natively supported by HTML forms, the form will be set to POST and a hidden input called _method will carry the intended verb for the server to interpret.
But nesting the special _method key inside record_type breaks this mechanism. Is the scope really necessary? I'd try removing it, it should work fine without it. The correct HTTP verb to use would be PUT or PATCH. Adding an additional POST route breaks the regular Rails structure without any real gain.
I am using the twitter gem for ruby and need to send a POST request to users/lookup endpoint.
As per the gem source code documentation(https://github.com/sferik/twitter/blob/4e8c6dce258073c4ba64f7abdcf604570043af71/lib/twitter/rest/users.rb), the request should be POST by default, unless I pass :get :
#option options [Symbol, String] :method Requests users via a GET request instead of the standard POST request if set to ':get'.
def users(*args)
arguments = Twitter::Arguments.new(args)
request_method = arguments.options.delete(:method) || :post
flat_pmap(arguments.each_slice(MAX_USERS_PER_REQUEST)) do |users|
perform_with_objects(request_method, '/1.1/users/lookup.json', merge_users(arguments.options, users), Twitter::User)
end
end
I am calling it as follows:
users = #client.users(twitter_screen_names_arr, [:method, :post])
However, I am not sure if this is actually resulting in a POST request / a GET request.
How can I make sure if this is a POST/GET? I would like to print the request that is being made to get a clarity on what actually gets sent.
Thanks!
As you can see from the code it uses POST by default. This behavior is also specified with RSpec.
You can invoke the users method like this:
#client.users(twitter_screen_names_arr, :method => :post)
or simply
#client.users(twitter_screen_names_arr)
since POST is the default request method.
If you don’t trust the code or the specs, you could run the request through a proxy to verify this behavior manually.
I recently upgraded to Rails 3.1 (from 3.0), and for some reason one of my routes no longer works. I have a form that posts a csv file, and is processed by an items controller. The route looks like:
resources :items do
member do
post 'receive'
post 'show'
end
collection do
post 'csv_import'
get 'transactions'
get 'template'
end
end
And here's what I see in the logs--it looks like it's posting the correct action.
Started POST "/items/csv_import" for 127.0.0.1 at Tue May 08 11:09:52 -0400 2012
Processing by ItemsController#show as HTML
But it's being processed by the show action:
ActiveRecord::RecordNotFound in ItemsController#show
Couldn't find Item with id=csv_import
I can't for the life of me see what I'm doing wrong here.
Your post 'show' line is interfering with this, because when you post to /items/csv_import, rails thinks you mean items/csv_import/show, with csv_import being the id of the item you want to import. If you run rake routes, you'll see a part like this:
item POST /items/:id(.:format) items#show
csv_import_items POST /items/csv_import(.:format) items#csv_import
That first item is matching your post to /items/csv_import and it never even hits the second one.
You can move the member do ... end block to be after your collection do ... end block and it should work fine.
However, I would just recommend getting rid of post 'show' and renaming that method to something better, as it goes against the standard rails/rest conventions anyway.
I have the following nested resources:
resources :listings do
resources :offers do
member do
put "accept"
put "reject"
end
end
end
In my listings/show.html.haml, I have
= button_to "Make Offer", new_listing_offer_path(#listing)
Now, when I click button, rails generate a POST request, and thus an error:
Started POST "/listings/2/offers/new" for 127.0.0.1
ActionController::RoutingError (No route matches "/listings/2/offers/new"):
If I refresh (GET request), then the page displays correctly.
I believe this incorrect routing only happens when I added two extra actions: accept and reject, which happens to be POST actions.
Is it a bug in Rails, or it is my fault? How should I prevent this error?
Thank you.
The button_to helper creates a form for you which by default will send a POST request to the URL you've specified ("/listings/2/offers/new").
The routing you've specified will not generate a route to handle a POST request to /new. You can inspect your generated routes and the verbs to which they will respond by running the "rake routes" task.
If you are looking to merely link to the form, change your "button_to" to a "link_to" and add CSS for aesthetics.
= link_to "Make Offer", new_listing_offer_path(#listing)
(this GET would route to your OfferController's new action)
If you are looking to actually POST data, you will likely need to change your usage to:
= button_to "Make Offer", listing_offers_path(#listing)
(this POST would route to your OfferController's create action.)