davedub
davedub

Reputation: 94

NoMethodError in Rails method using has_one association

Here is my code:

// in swimmer.rb (model):

    belongs_to :user

// in user.rb (model):

    has_one :swimmer, :dependent => :destroy
    accepts_nested_attributes_for :swimmer, :allow_destroy => true   
    attr_accessible :swimmer_attributes

// in swimmers_controller.rb:

  def profile
    @swimmer = Swimmer.find_by_user_id(current_user)
    @swimmer_nickname = @swimmer.nickname
    @swimmer_gender = @swimmer.gender
    @title = "Swimmer Profile for #{@current_user.email}"
  end

// in profile.html.erb (in swimmers views folder)

   <% if @swimmer %>
     <h3><%= @title %></h3>
     <p>Nickname: <%= @swimmer_nickname %></p>
     <p>Gender: <%= @swimmer_gender %></p>
   <% else %>
     <h3>No Swimmer Profile for<%= current_user.email %></h3>
   <% end %>

If a swimmer object has a user_id that matches the @swimmer instance variable in the swimmers#controller method, because there is a logged in user (using the Devise gem) and a swimmer has been created for that user, then the profile view works as intended. If not, the page shows an error:

   NoMethodError (undefined method `nickname' for nil:NilClass):
     app/controllers/swimmers_controller.rb:66:in `profile'

But since the profile view has an if/else conditional, I want the lack of the swimmer object associated with the logged in user to force the view to show the else content. Apparently, the "Swimmer.find_by_user_id(current_user)" method is creating a nil object in the NilClass. How do I get it so that it doesn't create anything and thereby brings up the else conditional?

Repo is at https://github.com/drollwit/vst2/tree/ver2. This is an exercise, not a real project. There is probably an easy answer here but I can't figure it out (still learning Rails basics). Any help would be appreciated.

Upvotes: 1

Views: 1149

Answers (2)

Soundar Rathinasamy
Soundar Rathinasamy

Reputation: 6728

The cause of error exists in the below line of code.

@swimmer = Swimmer.find_by_user_id(current_user)

It never finds Swimmer based on user_id.Because

find_by_user_id

expects an ID.Try

@swimmer = Swimmer.find_by_user_id(current_user.id)

You will get swimmer object then the error will not occur.

And you view file can be improved by

<% if @swimmer %>
 <h3><%= "Swimmer Profile for #{@current_user.email}" %></h3>
 <p>Nickname: <%= @swimmer.nickname %></p>
 <p>Gender: <%= @swimmer.gender %></p>
<% else %>
 <h3>No Swimmer Profile for<%= current_user.email %></h3>
<% end %>

This will reduce below lines of code from controller.

@swimmer_nickname = @swimmer.nickname
@swimmer_gender = @swimmer.gender
@title = "Swimmer Profile for #{@current_user.email}"

Upvotes: 1

Rich Drummond
Rich Drummond

Reputation: 3509

The problem isn't the view, it's your profile method:

def profile
  @swimmer = Swimmer.find_by_user_id(current_user)
  @swimmer_nickname = @swimmer.nickname
  @swimmer_gender = @swimmer.gender
  @title = "Swimmer Profile for #{@current_user.email}"
end

If no swimmer can be found then @swimmer is nil and the following line fails because you are invoking the method 'nickname' on nil.

A simple fix is as follows:

def profile
  @swimmer = Swimmer.find_by_user_id(current_user)
  @swimmer_nickname = @swimmer.try(:nickname)
  @swimmer_gender = @swimmer.try(:gender)
  @title = "Swimmer Profile for #{@current_user.email}"
end

Now @simmwer_nickname and @swimmer_gender will be set to nil if @swimmer doesn't exist. See http://api.rubyonrails.org/classes/NilClass.html#method-i-try.

Personally, I wouldn't assign these extra instance variables in the controller like this. I would move this to the view or perhaps a helper.

Upvotes: 1

Related Questions