5.8 KiB
Executable File
%title 2010-07-25 20:09:29 %date On Grails :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
- Codecs are static (I forgot. Â You ever forget? Â Happened to me). Â I wasted a crapload of time on a problem in there before I switched to a tag-lib, et voila, everything's fixed
- Constraints are also static, so if you want to constrain yourself to a list of strings, but want to be able to change the strings, store an integer and use that as an index to an array of strings stored in Config. Â For bonus points, store a portion of a property key so you can internationalize, and use the portion of the key as the default value.
- If you want to always ensure that a condition is met when querying, stick the query in a service and pass the service a closure of your criteria:
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)
- If you want to associate something with anything (i.e.: a comment with any other domain), store the domain's class name (with
obj.class.toString().split(/\\./)[-1]
) with the comment, along with the object's id, then get it back the same way. Â You can check that the object exists with dynamic class-loading:
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.