10 More New Features in Ruby v2.5

With the upcoming release of ruby v2.5 scheduled (as per tradition) for 25th December, it’s good to know what’s changed in the language - so you can take advantage any new (or refined) features.

This popular blog post by Junichi Ito highlighted 10 new features; but since there are so many improvements to the language, let’s dive in and unravel 10 more handpicked highlights!

1. More public Module methods

(Features #14132 and #13133).

Module#attr, attr_accessor, attr_reader, attr_writer, define_method, alias_method, undef_method and remove_method are now all public.

For example:

# Ruby v2.4
Integer.alias_method :plus, :+
#=> NoMethodError: private method `alias_method' called for Integer:Class

# Workaround 1:
Integer.send(:alias_method, :plus, :+)

# Workaround 2:
class Integer
  alias_method :plus, :+
end

# Ruby v2.5
Integer.alias_method :plus, :+

This update follows nicely from similar changes back in Ruby v2.1.0, where Module#include and Module#prepend were made public.

I think this is a great example of ruby being a community-driven language: Its design has evolved over time, influenced by how its users want it to be; not set in stone due to the original designers’ opinions.

2. String#start_with? supports regexp

(Feature #13712).

This enhancement provides some nice syntactic sugar, which could help prevent a common (and sometimes security-related) mistake in ruby:

# Checking whether a string starts with a lower-case letter

string.match?(/^[a-z]/) # BAD!
string.match?(/\A[a-z]/) # Good

The mistake arrises because, unlike most other languages, the ^ anchor means “starts of line” in ruby; not “start of string”. (That’s what \A is for.)

So, that first line of code would also return true when string = "123\ntest".

Using the new String#start_with? method, such a mistake wouldn’t happen:

# Ruby v2.5
string.start_with?(/[a-z]/) # Also good

3. Improvements to binding.irb

(Features #13099 and #14124).

For a few years now, the recommended ruby toolkit for developer consoles and debuggers has been dominated by pry, byebug, or even some hybrid of the above.

…Which is a little odd, since ruby already comes with a pretty good built-in REPL: irb (“Interactive Ruby”).

But as of ruby v2.5, we see two enhancements to the library that help bridge the gap, and make it a little less likely to feel the need for pry in every application:

  • require 'irb' is no longer needed in your code, in order to invoke binding.irb.
  • Show source around binding.irb is now shown on startup.
# Ruby v2.4

# test.rb:
require 'irb'
def test
  binding.irb
end
test

# Running the file yields:
irb(main):001:0>

# Ruby v2.5:
# test.rb:
def test
  binding.irb
end
test

# Running the file yields:
From: test.rb @ line 2 :

    1: def test
 => 2:   binding.irb
    3: end
    4: test

irb(main):001:0>

4. Integer.sqrt added

(Feature #13219)

Quite often in algorithms, you need to find “the largest integer less than sqrt(n)”. For example, a simple algorithm to check whether n is prime is to simply try dividing n by all primes up to sqrt(n).

This operation can now be executed more succinctly, within the core library methods:

# Ruby v2.4
Math.sqrt(4) #=> 2.0
Math.sqrt(4).to_i #=> 2

Math.sqrt(10) #=> 3.1622776601683795
Math.sqrt(10).to_i #=> 3

# Ruby v2.5
Integer.sqrt(4) #=> 2
Integer.sqrt(10) #=> 3

5. String#casecmp and casecmp? now return nil for non-string arguments

(…instead of raising a TypeError)

(Bug #13312).

Fairly self-explanatory:

# Ruby v2.4
"foo".casecmp 123
#=> TypeError: no implicit conversion of Integer into String

# Ruby v2.5
"foo".casecmp 123
#=> nil

Changes like this usually slip under the radar, as they are not “exciting” features. But it just goes to show: There’s still all sorts of simple room for improvement in the language; and sometimes the change you wish to make may not even be very complicated!

But all of these many incremental changes add up, and are what makes modern ruby such a robust, stable language.

6. mathn.rb removed from stdlib

(Feature #10169).

It may sound odd to announce the “removal” of a library as a new feature, but please hear me out!…

mathn is an unusual library. It not only adds to, but also changes, the behaviour of Integers!

When you require 'mathn', this in turn loads other libraries such as require 'prime', which adds the methods: Integer.each_prime, Integer.from_prime_division, Integer#prime? and Integer#prime_division.

However, more bizarrely, it also redefines Integer#/ to be an alias for Numeric#quo!

1/3 # => 0
(1/3).class #=> Integer

# In Ruby v2.5, this line will `raise LoadError: cannot load such file -- mathn`
# (Unless you also install the deprecated `mathn` gem.)
require 'mathn'
1/3 # => (1/3)
(1/3).class #=> Rational

This isn’t always a problem, but it certainly doesn’t conform the the principle of least surprise.

For example, there was a case about 9 years ago where the Rubinius VM essentially “blew up” due to some obscure code simply running require 'mathn'. Even today, people are sometimes left confused by different behaviour on development vs production servers, due to some production-specific dependency loading the library!

This behaviour-changing aspect of the mathn library has been a known issue for several years, now. The library was officially “deprecated” as of Ruby v2.2, but that doesn’t stop people from using it of course!

However, thanks to this change in Ruby v2.5, you can now only load mathn as a gem. This finally means:

  • Developers will need to bundle a deprecated gem into their project, in order to use it. (Which means usage is much less likely!)
  • …And therefore, even if some dependency in your project does require mathn, you will have traceability of the root culprit in the Gemfile.lock!

7. Default template file encoding is changed from ASCII-8BIT to UTF-8 in erb command

(Bug #14095).

Similar to #5 above, this just goes to show how well the open source community works to improve the language. What started out as a discussion on Reddit was then raised with the core developers, and promptly fixed. And, once again, the code fix was simple.

This (obscure) bug was in fact a lingering legacy of Ruby v1.9’s implementation! However, its fix could prove to be quite useful in conjunction with other changes in Ruby v2.5.

8. Hash#slice added

(Feature #8499).

The ActiveSupport library, which comes bundled with the popular Ruby on Rails framework (but can also be used in isolation) provides many - often controversial - extensions to Ruby’s core classes.

Over the past few years, however, Ruby has gradually been cherry-picking the “best” features and merging them into the core language. Hash#slice is one such method; continuing the trend.

# Ruby v2.5
{a: 1, b: 2, c: 3}.slice(:a, :b)
#=> {:a=>1, :b=>2}

9. SecureRandom.alphanumeric added

(Feature #10849).

One thing that’s always struck me as a little odd in ruby, given its vastly rich core library methods, is its lack of a clear “random string” generator.

This StackOverflow question, for example, demonstrates just some of the many (often surprising!) popular methods used by developers, to generate a “random string”. Here are a few of them:

# 1.
(0...50).map { ('a'..'z').to_a[rand(26)] }.join

# 2.
('a'..'z').to_a.shuffle[0,8].join

# 3.
rand(36**length).to_s(36)

# 4.
require 'securerandom'
SecureRandom.hex

Each approach has various pros and cons with regard to its flexibility, readability and entropy (“true randomness”). For instance, only option 4 above should be considered cryptographically secure. Other SecureRandom methods include:

SecureRandom.base64 # e.g. "/qWJPsvoxnSe17HrTlzQ7Q=="
SecureRandom.urlsafe_base64 # e.g. "IINbXW4YKjfoncJMpP-CkQ"
SecureRandom.random_bytes # e.g. "\t\x85\xB9\x9C\xD1\f\xD3\xE6t\xB4S^\v-\xFFo"
SecureRandom.random_number # e.g. 0.8993676724126768

Due to the common use case of needing a purely alphanumeric string, the new SecureRandom.alphanumeric method fits in nicely with this collection.

Unfortunately though, if you need to generate a cryptographically secure random string with some other specific characters, you’ll need still to write something a little clunky - e.g.

# 20 random chars; chosen from a, b, c, d, e, v, w, x, y and z
(1..20).flat_map { [*'a'..'e', *'v'..'z'].sample(1, random: SecureRandom) }

… Or if you’re just looking for a clean syntax for arbitrary random strings, why not check out my regexp-examples library?

10. Net::HTTP::STATUS_CODES

(Feature #12935).

Ruby v2.5 has now defined a hash of all HTTP status codes!

Previously, this was only recorded in documentation, and it was left up to web frameworks such as Rails to define their own “human-friendly” mappings.

Additionally, definitions of the following status codes have been added to ruby:

# Ruby v2.5
require 'net/http/status_codes'
Net::HTTP::STATUS_CODES
{
  # ...
  208 => 'Already Reported',
  308 => 'Permanent Redirect',
  421 => 'Misdirected Request',
  506 => 'Variant Also Negotiates',
  508 => 'Loop Detected',
  510 => 'Not Extended',
  # ...
}

Bonus: Unicode 10.0 support

(Feature #13685).

Keeping up with the new unicode standard, released in June 2017, Ruby v2.5 has been updated to support the new characters.

The new characters include a Bitcoin symbol and 56 new emojis!

Conclusion

The ruby language has increasingly stabilised over the past few years. And while it’s fair to say that these changes are far less revolutionary than what we saw back in Ruby v2.0 for instance, there is still a lot going on within this active community.

Ruby v2.5 will be released on Christmas Day, but you can download a pre-release version today and try it out!

Written on December 11, 2017