Ruby 2.3.1的OpenStruct问题

Bri*_*313 2 ruby ruby-on-rails openstruct ruby-2.3.1

在Ruby 2.1.5和2.2.4中,创建一个新的收集器会返回正确的结果.

require 'ostruct'
module ResourceResponses
  class Collector < OpenStruct
    def initialize
      super
      @table = Hash.new {|h,k| h[k] = Response.new }
    end
  end

  class Response
    attr_reader :publish_formats, :publish_block, :blocks, :block_order
    def initialize
      @publish_formats = []
      @blocks = {}
      @block_order = []
    end
  end  
end

 > Collector.new
 => #<ResourceResponses::Collector>
 Collector.new.responses
 => #<ResourceResponses::Response:0x007fb3f409ae98 @block_order=[], @blocks=  {}, @publish_formats=[]>
Run Code Online (Sandbox Code Playgroud)

当我升级到Ruby 2.3.1时,它开始返回nil而不是.

> Collector.new
=> #<ResourceResponses::Collector>
> Collector.new.responses
=> nil
Run Code Online (Sandbox Code Playgroud)

我已经做了很多关于OpenStruct如何在2.3中快10倍的阅读但是我没有看到会破坏收集器和响应之间关系的改变.非常感谢任何帮助.Rails的版本是4.2.7.1.

spi*_*ann 7

我们来看看method_missing当前实现中的实现:

def method_missing(mid, *args) # :nodoc:
  len = args.length
  if mname = mid[/.*(?==\z)/m]
    if len != 1
      raise ArgumentError, "wrong number of arguments (#{len} for 1)", caller(1)
    end
    modifiable?[new_ostruct_member!(mname)] = args[0]
  elsif len == 0
    if @table.key?(mid)
      new_ostruct_member!(mid) unless frozen?
      @table[mid]
    end
  else
    err = NoMethodError.new "undefined method `#{mid}' for #{self}", mid, args
    err.set_backtrace caller(1)
    raise err
  end
end
Run Code Online (Sandbox Code Playgroud)

有趣的部分是当方法名称没有结束时=以及没有附加参数时运行的中间块:

if @table.key?(mid)
  new_ostruct_member!(mid) unless frozen?
  @table[mid]
end
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,在实际读取值之前,实现首先检查密钥是否存在.

Response.new当没有设置键/值时,这将使用返回new的哈希中断您的实现.因为只是调用key?不会触发默认值的设置:

hash = Hash.new { |h,k| h[k] = :bar }
hash.has_key?(:foo)
#=> false
hash
#=> {}
hash[:foo]
#=> :bar
hash
#=> { :foo => :bar }
Run Code Online (Sandbox Code Playgroud)

Ruby 2.2没有这种优化.它刚刚返回@table[mid]而没有@table.key?先检查.