Skip to content

Ruby 2.7 Notes

Here are some of the biggest changes coming out Ruby 2.7

Pattern Matching

Pattern matching uses a case statement combined with the in keyword (instead of when) to match arrays and hashes. You can also augment with an if or unless guard.

ruby 2.7 pattern matching pattern_matching.rb

def parse_specific_age json
  case JSON.parse(json, symbolize_names: true)
    in {name: 'Alice', children: [{name: 'Bob', age: age}]} if age < 5
      p "Alice's kid, Bob, is only #{age} years old"
    in {name: 'Alice', children: [{name: 'Bob', age: age}]}
      p "Alice's kid, Bob, is #{age} years old"
    else
      p 'No Alice with a child named Bob'
  end
end

parse_specific_age '{ "name": "Alice", "children": [{ "name": "Brenda", "age": 20 }] }'
# => No Alice with a child named Bob

parse_specific_age '{ "name": "Alan", "children": [{ "name": "Bob", "age": 20 }] }'
# => No Alice with a child named Bob

parse_specific_age '{ "name": "Alice", "children": [{ "name": "Bob", "age": 6 }] }'
# => Alice's kid, Bob, is 6 years old

parse_specific_age '{ "name": "Alice", "children": [{ "name": "Bob", "age": 3 }] }'
# => Alice's kid, Bob, is only 3 years old

Ruby will try to match the variable with the case statement, and assign any supplied variables their corresponding values. So, in the first in clause above, the provided hash must have a :name key with a value of Alice . It must also have a :children key consisting of an array of hashes. That hash must have a key of :name with a value of Bob and a key of :age , the value of which will be assigned to the variable age , if age is less than 5.

Note that the case statement halts after the first match, so the order of the conditions is important. You need to start most specific -> least specific.

In the example above, what if you wanted to match against a value in a variable? So, instead of looking for the name Bob , we want to match to whatever is passed in the variable child_name

variable pattern matching variable_pattern_matching.rb

in {name: 'Alice', children: [{name: child_name, age: age}]} if age < 5

Well, the syntax we’ve just described above says that this will match any name, and assign that name to the variable child_name, just like it assigns the variable age. That’s not what we want.

So, we pin this variable

in {name: 'Alice', children: [{name: ^child_name, age: age}]} if age < 5

So, now we can do:

def parse_some_age json, child_name
  case JSON.parse(json, symbolize_names: true)
    in {name: 'Alice', children: [{name: ^child_name, age: age}]} if age < 5
      p "Alice's kid, #{child_name}, is only #{age} years old"
    in {name: 'Alice', children: [{name: ^child_name, age: age}]}
      p "Alice's kid, #{child_name}, is #{age} years old"
    else
      p "No Alice with a child named #{child_name}"
  end
end

parse_some_age '{ "name": "Alice", "children": [{ "name": "Brenda", "age": 20 }] }', 'Brenda'
# => Alice's kid, Brenda, is 20 years old
parse_some_age '{ "name": "Alan", "children": [{ "name": "Bob", "age": 20 }] }', 'Blake'
# => No Alice with a child named Blake
parse_some_age '{ "name": "Alice", "children": [{ "name": "Bob", "age": 6 }] }', 'Bob'
# => Alice's kid, Bob, is 6 years old
parse_some_age '{ "name": "Alice", "children": [{ "name": "Bob", "age": 3 }] }', 'Bob'
# => Alice's kid, Bob, is only 3 years old

This is interesting, as this isn’t just a syntactic sugar change, this is a nice whole new language feature that adds a method of programming that is quite new. Elixir has a very powerful pattern matching syntax a bit like this, and it is used all over the place.

This is entirely new to ruby, though, and I’m interested to see how it is adopted and whether it will change common ruby idioms.

Keyword Arguments

From 2.7, keywords arguments are changing. The old style will be deprecated. This is a SemVer breaking change, and thus, the new style will be mandatory in ruby 3.0.

Old Style

As a recap, the old style keyword arguments was a first-class version of a hash passed as an argument in a method call. If the last argument in a method call was a hash, it was converted into keyword arguments.

ruby 2.6 keyword arguments keyword_arguments_2_6.rb

def old_style_method(regular_param, optional_param: 'value', required_param:)
  p "#{regular_param}, #{optional_param} and #{required_param}"
end

old_style_method 'a', required_param: 'c'
# => "a, value and c"

old_style_method 'a', optional_param: 'b', required_param: 'c'
# => "a, b and c"

old_style_method 'a', optional_param: 'b'
# => ArgumentError

These let you pass in named arguments (which is handy for self-documenting your code), without worrying about argument position.

New Style

In Ruby 3, hashes will no longer be converted into keyword arguments. From 2.7 you will get deprecation warnings for all sorts of scenarios where this can happen. You will need to explicitly handle the conversion to keyword arguments.

You should: Use the splat operators for array (*) and hash (**) to explicitly deconstruct an array or hash into keyword arguments.

Use {} braces to explicitly pass a hash, so the parser is sure you are not passing keyword arguments.

ruby 2.7 keyword arguments keyword_arguments_2_7.rb

def keyword_argument_method(foo: 'default')
  p foo
end

def hash_argument_method(h={foo: 'default'})
  p h[:foo]
end

def long_keyword_argument_method(_, foo: 'default')
  p foo
end

def long_hash_argument_method(_, h={foo: 'default'})
  p h[:foo]
end

example = { foo: 'hello' }

keyword_argument_method
# Ruby 2.7 => "default"
# Ruby 3.0 => "default"

hash_argument_method
# Ruby 2.7 => "default"
# Ruby 3.0 => "default"

keyword_argument_method example
# Ruby 2.7 =>"hello"
# Ruby 3.0 => ArgumentError

hash_argument_method example
# Ruby 2.7 => "hello"
# Ruby 3.0 => "hello"

keyword_argument_method(example)
# Ruby 2.7 => "hello"
# Ruby 3.0 => ArgumentError

keyword_argument_method(**example)
# Ruby 2.7 => "hello"
# Ruby 3.0 => "hello"

hash_argument_method(foo: "goodbye")
# Ruby 2.7 => goodbye
# Ruby 3.0 => goodbye

hash_argument_method({foo: "goodbye"})
# Ruby 2.7 => goodbye
# Ruby 3.0 => goodbye

long_hash_argument_method('dummy', foo: "goodbye")
# Ruby 2.7 => goodbye
# Ruby 3.0 => goodbye

long_hash_argument_method('dummy', {foo: "goodbye"})
# Ruby 2.7 => goodbye
# Ruby 3.0 => goodbye

long_keyword_argument_method('dummy', foo: "goodbye")
# Ruby 2.7 => goodbye
# Ruby 3.0 => goodbye

long_keyword_argument_method('dummy', {foo: "goodbye"})
# Ruby 2.7 => goodbye
# Ruby 3.0 => ArgumentError

long_keyword_argument_method('dummy', **{foo: "goodbye"})
# Ruby 2.7 => goodbye
# Ruby 3.0 => goodbye

Argument Forwarding

Also, argument forwarding (...) is a thing that exists.

Numbered Parameters

You can now access arguments passed to a block using implicit numbered arguments, because apparently, that’s more readable.

[1,2,3].map { _1 * 2 }

irb

The irb REPL is also improved: It has syntax highlighting.

IRB syntax highlighting

See Also

A comprehensive description of all the changes is here.