为什么`a.is_a?数组 && !a.empty?` 引发 NoMethodError?

Why does `a.is_a? Array && !a.empty?` raise NoMethodError?

我是一名 Python 开发人员,我刚刚开始学习 Rails,所以这可能是一个菜鸟问题。我在我的代码中发现了一些令人惊讶的行为:

<!-- render flash message array if set -->
<% if flash[:notice].is_a? Array && !flash[:notice].empty? %>

这会导致以下错误:undefined method `empty?' for nil:NilClass

似乎 Ruby 不应该在该子句的第二部分调用 .empty?。我确认短路评估在其他情况下有效:

# this is more what I expected
[3] pry(main)> a = nil
=> nil
[5] pry(main)> !a.nil? && a.empty?
=> false
...
[6] pry(main)> a = 1
=> 1
[7] pry(main)> !a.nil? && a.empty?
NoMethodError: undefined method `empty?' for 1:Integer
from (pry):7:in `__pry__'

有人知道为什么会发生这种行为吗?似乎只在我使用 is_a? 而不是其他运算符时触发。

因为NilClass没有回复empty:

nil.class #=> NilClass
nil.respond_to? :empty?
#=> false

当它响应 is_a? 时:

nil.respond_to? :is_a?
#=> true

显示所有方法nil响应:

nil.methods
#=> [:&, :inspect, :to_a, :to_s, :===, :to_f, :to_i, :=~, :to_h, :nil?, :to_r, :rationalize, :|, :to_c, :^, :instance_variable_defined?, :remove_instance_variable, :instance_of?, :kind_of?, :is_a?, :tap, :instance_variable_set, :protected_methods, :instance_variables, :instance_variable_get, :public_methods, :private_methods, :method, :public_method, :public_send, :singleton_method, :define_singleton_method, :extend, :to_enum, :enum_for, :<=>, :!~, :eql?, :respond_to?, :freeze, :object_id, :send, :display, :hash, :class, :singleton_class, :clone, :dup, :itself, :yield_self, :then, :taint, :tainted?, :untaint, :untrust, :untrusted?, :trust, :frozen?, :methods, :singleton_methods, :equal?, :!, :==, :instance_exec, :!=, :instance_eval, :__id__, :__send__]


如果您通过首先检查键 :notice 的值是否存在来修复,如本例所示:https://api.rubyonrails.org/classes/ActionDispatch/Flash.html,则会引发语法错误,指出缺少括号:

flash[:notice] && flash[:notice].is_a? Array && flash[:notice].empty?
# syntax error, unexpected tCONSTANT, expecting end-of-input
# ...] && flash[:notice].is_a? Array && flash[:notice].empty?
#                              ^~~~~

发生这种情况是因为您使用的是 space 而不是 is_a? 函数参数的方括号,因此 rails 试图将整个参数作为参数发送。试试这个

<% if flash[:notice].is_a?(Array) && !flash[:notice].empty? %>

解释:

当你写 flash[:notice].is_a? Array && !flash[:notice].empty? rails 时将其解释为 flash[:notice].is_a?(Array && !flash[:notice].empty?) 所以 Array 被评估为 true 并且 !flash[:notice].empty? 被评估从而提高异常。