Sunday, June 27, 2010

Unordered notes from the RoCoCo unconference in Montreal this weekend

I decided to attend the Recent Changes Camp 2010: Montreal
Here are a few notes I made for myself, and that I'm sharing now, at least until I get around to writing a proper entry:

Wikis (well, wiki software) could be a way to implement addventure (a "you are the hero" collaborative story telling game originally written by Allen Firstenberg). Wiki red links are very much like "this episode has not been written yet".

Wikis synthesize (focus), where forums divide (or disperse in their focus).

Ontology vs folksonomy.

Look into:
HTLit
Inform 7
Universal Edit Button
Etherpad
Semantic web; the semantic triple: [subject, predicate, object]
Resource Definition Framework (RDF), SPARQL (query language for RDF)
confoo
appropedia
Google wheel
microformats


To read:
The wisdom of crowds


I also got to lead a session (which is a lot easier than you might expect since all the participants are interested in the topic anyway - or they leave, and because they participate willingly). And we started a new project!

Thursday, June 10, 2010

How to transform one type of object to another in Groovy/Grails (as long as it's not a domain object)

I've been working on a system that will be using remote calls to communicate between a client (browser, mobile phone, possitbly a GWT client) and the server. The client sends a request, and a grails controller returns a grails domain object encoded using JSON. Relatively straight-forward stuff, but I hit a few snags. I was thankful when I discovered http://blog.lourish.com/ which goes into some details into how to make it happen. Detailed post are here, here, and here.

I debated using the ObjectMarshaller to restrict the data sent (afterall, the client doesn't need to know the class name of my objects), but in the end, I decided to use Data Transfer Objects. I can see a future development where these objects will be used as commands, for example.

The problem that's been keeping me awake tonight, tho, is in the translation from domain object to DTO. Based on my reading, it looked like I could transform any kind of object into any other kind of object, as long as the initial object knew what to do.
class User {
// grails will contribute fields for id and version
String lastName
String firstName
Address workAddress //
Address homeAddress // The client does not need that info and SHOULD NOT ever see it
static hasMany [roles: Role, groups: Groups]  // etc

doThis() {
//..
}

doThat() {
//...
}
}

class UserDTO {
String lastName
String firstName
}
How do you take a User object and make a UserDTO out of it? Well, you should certainly have a look at Peter Ledbrook's DTO plugin. But for my needs, I thought I'd stick with something simpler. Just use the groovy "as" operator.
All you need to do something like
def dto = User as DTO
is to have User implement asType(Class clazz) and to handle (by hand) the case where clazz is DTO:
class User {
// same fields as before, etc
Object asType(Class clazz) {
if (clazz.isAssignableFrom(UserDTO)) {
return new UserDTO(lastName: lastName, firstName:firstName)
} else {
return super.asType(clazz)
}
}
}
All works well. Unit tests confirm, there's nothing to it.
void testUserAsUserDTO() {
String lastName = 'Lovelace'
String firstName = 'Ada'
User u = new User(lastName: lastName, firstName: firstName);
UserDTO dto = u as UserDTO;
assertEquals(UserDTO.class, dto.class)
assertEquals(lastName, dto.lastName);
assertEquals(firstName, dto.firstName);
}
Integration test. I want to make sure my controller sends the right data
The controller:
def whoAmI = {
def me = authenticateService.userDomain() // acegi plugin; this returns a User
if (me) {
def dto = me as UserDTO
render dto as JSON
} else {
render [error: "You are not logged in"] as JSON
}
}
The test:
class RpcWhoAmITest extends ControllerUnitTestCase {
void testWhoAmI() {
String lastName = 'Lovelace';
String firstName = 'Ada';
User u = new User(lastName: lastName, firstName: firstName)
mockDomain(User.class,[u])
mockLoginAs(u)
controller.whoAmI()
def returnedUser = JSON.parse(controller.response.contentAsString)
assertNotNull(returnedUser)
assertEquals(lastName, returnedUser.lastName)
assertEquals(firstName, returnedUser.firstName)
}
}
And that... fails! The message is
org.codehaus.groovy.runtime.typehandling.GroovyCastException: Cannot cast object 'my.package.User : 1' with class 'my.package.User' to class 'my.package.rpc.UserDTO'
at org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation.castToType(DefaultTypeTransformation.java:348)
at ...

What went wrong? The call to mock my domain object is what went wrong. It replaces my asType(Class clazz) with its own. Fortunately, that's relatively easy to fix. I needed to override the method addConverters in grails.test.GrailsUnitTestCase to replace asType(Class) only if it didn't already exist (in my test class):
@Override
protected void addConverters(Class clazz) {
registerMetaClass(clazz)
if (!clazz.metaClass.asType) {
clazz.metaClass.asType = {Class asClass ->
if (ConverterUtil.isConverterClass(asClass)) {
return ConverterUtil.createConverter(asClass, delegate, applicationContext)
}
else {
return ConverterUtil.invokeOriginalAsTypeMethod(delegate, asClass)
}
}
}
}


Sadly, after all this work, I deploy, launch, and still get GroovyCastExceptions. It turns out that the instrumentation of domain class objects essentially throws out my "asType()" method. In the end, I switched to the DTO plugin (which post-instruments the domain object to do it's own stuff, something I considered doing, but at some point, the "quick, home-made solution" just isn't.