Rob's Tips and Tricks

I have been working on a back-end management UI for a client of mine, and ran into an old, old problem in web development - plural nouns. I found my code filled with lots of conditional code to determine when to add an "s" to things, eg: "You have 1 active listing" vs "You have 2 active listings"

In Rails, I have a pluralize method to help (though it only goes so far), and I was looking for something similar in PHP, which led me to this excellent post over at kuwamoto.org.

It was the perfect thing - a port of Rails' pluralize/singularize methods in PHP, with a few improvements to boot.

Once I had the code installed on our system, however, I found that it wasn't enough. I still had conditionals. I still had blocks of ugly code to make things work. So I came up with something better...

The Clever Bit


I wrote the following function to take not a single word, but a phrase. It pulls out the last word in the phrase, and pluralizes it. The clever bit is, if there's a number in the phrase, it conditionally pluralizes the last word. This was exactly what I needed in my code, and allows me to concisely and elegantly solve one of those niggling problems all web developers deal with.

Check out the function here:

The function
/**
 * Takes a string or phrase, and pluralizes the
 * last word in the phrase.  If there's a number in the phrase,
 * conditionally pluralizes the last word based on the number.
 */
function smart_pluralize($str) {
  // Extract
  $regex ='/[^0-9]*([0-9,]+)?.*(?:[^a-zA-Z]|^)([a-zA-Z]+)[^a-zA-Z]*$/';
  preg_match($regex, $str, $res);
  list($amt, $word) = array_shift($res);
  if ($word) {
    if (strlen($amt)) {
      $word = Inflector::pluralize_if($amt, $word);
    } else {
      $word = Inflector::pluralize($word);
    }
    $str = preg_replace('/([^a-zA-Z]|^)([a-zA-Z]+)([^a-zA-Z]*)$/', "$1$word$3", $str);
  }
  return $str;
}

Examples


Here are some sample strings, and the result of running them through smart_pluralize():

  • "bunny" => "bunnies"
  • "1 dog" => "1 dog"
  • "5 dog" => "5 dogs"
  • "25 happy person!!!" => "25 happy people!!!"

Integrating it with Smarty


Now that I had a smart phrase pluralizer, I needed to integrate it into Smarty, my template system of, er, choice. :-)

I wrote a Smarty block helper named "pluralize" that simply passed the contained text into smart_pluralize() and voila! Clean templates:

Smarty integration
Welcome to WidgetCorp.com.  You have {pluralize}{$count} widget
item{/pluralize} in your shopping cart.

Just write your text in singular, include the count in the phrase somewhere, and you're off and running.

It's been a real blessing, hope you find it helpful as well!
I do a lot of procedural content generation - thumbnailing, gradients, css, you name it. With luck, many of those pieces of code will make it up onto this site at some point. But today, I'd like to introduce a nice helper tool that I use across the board - Color.rb.

What's it do?


The Color class represents a web color (rgb + a) and allows manipulating it. You can create a color by values (255,0,0) or by string ('#FF0000').

You can lighten, darken, and blend colors together by percentage amounts.

Let's start with a few examples:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
require 'color'
col = Color.new('#ff0000')
# => #FF0000
col.darken
# => #CC0000
col.darken(0.5)
# => #800000
col.grayscale?
# => false
col.grayscale!
# => #363636
col.grayscale?
# => true
Color.blend('#fff', '#000', 0.5)
# => #808080

Real-world Usage


So that's nice, but where would you actually use this? A simple example is parametric CSS rule generation. Here's a sample CSS file:

sample.css
1
2
body { color: #000; background: #fff; }
.grayed { color: #888; }

Obviously simplified, but it will suffice. You hard-code your values, and manually set gray to be halfway between your text and your background. Simple and clean. However, when you decide you'd like to modify your site's color scheme, it's a major pain going through and changing all the color values. A better way to set things up would be to generate your CSS using ERB, like this:

sample.css.erb
1
2
3
4
5
6
<%
  text = '#520'
  bg = '#e5eeff'
%>
body { color: <%=text%>; background: <%=bg%>; }
.grayed { color: #888; }

Now you can change the colors at the top, and re-use them throughout your rule set. But you can quickly see the problem. The .grayed rule really means "half way between the foreground and background colors." With simple strings for your color values, you can't achieve this. But, with the magic of the Color class, you can write your rules as you mean them and have it work automatically when you adjust your color scheme.

sample.css.erb
1
2
3
4
5
6
<%
  text = rgb('#520')
  bg = rgb('#e5eeff')
%>
body { color: <%=text%>; background: <%=bg%>; }
.grayed { color: <%=text.blend(bg, 0.5)%>; }

This is a very small sample, but I think you'll find as I have that when you have hundreds of CSS rules referencing various values, blending between colors, and so on, having the ability to manage the colors is very valuable.

Download the file here: Color.rb
String#extract is an extension to the core String class that simplifies extracting values from a string using regular expressions.

The code


First, let me show you the method:

# Returns the various captured elements from this string with the regex applied.
#   Usage:  a,b = 'abc123 is cool'.extract(/([a-z]*)([0-9]*)/)
#   Result: a = 'abc', b = '123'
# With no capture in regex, returns full match
# If no match, returns nil
def extract(regex)
  data = self.match(regex)
  return nil unless data
  if data.size > 1
    return *(data.to_a[1..-1])
  else
    return data[0]
  end
end

So, what does it do?


I always find using String#match and its Regex equivalents to be tedious and hard to follow.

Here's an example of trying to pull the various phone fields out of a user-entered phone number:

# A string, and a regex to parse it out
number = "(800) 555-1212"
regex = /([0-9]{3})?[^0-9]*([0-9]{3})[^0-9]*[0-9]{4}/
# This next bit is a bit verbose for my taste
match = number.match(regex)
if match
  area = match[1]
  prefix = match[2]
  suffix = match[3]
end

Instead, check out using the extract method:

# Same setup
number = "(800) 555-1212"
regex = /([0-9]{3})?[^0-9]*([0-9]{3})[^0-9]*[0-9]{4}/
# Short, sweet, and easy to read
area, prefix, suffix = number.extract(regex)

Basically, #extract allows you to use a regex to pull a value out of a string instance, cleanly. Here are a few more examples:

"Hey, Rob!  Cool method!".extract(/R[a-z]*/)
# "Rob"
"$2,700.00".extract(/([0-9,]+)\.([0-9]{2})/)
# ["2,700", "00"]
first, last = "Rob Morris".extract(/([a-z]+)\s+([a-z]+)/i)
# first = "Rob", last = "Morris"

So there you have it. Extract a single value with a plain regular expression, or one or more values using regexen with capture groups in them. And the syntax is clean and elegant. Hope you like it!
Thanks for stopping by. My name is Rob Morris, and I'm a web developer/software architect/code dude located in North Carolina. I've set up this blog as a home for tips and techniques that I run across or develop in the line of duty.

I primarily work in Ruby/Rails these days, with a heavy helping of jQuery and PHP on the side, so that's what you'll mostly find in these posts. I hope you find the information useful, and please feel free to join the discussion.

Cheers!

-Rob

About

Web home of Rob Morris, software guru and all-around great guy. A place to post and discuss code and the software development lifestyle.

Archive

July 2010 (1)September 2009 (1)August 2009 (2)