
I recently had the difficult time of trying to truncate a string in a model. Rails enforces this policy because truncation is thought to be in the domain of the view of the data. This makes sense, because you are “seeing” a portion of the string. However, in this case, I was using the truncated string in the generation of an SEO-friendly URL.
I looked around on the web and found something even better, a truncate which is aware of word boundaries. It’s called awesome_truncate and consists of this simple (but useful) 5-line script:
# Awesome truncate # First regex truncates to the length, plus the rest of that word, if any. # Second regex removes any trailing whitespace or punctuation (except. # Unlike the regular truncate method, this avoids the problem with cutting # in the middle of an entity ex.: truncate("this & that",9) => "this &am..." # though it will not be the exact length. def awesome_truncate(text, length = 30, truncate_string = "...") return if text.nil? l = length - truncate_string.chars.length text.chars.length > length ? text[/\A.{#{l}}\w*\;?/m][/.*[\w\;]/m] + truncate_string : text end
Source: awesome_truncate
This is exactly what I was looking for. I pasted it into the model to test it out and sure enough, it worked like a charm. But, I wanted to allow the whole app to use awesome_truncate so I moved it out of the model into a file in /lib. Since this was a new app, I didn’t have much in /lib, so I created a file called utils.rb for storing my utility methods like this.
< br/>
Here’s what my utils.rb file looks like:
module Utils # Awesome truncate # First regex truncates to the length, plus the rest of that word, if any. # Second regex removes any trailing whitespace or punctuation (except. # Unlike the regular truncate method, this avoids the problem with cutting # in the middle of an entity ex.: truncate("this & that",9) => "this &am..." # though it will not be the exact length. def awesome_truncate(text, length = 30, truncate_string = "...") return if text.nil? l = length - truncate_string.chars.length text.chars.length > length ? text[/\A.{#{l}}\w*\;?/m][/.*[\w\;]/m] + truncate_string : text end end
Pretty simple, right?
< br/>
Next, I wanted to expose it to all models in the entire app. It worked fine when I pasted it into the model I was truncating, but I certainly wasn’t going to paste the same code into each of my models.
< br/>
After poking around the web, I found that it was supposed to be difficult to call text helpers from models. There are a couple of informative posts on this thread on stackoverflow.com. It has a post giving a solution to accessing string helpers in models, as well as a post explaining why it is difficult to do. First, let’s look at the solution. Then we’ll examine why it is a little strange.
The solution, from Ian Terrell, is as follows:
class MyLib
include ActionView::Helpers::TextHelper
def five_things(x)
pluralize 5, x
end
end
>> MyLib.new.five_things "dog"
=> "5 dogs"
That’s not too bad. I wanted to call truncate and not Utils.new.truncate but it is still acceptable. Now the model code looks like this:
seo_url = Utils.new.awesome_truncate(text, 90).downcase.gsub(/[^[:alnum:]]/,'-').gsub(/-{2,}/,'-')
Ignoring the gsub stuff which is just there to generate SEO-friendly URLs, we can see that it initially truncates the URL to 90 characters. It isn’t ideal, but it works.
In the words of a poster named Larry K, “The idea of MVC (Model View Controller) is that only the views should have to deal with formatting/outputting text. So Rails makes it VERY hard to use TextHelper from a class that you would put into the /lib folder.” Good to know! I can see why it is a bit tricky now, though there is probably an easier way to include the method for all models (kind of a model helper) that I am simply not aware of.
Do you know of a better way to truncate in models? Please leave your solutions in the comments.

Activity
Geir Freysson
Andrew DiFiore
zeming
Arun, Neil, shashwat
Eric, Fernando Zanatta, Jörn Zaefferer