New Methods in Ruby 2.2

Share this article

vector illustration of dark red shield with ruby programming lan

Most of the fanfare around Ruby 2.2 has focused on garbage collection (GC) upgrades. The GC will now clean up symbols and has a new incremental algorithm that reduces the pause time. These changes are very exciting, but there were some new methods added as well. Let’s see what other new toys 2.2 delivered.

Binding#local_variables

If you’ve ever wanted to know the local variables defined in a scope, have I got a method for you. Stick this anywhere and find out what vars are in use:

def addition(x, y)
  puts binding.local_variables.inspect
  z = x + y
  puts binding.local_variables.inspect
  z
end

> addition(2, 3)
[:x, :y, :z]
[:x, :y, :z]
5

Binding#receiver

Maybe you don’t care about the local vars. Do you want to know which object is receiving the method call?

class Cat
  def self.type
    binding.receiver
  end
end

> Cat.type
Cat

class Tiger < Cat
end

> Tiger.type
Tiger

Most of us don’t directly deal with bindings, but if you do life just got a little easier.

Dir#fileno

Getting the file descriptor for an IO object is nothing new. Now you can get it for a directory too. File descriptors are a way to reference a particular open file, directory, etc. You’ve probably used them before without even realizing it. On POSIX systems, 0, 1, and 2 are reserved for standard in, standard out, and standard error, respectively.
Have you ever used something like 2>&1 on the command line? You were referencing the file descriptors.

> $stdout.fileno
1
> Dir.new('.').fileno
8

Be warned, this new method is only available on POSIX systems. If you try to call it on Windows, it’ll throw a NotImplementedError.

Enumerable#slice_after

This method is a complement to the existing slice_before method. I haven’t run across slice_before in the wild so I had to take a look at how it works.

As the name suggests, slice_before is used to slice and dice enumerables. Given a way to match an element in the enumerable, it will find a match and cut it apart just prior to the match:

> [1, 'a', 2, 'b', 'c', 3, 'd', 'e', 'f'].slice_before { |e| e.is_a?(Integer) }.to_a
[[1, "a"], [2, "b", "c"], [3, "d", "e", "f"]]

In this case, it made cuts before the integers 1, 2, and 3.

An argument can be passed in place of a block. When that happens, the argument is checked using === (i.e. case equality). We could get the same result as above by passing Integer:

> [1, 'a', 2, 'b', 'c', 3, 'd', 'e', 'f'].slice_before(Integer).to_a
[[1, "a"], [2, "b", "c"], [3, "d", "e", "f"]]

Want to guess at what slice_after does? Rather than slicing before the match, it slices after:

> [1, 'a', 2, 'b', 'c', 3, 'd', 'e', 'f'].slice_after(Integer).to_a
[[1], ["a", 2], ["b", "c", 3], ["d", "e", "f"]]

Enumerable#slice_when

A particularly fun addition is slice_when. Unlike slice_after, this method only accepts a block. It walks an enumerable, passing pairs of elements to the block. When the block returns true, the enumerable is sliced between the pair of elements:

> [1, 3, 4, 5, 7, 8, 9, 10, 12].slice_when { |a, b| a + 1 != b }.to_a
[[1], [3, 4, 5], [7, 8, 9, 10], [12]]

Here, we’ve found runs of consecutive numbers by slicing when the second number is not the first number plus one.

Given an array of numbers, you could easily find out how many of each one you have:

> Array.new(10) { rand(3) + 1 }.sort.slice_when(&:!=).map { |x| [x.first, x.size] }
[[1, 4], [2, 4], [3, 2]]

On this run, we got 1 four times, 2 four times, and 3 twice.

Float#next_float, Float#prev_float

These functions return the next or previous representable float. Note the word “representable” in that sentence, not all floats can be represented.

> 1.0.next_float
1.0000000000000002

Notice how it skipped 1.0000000000000001? It can’t be represented using a Float:

> 1.0000000000000001
1.0

It’s also worth noting that the distance between two steps isn’t always the same:

> 2.0.prev_float
1.9999999999999998

> 2.0.next_float
2.0000000000000004

