Member-only story

Freeze Your Constants in Ruby

Patrick Brown
3 min readJan 12, 2019

--

Close-up photograph of a ruby gemstone
Photo by Joshua Fuller on Unsplash

Years ago, I worked on a very large Ruby on Rails codebase that used constants to hold lists of credit card transaction states. For example:

class Txn
ACTIONABLE_STATES
= [:authenticated, :to_settle]
DONE_STATES = [:settled, :declined]
end

However, we had a bug where a settled transaction would return true when txn.state.in? ACTIONABLE_STATES was called on it.

txn.state
# :settled
txn.state.in? ACTIONABLE_STATES
# true

This was obviously wrong because :settled is not in ACTIONABLE_STATES! After hours of searching, we found the offending code in a completely different part of the codebase.

def some_method
all_states
= Txn::ACTIONABLE_STATES.concat(Txn::DONE_STATES)

all_states.each do |state|
# ...
end
end

If you’re already knowledgeable of the Ruby Array class, you’ve probably spotted the offending line already. #concat mutates the original list, even though it doesn’t end with the idiomatic !, and because ACTIONABLE_STATES is held statically in memory, when this code gets executed it changes ACTIONABLE_STATES state for the rest of that Ruby virtual machine’s life.

In other words, after some_method gets executed, ACTIONABLE_STATES becomes[:authenticated, :to_settle, :settled, :declined] until you…

--

--

Patrick Brown
Patrick Brown

Written by Patrick Brown

Software Engineer. 70% of my body is made of code.

No responses yet