How to pluralize words with a GSP tag

Let’s say you need to output the number of results for a query. For example:

	2 results found for your query.

Your GSP looks probably something like that:

// ...
	${count} results found for your query.
// ...

All is good until there is only one result:

	1 results found for your query.

Oops, that looks bad 🙂

Here is one way to fix it:

// ...
	${count} <%= count > 1 ? "results" : "result" %> found for your query.
// ...

Wouldn’t it be nice it we could embed this behavior in some view helper function that we could reuse?

In Grails, you can do that with a custom GSP tag.

So let’s code our custom pluralize tag using TDD.

Here is a first test:

// file: test/unit/pluralize/ViewHelperTagLibSpec.groovy
package pluralize

import grails.test.mixin.TestFor
import spock.lang.Specification
import spock.lang.Unroll

@TestFor(ViewHelperTagLib)
class ViewHelperTagLibSpec extends Specification {

    @Unroll
	def "Should pluralize by adding 's' to the singular form"() {
		given:
		    def template = '<my:pluralize count="${count}" singular="${singular}" />'
		expect:
		    renderedContent == applyTemplate(template, [count:count, singular:singular])
		where:
			renderedContent || count | singular
			'1 result'      || 1     | 'result'
			'2 results'     || 2     | 'result'
	}
	
}

If you run this test with the command grails test-app unit:spock ViewHelperTagLib, you should get an error since there is no ViewHelperTagLib. So let’s create it:

// file: grails-app/taglib/pluralize/ViewHelperTagLib.groovy
package pluralize

class ViewHelperTagLib {
    
    static namespace = "my"

    def pluralize = { attrs, body ->
        out << body()
    }
    
}

If you run the test again, you should see no more compilation errors but the tests are still failing.

Now, let’s code the pluralize behaviour:

// file: grails-app/taglib/pluralize/ViewHelperTagLib.groovy
package pluralize

class ViewHelperTagLib {
    
    static namespace = "my"

    def pluralize = { attrs, body ->
        out << attrs['count'] + " " + ( attrs['count'] > 1 ? attrs['singular'] + "s" : attrs['singular'] )
    }
    
}

This time, the tests pass and you can use the tag like this:

// ...
	<my:pluralize count="${count}" singular="result" /> found for your query.
// ...

Unfortunately not all noun gets pluralized with ‘s’. For example:

	1 person, 2 people
	1 tooth, 2 teeth
	1 mouse, 2 mice

Let’s add a second test to describe this behavior:

// file: test/unit/pluralize/ViewHelperTagLibSpec.groovy

    

    @Unroll
	def "Should appropriately pluralize exceptions"() {
		given:
		    def template = '<my:pluralize count="${count}" singular="${singular}" plural="${plural}" />'
		expect:
		    renderedContent == applyTemplate(template, [count:count, singular:singular, plural:plural])
		where:
			renderedContent || count | singular | plural
			'1 person'      || 1     | 'person' | 'people'
			'2 people'      || 2     | 'person' | 'people'
	}

And here is our updated custom tag:

// file: grails-app/taglib/pluralize/ViewHelperTagLib.groovy
package pluralize

class ViewHelperTagLib {
    
    static namespace = "my"

    def pluralize = { attrs, body ->
        def plural = attrs['plural'] ?: attrs['singular'] + "s"
        out << attrs['count'] + " " + ( attrs['count'] > 1 ? plural : attrs['singular'] )
    }
    
}

Back to our GSP view:

// ...
	<my:pluralize count="${count}" singular="person" plural="people" /> found for your query.
// ...

And the HTML output:

	1 person found for your query.
	2 people found for your query.

That's it for today.

Advertisements