Search code examples
ruby-on-railsrubydevisehas-many

Rails User ID in an associated model, why can't I call the User's information?


I have a meetings table that has a requester_id and requestee_id which are both user ids (using devise).

create_table "meetings", force: :cascade do |t|
 t.integer  "requestor_id"
 t.integer  "requestee_id"
 t.datetime "meeting_time"
 t.boolean  "accepted"
 t.datetime "created_at",   null: false
 t.datetime "updated_at",   null: false
end

When calling the requestor_id.first_name:

View

<tbody>
 <% @meetings.each do |meeting| %>
  <tr>
    <td><%= meeting.requestor_id %></td>
    <td><%= @requestor.first_name %></td>
    <td><%= meeting.requestee_id %></td>
    <td><%= meeting.meeting_time %></td>
    <td><%= meeting.accepted %></td>
    <td><%= link_to 'Show', meeting %></td>
    <td><%= link_to 'Edit', edit_meeting_path(meeting) %></td>
    <td><%= link_to 'Destroy', meeting, method: :delete, data: { confirm: 'Are you sure?' } %></td>
  </tr>
 <% end %>
</tbody>

Controller

 def show
  @requestor = User.find(params[:requestor_id])
 end

It says undefined method `first_name' for nil:NilClass

I'm not sure if this has any relevance, but when I rake routes, none of the meetings have a path for some reason. It just says:

PUT    /languages_users/:id(.:format)               languages_users#update
DELETE /languages_users/:id(.:format)               languages_users#destroy
GET    /meetings(.:format)                          meetings#index
POST   /meetings(.:format)                          meetings#create
GET    /meetings/new(.:format)                      meetings#new
GET    /meetings/:id/edit(.:format)                 meetings#edit
GET    /meetings/:id(.:format)                      meetings#show
PATCH  /meetings/:id(.:format)                      meetings#update
PUT    /meetings/:id(.:format)                      meetings#update
DELETE /meetings/:id(.:format)                      meetings#destroy

some of my config/routes.rb

  Rails.application.routes.draw do
   resources :meetings
   resources :meetings

   devise_for :users

   root 'welcome#index'
  end

app/model/meetings.rb

  class Meeting < ActiveRecord::Base
   has_many :users
  end

How can I call the User table, with the integer in the requestor_id, and requestee_id. As well, conceptually what am I missing or not understanding here?

Thanks in advance!


Solution

  • I notice a few opportunities for improvement in the code you've provided.

    First, it appears as though you have resources :meetings listed twice in your routes file. That may have been a copy-paste error on your part into your Stack Overflow question, or, if that is the actual code, it may be why you say "none of the meetings have a path for some reason."

    Independent of that, let's look at a subset of your rake: routes, specifically your meetings#show route:

    GET    /meetings/:id(.:format)                      meetings#show
    

    With this resourceful route, you get params[:id] sent to your controller, in this case, the id of a meeting.

    Now, one piece that confuses me. In your view code, you refer to @meetings, making me think this is the index view, and not the show view, where I would expect to see a variable called @meeting. I'm going to move forward right now discussing your show action. However, I'll circle back to your index view at the end.

    In your show controller action, you have the line:

    @requestor = User.find(params[:requestor_id])
    

    However, your route is not providing a requestor_id to the params hash, which means you're calling User.find(nil). With the Controller code as written, I would be expecting you to be hitting an error along the lines of: ActiveRecord::RecordNotFound: Couldn't find User with 'id'= or something similar when hitting show.

    What you want to do instead is Find your Meeting, and get the Requestor based off the found Meeting.

    So you could do something like this in your controller:

    app/controllers/meetings_controller.rb

    def show
      @meeting = Meeting.find(params[:id])
      @requestor = User.find(@meeting.requestor_id)
    end
    

    ...at which point I would expect your view to render correctly. However, there's a more Rails-y way to do this, which takes me to your meetings model. Here are the relationships I would define:

    class Meeting < ActiveRecord::Base
      belongs_to :requestor, class_name: "User"
      belongs_to :requested, class_name: "User"
    end
    

    From the Rails Guide for the belongs_to association:

    4.1.2.2 :class_name

    If the name of the other model cannot be derived from the association name, you can use the :class_name option to supply the model name.

    Unless your User model has a column titled meeting_id, I suspect your Meeting.users association is not wired up to do anything.

    The associations I've written above works even though you don't actually have a table called Requestor. Instead you specify that both requestors and requestees are just special kinds of Users. That way, you don't need to define @requestor in your Controller at all, leaving you instead with...

    app/controllers/meetings_controller.rb

    def show
      @meeting = Meeting.find(params[:id])
    end
    
    def index
      @meetings = Meeting.all
    end
    

    ...and in your index view (I told you I'd come back to it)...

    app/views/meetings/index.html.erb

     <% @meetings.each do |meeting| %>
      <tr>
        <td><%= meeting.requestor.first_name %></td>
        <td><%= meeting.requestee.first_name %></td>
        <td><%= meeting.meeting_time %></td>
        <td><%= meeting.accepted %></td>
        <td><%= link_to 'Show', meeting %></td>
    

    ...and so on...

    Let me know if that makes sense!