Zk | 2010-07-25 20:18:21

blog fossil diary

Working on a rather large project in grails, I've come to realize two things: there is some absolutely amazing software frameworks out there, and some people who write documentation appear to be 3-year-old kids who speak English as a second language.  The embarrassing part about that is that, since the project is open-source, I could contribute to the documentation very easily, fixing problems that I see and adding where I see fit.  However, the problem is so large and daunting (and the project I'm working on way more interesting), so instead, I wind up just living with it and searching El Goog over and over again for the same things.

I'm going to try to change this as best I can, and I'm going to start by collecting a few nifty tips and tricks I've pulled out of thin air here, and hopefully pull them all together into one place soon enough.

Permissions

The current project is using the grails Acegi plugin to manage users and security.  The plugin was recently deprecated, but it appears to have been done so before the replacement was completed, so I'm using it for the time being.  The idea of roles is pretty standard, but I wanted some finer-grained control over permissions to specific views, especially pertaining to objects that have lists of users associated with them.  After fiddling around with specifics in the controllers, I abstracted it into a service that I can use everywhere:

grails-app/services/package/PermissionsService.groovy
class PermissionsService {
    static transactional = true

def authenticateService

def groups = [
    userCanRead: { group ->
        if (group.exclusive) {
            if (authenticateService.principal().domainClass in group.members
                || authenticateService.principal().domainClass.id == group.admin.id
                || authenticateService.ifAnyGranted("ROLE_ADMIN")) {
                return true
            }
        } else {
            return true
        }
    },
    // ...
]

}

As you can see, the service contains a list of permissions - closures that do a few tests and return true or false - organized into lists in order to separate them into logical groups (in this case, the groups list pertains to the groups domain class, controller, and views). Each closure expects one argument - the object to test the permissions of. This comes in handy for the corresponding TagLib:
grails-app/taglib/package/PermissionsTagLib.groovy
class PermissionsTagLib {
    static namespace = "my"
    def permissionsService
    def withPermission = { attrs, body ->
        if (permissionsService."${attrs['class']}"."${attrs['permission']}"(attrs['arg'])) {
            out << body()
        }
    }
}
The tag-lib allows us to write logical tags that will only output data if the user passes the test, i.e:
In a view...
<my:withPermission class="groups" permission="userCanPost" arg="${group}">
    Post new topic
</my:withPermission>
After all, it is nice to ask permission...

Comma-separated tags

Just a little snippet, but I'm using comma-separated tags for tagging some domains, and I wasn't really keen on some of the solutions I saw out there, so I scribbled out a 'one-liner' for tagging stuff:
grails-app/services/package/TagService.groovy
tags.split(/(?!(?<=\)),/)     .collect { it.trim().replaceAll(/\,/, ",") }     .each {         if (it.size() > 0) {
            obj.tags.each { otag ->
                if (it.tag == it) {
                    // skip if we already have it tagged
                    return
                }
            }
            def t
            if (Tag.countByTag(it) > 0) {
                t = Tag.findByTag(it)
            } else {
                t = new Tag(tag: it).save()
            }
            obj.addToTags(t).save(flush: true)
        }
    }
// ...
This way, we can even have commas in tags, via: "foo, bar\, baz" (tags: ["foo", "bar, baz"])

Tips

In your service...
// ...
def ListWithRating (Closure c) {
    Obj.withCriteria {
        and {
            c.delegate = delegate
            c()
            le('rating', maxRating)
        }
    }
}
In your controller...
// ...
def criteria = {
    eq('type', params.type)
}
listService.listWithRating(criteria)
def object = Class.forName("package.${objectType}", true, Thread.currentThread().getContextClassLoader())
    .get(objectId)
That's about all for now, but I'm sure as the project progresses, I'll come up with more and I'll either post about them or collect them somewhere.