Search code examples
ruby-on-railsrubydrylink-to

How can link_to a master object look cleaner in the view?


I have User model, that has many Details.

In the Detail list(index) page (./details) have a link_to back the master table, or the User that owns the details (./user/1).

Basically, I'm wondering if there is a way to make the syntax of the link_to method, look any cleaner with ruby or rails conventions that I'm not aware of.

<% @details.each do |detail| %>
  <tr>
    <td><%= detail.name %></td>
    <td><%= link_to User.find_by_id(detail.foreign_key_id).name, \
            User.find_by_id(detail.foreign_key_id) %></td>
    ...

This works fine, but I seem to recall seeing a cleaner syntax at some point.

[EDIT] Also in this case, I have two ways that the User can be associated to the Detail.

The data model I'm thinking of is similar to a Project, so I'll present the problem in that way. So a user can be both an owner of the project, and/or, a sponsor of the project.

So, the User class would have the following....

 class User < ActiveRecord::Base
   has_many :owned_projects, class_name: "Project", foreign_key: :owner_id
   has_many :sponsored_projects, class_name: "Project", foreign_key: :sponsor_id
 end

Then the Project class would have this in it's definition

class Project < ActiveRecord::Base
  belongs_to :owner,    :foreign_key => :owner_id,    :class_name => "User"
  belongs_to :sponsor,  :foreign_key => :sponsor_id,  :class_name => "User"
end

The project table has both an owner_id field and a sponsor_id field, so the User class can be linked twice to a Project table.

Given all of that, back to question at hand, I don't see how to use a cleaner syntax as described by @Michael Gaskill

If it were to look like that, would it be like this?%= link_to @project.sponsor.name, project.sponsor_id %></td>

Rails magic doesn't seem to be figuring that out, or I have it configured wrong.

I have tested following out, and this will work, as I mentioned before, but I am trying to make it sure there's no cleaner way.

(the goal is to see a page about the detail, or Project in this example, and see both category of User that is associated to it, and have their names be clickable links to their show page profiles)

<p>
  <strong>Name:</strong>
  <%= @project.name %>
</p>

<p>
  <strong>Owner:</strong>
  <%= link_to User.find_by_id(@project.owner_id).name, User.find_by_id(@project.owner_id) %>
</p>

<p>
  <strong>Sponsor:</strong>
  <%= link_to User.find_by_id(@project.sponsor_id).name, User.find_by_id(@project.sponsor_id) %>
</p>

Solution

  • If your relationships are set up correctly, you should be able to do something like this:

    %= link_to detail.user.name, detail.user %></td>
    

    You want to avoid doing any ActiveRecord queries in the view if you can possibly help it. If you do have to do queries in the view, make sure that you're not duplicating them. Queries in the view tend to cause the N+1 problem, and duplicating them can cause that to become 2*(N+1), or worse.

    Based on the new relationships shown in your question, you should be able to use this in place of your view code:

    <p>
      <strong>Name:</strong>
      <%= @project.name %>
    </p>
    
    <p>
      <strong>Owner:</strong>
      <%= link_to @project.owner.name, @project.owner) %>
    </p>
    
    <p>
      <strong>Sponsor:</strong>
      <%= link_to @project.sponsor.name, @project.sponsor) %>
    </p>
    

    Simply put, the Rails magic knows how to traverse the relationships to retrieve the name from each of the sponsor and owner model objects. Likewise, link_to knows how to automagically retrieve the path information from the sponsor and owner (given as the second parameter to link_to) to create the right link for each object. Note that this works when you use the model object (e.g. "owner"), instead of object_id (e.g. "owner_id").

    The link_to API documentation gives some deeper guidance on how to use link_to with your model data. Example #2 demonstrates the exact technique that this answer uses; however, we choose to provide the link text directly from the ActiveRecord model (as the owner or sponsor name), rather than hardcoding it as is done in the example.

    You can also be more explicit in which route you want to link to, by using the route helpers to define the path that you want to use. Let's make a similar view page that shows how to link to the edit pages for the owner and sponsors of the project:

    <p>
      <strong>Name:</strong>
      <%= @project.name %>
    </p>
    
    <p>
      <strong>Owner:</strong>
      <%= link_to @project.owner.name, edit_owner_path(@project.owner) %>
    </p>
    
    <p>
      <strong>Sponsor:</strong>
      <%= link_to @project.sponsor.name, edit_owner_path(@project.sponsor) %>
    </p>
    

    You can use any of the route helpers that Rails creates for you to navigate to any of your models' actions. To find out which route helpers are available for your project, you can run rake routes from the command line, and see results like this:

          Prefix Verb   URI Pattern                  Controller#Action
        projects GET    /projects(.:format)          projects#index
                 POST   /projects(.:format)          projects#create
     new_project GET    /projects/new(.:format)      projects#new
    edit_project GET    /projects/:id/edit(.:format) projects#edit
         project GET    /projects/:id(.:format)      projects#show
                 PATCH  /projects/:id(.:format)      projects#update
                 PUT    /projects/:id(.:format)      projects#update
                 DELETE /projects/:id(.:format)      projects#destroy
           users GET    /users(.:format)             users#index
                 POST   /users(.:format)             users#create
        new_user GET    /users/new(.:format)         users#new
       edit_user GET    /users/:id/edit(.:format)    users#edit
            user GET    /users/:id(.:format)         users#show
                 PATCH  /users/:id(.:format)         users#update
                 PUT    /users/:id(.:format)         users#update
                 DELETE /users/:id(.:format)         users#destroy
    

    This is the standard set of routes defined by having these definitions in your config/routes.rb:

    resources :project
    resources :user
    

    In the output from rake routes, you see the route helpers shown in the "Prefix" column at the left. These are the identifiers that you'll use to supply to link_to for explicit page links. These are called "prefix", because they're used with either "path" or "url" to generate a relative path or absolute url, respectively. Consider these uses:

    user_path(@project.owner)      # ==> "/user/9"
    user_url(@project.sponsor)     # ==> "http://example.com/user/23"
    new_user_path                  # ==> "/user/new"
    new_user_url                   # ==> "http://example.com/user/new"
    

    Note that with some of these route helpers have to specify an argument, and some you don't. You can read this from the rake routes output as arguments are supplied in the order of appearance of replacement parameters (e.g. :id), excluding the optional replacement parameters (e.g. (.:format)). The standard set of routes for a resource give you these route helpers that you can use:

    users_path                     # ==> "/user"
    new_user_path                  # ==> "/user/new"
    user_path(@user)               # ==> "/user/139"
    edit_user_path(@user)          # ==> "/user/139/edit"
    

    The other default routes aren't typically used directly in your project, so you wouldn't refer to them directly.

    So, any time that you have a model object, you can create any of the links that you want to use, either using the implicit path approach:

    <%= link_to @user.name, @user %>
    

    or the path helpers approach:

    <%= link_to @user.name, user_path(@user) %>
    

    Both of these should produce identical links for user #69, "Scott Tenorman":

    <a href="/user/69">Scott Tenorman</a>
    

    And that's all there is to the magic. It's not really magic at all, but a series of smart, carefully crafted conventions that work together to make it easy to do easy things. Once you know how they work. Happy linking!