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.
Related
I have a simple question about rails.
I followed a tutorial (from CrashLearner).
In that tutorial we have a simple resource message that generates the regular following routes (excerpt from rake routes)
Prefix Verb URI Pattern Controller#Action
messages GET /messages(.:format) messages#index
POST /messages(.:format) messages#create
new_message GET /messages/new(.:format) messages#new
edit_message GET /messages/:id/edit(.:format) messages#edit
message GET /messages/:id(.:format) messages#show
PATCH /messages/:id(.:format) messages#update
PUT /messages/:id(.:format) messages#update
DELETE /messages/:id(.:format) messages#destroy
As I understand the route to get to the show action of this controller is like something /messages/17, I mean we have to put the :id of that particular message we want to view.
So, if I needed to redirect the user to this message view after he modified it (in the #update action) I should use:
redirect_to message_path(17)
But it turns out that omitting this :id actually works great:
redirect_to message_path
Why and how this last one work ?
Since this works from an action that actually received the :id param I suppose that the controller keep it in memory and pass it through by default under the hood when it is missing but I would like to understand where this behavior come from ?
I found nothing in the rails documentation.
Here is the github repository of the tutorial, so the exact specific place of the above line is here in this controller.
And I confirm that this works.
There is also a Comment resource that is nested from the previous Message resource.
As you can see in that controller on the update action, after updating a comment (which is nested within a message) the controller redirects to the message_path but in that case the :id parameter is present through the instance variable #message (and I learned that this works because the object Message respond to an .id method otherwise it should be #message.id)
I supposed that the reason that why here the :id is still passed is because we are in the Comments controller and the :id of another resource could not be passed under the hood, thus why it is explicitely written.
I don't have another explication..
Can anyone explain me why this works ?
I found this in Rails source:
Missing routes keys may be filled in from the current request's
parameters (e.g. +:controller+, +:action+, +:id+ and any other
parameters that are placed in the path).
So here :id exists in the current request params and it's used for this route.
If you define singular resource you will have show action without :id param
http://edgeguides.rubyonrails.org/routing.html#singular-resources
Rails 4.2.1
Ruby 2.1.5
In my routes.rb, I have:
get '/activate_test_set' => 'test_sets#activate_test_set'
In test_sets_controller.rb, I have:
def activate_test_set test_set
test_set.update_attribute(status: 'active')
render test_sets_url
end
In my views/test_sets/index.html, I have:
<%= link_to "#{t('activate')}", activate_test_set_path(test_set) %>
When I'm in the view, if I click on the link, I get the following in the development error log:
Started GET "/activate_test_set.1" for ::1 at 2016-01-01 13:23:55 -0800
Processing by TestSetsController#activate_test_set as
Completed 500 Internal Server Error in 5ms (ActiveRecord: 0.0ms)
ArgumentError (wrong number of arguments (0 for 1)):
app/controllers/test_sets_controller.rb:69:in `activate_test_set'
Whats' the proper routes.rb declaration to make this work correctly?
I also tried this in routes.rb:
get '/activate_test_set/:id' => 'test_sets#activate_test_set'
and this is what I get:
Started GET "/activate_test_set.1" for ::1 at 2016-01-01 15:14:57 -0800
SELECT `schema_migrations`.* FROM `schema_migrations`
ActionController::RoutingError (No route matches [GET] "/activate_test_set.1"):
Solution:
Following some additional experimentation, here's how I solved this problem. Being a newbie, I am not sure this is the best solution, but here it goes:
match "activate_test_set/:id", :to => "test_sets#activate_test_set", :as => :activate_test_set, via: [:get]
In routes, you want to set a parameter
get '/activate_test_set/:id' => 'test_sets#activate_test_set'
Then, when you link to it, it will add the test_set_id to the params hash and you'll be able to get it like so
def activate_test_set
test_set = TestSet.find(params[:id])
test_set.update_attribute(:status, 'active')
...
end
Also note, this error
Started GET "/activate_test_set.1" for ::1 at 2016-01-01 13:23:55 -0800
the .1 is the format (default is html). When you sent the test_set as the param, it put the id of the test_set in the url as the format. When you see this, it's is almost always not what you want. It happens when you pass one too many parameters to the path method.
Try what Was suggested in the comments:
Changing get '/active_test_set'... to get '/active_test_set/:id'
Other way is to use normal params in which case you need to:
change the link tag to:
<%= link_to "#{t('activate')}", activate_test_set_path(id: test_set) %>
Whichever route fix you choose, you don't need arguments in your controller action. Whenever your controller actions are used with routing, you don't need to have arguments for that action, but instead set parameters in the link tag(like showed in Fix 2) and the access them through params hash inside the controller action like so:
def activate_test_set
test_set = TestSet.find(params[:id])
test_set.update_attribute(status: 'active')
render test_sets_url
end
EDIT:
Use rake routes to see what the link method looks like for that action.
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
This irritates me right now, and I've been at it for more than an hour. Maybe one of you has an idea.
I've defined the following routes in routes.rb:
resources :ads do
member do
post :preview_revision
get :revise
end
collection do
get :search
post :preview
end
end
When I run rake routes, the preview_revision route is shown correctly:
preview_revision_ad POST /ads/:id/preview_revision(.:format) {:action=>"preview_revision", :controller=>"ads"}
However, when I make a POST request to /ads/145/preview_revision, I get the following error:
Started POST "/ads/145/preview_revision" for 127.0.0.1 at Mon Nov 15 17:45:51 +0100 2010
ActionController::RoutingError (No route matches "/ads/145/preview_revision"):
Rendered /Library/Ruby/Gems/1.8/gems/actionpack-3.0.1/lib/action_dispatch/middleware/templates/rescues/routing_error.erb within rescues/layout (1.2ms)
The id (145) exists, yes.
The action and the controller (action: preview_revision, controller: ads) exist as well.
All the other routes work perfectly fine
I've tried restarting the server several times
I double-checked that it's actually a POST request, that the ID is correct, I really don't know what to do anymore.
Rails version is 3.0.1
--- UPDATE ---
I tried changing the method from POST to PUT, and now it works. Apparently POST routes on member level are not working, or not allowed.
Still, I want to know why. I do know that POST is for creating a member, so a POST request on a member makes no sense, but I didn't find this mentioned anywhere in the Rails guides, and why would rake routes display the route, if it doesn't actually work? Is that a bug?
--- UPDATE 2 ---
The Rails Routing guide (Edge version) specifically says:
2.9.1 Adding Member Routes
To add a member route, just add a
member block into the resource block:
resources :photos
do member do
get 'preview'
end
end
This will recognize /photos/1/preview
with GET, and route to the preview
action of PhotosController. It will
also create the preview_photo_url and
preview_photo_path helpers.
Within the block of member routes,
each route name specifies the HTTP
verb that it will recognize. You can
use get, put, post, or delete here. If
you don’t have multiple member routes,
you can also pass :on to a route,
eliminating the block [...]
So is the POST on member possible or not possible? Am I missing something? Just to be clear, I don't wanna use it anymore, but still, it bugs me not knowing why it doesn't work.
The best thing to do, if you find a bug in actively maintained open-source software, is:
to submit a bug report to the project's bug tracker isolating the problem as much as possible and with as much detail as possible, and, optionally,
to include a patch that fixes the bug if you can.
The bug tracker for Rails is at rails.lighthouseapp.com.
---edit---
The bug tracker for Rails is now at github.com/rails/rails/issues.
That should be working. I've created a blank app with 3.0.1 and added those routes and it worked.
Have you tried passing the :on param? Something like:
resources :ads do
post :preview_revision, :on => :member
get :revise, :on => :member
get :search, :on => :collection
post :preview, :on => :collection
end
I have the following route in rails 3:
resources :jobs do
member do
post :seller_job_submitted
end
end
And the following form
=form_for job, :url=>seller_job_submitted_job_path(job), :remote=>true do |f|
I know it's not very restful, but it's kind of a stop gap for now. In any case, I keep getting this error when submitting the form
Started POST "/jobs/74/seller_job_submitted" for 127.0.0.1
ActionController::RoutingError (No route matches "/jobs/74/seller_job_submitted"):
but when I run rake routes | grep seller_job_submitted, I think the correct results come up:
seller_job_submitted_job POST /jobs/:id/seller_job_submitted(.:format) {:action=>"seller_job_submitted", :controller=>"jobs"}
Any ideas about what might be going on?
Thanks!
Assuming you have defined method seller_job_submitted in model and controller.
Replace your code with
resources :jobs
match "jobs/:id/seller_job_submitted" => "jobs#seller_job_submitted", :as => "seller_job_submitted"
Then in form_for tag use :url=>seller_job_submitted_path
This should fix your problem: you did not define seller_job_submitted_job_path explicitly.
Perhaps use put instead of post? Or use :post as the method in the submit form.
You can tell if this is the issue by looking at what the REST method is for the generated form (look for the hidden field in the page source).
So in short, maybe Rails is somehow expecting a POST on that URL but it's receiving a PUT.
Yes, this is a regression bug with Rails 3.
It turns out you need to be careful about using POST in your routes.rb.
resources :jobs do
member do
post :seller_job_submitted # will not work
put :seller_job_submitted # will just work
end
This is even though the FORM method says POST.