Rails验证错误,嵌套对象的未定义方法...适用于nil:NilClass

时间:2018-11-08 12:56:23

标签: ruby-on-rails ruby-on-rails-4 rails-activerecord

我制作了一个调度应用程序,每天将人员分配到房间。在星期四必须指派一个人进行“寻呼机接送”,我在验证时遇到问题,无法进行检查。

型号

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?

但这很脆弱,使我无法在第一个之后添加第二个可选的传呼机代答人“第二个传呼机代答”。

朱利安的观点:

计划控制器

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 :(得分:2)

如果我正确地阅读了您的代码,则说明您有一个未保存的对象,并且您正在尝试对其进行此验证:

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上操作的。

答案 1 :(得分:2)

您面临的问题是您试图对尚未保存到数据库中的模型进行查询。它没有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?

这是您唯一需要做的更改,但是您应该了解原因。

答案 2 :(得分:0)

好吧,首先

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

可能会返回多个对象,因此它不会给您一个Room对象,但可能会给您一个ActiveRecord::Relation对象,因此您需要在{{1 }}像这样获得.first

.initials

但是无论如何,错误指出它没有找到任何东西,因此似乎在进行验证时,此计划的Room关系不存在或为空,因为它声称是{{1} }从该查询返回,而不是返回一个空数组,因此我猜测您的验证发生在实际创建/保存房间之前。

也许向我们展示self.rooms.where(name: "Pager Pickup").first.initials.blank? 的{​​{1}}动作,看看那里是否有问题。

更新

看到额外的代码后,问题在于您正在将add_rooms调用到与正在创建的日程表对象不同的日程表对象上,我想您的印象是实例变量(以{{1}开头的变量)在请求之间保持不变,但不是,因此,您的rooms动作中的nil对象与您的create动作中的对象不同,因此它还没有房间,更新您的SchedulesController操作,以执行以下操作填充该对象的房间:

@