2

Ruby 2.1.5 および 2.2.4 では、新しい Collector を作成すると正しい結果が返されます。

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=[]>

Ruby 2.3.1 にアップグレードすると、代わりに nil が返され始めます。

> Collector.new
=> #<ResourceResponses::Collector>
> Collector.new.responses
=> nil

OpenStruct が 2.3 で 10 倍速くなった方法について多くのことを読みましたが、Collector と Response の関係を壊すような変更が行われたことはわかりません。どんな助けでも大歓迎です。Rails のバージョンは 4.2.7.1 です。

4

1 に答える 1

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

=興味深い部分は、メソッド名が an で終わらなかった場合、および追加の引数がない場合に実行される中央のブロックです。

if @table.key?(mid)
  new_ostruct_member!(mid) unless frozen?
  @table[mid]
end

ご覧のとおり、実装は実際に値を読み取る前に、キーが存在するかどうかを最初にチェックします。

Response.newこれにより、キー/値が設定されていない場合にnew を返すハッシュで実装が壊れます。呼び出すだけkey?ではデフォルト値の設定がトリガーされないため:

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

Ruby 2.2にはこの最適化がありませんでした。@table[mid]最初に確認せずに戻ってきただけ@table.key?です。

于 2016-09-01T20:28:40.827 に答える