def wrap(string, before: "<", after: ">")
"#{before}#{string}#{after}" # no need to retrieve options from a hash
end
# optional
wrap("foo") #=> "<foo>"
# one or the other
wrap("foo", before: "#<") #=> "#<foo>"
wrap("foo", after: "]") #=> "<foo]"
# order not important
wrap("foo", after: "]", before: "[") #=> "[foo]"
One of the nice things about this compared to the hash-as-fake-keyword-args is that you errors when you make a mistake.
begin
wrap("foo", befoer: "#<")
rescue ArgumentError => e
e.message #=> "unknown keyword: befoer"
end
You can use double splat to capture all keyword arguments, just like a single splat to capture all regular arguments. You can also use the double splat to unpack a hash to keyword arguments.
# arguments
def capture(**opts)
opts
end
capture(foo: "bar") #=> {:foo=>"bar"}
# keys must be symbols
opts = {:before => "(", :after => ")"}
wrap("foo", **opts) #=> "(foo)"
The old hash style syntax is still accepted for keyword arguments, so you can update your method definitions without having to fix all the callers.
wrap("foo", :before => "{", :after => "}") #=> "{foo}"
If you're writing a library that needs to be compatible across ruby 2.0 and 1.9 the old hash-as-fake-keyword-args trick still works, and can be called as if it was using keyword arguments
def wrap(string, opts={})
before = opts[:before] || "<"
after = opts[:after] || ">"
"#{before}#{string}#{after}"
end
wrap("foo", before: "[", after: "]") #=> "[foo]"
%i{an array of symbols} #=> [:an, :array, :of, :symbols]
and with interpolation
%I{#{1 + 1}x #{2 + 2}x} #=> [:"2x", :"4x"]
Refinements is a neat idea, but the original implementation came with a weird edge cases and possible performance penalties, so what we get with Ruby 2.0.0 is a rather scaled back, and slightly less useful version of the original.
You create a refinement to a class, name spaced inside a module
module NumberQuery
refine String do
def number?
match(/\A[1-9][0-9]*\z/) ? true : false
end
end
end
this refinement isn't visible by default
"123".respond_to?(:number) #=> false
once you declare that you are 'using' the module with the refinement, then it becomes visible.
using NumberQuery
"123".number? #=> true
however 'using' is only available at the top level, and only applies to lines after it in the same source file it's declared. You also get a warning that refinements are experimental if you're running with warnings enabled.
Module gains #prepend as a compliment to #include, it works just like include, but it inserts the module in to the inheritance chain as if it were a subclass rather than a superclass.
Object
superclass
included module
class
prepended module
This takes over from Rails' #alias_method_chain or the trick of aliasing a method to a new name before redefining it and calling the original.
class Foo
def do_stuff
puts "doing stuff"
end
end
module Wrapper
def do_stuff
puts "before stuff"
super
puts "after stuff"
end
end
class Foo
prepend Wrapper
end
Foo.new.do_stuff
outputs:
before stuff
doing stuff
after stuff
there's also ::prepended and ::prepend_features method that work like ::included and ::append_features
This one might sound like gibberish, or some minor change to something you'll never use, but it's actually a really great new feature.
You can get ahold of a method object from any class or module, with instance_method
Object.instance_method(:to_s) #=> #<UnboundMethod: Object(Kernel)#to_s>
However, this method isn't bound to anything, it has no 'self', and can't be called. To call it you have to bind the method to an object, but methods have to be bound to an object of the same class as the one the method was taken from.
But now we can take a method from a module and bind it to any object
module Bar
def bar
"bar"
end
def baz
"baz"
end
end
Bar.instance_method(:bar).bind(Object.new).call #=> "bar"
This means define_method also accepts unbound methods for modules, which will let us implement a selective include
module Kernel
def from(mod, include: [])
raise TypeError, "argument must be a module" unless Module === mod
include.each do |name, original|
define_method(name, mod.instance_method(original || name))
end
end
end
class Foo
from Bar, include: {:qux => :bar}
end
f = Foo.new
f.qux #=> "bar
f.respond_to?(:baz) #=> false
class Foo
module Bar
Baz = 1
end
end
Object.const_get("Foo::Bar::Baz") #=> 1
Hash, along with ENV, nil, Struct, and OpenStruct all get a #to_h method which returns a hash
{:foo => "bar"}.to_h #=> {:foo=>"bar"}
nil.to_h #=> {}
Struct.new(:foo).new("bar").to_h #=> {:foo=>"bar"}
require "ostruct"
open = OpenStruct.new
open.foo = "bar"
open.to_h #=> {:foo=>"bar"}
There is also a Hash() method, like Array(), that delegates to #to_h
Hash({:foo => "bar"}) #=> {:foo=>"bar"}
Hash(nil) #=> {}
Array and Range get a binary search method with #bsearch. This has two modes of working, find-minimum and find-any. Both modes take a block, and the array will have to be sorted with regards to this block.
In find-minimum mode it will return the first element greater than or equal to a chosen value. To use this mode you supply a block that returns true when the supplied element is greater than or equal to the chosen value, and false otherwise.
array = [2, 4, 8, 16, 32]
array.bsearch {|x| x >= 4} #=> 4
array.bsearch {|x| x >= 7} #=> 8
array.bsearch {|x| x >= 9} #=> 16
array.bsearch {|x| x >= 0} #=> 2
array.bsearch {|x| x >= 33} #=> nil
In find any mode you need to supply a block that returns a positive number if the supplied element is less than your chosen value, a negative number if it is greater, and 0 if it is the chosen value.
array.bsearch {|x| 4 <=> x} #=> 4
array.bsearch {|x| 7 <=> x} #=> nil
Your block can return 0 for a range of values, in which case any of them may be chosen.
array = [0, 4, 7, 10, 12]
array.map {|x| 1 - x / 4 } #=> [1, 0, 0, -1, -2]
array.bsearch {|x| 1 - x / 4 } #=> 4 or 7
#lazy called on any Enumerable (Array, Hash, File, Range, etc) will return a lazy enumerator, that doesn't perform any calculations till it is forced to. In addition, elements of the enumerable will be sent though the whole chain one by one, rather than evaluating the entire enumerable at each step. In some cases this will result in less work.
You can deal with infinite collections
[1,2,3].lazy.cycle.map {|x| x * 10}.take(5).to_a #=> [10, 20, 30, 10, 20]
Or avoid consuming large resources, when you know you'll only need a little
File.open(__FILE__).lazy.each.map(&:chomp).reject(&:empty?).take(3).force
This example only reads as many lines as it takes to find 3 that aren't empty.
#lazy does incur a performance penalty, so you'll need to make sure you're only using it when it makes sense.
There's also a Enumerator::Lazy class to create your own lazy enumerators. The example above could be written as:
def populated_lines(file, &block)
Enumerator::Lazy.new(file) do |yielder, line|
string = line.chomp
yielder << string unless string.empty?
end.each(&block) # evals block, or returns enum if nil, like stdlib
end
populated_lines(File.open(__FILE__)).take(3).force
Enumerator#size will return the size of an enumerator, without evaluating the whole Enumerator. Range benefits from this too.
array = [1,2,3,4]
array.cycle(4).size #=> 16
array.cycle.size #=> Infinity
# nil is returned if the size can't be calculated
array.find.size #=> nil
# Range too
(1..10).size #=> 10
To enable this in Enumerators returned from your own code, Enumerator.new now accepts an argument to calculate the size.
This can be a value
enum = Enumerator.new(3) do |yielder|
yielder << "a"
yielder << "b"
yielder << "c"
end
enum.size #=> 3
or an object responding to #call
def square_times(num, &block)
Enumerator.new(-> {num ** 2}) do |yielder|
(num ** 2).times {|i| yielder << i}
end.each(&block)
end
square_times(6).size #=> 36
#to_enum (and it's alias #enum_for) also now take a block to calculate the size, so the above could be written like so:
def square_times(num)
return to_enum(:square_times) {num ** 2} unless block_given?
(num ** 2).times do |i|
yield i
end
end
square_times(6).size #=> 36
warn now works just like puts, taking multiple arguments, or an array, and outputting them, but on stderr rather than stdout
warn "foo", "bar"
warn ["foo", "bar"]
TracePoint is a new object oriented version of the old Kernel#set_trace_func. It allows you to trace the execution of your Ruby code, this can be really handy when debugging code that isn't terribly straight forward.
# set up our tracer, but don't enable it yet
events = %i{call return b_call b_return raise}
trace = TracePoint.new(*events) do |tp|
p [tp.event, tp.event == :raise ? tp.raised_exception.class : tp.method_id, tp.path, tp.lineno]
end
def twice
result = []
result << yield
result << yield
result
end
def check_idempotence(&block)
a, b = twice(&block)
raise "expected #{a} to equal #{b}" unless a == b
true
end
trace.enable
a = 1
begin
check_idempotence {a += 1}
rescue
end
trace.disable
outputs:
[:call, :check_idempotence, "/Users/mat/Dropbox/ruby-2.0.0.rb", 841]
[:call, :twice, "/Users/mat/Dropbox/ruby-2.0.0.rb", 834]
[:b_call, nil, "/Users/mat/Dropbox/ruby-2.0.0.rb", 850]
[:b_return, nil, "/Users/mat/Dropbox/ruby-2.0.0.rb", 850]
[:b_call, nil, "/Users/mat/Dropbox/ruby-2.0.0.rb", 850]
[:b_return, nil, "/Users/mat/Dropbox/ruby-2.0.0.rb", 850]
[:return, :twice, "/Users/mat/Dropbox/ruby-2.0.0.rb", 839]
[:raise, #<RuntimeError: expected 2 to equal 3>, "/Users/mat/Dropbox/ruby-2.0.0.rb", 843]
[:return, :check_idempotence, "/Users/mat/Dropbox/ruby-2.0.0.rb", 843]
Ruby threads can be killed or have an exception raised in them by another. This isn't a terribly safe feature as the killing thread doesn't know what the thread being killed is doing, you could end up killing a thread in the middle of some important resource allocation or deallocation. We now have a feature to deal with this more safely.
th = Thread.new do
Thead.handle_interrupt(RuntimeError => :never) do
begin
# You can write resource allocation code safely.
Thread.handle_interrupt(RuntimeError => :immediate) do
...
end
ensure
# You can write resource deallocation code safely.
end
end
end
Thread.pass
# ...
th.raise "stop"
This can also make the stdlib timeout safer to use
require "timeout"
Thread.handle_interrupt(TimeoutError => :never) do
timeout(10) do
# TimeoutError doesn't occur here
Thread.handle_interrupt(TimeoutError => :on_blocking) do
# possible to be killed by TimeoutError while blocking operation
end
# TimeoutError doesn't occur here
end
end
There's also Thread.pending_interrupt? to check to see if an exception has been raised in the current thread by another thread.
There are a few improvements to the garbage collector in Ruby 2.0, the main one making Ruby play nicer with Copy-on-Write. This means applications that fork multiple processes, like a Rails app running on Unicorn, will use less memory.
The GC::Profiler class also gets a ::raw_data method, to return the raw data of the profile as an array of hashes, rather than a string, making it easier to log this data with say, statsd.
GC::Profiler.enable # turn on the profiler
GC.start # force a GC run, so there will be some stats
GC::Profiler.raw_data
#=> [{:GC_TIME=>0.0012150000000000008, :GC_INVOKE_TIME=>0.036716,
# :HEAP_USE_SIZE=>435920, :HEAP_TOTAL_SIZE=>700040,
# :HEAP_TOTAL_OBJECTS=>17501, :GC_IS_MARKED=>0}]
Backtrace strings are now only created on demand, from a light weight collection of object, rather than with each exception. You can get ahold of these objects with caller_locations or Thread#backtrace_locations. Curiously they aren't available from an Exception.
def foo
bar
end
def bar
caller_locations
end
locations = foo
locations.map(&:label) #=> ["foo", "<main>"]
locations.first.class #=> Thread::Backtrace::Location
caller also now accepts a limit as well as an offset, or a range
def bar
caller(2, 1)
end
foo #=> ["/Users/mat/Dropbox/ruby-2.0.0.rb:361:in `<main>'"]
def bar
caller(2..2)
end
foo #=> ["/Users/mat/Dropbox/ruby-2.0.0.rb:368:in `<main>'"]
You can now use useful characters outside of US-ASCII without magic encoding comments or inscrutable escape sequences
currency = "€" #=> "€"
String#b as a shortcut to get an ASCII-8BIT (aka binary) copy of a string
s = "foo"
s.encoding #=> #<Encoding:UTF-8>
s.b.encoding #=> #<Encoding:ASCII-8BIT>
lines, chars, codepoints, bytes now all return arrays rather then enumerators
s = "foo\nbar"
s.lines #=> ["foo\n", "bar"]
s.chars #=> ["f", "o", "o", "\n", "b", "a", "r"]
s.codepoints #=> [102, 111, 111, 10, 98, 97, 114]
s.bytes #=> [102, 111, 111, 10, 98, 97, 114]
These still accept a block for backwards compatibility, but you should use #each_line etc for that use case
The similarly named methods on IO, ARGF, StringIO, and Zlib::GzipReader still return enumerators, but are deprecated, use the each_ versions.
Returns the file path to the executing file, like FILE, but without the file name, useful for things like
YAML.load_file(File.join(__dir__, "config.yml"))
callee is back to returning the name of the method as called, not as defined with an aliased method. This can actually be useful.
def do_request(method, path, headers={}, body=nil)
"#{method.upcase} #{path}"
end
def get(path, headers={})
do_request(__callee__, path, headers)
end
alias head get
get("/test") #=> "GET /test"
head("/test") #=> "HEAD /test"
The is a fork of the Oniguruma regexp engine used by 1.9, with a few more features. More details at https://github.com/k-takata/Onigmo The new features seem Perl-inspired, this seems to be a good refrence: http://perldoc.perl.org/perlre.html
(?(cond)yes|no)
if cond is matched, then match against yes, if cond is false match against no cond references a match either by group number or name, or is a look-ahead/behind
example only matches a trailing cap if there is a leading cap
regexp = /^([A-Z])?[a-z]+(?(1)[A-Z]|[a-z])$/
regexp =~ "foo" #=> 0
regexp =~ "foO" #=> nil
regexp =~ "FoO" #=> 0
No longer will you have to call hash.default = nil to clear hash.default_proc now your code will actually make sense!
hash = {}
hash.default_proc = Proc.new {|h,k| h[k] = []}
hash[:foo] << "bar"
hash[:foo] #=> ["bar"]
hash.default_proc = nil
hash[:baz] #=> nil
Previously #values_at would behave in an unexpected way when given a range, and you'd only get a single nil for all out-of-range indexes, now you get one for each
[2,4,6,8,10].values_at(3..7) #=> [8, 10, nil, nil, nil]
If for some reason you ever find yourself needing to do shell filename glob matches in Ruby you'll be happy to know you can now use pattens like {foo,bar}
# 3rd argument enables the brace expansion
File.fnmatch?("{foo,bar}", "foo", File::FNM_EXTGLOB) #=> true
File.fnmatch?("{foo,bar}", "foo") #=> false
# or together multiple options old-school C style
casefold_extglob = File::FNM_CASEFOLD | File::FNM_EXTGLOB
File.fnmatch?("{foo,bar}", "BAR", casefold_extglob) #=> true
when using exec all open files/sockets, other than stdin, stdout and stderr will be closed for the new process. This was previously and option with exec(cmd, close_others: true) but it's not the default.
protected methods are now hidden from #respond_to? unless true is passed as a second argument, just like private methods.
class Foo
protected
def bar
"baz"
end
end
f = Foo.new
f.respond_to?(:bar) #=> false
f.respond_to?(:bar, true) #=> true
Under Ruby 1.9 #inspect gained the odd behaviour of delegating to #to_s if a custom #to_s method had been defined, this has been removed
class Foo
def to_s
"foo"
end
end
Foo.new.inspect #=> "#<Foo:0x007fb4a2887328>"
Load error now has a #path method to retrieve the path of the file that couldn't be loaded. That was already in the message, but now it's more easily accessible to code
begin
require_relative "foo"
rescue LoadError => e
e.message #=> "cannot load such file -- /Users/mat/Dropbox/foo"
e.path #=> "/Users/mat/Dropbox/foo"
end
getsid return the processes session ID. This only works on unix/linux systems
Process.getsid #=> 240
A signame method has been added to get the name for a signal number
Signal.signame(9) #=> "KILL"
Signal.trap now raises an ArgumentError if you try and trap :SEGV, :BUS, :ILL, :FPE, or :VTALRM. These are used internally by Ruby, so you wouldn't be able to trap them anyway.
As of Ruby 1.9 Thread#[], #[]=, #keys and #key? would get/set fiber local variables, Thread now gets the methods #thread_variable_get, #thread_variable_set, #thread_variables, #thread_variable? as equivalents that are fiber local
Fiber local:
b = nil
a = Fiber.new do
Thread.current[:foo] = 1
b.transfer
Thread.current[:foo]
end
b = Fiber.new do
Thread.current[:foo] = 2
a.transfer
end
p a.resume #=> 1
Thread local:
b = nil
a = Fiber.new do
Thread.current.thread_variable_set(:foo, 1)
b.transfer
Thread.current.thread_variable_get(:foo)
end
b = Fiber.new do
Thread.current.thread_variable_set(:foo, 2)
a.transfer
end
p a.resume #=> 2
If you attempt to call #join or #value on the current or main thread you now get a ThreadError raised, which inherits from StandardError, rather than 'fatal' which inherits from Exception.
begin
Thread.current.join
rescue => e
e #=> #<ThreadError: Target thread must not be current thread>
end
I can't think of a particularly interesting example for this, but you can now check if the current thread owns a mutex.
require "thread"
lock = Mutex.new
lock.lock
lock.owned? #=> true
Thread.new {lock.owned?}.value #=> false
Also affecting Mutex, methods that change the state of the mutex are no longer allowed in signal handlers; #lock, #unlock, #try_lock, #synchronize, and #sleep
And apparently #sleep may wake up early, so you'll need to double check the correct amount of time has passed if precise timings are important.
sleep_time = 0.1
start = Time.now
lock.sleep(sleep_time)
elapsed = Time.now - start
lock.sleep(sleep_time - elapsed) if elapsed < sleep_time
The following environment variables can be set to alter the stack sizes used by threads and fibers. Ruby only checks these as your program starts up.
RUBY_THREAD_VM_STACK_SIZE: vm stack size used at thread creation. default: 128KB (32bit CPU) or 256KB (64bit CPU). RUBY_THREAD_MACHINE_STACK_SIZE: machine stack size used at thread creation. default: 512KB or 1024KB. RUBY_FIBER_VM_STACK_SIZE: vm stack size used at fiber creation. default: 64KB or 128KB. RUBY_FIBER_MACHINE_STACK_SIZE: machine stack size used at fiber creation. default: 256KB or 256KB.
you can get the defaults with
RubyVM::DEFAULT_PARAMS #=> {:thread_vm_stack_size=>1048576,
:thread_machine_stack_size=>1048576,
:fiber_vm_stack_size=>131072,
:fiber_machine_stack_size=>524288}
A fiber that has been transferred to now must be transferred back to, instead of cheating and using resume.
require "fiber"
f2 = nil
f1 = Fiber.new do
puts "a"
f2.transfer
puts "c"
end
f2 = Fiber.new do
puts "b"
f1.transfer # under 1.9 this could have been a #resume
end
f1.resume
RubyVM::InstructionSequence isn't new, but it gains a couple of features, and even more helpfully, [is_docs][detailed documentation]
You can now get the instruction sequence an existing method
class Foo
def add(x, y)
x + y
end
end
instructions = RubyVM::InstructionSequence.of(Foo.instance_method(:add))
and when you have that instruction sequence you can get some details about where it was defined
instructions.path #=> "/Users/mat/Dropbox/ruby-2.0.0.rb"
instructions.absolute_path #=> "/Users/mat/Dropbox/ruby-2.0.0.rb"
instructions.label #=> "add"
instructions.base_label #=> "add"
instructions.first_lineno #=> 654
This class is mainly intended to be part of WeakRef's implementation, so you should probably use that (require "weakref"). It holds a weak reference to the objects stored, which means they may be garbage collected.
map = ObjectSpace::WeakMap.new
# keys can't be immediate values (numbers, symbols), and you must use the
# exact same object, not just one that is equal.
key = Object.new
map[key] = "foo"
map[key] #=> "foo"
# force a garbage collection run
sleep(0.1) and GC.start
map[key] #=> nil
define_method can now be used at the top level; it doesn't have to be inside a class or module
Dir["config/*.yml"].each do |path|
%r{config/(?<name>.*)\.yml\z} =~ path
define_method(:"#{name}_config") {YAML.load_file(path)}
end
This method will generate warnings that the family, port, and host variables are unused.
def get_ip(sock) family, port, host, address = sock.peeraddr address end
Using underscores will stop the warnings, but lose the self-documenting nature of the code
def get_ip(sock)
_, _, _, address = sock.peeraddr
address
end
As of Ruby 2.0.0 we can get the best of both worlds by starting the variables with an _
def get_ip(sock)
_family, _port, _host, address = sock.peeraddr
address
end
Under Ruby 1.9.3 Procs with the same body and binding were equal, but you'd only get procs like this when you'd cloned one from another. This has now been removed, which is no great loss as it wasn't really very useful.
proc = Proc.new {puts "foo"}
proc == proc.clone #=> false
ARGF (which is a concatination of the files supplied on the command line) gets an #each_codepoint method like IO.
count = 0
ARGF.each_codepoint {|c| count += 1 if c > 127}
puts "there are #{count} non-ascii chacters in the given files"
The encoding of the string returned from Time#to_s changes from ASCII-8BIT (aka binary) to US-ASCII.
Time.now.to_s.encoding #=> #<Encoding:US-ASCII>
This is a small change, that will probably have no affect on you at all, but now when supplying a random param to the #shuffle!, #shuffle, and #sample methods on Array it's now expected to take a 'max' argument, and return an integer between 0 and max, rather than a float between 0 and 1.
array = [1, 3, 5, 7, 9]
randgen = Object.new
def randgen.rand(max)
max #=> 4
1
end
array.sample(random: randgen) #=> 3
CGI from the stdlib gets an HTML5 mode for its tag builder interface.
require "cgi"
cgi = CGI.new("html5")
html = cgi.html do
cgi.head do
cgi.title {"test"}
end +
cgi.body do
cgi.header {cgi.h1 {"example"}} +
cgi.p {"lorem ipsum"}
end
end
puts html
The old #header method (to send the HTTP header) is now called #http_header, although as long as you're not in HTML5 mode it's aliased as #header for backwards compatibility.
CSV::dump and CSV::load have been removed. They allowed you to dump/load an array of Ruby objects to a CSV file, and have them serialised and deserialised. They've been removed as they were unsafe.
Iconv has been removed, in preference of String#encode.
Where previously you might have written something like:
require "iconv"
Iconv.conv("ISO-8859-1", "UTF8", "Résumé") #=> "R\xE9sum\xE9"
You'd now write
"Résumé".encode(Encoding::ISO_8859_1) #=> "R\xE9sum\xE9"
io/console isn't new, but the [io_docs][documentation] is so now you can actually figure out how to use it. The Ruby 2.0.0 NEWS file claims the IO#cooked and #cooked! methods are new, but they seem to be available in 1.9.3.
require "io/console"
IO.console.raw!
# console in now in raw mode, disabling line editing and echoing
IO.console.cooked!
# back in cooked mode, line editing works like normal
#raw! and #raw get two new arguments, min and time.
IO.console.raw!(min: 5) # reading from console buffers for 5 chars
IO.console.raw!(min: 5, time: 1) # read after 1 second if buffer not full
io/wait adds a #wait_writeable method that will block till an IO can be written to. #wait gets renamed to #wait_readable, and there's a #wait alias for backwards compatibility.
require "io/wait"
timeout = 1
STDOUT.wait_writable(timeout) #=> #<IO:<STDOUT>>
Net::HTTP now automatically requests and decompresses gzip and deflate compression by default. This should play very nicely with the new non-GIL-blocking Zlib.
SSL sessions are also now reused, cutting down on time spent negotiating connections.
If for some reason you need to specify the local host/port to connect from, along with the host/port to connect to, you now can
http = Net::HTTP.new(remote_host, remote_port)
http.local_host = local_host
http.local_port = local_port
http.start do
# ...
end
This method returns all the objects directly reachable from the given object.
require "objspace"
Response = Struct.new(:code, :header, :body)
res = Response.new(200, {"Content-Length" => "12"}, "Hello world!")
ObjectSpace.reachable_objects_from(res)
#=> [Response, {"Content-Length"=>"12"}, "Hello world!"]
You can combine this with ObjectSpace.memsize_of to get an idea of the memory size of an object and all the objects it references; very handy for debugging memory leaks
def memsize_of_all_reachable_objects_from(obj)
memsize = 0
seen = {}.tap(&:compare_by_identity)
to_do = [obj]
while obj = to_do.shift
ObjectSpace.reachable_objects_from(obj).each do |o|
next if seen.key?(o) || Module === o
seen[o] = true
memsize += ObjectSpace.memsize_of(o)
to_do << o
end
end
memsize
end
memsize_of_all_reachable_objects_from(res) #=> 192
OpenStruct gains #[], #[]= and #each_pair methods so it can be used like a hash.
require "ostruct"
o = OpenStruct.new
o.foo = "test"
o[:foo] #=> "test"
o[:bar] = "example"
o.bar #=> example
It also gains #hash and #eql? methods, which are used internally by Hash to check equality. These allow it to play better as a hash key, with equal objects acting as the same key.