Ruby 2.7 has added FrozenError#receiver
to return the frozen object on which modification was attempted.
It is similar to NameError#receiver
. This can help in pinpointing the frozen object.
Before
In Ruby, we use freeze
on objects,
to make sure objects are not allowed to be mutated by others.
If we try to modify such a frozen object, it will throw a FrozenError
.
a = [].freeze
#=> []
a << 1 rescue (e = $!)
#=> #<FrozenError: can't modify frozen Array>
But with this error, its not easy to simply identify or perform some cleanup operations on the frozen object
We can also manually initialize and throw a FrozenError
:
raise FrozenError.new("Error Message")
FrozenError#receiver
When we try to modify frozen object, it gives FrozenError
.
With this error, we can pinpoint the frozen object by calling FrozenError#receiver
method.
a = [].freeze
#=> []
a << 1 rescue (e = $!)
#=> #<FrozenError: can't modify frozen Array>
e.receiver
#=> []
While initializing the FrozenError
, we can pass the frozen object as the second argument.
# Trying to initialize FrozenError with receiver as second argument
error = FrozenError.new("Error Message", [].freeze)
#=> #<FrozenError: Error Message>
error.receiver
#=> []
Usage
FrozenError#receiver
gives us the flexibility
to handle the FrozenError
exception
specific to receiver,
in a graceful manner.
# Defining sub_scorer which is used at multiple places
# and based on some condition it is updating scores
def sub_scorer(scores)
...
scores << 50 if condition
...
end
# Defining final_scorer which may call sub_scorer
def final_scorer
begin
scores = [10, 20, 30].freeze
sub_scorer(scores)
rescue FrozenError => e
# We can now gracefully handle Frozen object violations
# based on the receiver
if e.receiver == scores
return "Can not modify scores"
else
return "Can not modify frozen objects"
end
end
end
final_scorer
#=> "Can not modify scores"