• No results found

Substitute a benign value for nil

In document Confident Ruby (Page 176-181)

Collecting Input

4.19 Substitute a benign value for nil

Indications

A non-essential input may not always be supplied. For instance, in a listing of social group members, a person's geolocation data may not always be available.

Synopsis

Substitute a benign, known-good value for missing parameters.

Rationale

A known-good placeholder can eliminate tedious checks for the presence of optional information.

Example: Displaying member location data

Let's say we're working on a social network for bibliophiles, with a focus on putting together local meet-ups for users who all live in the same metropolitan area.

Naturally, it's called "Bookface".

A group organizer can request that the system dump a member report for the group they organize. A member report is a tabular listing of members, including names, photos, and their approximate location (for the purpose of identifying a good central location to meet up).

Here's a method which is used to render each member in the report.

def render_member(member) html = ""

html << "<div class='vcard'>"

html << " <div class='fn'>#{member.fname} #{member.lname}</div>"

html << " <img class='photo' src='#{member.avatar_url}'/>"

location = Geolocatron.locate(member.address)

html << " <img class='map' src='#{location.map_url}'/>"

html << "</div>"

end

Most of this is just filling in HTML blanks with member data. The one exception is showing the member's location. Before rendering the location, the code first interrogates a service called Geolocatron for a location object, using the member's address as an input.

Unfortunately, we've discovered that this line isn't 100% reliable. From time to time it fails to return a location object, returning nil instead. This may be because of a bad or unrecognized address, or just because the web service behind it is having a bad day. Whatever the reason, when it returns nil, the next line causes a crash as it attempts to call nil.map_url.

Showing the member map is nonessential "extra credit"—we'd like to go on

rendering the rest of the member list even if a few members are missing their maps.

There are a few ways we could address this problem.

One way would be to wrap the iffy code in a begin/rescue/end block.

def render_member(member) html = ""

html << "<div class='vcard'>"

html << " <div class='fn'>#{member.fname} #{member.lname}</div>"

html << " <img class='photo' src='#{member.avatar_url}'/>"

begin

location = Geolocatron.locate(member.address)

html << " <img class='map' src='#{location.map_url}'/>"

rescue NoMethodError end

html << "</div>"

end

Another is to check for the presence of the location before using it:

def render_member(member) html = ""

html << "<div class='vcard'>"

html << " <div class='fn'>#{member.fname} #{member.lname}</div>"

html << " <img class='photo' src='#{member.avatar_url}'/>"

location = Geolocatron.locate(member.address) if location

html << " <img class='map' src='#{location.map_url}'/>"

end

html << "</div>"

end

Both of these approaches are problematic from a narrative flow point of view.

Showing a member map is an optional, secondary objective of the

#render_membermethod. But both the begin/rescue/end and the if block put a spotlight on the possibility of a missing location. This is "squeaky wheel" code:

it might not be the most important code in the method, but by crying out and demanding special treatment it has eclipsed everything else that is going on.

If you're benign, you'll be fine!

What if, instead of adding special case code for a missing location, we supplied a back-up location instead? We might use a location which identifies the overall metropolitan area this group is organized within. While we're at it, we also move the location-finding code up to the top of the method, where it doesn't disrupt the cadence of appending text to an HTML string.

def render_member(member, group)

location = Geolocatron.locate(member.address) ||

group.city_location html = ""

html << "<div class='vcard'>"

html << " <div class='fn'>#{member.fname} #{member.lname}</div>"

html << " <img class='photo' src='#{member.avatar_url}'/>"

html << " <img class='map' src='#{location.map_url}'/>"

html << "</div>"

end

In this version, if a member's specific location can't be identified, a map of the whole city will be shown in its place. This is an example of a benign value - a known-good object that stands in for a missing input

You might find this similar to the Null Object [page 155] examples, and indeed, the line between a null object and a benign value is a fuzzy one. A null object is

expressly defined with "semantically null behavior"—do-nothing commands, and queries which return zeroes, empty strings, nil, or more null objects. A benign value, on the other hand, can have semantically meaningful data or behavior associated with it, as in the case of our substitute location. It's not a null

location; it's a known-good location which (hopefully) won't cause any problems in our rendering code.

Conclusion

Missing information isn't always the end of the world. If the data in question is non-vital, we can substitute a benign, known-good placeholder and move on rather than constructing special case code around every reference to the missing info.

In document Confident Ruby (Page 176-181)