Community

10 new features in Ruby 2.5

This is a guest post by Junichi Ito (@jnchito). Junichi is a Ruby programmer at SonicGarden.jp, translator of Everyday Rails Testing with RSpec, and one of the most popular Ruby writers/bloggers in Japan

Ruby 2.5.0-preview1 was released on October 10 2017. It introduces lots of new features and performance improvements. I have handpicked 10 new features to go over. I hope this article will help you understand some of the coming changes available in Ruby 2.5!

rescue/else/ensure are allowed inside do/end blocks without begin/end

You can write rescue/else/ensure clauses without begin/end keyword within do/end blocks:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
[1].each do |n|
n / 0
rescue
# rescue
else
# else
ensure
# ensure
end
[1].each do |n| n / 0 rescue # rescue else # else ensure # ensure end
[1].each do |n|
  n / 0
rescue
  # rescue
else
  # else
ensure
  # ensure
end

But you’ll get an error if you use {} blocks:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
[1].each { |n|
n / 0
rescue
# rescue
else
# else
ensure
# ensure
}
#=> SyntaxError: (irb):3: syntax error, unexpected keyword_rescue, expecting '}'
# rescue
# ^~~~~~
[1].each { |n| n / 0 rescue # rescue else # else ensure # ensure } #=> SyntaxError: (irb):3: syntax error, unexpected keyword_rescue, expecting '}' # rescue # ^~~~~~
[1].each { |n|
  n / 0
rescue
  # rescue
else
  # else
ensure
  # ensure
}
#=> SyntaxError: (irb):3: syntax error, unexpected keyword_rescue, expecting '}'
#     rescue
#     ^~~~~~

Top-level constant lookup is removed

In Ruby 2.4, the following code works with a warning:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
class Staff; end
class ItemsController; end
Staff::ItemsController
#=> warning: toplevel constant ItemsController referenced by Staff::ItemsController
#=> ItemsController
class Staff; end class ItemsController; end Staff::ItemsController #=> warning: toplevel constant ItemsController referenced by Staff::ItemsController #=> ItemsController
class Staff; end
class ItemsController; end

Staff::ItemsController
#=> warning: toplevel constant ItemsController referenced by Staff::ItemsController
#=> ItemsController

This is because the top-level constants are defined under Object class, and Ruby tries to look up the superclass of Staff class, and eventually finds ItemsController under the Object class which is a superclass of Staff class. The details were discussed here:

But in Ruby 2.5, Ruby won’t look up superclass. So the previous code fails with error:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
Staff::ItemsController
#=> NameError: uninitialized constant Staff::ItemsController
# Did you mean? ItemsController
Staff::ItemsController #=> NameError: uninitialized constant Staff::ItemsController # Did you mean? ItemsController
Staff::ItemsController
#=> NameError: uninitialized constant Staff::ItemsController
#   Did you mean?  ItemsController

Bundler gem is now bundled

This change is postponed due to a big issue. Please see also:
https://github.com/ruby/ruby/commit/7825e8363d4b2ccad8e2d3f5eeba9e26f6656911

Bundler is bundled as a standard library. So you can use it without `gem install bundler`!

In Ruby 2.5, backtrace and error message are printed in reverse order. For example, in Ruby 2.4:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
$ ruby ./test/error_example.rb
./test/error_example.rb:7:in `/': divided by 0 (ZeroDivisionError)
from ./test/error_example.rb:7:in `method_2'
from ./test/error_example.rb:2:in `method_1'
from ./test/error_example.rb:10:in `'
$ ruby ./test/error_example.rb ./test/error_example.rb:7:in `/': divided by 0 (ZeroDivisionError) from ./test/error_example.rb:7:in `method_2' from ./test/error_example.rb:2:in `method_1' from ./test/error_example.rb:10:in `'
$ ruby ./test/error_example.rb
./test/error_example.rb:7:in `/': divided by 0 (ZeroDivisionError)
    from ./test/error_example.rb:7:in `method_2'
    from ./test/error_example.rb:2:in `method_1'
    from ./test/error_example.rb:10:in `'

In Ruby 2.5:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
$ ruby ./test/error_example.rb
Traceback (most recent call last):
3: from ./test/error_example.rb:10:in `'
2: from ./test/error_example.rb:2:in `method_1'
1: from ./test/error_example.rb:7:in `method_2'
./test/error_example.rb:7:in `/': divided by 0 (ZeroDivisionError)
$ ruby ./test/error_example.rb Traceback (most recent call last): 3: from ./test/error_example.rb:10:in `' 2: from ./test/error_example.rb:2:in `method_1' 1: from ./test/error_example.rb:7:in `method_2' ./test/error_example.rb:7:in `/': divided by 0 (ZeroDivisionError)
$ ruby ./test/error_example.rb
Traceback (most recent call last):
        3: from ./test/error_example.rb:10:in `'
        2: from ./test/error_example.rb:2:in `method_1'
        1: from ./test/error_example.rb:7:in `method_2'
./test/error_example.rb:7:in `/': divided by 0 (ZeroDivisionError)

As of preview1, this is an experimental feature and applied only when STDERR is unchanged and a tty.

Kernel#yield_self

Kernel#yield_self is introduced. This method takes a receiver as a block argument and returns a block value as it is:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
2.yield_self { |n| n * 10 } #=> 20
names = ['Alice', 'Bob']
names.join(', ').yield_self { |s| "(#{s})" } #=> "(Alice, Bob)"
2.yield_self { |n| n * 10 } #=> 20 names = ['Alice', 'Bob'] names.join(', ').yield_self { |s| "(#{s})" } #=> "(Alice, Bob)"
2.yield_self { |n| n * 10 } #=> 20

names = ['Alice', 'Bob']
names.join(', ').yield_self { |s| "(#{s})" } #=> "(Alice, Bob)"

String#delete_prefix/delete_suffix

String#delete_prefix/delete_suffix can remove prefix and suffix from a string:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
'invisible'.delete_prefix('in') #=> "visible"
'pink'.delete_prefix('in') #=> "pink"
'worked'.delete_suffix('ed') #=> "work"
'medical'.delete_suffix('ed') #=> "medical"
'invisible'.delete_prefix('in') #=> "visible" 'pink'.delete_prefix('in') #=> "pink" 'worked'.delete_suffix('ed') #=> "work" 'medical'.delete_suffix('ed') #=> "medical"
'invisible'.delete_prefix('in') #=> "visible"
'pink'.delete_prefix('in') #=> "pink"

'worked'.delete_suffix('ed') #=> "work"
'medical'.delete_suffix('ed') #=> "medical"

Array#prepend/append as aliases of unshift/push

Array#prepend/append are added as aliases of unshift/push methods:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
array = [3, 4]
array.prepend(1, 2) #=> [1, 2, 3, 4]
array #=> [1, 2, 3, 4]
array = [1, 2]
array.append(3, 4) #=> [1, 2, 3, 4]
array #=> [1, 2, 3, 4]
array = [3, 4] array.prepend(1, 2) #=> [1, 2, 3, 4] array #=> [1, 2, 3, 4] array = [1, 2] array.append(3, 4) #=> [1, 2, 3, 4] array #=> [1, 2, 3, 4]
array = [3, 4]
array.prepend(1, 2) #=> [1, 2, 3, 4]
array               #=> [1, 2, 3, 4]

array = [1, 2]
array.append(3, 4)  #=> [1, 2, 3, 4]
array               #=> [1, 2, 3, 4]

Hash#transform_keys/transform_keys!

Hash#transform_keys can change keys according to the return value of a block:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
hash = { a: 1, b: 2 }
hash.transform_keys { |k| k.to_s }
#=> { 'a' => 1, 'b' => 2 }
hash = { a: 1, b: 2 } hash.transform_keys { |k| k.to_s } #=> { 'a' => 1, 'b' => 2 }
hash = { a: 1, b: 2 }
hash.transform_keys { |k| k.to_s }
#=> { 'a' => 1, 'b' => 2 }

`transform_keys!` is a destructive version:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
hash = { a: 1, b: 2 }
hash.transform_keys! { |k| k.to_s }
#=> { 'a' => 1, 'b' => 2 }
hash
#=> { 'a' => 1, 'b' => 2 }
hash = { a: 1, b: 2 } hash.transform_keys! { |k| k.to_s } #=> { 'a' => 1, 'b' => 2 } hash #=> { 'a' => 1, 'b' => 2 }
hash = { a: 1, b: 2 }
hash.transform_keys! { |k| k.to_s }
#=> { 'a' => 1, 'b' => 2 }

hash
#=> { 'a' => 1, 'b' => 2 }

Dir.children/each_child

You might have used Dir.entries method:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
Dir.entries('./test/dir_a')
#=> [".", "..", "code_a.rb", "text_a.txt"]
Dir.entries('./test/dir_a') #=> [".", "..", "code_a.rb", "text_a.txt"]
Dir.entries('./test/dir_a')
#=> [".", "..", "code_a.rb", "text_a.txt"]

If you want to remove “.” and “..” from the returned values, you can Dir.children instead:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
Dir.children('./test/dir_a')
#=> ['code_a.rb', 'text_a.txt']
Dir.children('./test/dir_a') #=> ['code_a.rb', 'text_a.txt']
Dir.children('./test/dir_a')
#=> ['code_a.rb', 'text_a.txt']

Dir.each_child returns not an array but an Enumerator object:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
Dir.each_child('./test/dir_a')
#=> #<Enumerator: Dir:each_child(\"./test/dir_a\")>"
Dir.each_child('./test/dir_a').to_a
#=> ['code_a.rb', 'text_a.txt']
Dir.each_child('./test/dir_a') #=> #<Enumerator: Dir:each_child(\"./test/dir_a\")>" Dir.each_child('./test/dir_a').to_a #=> ['code_a.rb', 'text_a.txt']
Dir.each_child('./test/dir_a')
#=> #<Enumerator: Dir:each_child(\"./test/dir_a\")>"

Dir.each_child('./test/dir_a').to_a
#=> ['code_a.rb', 'text_a.txt']

ERB#result_with_hash

The following code shows how to assign local variables to ERB template in Ruby 2.4:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
require 'erb'
require 'ostruct'
namespace = OpenStruct.new(a: 2, b: 3)
template = 'Result: <%= a * b %>'
ERB.new(template).result(namespace.instance_eval { binding }) #=> "Result: 6"
require 'erb' require 'ostruct' namespace = OpenStruct.new(a: 2, b: 3) template = 'Result: <%= a * b %>' ERB.new(template).result(namespace.instance_eval { binding }) #=> "Result: 6"
require 'erb'
require 'ostruct'

namespace = OpenStruct.new(a: 2, b: 3)
template = 'Result: <%= a * b %>'
ERB.new(template).result(namespace.instance_eval { binding }) #=> "Result: 6"

But in Ruby 2.5, you can rewrite with ERB#result_with_hash like this:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
require 'erb'
template = 'Result: <%= a * b %>'
ERB.new(template).result_with_hash(a: 2, b: 3) #=> "Result: 6"
require 'erb' template = 'Result: <%= a * b %>' ERB.new(template).result_with_hash(a: 2, b: 3) #=> "Result: 6"
require 'erb'

template = 'Result: <%= a * b %>'
ERB.new(template).result_with_hash(a: 2, b: 3) #=> "Result: 6"

Other changes

You can find other changes on the Ruby 2.5 NEWS page: NEWS for Ruby 2.5.0

Love Ruby 2.5!

image description