Ruby
Article

New Methods in Ruby 2.2

By Aaron Lasseigne

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.

Comments
madjo

Hello Aaron,

good article. However you miss "self" for the class method in Binding#receiver example.

class Cat
def type => (must be self.type)
binding.receiver
end
end

Thanks

AaronLasseigne

Ah yes, good catch.

scrozier

Good stuff, Aaron. The surprise win for me was that you took me on a sidetrack to finally grok the === operator, which I'm embarrassed to say I hadn't really understood until now.

Greg

You'll find the slice_* methods used often when parsing text files, such as device configurations, where there are blocks of commands or settings that need to be grouped.

I've written several such device parsers, and slice_before made it almost trivial.

ColinDKelley

I think there's a typo in the curry setup. You still need an & to call to_proc on :+ and pass it as the block param, right?

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

You don't need the & when using reduce. It will accept a symbol and call to_proc for you.

tom_lord

In general, yes. But as of ruby 1.9+, there is a shortcut for Enumerable#inject to only pass a symbol (which is nice, since this is the most common use for the method!).

proton

I can't understand Binding#receiver

I have same result with self:

class Cat
  def self.type
    binding.receiver
  end
end

> Cat.type
Cat

class Tiger < Cat
end

> Tiger.type
Tiger
proton
class Cat
  def self.type
    self
  end
end
AaronLasseigne

In that particular example it's serving the same purpose as self. The difference is that a binding can be passed around.

class A
  def get_binding
    binding
  end
end

> a = A.new
# => #<A:0x007fe9b44cd880>
> b = a.get_binding
# => #<Binding:0x007fe9b44fcfe0>
> b.receiver
# => #<A:0x007fe9b44cd880>

Bindings are a window into a particular scope. Now it's easier to find where that scope came from. Does that help clarify it?

proton

Does that help clarify it?

Yes, than you!

Recommended
Sponsors
Because We Like You
Free Ebooks!

Grab SitePoint's top 10 web dev and design ebooks, completely free!

Get the latest in Ruby, once a week, for free.