Reputation: 47
Background: Novice rails developer here learning from Michael Hartl's Ruby on Rails Tutorial (https://www.railstutorial.org/) in which you develop a basic twitter-like app with a User model and Post model. The users routes are generated from what I understand is the common method of inserting the following into the routes.rb file:
resources :users
Question: I don't understand why when you use:
redirect_to @user
rails sends that request to UsersController#show, but it does so by calling user_url(@user) via:
get "/users/id" => "users#show"
Where does the singular ("user") in user_url come from in the code immediately above? I would think user-url should be users_url or users_path (as I see come up in some places). Just trying to figure out where this singular is coded into rails.
Thanks!
Upvotes: 1
Views: 104
Reputation: 3870
Let's follow some code down the rabbit hole, starting with redirect_to @user
redirect_to
performs a redirect with location
set to url_for(@user)
link
def redirect_to(options = {}, response_status = {}) #:doc:
...
self.location = _compute_redirect_to_location(request, options)
...
end
def _compute_redirect_to_location(request, options) #:nodoc:
...
else
url_for(options)
...
end
So far, so good. redirect_to
has no say in how the path is determined. Next, let's look at url_for
. link
def url_for(options = nil)
...
else
...
builder = ActionDispatch::Routing::PolymorphicRoutes::HelperMethodBuilder.send(method)
...
builder.handle_model_call(self, options)
...
end
Looks like url_for
is in charge of deciding how the url should be built. In this case, it gets sent to HelperMethodBuilder. link
def handle_model_call(target, model)
method, args = handle_model model
target.send(method, *args)
end
def handle_model(record)
...
named_route = if model.persisted?
...
get_method_for_string model.model_name.singular_route_key
...
[named_route, args]
end
def get_method_for_string(str)
"#{prefix}#{str}_#{suffix}"
end
There we go. handle_model
gets the the (persisted) record's model_name
, which returns an ActiveModel::Name
object, and gets the singular_route_key
from it.
pry(main)> User.first.model_name.singular_route_key
=> "user"
get_method_for_string
uses the singular_route_key
to complete the helper method to call. I will leave the logic to derive "prefix"
/"suffix"
as an academic exercise, but it should return "user_path"
.
So, to answer the question, the singular form is coded into ActiveModel::Name and HelperMethodBuilder. Hope that helps!
Upvotes: 1
Reputation: 7339
Welcome to rails.
If you view all of your routes, you might notice why this happens straight away. But since devise adds lots of extra routes that might be confusing too.
Rail's routing uses the notion of plural and singlar models.
So lets say you have A user, the path is singluar -- user_path(@user)
- and the url for this will be /users/1
.
If you want to view a collection of ALL users, the path is plural -- users_path
-- and the URL for this will be /users
The same goes for all routes related to that user. When you're talking about actions that only affect a single object the route is singular, and actions that affect multiple objects are plural. A long read but because it dictates how actions are resolved, an excellent resource is: http://guides.rubyonrails.org/routing.html
Upvotes: 1