In a previous article I showed a snippet of code I’ve used for displaying address information. There were some tricks to getting it right that are valuable to know when you have to handle missing data.
Here’s the problem, and how to solve it.
Let’s setup some simple data to use:
street = "123 Main St." city = "Arlington" province = "VA" postal_code = "222222"
The original implementation of displaying an address looked like this:
"".tap do |string| string << street unless street.nil? string << city unless city.nil? string << province unless province.nil? string << postal_code unless postal_code.nil? end
While that code will skip missing data if there is any, it will just mash all the bits together creating this:
"123 Main St.ArlingtonVA22222"
That’s not particularly helpful for displaying a readable address. I’m looking for it to display like this:
"123 Main St. Arlington, VA 22222"
Here’s what I tried next:
[street, [city, [province, postal_code].join(' ')].join(', ')].join("\n")
Thinking I was very clever, I ran the code with complete data and it worked quite well.
When I ran it with incomplete data I found that it didn’t work the way I expected. For example, if the city value was nil, I got this:
"123 Main St. , VA 22222"
That leading comma is just visual noise, so we need to remove that. Realizing that I had nil values in my arrays, I knew I could reach for the compact method to strip them out. After compacting the array, the join wouldn’t have nil values to address; they’d be gone.
[street, [city, [province, postal_code].compact.join(' ')].compact.join(', ')].compact.join("\n")
This worked perfectly, to remove the leading comma:
"123 Main St. VA 22222"
Just to be sure I got it right, I began checking other data. Next, with the city set to “Arlington” and this time with the province and postal_code set to nil I saw this:
"123 Main St. Arlington, "
Ugh! Now I had a trailing comma. Why wasn’t this working!? Using compact should remove the nil values.
The problem was that I had an empty array for the province and postal_code. That meant that with both values removed from the array using compact, this was happening:
.join(' ') #=> ""
And because that empty array returned a value of an empty string, I was joining the city value with an empty string. In this case, compact was doing nothing for me.
[city, [province, postal_code].compact.join(' ')].compact.join(', ') # is the same as: [city, ""].compact.join(', ')] # which yields: "Arlington, "
So there it was. Finally I found my problem that what I thought was nil, wasn’t.
I decided to change the values to nil if they were empty strings:
province_and_postal_code = [province, postal_code].compact.join(' ') province_and_postal_code = nil if province_and_postal_code.empty? city_province_postal_code = [city, province_and_postal_code].compact.join(', ') city_province_postal_code = nil if city_province_postal_code.empty? [street, city_province_postal_code].compact.join("\n")
Finally, I got the output I needed:
"123 Main St. Arlington" "123 Main St. VA 22222" "123 Main St. Arlington, VA 22222"
My clever one-liner gave me unexpected behavior. While it was nice and short, it was also wrong. Eventually I ended up with code which is not just correct, but much more readable too. You might have your own preferences for how to handle these nil values. What would you do differently?