Skip to content

Ruby 2.3 in Detail #13

Description

@JuanitoFatas

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"deffast(user=nil)user&.emailenddefslow(user=nil)user && user.emailendBenchmark.ipsdo |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:

String

<<~ Indented heredoc

sql=<<~SQL SELECT * FROM MICHELIN_RAMENS  WHERE STAR = 1SQL

squish

Active Support's String#squish and String#squish! has been ported to Ruby 2.3.

Module

#deprecate_constant

classDeppbotGITHUB_URL="http://github.com/jollygoodcode/deppbot"deprecate_constant:GITHUB_URLGITHUB_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"classNumericdefmy_positive?self > 0endendBenchmark.ipsdo |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"classNumericdefmy_negative?self < 0endendBenchmark.ipsdo |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.ipsdo |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)#=> 1o.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.ipsdo |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 throwKeyError` 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: keynotfound: :project_manager

Benchmark

require"benchmark/ips"jollygoodcoders={principal_engineer: "Winston",jolly_good_coder: "Juanito",}Benchmark.ipsdo |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

Thanks for reading!

@JuanitoFatas ✏️ Jolly Good Code

About Jolly Good Code

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions