Ruby 2.3.0 will be released this Christmas, and the first preview release was made available a few weeks ago. I’ve been playing around with it and looking at what new features have been introduced.
# Install using RVM
rvm install 2.3.0
# Using Rbenv
brew upgrade ruby-build --HEAD
rbenv install 2.3.0
Safe navigation operator
A new operator (&.) has been introduced. It can be very useful in cases where you need to check if an object is nil before calling a method on it. It will return nil if the object equals nil, otherwise it calls the method on the object.
# Ruby <= 2.2.x
if user && user.admin?
# do something
end
# Ruby 2.3
if user&.admin?
# do something
end
There are some caveats to consider. The first version would evaluate to false if user were set to false, but the safe navigation operator would throw a NoMethodError. This is similar to Rails’
#try! method.
Frozen string literals
Until Ruby 2.2, strings were mutable by default in Ruby. We’re allowed to do something like str[2] = 'z'. If we want to make strings immutable, we need to call #freeze on it (eg. str = 'foobar'.freeze).
To make the transition easier, Ruby 2.3 allows you to optionally make all strings literals frozen by default. You can enable this by adding a comment frozen_string_literal: true at the start of the file. When enabled, all the string literals in the file are frozen even without calling #freeze on them. Note that this only enables the feature on files that have the comment.
# frozen_string_literal: true
str = 'cat'
str[0] = 'b'
# frozen.rb:5:in `[]=': can't modify frozen String (RuntimeError)
# from frozen.rb:5:in `<main>'
Although this doesn’t seem like a major change right now, this will pave the way for a smooth transition to Ruby 3.0 a few years from now.
Array#dig and Hash#dig
This is another useful addition to the standard library. We can now access nested elements in arrays and hashes with a much simpler API.
We can now do this with arrays:
list = [
[2, 3],
[5, 7, 9],
[ [11, 13], [17, 19] ]
]
list.dig(1, 2) #=> 9
list.dig(2, 1, 0) #=> 17
list.dig(0, 3) #=> nil
list.dig(4, 0) #=> nil
Hashes:
dict = {
a: { x: 23, y: 29 },
b: { x: 31, z: 37 }
}
dict.dig(:a, :x) #=> 23
dict.dig(:b, :z) #=> 37
dict.dig(:b, :y) #=> nil
dict.dig(:c, :x) #=> nil
This could be very useful when dealing with JSON data that we’ve parsed into a hash.
“Did you mean?”
When you get a NoMethodError because of a typo in the method name, Ruby now helpfully suggests other method names similar to that one.
2.3.0-preview1 :001 > "foo bar".uppcase
NoMethodError: undefined method `uppcase' for "foo bar":String
Did you mean? upcase
upcase!
This might look like a small change, but it’s my favorite feature in 2.3. Making error messages more helpful has a huge impact on making the language easier to use, especially for beginners.
Hash “comparison”
Hashes now have the comparison methods defined on them. If you see a >= b, it is checking if all the key-value pairs in b are also present in a.
{ x: 1, y: 2 } >= { x: 1 } #=> true
{ x: 1, y: 2 } >= { x: 2 } #=> false
{ x: 1 } >= { x: 1, y: 2 } #=> false
In the first example above, the key-value pair [:x, 1] in the RHS is a subset of those in the LHS - [ [:x, 1], [:y, 2] ], so it returns true.
This also applies to all other comparison operators. Olivier Lacan, who proposed this feature in Ruby, wrote an excellent explanation of
hash comparison in Ruby 2.3.
Hash#to_proc
Hash#to_proc returns a lambda that maps the key with the value. When you call the lambda with a key, it returns the corresponding value from the hash.
h = { foo: 1, bar: 2, baz: 3}
p = h.to_proc
p.call(:foo) #=> 1
p.call(:bar) #=> 2
p.call(:quux) #=> nil
This might not seem useful in itself. Why not use [] to access the elements? But it gets interesting when we use the & operator to create a proc and pass it to an Enumerable block.
h = { foo: 1, bar: 2, baz: 3}
# instead of this:
[:foo, :bar].map { |key| h[key] } #=> [1, 2]
# we can use this syntax:
[:foo, :bar].map(&h) #=> [1, 2]
Hash#fetch_values
This method works like Hash#values_at - it fetches the values corresponding to the list of keys we pass in. The difference is that #values_at returns nil when the key doesn’t exist, while #fetch_values raises a KeyError for keys that aren’t present.
h = { foo: 1, bar: 2, baz: 3}
h.fetch_values(:foo, :bar) #=> [1, 2]
h.values_at(:foo, :quux) #=> [1, nil]
h.fetch_values(:foo, :quux) #=> raise KeyError
Enumerable#grep_v
The grep_v method is equivalent to the -v option in the command line grep utility. It returns the list of items that do not match the condition.
list = %w(foo bar baz)
list.grep_v(/ba/)
#=> ['foo']
list.grep(/ba/)
#=> ['bar', 'baz']
Numeric#positive? and #negative?
These functions have been around in Rails core extensions for a while, and now have been included in Ruby.