Norman Chan
Norman Chan

Reputation: 496

undefined method `email' for nil:NilClass when rendering form inside index

I can't seem to figure why I get undefined method `email' for nil:NilClass whenever I try to call any methods on my message class.

index.html.erb messages:

 <div class="callout">
  <div class="messages-box">
    <% @messages.each do |message| %>
      <% user_status = message.user_id == current_user.id ? "reciever" : "sender" %>
      <div class="<%= user_status %> callout">
        <p><%= message.body %></p>
        <p><%= message.user.email %></p>
      </div>
    <% end %>
    <%= simple_form_for([@conversation, @message]) do |f| %>
      <%= f.input :body %>
      <%= f.hidden_field :user_id, value: current_user.id  %>
      <%= f.submit "Add Reply", class: 'button' %>
    <% end %>
  </div>
</div>

message controller:

class MessagesController < ApplicationController
  before_action :setup_conversation

  def index
    @messages = @conversation.messages
    @message = @conversation.messages.new
  end

  def new
    @message = @conversation.messages.new
  end

  def create
    @message = @conversation.messages.new(message_params)

    redirect_to conversation_messages_path(@conversation) if @message.save
  end

  private

  def setup_conversation
    @conversation = Conversation.find(params[:conversation_id])
  end

  def message_params
    params.require(:message).permit(:body, :user_id)
  end
end

works fine if I remove the form or If remove the @message instance variable from the index action in the corresponding controller but I need it for the form in the index view.

Upvotes: 0

Views: 66

Answers (1)

coreyward
coreyward

Reputation: 80070

This is a pretty interesting case because you're getting bit by ActiveRecord collection proxies.

In this line, you assign @messages, which is an instance of a subclass of ActiveRecord_Associations_CollectionProxy, which doesn't actually have any records from the database:

@messages = @conversation.messages

What it does have, however, is a reference to the instance of Conversation that you've already assigned to @conversation.

On the next line, you create a new instance of Message associated with the same @conversation:

@message = @conversation.messages.new

You still haven't made any SQL queries at this point. Then we get into rendering, where you call each on @messages, which triggers the query:

@message.each do |message|
  ...
end

Because CollectionProxy is more sophisticated than a simple AssociationRelation, it coalesces the data from your database — saved records with a User association — with the new data in the collection — your non-persisted @message.

The result is that inside of this block, the last message.user is nil, having not been set. This doesn't explode on the previous lines because Ruby is happy to render message.body (which is nil) and compare nil with [email protected].

Upvotes: 1

Related Questions