Chaining conditions in regular expressions
I recently had to make a semi-complex check on a string to validate it.
Basically, I have this model method which produces a serial number based on the ID of the object. Valid returned values for this method are L00000002
, L00000587
or L00014522
. In short, they should be constituted of a capital L
followed by 8 digits composed of the id
of the object padded with zeros.
So I had to find a way to simply created a matcher for this to use in my model spec. I could have built two expectations, one for the length, and one for the composition. But I just decided to believe that regular expressions are powerful enough to do both at the same time. So I headed to rubular and started fiddling and googling around.
The solution I came up with was the following (with an id
of 12
, for instance):
/^L(?=[0-9]{8}$)0*12$/
What this does, is - after the starting ^L
character - create a condition matching whatever follows the ?=
inside the parentheses. If this condition is true, then the rest of the expression is read. So I just check for any digit ([0-9]
- it could have been \d
but readability make the first option a better fit IMHO) exacty 8 times ({8}
) and then the end of the string with $
. If this is matched, then I check any number of zeros (0*
) followed by the id (12
) and the end of the string ($
).
Now I could use this expression in my spec, using the Regexp.escape
method on my model’s id
within a string interpolation:
# spec/models/my_model_spec.rb
describe "serial_number" do
let(:my_model) { create(:my_model) } # I use the FactoryBot gem
it "returns a valid serial number" do
expect(my_model.serial_number).to match(/^L(?=[0-9]{8}$)0*#{Regexp.escape(my_model.id.to_s)}$/)
end
end
… and create the corresponding method like so:
# app/models/my_model.rb
def serial_number
"L#{id.to_s.rjust(8, "0")}"
end
Don’t you just love regular expressions?