代码之家  ›  专栏  ›  技术社区  ›  BenU

rails验证错误嵌套对象未定义方法…对于nil:NilClass

  •  0
  • BenU  · 技术社区  · 6 年前

    模型

    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
    

    通过将“寻呼机接收”室添加到时间表中,我可以使用以下代码进行验证:

    ... 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 回复  |  直到 4 年前
        1
  •  2
  •   Marcin Kołodziej    6 年前

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

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

    .where on association将运行数据库查询(或者更确切地说,如果保存了对象,它将运行查询,但对未保存的关系不做任何操作)。这对您不起作用,您还没有保存任何内容,您必须对内存中的对象进行操作。如果您将该行更改为:

    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::关系。

        2
  •  2
  •   EmmanuelB    6 年前

    您面临的问题是,您正在尝试对尚未保存到数据库中的模型执行查询。它没有一个 id rooms 他们自己也是。

    您的验证引发错误,因为 .where 调用数据库,但它找不到模型,因为它们当前只存在于内存中。

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

    如果您调试应用程序以便 Schedule 已生成(尚未保存)并且您添加了一些 对于它(尚未保存),您将看到以下行为:

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

    为什么?因为 .length .count length 当然。

    哪里 但在记忆中,用一个简单的 .select

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

    这是你唯一需要做的改变,但你应该明白为什么。

        3
  •  0
  •   Julien    6 年前

    好的,那么首先

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

    可能会返回多个对象,因此它不会给您 Room ActiveRecord::Relation 所以你需要添加 .first 之前 .initials 房间 像这样:

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

    但无论如何,错误表明它没有找到任何东西,所以在验证时 rooms 此计划的关系不存在或为“空”,因为它正在声明 nil 是从该查询返回的,而不是空数组,因此我猜您的验证发生在实际创建/保存文件室之前。

    create 政府的行动 SchedulesController 看看有没有什么不对劲。

    更新

    在看到额外的代码后,问题是您调用add\u rooms的调度对象与创建的调度对象不同,我猜您的印象是实例变量(变量以 @ )在请求之间持久化,但它们不是,因此 @schedule 在你的 行动和你的计划不一样 new 操作因此它还没有任何房间,请更新您的 填充该对象的房间的操作如下:

      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