rails 验证错误嵌套对象未定义方法... nil:NilClass
rails validation error nested object undefined method... for nil:NilClass
我制作了一个日程安排应用程序,每天都会将人们分配到房间。在星期四,必须为 'pager pickup' 分配某人,而我在验证方面遇到了问题。
型号
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?
但这很脆弱,我无法在第一个之后添加第二个可选的传呼机接听人,“第二个传呼机接听人”。
根据 Julien 的观点:
调度控制器
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
大家有什么想法吗?
提前致谢!
好的,首先
self.rooms.where(name: "Pager Pickup")
可能会 return 多个对象,所以它不会给你一个 Room
对象,但可能是一个 ActiveRecord::Relation
对象,所以你需要添加类似 .first
在 .initials
之前得到一个 Room
像这样:
self.rooms.where(name: "Pager Pickup").first.initials.blank?
但无论如何,错误表明它没有找到任何东西,因此在验证时似乎此计划的 rooms
关系不存在或者是 "empty" 因为它声称nil
是从该查询而不是空数组中 return 编辑的,所以我猜你的验证发生在房间实际 created/saved.
之前
也许向我们展示 SchedulesController
的 create
操作,看看那里是否有问题。
更新
看到你的额外代码后,问题是你正在调用 add_rooms 到一个不同于你正在创建的调度对象,我猜你的印象是实例变量(变量开始@
) 在请求之间持续存在,但它们不是,因此 create
操作中的 @schedule
对象与 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
如果我没看错你的代码,你有一个未保存的对象,你正试图运行对其进行验证:
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">
注意:你的 "hack" 和 .last
工作是因为它在数组上运行,而不是 ActiveRecord::Relation.
您面临的问题是您正在尝试对尚未保存到数据库中的模型进行查询。它没有 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?
这是您唯一需要做的更改,但您应该了解原因。
我制作了一个日程安排应用程序,每天都会将人们分配到房间。在星期四,必须为 'pager pickup' 分配某人,而我在验证方面遇到了问题。
型号
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?
但这很脆弱,我无法在第一个之后添加第二个可选的传呼机接听人,“第二个传呼机接听人”。
根据 Julien 的观点:
调度控制器
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
大家有什么想法吗?
提前致谢!
好的,首先
self.rooms.where(name: "Pager Pickup")
可能会 return 多个对象,所以它不会给你一个 Room
对象,但可能是一个 ActiveRecord::Relation
对象,所以你需要添加类似 .first
在 .initials
之前得到一个 Room
像这样:
self.rooms.where(name: "Pager Pickup").first.initials.blank?
但无论如何,错误表明它没有找到任何东西,因此在验证时似乎此计划的 rooms
关系不存在或者是 "empty" 因为它声称nil
是从该查询而不是空数组中 return 编辑的,所以我猜你的验证发生在房间实际 created/saved.
也许向我们展示 SchedulesController
的 create
操作,看看那里是否有问题。
更新
看到你的额外代码后,问题是你正在调用 add_rooms 到一个不同于你正在创建的调度对象,我猜你的印象是实例变量(变量开始@
) 在请求之间持续存在,但它们不是,因此 create
操作中的 @schedule
对象与 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
如果我没看错你的代码,你有一个未保存的对象,你正试图运行对其进行验证:
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">
注意:你的 "hack" 和 .last
工作是因为它在数组上运行,而不是 ActiveRecord::Relation.
您面临的问题是您正在尝试对尚未保存到数据库中的模型进行查询。它没有 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?
这是您唯一需要做的更改,但您应该了解原因。