Description
According to the Ruby Version Policy, Ruby will release a new MINOR version every Christmas as a Christmas gift, and so we are expecting 2.3.0 this year. This release brings many new features and improvements and this post helps you catch up on the changes.
General
Did you mean gem is now bundled by default
Have you ever made a typo, this gem shows the possible corrections for errors like NoMethodError
and NameError
:
"".downcasr
NoMethodError: undefined method `downcasr' for "":String
Did you mean? downcase
downcase!
Safe operator (&.
)
This new operator helps to avoid method invocation on nil
.
Consider this:
user && user.name
Can now can be reduced to:
user&.name
Without method name will raise SyntaxError
:
obj&. {} # syntax error
Only invoked when needed:
user&.address( telphone() ) # telphone() is conditionally evaluated
Useful for attribute assignment too:
user&.sign_in_count += 1
Benchmark
require "benchmark/ips"
def fast(user = nil)
user&.email
end
def slow(user = nil)
user && user.email
end
Benchmark.ips do |x|
x.report("&.") { fast }
x.report("check") { slow }
x.compare!
end
Calculating -------------------------------------
&. 155.950k i/100ms
check 150.034k i/100ms
-------------------------------------------------
&. 7.750M (± 9.1%) i/s - 38.520M
check 7.556M (± 9.2%) i/s - 37.508M
Comparison:
&.: 7750478.7 i/s
check: 7555733.4 i/s - 1.03x slower
Trivia: Matz also calls this the "lonely operator" in his RubyConf 2015 keynote RubyConf 2015 (a person sitting alone and looking at a dot).
Further Reading:
Frozen String Literal Pragma
Add this Magic Comment to the top of a file, and all Strings in the file will then be frozen by default.
# frozen_string_literal: true
This gem can help to add magic comments to all your ruby files.
Alternatively, you can also:
- Compile Ruby with
--enable=frozen-string-literal
or--disable=frozen-string-literal
- Invoke Ruby with ENV variable like so
$ RUBYOPT="--enable=frozen-string-literal" ruby
Known Issue:
Related Issue:
Further Reading:
- Unraveling String Key Performance in Ruby 2.2 by Richard Schneems
- The let_it_go gem can help to identify hotspots that can easily accept frozen string in your application.
String
<<~
Indented heredoc
sql = <<~SQL
SELECT * FROM MICHELIN_RAMENS
WHERE STAR = 1
SQL
squish
Active Support's String#squish
and String#squish!
has been ported to Ruby 2.3.
Module
#deprecate_constant
class Deppbot
GITHUB_URL = "http://github.com/jollygoodcode/deppbot"
deprecate_constant :GITHUB_URL
GITHUB_URI = "https://github.com/jollygoodcode/deppbot"
end
Deppbot::GITHUB_URL
will throw a warning: warning: constant Deppbot::GITHUB_URL is deprecated
.
Numeric
#positive?
42.positive? # => true
Benchmark
require "benchmark/ips"
class Numeric
def my_positive?
self > 0
end
end
Benchmark.ips do |x|
x.report("#positive?") { 1.positive? }
x.report("#my_positive?") { 1.my_positive? }
x.compare!
end
Calculating -------------------------------------
#positive? 166.229k i/100ms
#my_positive? 167.341k i/100ms
-------------------------------------------------
#positive? 10.098M (± 9.7%) i/s - 50.035M
#my_positive? 10.094M (± 8.9%) i/s - 50.035M
Comparison:
#positive?: 10098105.6 i/s
#my_positive?: 10093845.8 i/s - 1.00x slower
#negative?
-13.negative? # => true
Benchmark
require "benchmark/ips"
class Numeric
def my_negative?
self < 0
end
end
Benchmark.ips do |x|
x.report("#negative?") { 1.negative? }
x.report("#my_negative?") { 1.my_negative? }
x.compare!
end
Calculating -------------------------------------
#negative? 154.580k i/100ms
#my_negative? 155.471k i/100ms
-------------------------------------------------
#negative? 9.907M (±10.3%) i/s - 49.002M
#my_negative? 10.116M (±10.4%) i/s - 50.062M
Comparison:
#my_negative?: 10116334.8 i/s
#negative?: 9907112.3 i/s - 1.02x slower
Array
#dig
foo = [[[0, 1]]]
foo.dig(0, 0, 0) => 0
Faster than foo[0] && foo[0][0] && foo[0][0][0]
Benchmark
require "benchmark/ips"
results = [[[1, 2, 3]]]
Benchmark.ips do |x|
x.report("Array#dig") { results.dig(0, 0, 0) }
x.report("&&") { results[0] && results[0][0] && results[0][0][0] }
x.compare!
end
Calculating -------------------------------------
Array#dig 144.577k i/100ms
&& 142.233k i/100ms
-------------------------------------------------
Array#dig 8.263M (± 8.3%) i/s - 41.060M
&& 7.652M (± 9.3%) i/s - 37.976M
Comparison:
Array#dig: 8262509.7 i/s
&&: 7651601.9 i/s - 1.08x slower
#bsearch_index
While Array#bsearch
returns a match in sorted array:
[10, 11, 12].bsearch { |x| x < 12 } # => 10
Array#bsearch_index
returns the index instead:
[10, 11, 12].bsearch_index { |x| x < 12 } # => 0
Please note that #bsearch
and #bsearch_index
only works for sorted array.
Struct
#dig
klass = Struct.new(:a)
o = klass.new(klass.new({b: [1, 2, 3]}))
o.dig(:a, :a, :b, 0) #=> 1
o.dig(:b, 0) #=> nil
OpenStruct
OpenStruct is now 3X-10X faster in Ruby 2.3 than it was in earlier versions of Ruby.
If you are using Hashie
in any of your libraries (especially API wrappers), now is a good time to switch back to OpenStruct
.
Hash
#dig
info = {
matz: {
address: {
street: "MINASWAN street"
}
}
}
info.dig(:matz, :address, :street) => 0
Faster than info[:matz] && info[:matz][:address] && info[:matz][:address][:street]
Benchmark
require "benchmark/ips"
info = {
user: {
address: {
street1: "123 Main street"
}
}
}
Benchmark.ips do |x|
x.report("#dig") { info.dig(:user, :address, :street1) }
x.report("&&") { info[:user] && info[:user][:address] && info[:user][:address][:street1] }
x.compare!
end
Calculating -------------------------------------
Hash#dig 150.463k i/100ms
&& 141.490k i/100ms
-------------------------------------------------
Hash#dig 7.219M (± 8.1%) i/s - 35.961M
&& 5.344M (± 7.6%) i/s - 26.600M
Comparison:
Hash#dig: 7219097.1 i/s
&&: 5344038.3 i/s - 1.35x slower
#>
, #<
, #>=
, #<=
Check if a hash's size is larger / smaller than the other hash, or if a hash is a subset (or equals to) of the other hash.
Check this spec on ruby/rubyspec to learn more.
Further Reading:
Note that there isn't Hash#<=>
.
#fetch_values
Extract many values from a Hash (fetch_values
is similar to values_at
):
jollygoodcoders = {
principal_engineer: "Winston",
jolly_good_coder: "Juanito",
}
> jollygoodcoders.values_at(:principal_engineer, :jolly_good_coder)
=> ["Winston", "Juanito"]
> jollygoodcoders.fetch_values(:principal_engineer, :jolly_good_coder)
=> ["Winston", "Juanito"]
However, fetch_values will throw
KeyError` when a given key is not found in the hash:
> jollygoodcoders.values_at(:principal_engineer, :jolly_good_coder, :project_manager)
=> ["Winston", "Juanito", nil]
> jollygoodcoders.fetch_values(:principal_engineer, :jolly_good_coder, :project_manager)
=> KeyError: key not found: :project_manager
Benchmark
require "benchmark/ips"
jollygoodcoders = {
principal_engineer: "Winston",
jolly_good_coder: "Juanito",
}
Benchmark.ips do |x|
x.report("Hash#values_at") { jollygoodcoders.values_at(:principal_engineer, :jolly_good_coder) }
x.report("Hash#fetch_values") { jollygoodcoders.fetch_values(:principal_engineer, :jolly_good_coder) }
x.compare!
end
Calculating -------------------------------------
Hash#values_at 133.600k i/100ms
Hash#fetch_values 123.666k i/100ms
-------------------------------------------------
Hash#values_at 5.869M (± 9.4%) i/s - 29.125M
Hash#fetch_values 5.583M (± 7.7%) i/s - 27.825M
Comparison:
Hash#values_at: 5868709.9 i/s
Hash#fetch_values: 5582932.0 i/s - 1.05x slower
#to_proc
hash = { a: 1, b: 2, c: 3 }
[:a, :b, :c].map &hash
=> [1, 2, 3]
Enumerable
#grep_v
This method returns the inverse of Enumerable#grep
. Do not confuse this with the $ grep -v
command.
#chunk_while
The positive form of Enumerable#slice_when
:
> pi = Math::PI.to_s.scan(/\d/).map(&:to_i)
=> [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8, 9, 7, 9, 3]
> pi.slice_when { |i, j| i.even? != j.even? }.to_a
=> [[3, 1], [4], [1, 5, 9], [2, 6], [5, 3, 5], [8], [9, 7, 9, 3]]
> pi.chunk_while { |i, j| i.even? == j.even? }.to_a
=> [[3, 1], [4], [1, 5, 9], [2, 6], [5, 3, 5], [8], [9, 7, 9, 3]]
Additional Resources
- New features in ruby 2.3 - BlockScore Blog
- Ruby 2.3.0 changes and features
- Preview of New Features in Ruby 2.3.0
- Ruby 2.3.0 preview 1 by Mario Alberto Chavez
- What's new in Ruby 2.3?
- ruby/ruby NEWS at v2_3_0
- ruby/ruby ChangeLog at v2_3_0
Thanks for reading!
@JuanitoFatas ✏️ Jolly Good Code
About Jolly Good Code
We specialise in Agile practices and Ruby, and we love contributing to open source.
Speak to us about your next big idea, or check out our projects.