New Features in Ruby 2.3

Ruby 2.3 was released on Christmas day so I’m a little late to the party, but I recently gave a talk at my local Ruby meetup about some nifty new additions to the language. (Many of them I learned about while preparing for the talk—success!) Here’s what I’m excited about.

Hash#fetch_values

hash = { puppies: "Cute", kittens: "Adorbs", turtles: "LOL" }

# Hash#fetch lets us grab one value and blows up if we typo
hash.fetch(:puppies) #=> "Cute"
hash.fetch(:pupies) #=> KeyError: key not found: :pupies

# And Hash.values_at is a neat way to grab multiple values
hash.values_at(:puppies, :turtles) #=> ["Cute", "LOL"]

# But it just returns nil if we typo.
hash.values_at(:pupies, :turtles) #=> [nil, "LOL"]

# With 2.3, we now have Hash#fetch_values which combines both!
hash.fetch_values(:puppies, :turtles) #=> ["Cute", "LOL"]
hash.fetch_values(:pupies, :turtles) #=> KeyError: key not found: :pupies

A new pragma (magic comment) to enable frozen strings by default

# Just one example of why it's cool to freeze strings
CONSTANT = "Steve"
CONSTANT #=> "Steve"
steve = CONSTANT
steve << " Grossi"
CONSTANT #=> "Steve Grossi" # LOLGUESSNOT

# Freeze lets us specify objects as "Should not change"
CONSTANT = "Steve".freeze
steve = CONSTANT  #=> "Steve"
steve << " Grossi" #=> RuntimeError: can't modify frozen String

# It also saves memory

# 1000 objects for the same string 1000 times!
1000.times.map{ "Steve".object_id }.uniq.size #=> 1000

# Just 1 object! Handy if you refer to strings a lot.
1000.times.map{ "Steve".freeze.object_id }.uniq.size #=> 1

# It's opt-in for now. Add this to the top of a .rb file:
# frozen_string_literal: false

# Word is this will be default in Ruby 3, so be prepared!

Enumberable#grep_v

# Say you've got an array of things:
peeps = [Steve.new, Miles.new, Dave.new]

# Before, you could filter out only the Steves like so:
# Enumberable#grep compares with ===
peeps.grep Steve #=> [#<Steve:0x007fb8b9186a90>]

# But what if (for some crazy reason) you wanted only the non-Steves?
# Now you can!
peeps.grep_v Steve #=> [#<Miles:0x007fb8b9186a18>, #<Dave:0x007fb8b9186a15>]

# It works with regexes, too:
["Steve", "Miles", "Dave"].grep_v /Dave/ #=> ["Steve", "Miles"]

# I think it stands for inVert or something...

Hash comparison operators

some = { a: 1 }
most = { a: 1, b: 2 }
all  = { a: 1, b: 2, c: 3 }
wat  = { puppies: "Cute" }

# Subsets: <= and >=
most >= some #=> true
most <= most #=> true

# Proper Subsets: < and >
most > some #=> true
most < most #=> false

# Always false if no comparison
most >= wat #=> false
some < wat #=> false

Hash#dig (and Array#dig)

# Imagine you get some data
data = { user: { name: "Steve", address: { street: "1 Awesome Lane"}}}

# We want the street
data[:user][:name][:street] #=> "1 Awesome Lane"

# But what if the data is bad? Esplode!
bad_data = { user: nil }
bad_data[:user][:name][:street] #=> undefined method `[]' for nil:NilClass

# Previously, we needed to guard against that. Yuck:
data[:user] && data[:user][:name] && data[:user][:name][:street] #=> "1 Awesome Lane"
bad_data[:user] && bad_data[:user][:name] && bad_data[:user][:name][:street] #=> nil

# Now, we can dig!
data.dig(:user, :address, :street) #=> "1 Awesome Lane"
bad_data.dig(:user, :address, :street) #=> nil

Hash#to_proc

# Now you can call hashes on things!
# It returns the value of the thing's key in the hash:
comments = {
  "A" => "Great jerb!",
  "B" => "Not bad!",
  "C" => "Meh.",
  "D" => "Pick it up...",
  "F" => "Sorry, try again."
}

report_card = ["A", "A", "B", "D"]
report_card.map(&comments)
#=> ["Great jerb!", "Great jerb!", "Not bad!", "Pick it up..."]

# It returns nil for unrecognized inputs
weird_report_card = ["A", nil, "Z"] #=> ["Great jerb!", nil, nil]
weird_report_card.map(&comments)

Numeric#positive? and Numeric#negative?

# Ugh, so mathy!
-1 < 0 #=> true

# Ah, that's better
-1.negative? #=> true

# But srsly, having a predicate method is handy for selection
numbers = -3..3

numbers.select(&:positive?) #=> [1, 2, 3]
numbers.select(&:negative?) #=> [-3, -2, -1]

The squiggly Heredoc

# Heredocs let us span strings over multiple lines
animals = <<-ANIMALS
  Puppies
  Kittens
  Turtles
  Those little piggy things
ANIMALS

# But they include all that unsightly whitespace
animals.split("\n").first #=> "  Puppies"

# ActiveSupport gave us strip_heredoc, but with a seperate library (or Rails)
animals = <<-ANIMALS.strip_heredoc
  Puppies
  Kittens
  Turtles
  Those little piggy things
ANIMALS

animals.split("\n").first #=> "Puppies"

# But now Ruby gives us THE SQUIGGLY HEREDOC
animals = <<~ANIMALS
  Puppies
  Kittens
  Turtles
  Those little piggy things
ANIMALS

animals.split("\n").first #=> "Puppies"

The safe navigation operator for nested objects

require "ostruct"

steve = OpenStruct.new(
  name: "Steve",
  address: OpenStruct.new(
    street: "1 Awesome Lane"
  )
)

khal = OpenStruct.new(
  name: "Khal Drogo",
  address: nil # He kinda wanders...
)

steve.address.street #=> "1 Awesome Lane"
khal.address.street #=> NoMethodError: undefined method `street' for nil:NilClass

# So we used to have to do
steve && steve.address && steve.address.street #=> "1 Awesome L
khal && khal.address && khal.address.street #=> nil

# But now we can:
steve&.address&.street #=> "1 Awesome Lane"
khal&.address&.street #=> nil

# R.I.P. Khal Drogo, though I guess it’s too late for that...