How to Truncate in Models



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.

Share and Enjoy:
  • Print
  • Digg
  • StumbleUpon
  • Slashdot
  • del.icio.us
  • Facebook
  • Mixx
  • Google Bookmarks
  • Share/Bookmark
  1. No comments yet.
(will not be published)