you can write this in the new syntax like
“`it { is_expected.to validate_numericality_of(:years_lived) }“`
More info about http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/
http://mitrev.net/ruby/2015/11/13/the-operator-in-ruby/
The most interesting addition to Ruby 2.3.0 is the Safe Navigation Operator(&.
). A similar operator has been present in C# and Groovy for a long time with a slightly different syntax – ?.
. So what does it do?
Imagine you have an account
that has an owner
and you want to get the owner
’s address
. If you want to be safe and not risk a nil error, you would write something like the following:
if account && account.owner && account.owner.address
...
end
This is really verbose and annoying to type. ActiveSupport includes the try
method which has a similar behaviour (but with few key differences that will be discussed later):
if account.try(:owner).try(:address)
...
end
It accomplishes the same thing – it either returns the address or nil
if some value along the chain is nil
. The first example may also return false
if, for example, the owner
is set to false
.
We can rewrite the previous example using the safe navigation operator:
account&.owner&.address
The syntax is a bit awkward but I guess we will have to deal with it because it does make the code more compact.
Let’s compare all three approaches in more detail.
account = Account.new(owner: nil) # account without an owner
account.owner.address
# => NoMethodError: undefined method `address' for nil:NilClass
account && account.owner && account.owner.address
# => nil
account.try(:owner).try(:address)
# => nil
account&.owner&.address
# => nil
No surprises so far. What if owner
is false
(unlikely but not impossible in the exciting world of shitty code)?
account = Account.new(owner: false)
account.owner.address
# => NoMethodError: undefined method `address' for false:FalseClass `
account && account.owner && account.owner.address
# => false
account.try(:owner).try(:address)
# => nil
account&.owner&.address
# => undefined method `address' for false:FalseClass`
Here comes the first surprise – the &.
syntax only skips nil
but recognizes false
! It is not exactly equivalent to the s1 && s1.s2 && s1.s2.s3
syntax.
What if the owner is present but doesn’t respond to address
?
account = Account.new(owner: Object.new)
account.owner.address
# => NoMethodError: undefined method `address' for #<Object:0x00559996b5bde8>
account && account.owner && account.owner.address
# => NoMethodError: undefined method `address' for #<Object:0x00559996b5bde8>`
account.try(:owner).try(:address)
# => nil
account&.owner&.address
# => NoMethodError: undefined method `address' for #<Object:0x00559996b5bde8>`
Oops, the try
method doesn’t check if the receiver responds to the given method. This is why it’s always better to use the stricter version of try
– try!
:
account.try!(:owner).try!(:address)
# => NoMethodError: undefined method `address' for #<Object:0x00559996b5bde8>`
nil.nil?
# => true
nil?.nil?
# => false
nil&.nil?
# => nil
As Joeri Samson pointed out in the comments, this section is actually wrong – I mistakenly used ?.
instead of &.
. But I still think that the last example is confusing and nil&.nil?
should return true
.
The #dig
method is, in my opinion, the most useful feature in this version. No longer do we have to write abominations like the following:
address = params[:account].try(:[], :owner).try(:[], :address)
# or
address = params[:account].fetch(:owner) { {} }.fetch(:address)
We can now simply use Hash#dig
and accomplish the same thing:
address = params.dig(:account, :owner, :address)
I really dislike dealing with nil
values in dynamic languages (check my previous posts) and think the addition of the safe operator and the dig
methods are really neat.
http://mitrev.net/ruby/2015/11/13/the-operator-in-ruby/
:foo
and "foo"
are considered to be the same.rgb = ActiveSupport::HashWithIndifferentAccess.new
rgb[:black] = '#000000'
rgb[:black] # => '#000000'
rgb['black'] # => '#000000'
rgb['white'] = '#FFFFFF'
rgb[:white] # => '#FFFFFF'
rgb['white'] # => '#FFFFFF'
Internally symbols are mapped to strings when used as keys in the entire writing interface (calling []=
, merge
, etc). This mapping belongs to the public interface. For example, given:
hash = ActiveSupport::HashWithIndifferentAccess.new(a: 1)
You are guaranteed that the key is returned as a string:
hash.keys # => ["a"]
Technically other types of keys are accepted:
hash = ActiveSupport::HashWithIndifferentAccess.new(a: 1)
hash[0] = 0
hash # => {"a"=>1, 0=>0}
but this class is intended for use cases where strings or symbols are the expected keys and it is convenient to understand both as the same. For example the params
hash in Ruby on Rails.
Note that core extensions define Hash#with_indifferent_access
:
rgb = { black: '#000000', white: '#FFFFFF' }.with_indifferent_access
which may be handy.
To access this class outside of Rails, require the core extension with:
require "active_support/core_ext/hash/indifferent_access"
which will, in turn, require this file.
“`
2.4.5 :013 > ((Time.current – 1.month.ago)/1.month.seconds).to_i
=> 1
2.4.5 :014 > ((Time.current – 10.month.ago)/1.month.seconds).to_i
=> 10
2.4.5 :015 > ((Time.current – 0.month.ago)/1.month.seconds).to_i
=> 0
“`
Also there is a good gem more human style like https://github.com/tmlee/time_difference a bit old but does the job.