ошибка проверки рельсов вложенный объект неопределенный метод ... для nil: NilClass - PullRequest
0 голосов
/ 08 ноября 2018

Я создал приложение для планирования, в котором люди назначаются в комнаты каждый день. По четвергам кто-то должен быть назначен на «пейджер пикап», и у меня возникают проблемы с проверкой, чтобы проверить это.

Модель

class Schedule < ActiveRecord::Base 
  has_many :rooms
  ...
  validate :thursday_schedule_must_have_pager_pickup
  ...

  def add_rooms
    return unless self.rooms.count == 0                           
    n = 1
    tomorrow = DateTime.tomorrow                                  
    Schedule.site_list.each do |site|                             
      Schedule.const_get(site).each do |room|                     
        self.rooms.build(order: n,                                
                      site: site.to_s,                         
                      name: room,
                      start_hour: get_start_hour(tomorrow),    
                      start_minute: get_start_minute(tomorrow, site.to_s))                   
        n += 1                                                    
      end
    end
    self.add_pager_pickup(n, tomorrow) if true # self.for_thursday?
    self.add_today_call_data(n) if no_call_data                   
  end
...
def add_pager_pickup(order, tomorrow)
  self.rooms.build(order: order,
      site: "TSH",
      name: "Pager Pickup",
      start_hour: 7,
      start_minute: get_start_minute(tomorrow, "TSH"))
  end
end

class Room < ActiveRecord::Base
  belongs_to :schedule
  ...
end

Код, который я хотел бы написать:

def thursday_schedule_needs_pager_pickup
  if self.for_thursday? && self.rooms.where(name: "Pager Pickup").first.initials.blank?
    errors.add(:rooms, "'Pager Pickup' can't be empty.  Select '-- late start' if no one should come in early to pick up pager.")
  end
end

Это генерирует следующие ошибки:

NoMethodError in SchedulesController#create
undefined method `initials' for nil:NilClass

Добавив комнату «Pager Pickup» в расписание в последний раз, я могу взломать валидацию с помощью следующего кода:

... self.rooms.last.initials.blank?

Но это хрупко и мешает мне добавить второго, необязательного, человека, отвечающего за пейджер, "2nd Pager Pickup", после первого.

За баллы Жюльена:

Диспетчер расписания

class SchedulesController < ApplicationController 
...
  def new
    s = current_user.schedules.new
    s.add_rooms
    @schedule = s
  end

  def create 
    @schedule = current_user.schedules.build(schedule_params)
    if @schedule.save 
      flash.now[:success] = "Draft Schedule Saved! Now Confirm or Edit."
      render :show
    else
    render :new
  end
...
end

У кого-нибудь есть мысли?

Заранее спасибо!

Ответы [ 3 ]

0 голосов
/ 23 ноября 2018

Если я правильно читаю ваш код, у вас есть несохраненный объект, и вы пытаетесь запустить на нем следующую проверку:

self.rooms.where(name: "Pager Pickup").first.initials.blank?

Проблема этого подхода заключается в том, что .where при ассоциациизапустит запрос к базе данных (или, если быть точным, он запустит запрос, если ваши объекты были сохранены, но ничего не сделает для несохраненных отношений).Это не сработает для вас, вы еще ничего не сохранили, вам нужно работать с объектами в памяти.Если вы измените эту строку на:

self.rooms.detect {|r| r.name == "Pager Pickup" }.initials.blank?

, она должна работать, но она по-прежнему подвержена ошибкам, если вы просто оставите ее в своей модели, так как в другом контексте может не бытькомната с таким именем и .initials все равно будет вызываться на nil.Я бы посоветовал вам перенести такую ​​логику на фабричные объекты, где вы можете строго привязать свои проверки к контексту.

Чтобы полностью понять концепцию, вы можете запустить это в rails console:

s = Schedule.new
# => #<Schedule id: nil>
s.rooms << Room.new(foo: "bar")
# => #<ActiveRecord::Associations::CollectionProxy [#<Room id: nil, schedule_id: nil, foo: "bar">]>
s.rooms.where(foo: "bar")
# => #<ActiveRecord::AssociationRelation []>
s.rooms.detect { |r| r.foo == "bar" }  
# => #<Room id: nil, schedule_id: nil, foo: "bar">

Примечание : ваш "хак" с .last сработал, потому что он работал с массивом, а не с ActiveRecord :: Relation.

0 голосов
/ 23 ноября 2018

Проблема, с которой вы сталкиваетесь, заключается в том, что вы пытаетесь выполнить запрос к модели, которая еще не сохранена в базе данных. У него нет id, а у связанных с ним моделей rooms тоже нет id.

Ваша проверка выдает ошибку, потому что .where вызывает базу данных и не может найти модели, так как они в настоящее время живут только в памяти.

self.rooms.where(name: "Pager Pickup").first.initials.blank?

Однако у вас есть информация по обеим моделям для правильной проверки, вы просто ищете ее не в том месте.

Если вы отлаживаете свое приложение так, что ваш экземпляр Schedule собран (еще не сохранен), и вы добавили к нему немного rooms (еще не сохранено), вы увидите следующее поведение:

@schedule.rooms.length # It will be some value bigger than 0

@schedule.rooms.count # It will be zero

Почему? Потому что .length работает над объектом как массив, а .count ищет его в базе данных. Как я уже упоминал ранее, ваши модели не существуют в БД, поэтому они не найдут их, но они находятся в памяти, поэтому вы можете измерить length.

Изменение, которое вам нужно сделать в этой проблеме, состоит в том, чтобы имитировать то, что делает .where, но в памяти, с помощью простого .select:

self.rooms.select { |r| r.name == 'Pager Pickup' }.first.initials.blank?

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

0 голосов
/ 09 ноября 2018

Хорошо, прежде всего

self.rooms.where(name: "Pager Pickup") 

потенциально собирается возвращать несколько объектов, поэтому он не дает вам объект Room, но, вероятно, объект ActiveRecord::Relation, поэтому вам нужно будет добавить что-то вроде .first перед .initials, чтобы получить Room вот так:

self.rooms.where(name: "Pager Pickup").first.initials.blank?

Но в любом случае ошибка говорит о том, что она ничего не находит, поэтому кажется, что на момент проверки отношение rooms для этого расписания не существует или является "пустым", поскольку оно утверждает, что nil возвращается из этот запрос вместо пустого массива, так что я предполагаю, что ваша проверка происходит до того, как комната действительно будет создана / сохранена.

Возможно, покажите нам create действие SchedulesController, чтобы увидеть, если что-то там не так.

UPDATE

После просмотра вашего дополнительного кода проблема заключается в том, что вы вызываете add_rooms для объекта расписания, отличного от того, который вы создаете. Я предполагаю, что у вас сложилось впечатление, что переменные экземпляра (переменные, начинающиеся с @) сохраняется между запросами, но это не так, поэтому объект @schedule в вашем действии create отличается от объекта в вашем действии new, поэтому в нем еще нет комнат, обновите действие create, чтобы заполнить комнаты этого объекта делают что-то вроде этого:

  def create 
    @schedule = current_user.schedules.build(schedule_params)
    @schedule.add_rooms    # <-- Add this line
    if @schedule.save 
      flash.now[:success] = "Draft Schedule Saved! Now Confirm or Edit."
      render :show
    else
    render :new
  end
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...