Apparently, methods like this are useful for finding ULP values and doing other things that you’ll most likely never do. They might not be the most globally useful methods, but if you need them you’ll be glad they’re there.

File.birthtime, File#birthtime, File::Stat#birthtime

We’ve had atime, ctime, and mtime to check a variety of access and modification times related to a file. What we haven’t had before 2.2 is the birth time (i.e. creation time) for a file. You’ll notice that it, like the others, comes in both class and instance flavors:

> File.new('test', 'w').birthtime
2015-01-06 19:24:44 -0600

> File.birthtime('test')
2015-01-06 19:24:44 -0600

Also available in File::Stat:

> File::Stat.new('test').birthtime
2015-01-06 19:24:44 -0600

Kernel#itself

Ruby went out and got itself an identity method. For those not familiar, an identity method returns the object it’s called on:

> 1.itself
1

At this point you might be wondering where this is useful. One of the most common examples is grouping:

> [2, 3, 3, 1, 2, 3, 3, 1, 1, 2].group_by(&:itself)
{2=>[2, 2, 2], 3=>[3, 3, 3, 3], 1=>[1, 1, 1]}

It can also be used as a way to no-op a method like map or select. In functional programming, this can be a good way to avoid what might otherwise be an awkward conditional.

Method#curry

You might not have realized that Ruby is capable of currying and partial application. In the past, you could only call curry on a Proc. This same power is now available to you on Method.

def sum(*args)
  args.reduce(:+)
end

> inc = method(:sum).curry(2).(1)
#<Proc:0x007ff68ac96728 (lambda)>
> inc.(3)
4

Method#super_method

Earlier we found the object receiving a method with binding.receiver. We can also find out about the method’s parent. Calling super_method returns the method that you would get if you called super. If the method has no parent, it returns nil.

class Cat
  def speak
    'meow'
  end
end

class Tiger < Cat
  def speak
    'roar'
  end
end

> Tiger.new.method('speak')
#<Method: Tiger#speak>
> Tiger.new.method('speak').super_method
#<Method: Cat#speak>
> Cat.new.method('speak').super_method
nil

String#unicode_normalize, String#unicode_normalize!, String#unicode_normalized?

Did you know that, in Unicode, some characters can be represented multiple ways? Take for example, an “e” with an acute accent. You can represent it with normalization form canonical composition (NFC) using a single code point:

> nfc = "\u{e9}"
"é"

You can also represent it with normalization form canonical decomposition (NFD) by combining two code points:

> nfd = "\u{65}\u{301}"
"é"

In this form, a regular “e” is combined with “◌́” to create the “é” you see above. While these two might produce a similar looking output they are not the same:

> nfc == nfd
false

What unicode_normalize does is convert strings from one form to another. By default it converts to NFC:

> nfc == nfd.unicode_normalize
true

You can also pass a symbol indicating the form to convert to:

> nfc.unicode_normalize(:nfd) == nfd
true

These forms exist because they have value in different situations. The NFC codes are shorter, but if you want to strip accents from a word for, let’s say search purposes, the NFD form would be easier to handle. While NFC and NFD are the most common, you can also pass :nfkc and :nfkd if needed.

The bang version of this does what you’d expect and modifies the string receiving the message.

Calling unicode_normalized? checks to see if the string matches the form you want. It takes the same argument as unicode_normalize:

> nfc.unicode_normalized?
true
> nfc.unicode_normalized?(:nfd)
false

Time to Refine

A lot of these methods aren’t every day tools. They’re incremental improvements that shore up the language. Ruby has experienced a lot of change in the last few years. The changes between 1.8, 1.9, and the 2.0 releases were monumental. Now, the language is focused on refining some of the rough edges created by that process. In fact, 2.2 lets you create quoted symbol keys in hashes with a trailing colon:

{ 'programming-language': :ruby }

No more symbol keyed hashes with an stray hash rocket thrown in because of a hyphened HTML attribute. I think that is something we can all get behind.

Aaron LasseigneAaron Lasseigne
View Author

Freelance coder. Organizer of the Dallas Ruby Brigade. Author of Mastering Ruby: Strings and Encodings. C‽O of Never Done & creator of checkt.

GlennG
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week