NoMethodError в методе Rails с использованием ассоциации has_one

Вот мой код:

// в swimmer.rb (модель):

    belongs_to :user

// в user.rb (модель):

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

// в 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

// в profile.html.erb (в папке представлений пловцов)

   <% 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 %>

Если у объекта пловца есть user_id, который соответствует переменной экземпляра @swimmer в методе swimmers#controller, потому что есть пользователь, вошедший в систему (с помощью драгоценного камня Devise), и пловец был создан для этого пользователя, то представление профиля работает как предназначена. Если нет, на странице отображается ошибка:

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

Но поскольку представление профиля имеет условие if/else, я хочу, чтобы отсутствие объекта пловца, связанного с вошедшим в систему пользователем, заставляло представление отображать содержимое else. По-видимому, метод «Swimmer.find_by_user_id(current_user)» создает нулевой объект в NilClass. Как мне сделать так, чтобы он ничего не создавал и тем самым вызывал условие else?

Репозиторий находится по адресу https://github.com/drollwit/vst2/tree/ver2. Это упражнение, а не настоящий проект. Вероятно, здесь есть простой ответ, но я не могу его понять (все еще изучаю основы Rails). Любая помощь будет оценена по достоинству.


person davedub    schedule 18.04.2012    source источник


Ответы (2)


Причина ошибки находится в приведенной ниже строке кода.

@swimmer = Swimmer.find_by_user_id(current_user)

Он никогда не находит Swimmer на основе user_id. Потому что

find_by_user_id

ожидает ID.Попробуйте

@swimmer = Swimmer.find_by_user_id(current_user.id)

Вы получите объект пловца, тогда ошибка не возникнет.

И вы можете улучшить просмотр файла с помощью

<% 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 %>

Это уменьшит количество строк кода из контроллера ниже.

@swimmer_nickname = @swimmer.nickname
@swimmer_gender = @swimmer.gender
@title = "Swimmer Profile for #{@current_user.email}"
person Soundar Rathinasamy    schedule 18.04.2012
comment
Идеально. Это позволяет избежать использования #try в представлении и делает контроллер чище. Я подумал, что должно быть что-то очевидное, что я упустил. Я предполагаю, что главный вывод — научиться правильно использовать эти динамические методы find_by. - person davedub; 19.04.2012
comment
Похоже, это ответило на ваш вопрос и должно быть помечено как решенное. - person Joe; 12.05.2012

Проблема не в представлении, а в методе вашего профиля:

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

Если пловец не найден, то @swimmer равно nil, и следующая строка завершается ошибкой, потому что вы вызываете метод 'nickname' для nil.

Простое исправление заключается в следующем:

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

Теперь @simmwer_nickname и @swimmer_gender будут равны нулю, если @swimmer не существует. См. http://api.rubyonrails.org/classes/NilClass.html#method-i-try.

Лично я бы не стал назначать эти дополнительные переменные экземпляра в контроллере таким образом. Я бы переместил это в представление или, возможно, в помощника.

person Rich Drummond    schedule 18.04.2012
comment
Это сработало - спасибо! Я подумал, что это проблема метода (а не представления). Я более подробно рассмотрю, как работает метод try. Что касается переноса всего этого на вспомогательный метод, вы правы - это не тощий контроллер. Я немного повозился с ним и переместил его в вид, но похоже, что там слишком много Ruby. Мне нужно посмотреть, как сделать его вспомогательным методом. - person davedub; 18.04.2012