Many times when working with user input, you won't necessarily want to display the entire string if it is over a certain length. Let's say I allow my users to set their company name:
"Kirk, McCoy, Chekov & Associates Law Firm of New Jersey"
If I wanted to display this text in a grid view, this may be significantly more text than I want to display. To deal with that, we can truncate the string on presentation. A fair way to do this is to display a set of characters to denote that a string has been truncated so that the user doesn't get worried that their data has been manipulated unexpectedly. A nice demarkation of that is the ellipsis (…).
Rails comes with this functionality built into ActiveSupport.
company_name = "Kirk, McCoy, Chekov & Associates Law Firm of New Jersey"
company_name.truncate(15)
# => "Kirk, McCoy, Chekov & Assoc..."
I ran into a weird bug with this method the other night. I wasn't actually trying to use #truncate
this way, but I was certainly surprised by the behavior. Calling #truncate
with a length less than the length of your omission (ellipsis by default) yields some unexpected results.
company_name.truncate(2)
# => "Kirk, McCoy, Chekov & Associates Law Firm of New Jerse..."
Huh?! What's going on here? I was a bit surprised by this, so my pair and I pulled up the code for the #truncate
method, seen below.
def truncate(truncate_at, options = {})
return dup unless length > truncate_at
options[:omission] ||= '...'
length_with_room_for_omission = truncate_at - options[:omission].length
stop = if options[:separator]
rindex(options[:separator], length_with_room_for_omission) || length_with_room_for_omission
else
length_with_room_for_omission
end
"#{self[0...stop]}#{options[:omission]}"
end
The key part to note here is the calculation of length_with_room_for_omission
. Since the ellipsis is a 3 character string, passing a value of 2 for truncate_at
will result in a negative number. Ruby has a property for arrays known as negative indexing. In many other languages (C#, Java, C), passing in a negative value as an index to an array results in some kind of indexing error/exception. However, in Ruby, negative values will wrap around the end of the string.
"hello"[-2]
# => "l"
So, passing stop as an index to the range for the string index results in the string wrapping around.