craig
craig

Reputation: 47

Quirky Ruby on Rails Syntax

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

Answers (2)

fylooi
fylooi

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

trh
trh

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

Related Questions