Reputation: 3770
wanted to define custom routes into certain resources in addition to the ones Rails defines by default. To do this, the relevant parts of my routes.rb
file look something like this:
resource :top, only: [:show]
scope module: :top do
resource :reso, only: [:show]
end
get 'foo_reso' => 'top/reso#foo'
get 'bar_reso' => 'top/reso#bar'
As you can see, I only want routes to ResoController
's methods show
, foo
, and bar
. This works, as rake routes
gives:
reso GET /reso(.:format) top/reso#show
foo_reso GET /foo_reso(.:format) top/reso#foo
bar_reso GET /bar_reso(.:format) top/reso#bar
This works: clicking on a link in the application that takes you to the route foo_reso
does result in a call ResoController#foo
, and the subsequent display of the associated view.
However, I thought that the route definition was just slightly ugly, and instead of defining the routes explicity, wanted Rails to generate them automatically by telling it that the resource has two additional REST methods, foo
and bar
(while still restricting the standard methods by means of the only:
argument).
I followed the advice in this answer, and changed routes.rb
to this:
resource :top, only: [:show]
scope module: :top do
resource :reso, only: [:show] do
member do
get :foo
get :bar
end
end
end
Now, rake routes
gives:
reso GET /reso(.:format) top/reso#show
foo_reso GET /reso/foo(.:format) top/reso#foo
bar_reso GET /reso/bar(.:format) top/reso#bar
Note the difference between the paths in the two cases: while the helper path and the controller#action are the same, the path has changed from /foo_reso(.:format)
to /reso/foo(.:format)
.
(I have all the resource names defined as uncountable in config/initializers/inflections.rb
, so I don't get automatic pluralization of the names, because in my application, each controller is associated with a particular screen, not with a model, so pluralization doesn't fit the picture. In this app, the REST methods are really more like function calls than operations on a resource, which is why I need a different set than the standard.)
Now, clicking on a link to foo_reso
in the application results in a Rails routing error page that says:
No route matches [GET] "/foo_reso"
Any ideas on what I could do to fix the situation, aside from just using my original solution?
Added on edit, 2013-07-12:
As I note in a comment below, according to the output of rake routes
, the helper route foo_reso
matches the controller and method I want to call, top/reso#foo
, and when I manually enter the matching URL (reso/foo
) into the URL bar, that works as intended. However, trying to open the route foo_reso
from within the application results in a No route matches [GET] "/foo_reso"
. foo_reso_path
and foo_reso_url
result in the same error.
What on earth can the problem be? Surely not a bug in Rails?
Added on edit, 2013-07-22:
To make the use case a bit clearer, the idea is that when the user presses a "Reset" button on a page, the controller's reset
method is called, which clears the page's inputs and outputs. I have been abstracting the question by changing the actual identifier reset
to foo
. (reso
and and a couple of other identifiers are also the result of "obfuscation".) I'll continue to use the same translations for consistency's sake, but the code below should be clearer when you bear in mind that foo
is actually reset
. (bar
is something else, but it doesn't matter, since the routing problem is the same.)
To answer Thong Kuah's and John Hinnegan's questions, this is how I use foo_reso_path
. The relevant parts of file reso_controller
:
class Top::ResoController < Top::ResoSuperController
# GET /reso
def show
...
@reset_path = "foo_reso"
...
end # show
# GET /foo_reso
def foo
perform_foo_action
redirect_to reso_path
end
...
end
The relevant parts of file app/views/top/reso/show.html.erb
:
<%= render partial: "top/resosuper_inputs", locals: { the_form_path: reso_path } %>
...
<input type="hidden" id="reset_path" name="reset_path" value="<%= @reset_path %>">
The form includes the partial _resosuper_inputs.html.erb
, whose relevant parts are:
<!-- For using the "Reset" button. -->
<%= javascript_include_tag "my_reset_form.js" %>
<%= simple_form_for :filtering_criteria, url: the_form_path, method: :get do |f| %>
... inputs elided ...
<%= f.button :submit, value: "Search" %>
<%= f.button :submit, type: 'button', value: "Reset"), id: 'reset_button' %>
<% end %>
resosuper
is the superclass of two different resources, but I don't think that has any effect on the case. Anyway, here's the Javascript file app/assets/javascripts/my_reset_form.js
:
$(document).ready(function() {
/* Hang functionality on the "Reset" button. */
$('#reset_button').click(function () {
var reset_path = $('#reset_path').val();
window.open(reset_path, "_self")
});
})
It's in jQuery, and as the comment says, makes the "Reset" button open the path whose value has been stored in the hidden input variable with the id reset_path
. That value is what was given to the form by Top::ResoController
in the variable @reset_path
, namely, "foo_reso"
.
Keep in mind that all this worked fine when I defined foo_reso
in routes.rb
like this:
get 'foo_reso' => 'top/reso#foo'
Also keep in mind that replacing foo_reso
with foo_reso_path
or with foo_reso_url
in the assignment statement in ResoController
made no difference.
The places where I use foo_reso
are in a controller and in a view, so that shouldn't be a problem.
Upvotes: 0
Views: 2336
Reputation: 3283
As posted in the description, here is the "usage".
# GET /reso
def show
...
@reset_path = "foo_reso"
...
end # show
However, all the above is doing is just setting the @reset_path
instance variable into a literal string `"foo_reso" (which coincidentally matches the old route)
What the poster really wants is :
@reset_path = foo_reso_path
which will generate the right path /reso/foo
into the instance variable @reset_path
Sidenote: The poster has done all the right things here to debug route problems. Most of the time, you can trust rake routes
. Checking that one can access the route directly is good, and so is checking the usage of the route helper is correct too is crucial
Upvotes: 1
Reputation: 3770
OK, I found out the problem. As often turns out to be the case after digging into a seemingly mysterious bug, the solution was very simple. My reso_controller.rb
was:
class Top::ResoController < Top::ResoSuperController
# GET /reso
def show
...
@reset_path = "foo_reso"
...
end # show
I replaced the assignment line by this:
@reset_path = foo_reso_path
without quotes around foo_reso_path
. Now the "Reset" button works as intended.
In summary, the following don't work:
@reset_path = "foo_reso"
@reset_path = "foo_reso_path"
@reset_path = "foo_reso_url"
@reset_path = foo_reso
And the following do work:
@reset_path = foo_reso_path
@reset_path = foo_reso_url
Don't I feel silly now. Sorry for the waste of time.
Upvotes: 0
Reputation: 13181
Short answer: There is no automatic way in Rails to do so.
The reason is that RESTul URL relies basically on the format /controller/action
. Meaning if your controller ResoController
has 3 methods show
, foo
, and bar
, a correct restful implementation will result in the URLS:
/reso
/reso/foo
/reso/bar
Rails can help you do achieve this, and the solution is using members
in your routes, exactly the way you described in your question.
Using URLs such as /foo_reso
or /bar_reso
- in other words /action_controller
is not Rails standard, and Rails base philosophy is "convention over configuration". So if you really need those you must declare them by hand
Upvotes: 0