Wednesday, October 22, 2008

UPDATED: Re-visiting MetaBuilder

I previously blogged about a new feature of MetaBuilder that I'm working on allows you to visit each object, as they are built, using a closure. I've updated the functionality so that the build visitor closure is given a CreateNodeEvent instance for each constructed node as follows:

public class CreateNodeEvent {
def name
def node
def parent
def isRoot // set to true, if the new node is a root node
}

If not a root node, the closure should return the node, or possibly a different node, when it is done.

Using a Build Visitor to Build a List of Root Nodes
An example closure that builds a list of all root nodes might be:

{ if(isRoot) { myList << it.node }; it.node }

In fact, I thought that usage might be so common that I included another set of buildList methods that returns a list of root nodes for you.

I'd also thought about exposing the ObjectGraphBuilder.postNodeCompletion() method, but it doesn't give you the node name, which is sometimes useful to have. It also doesn't tell you whether you're dealing with a root node (though you can probably derive that from the fact that parent == null for a root node.)

Using a Build Visitor to Build a Map of Root Nodes
Here's another build visitor example: say I have classes Order and OrderLine as follows and I want to use MetaBuilder to read Orders from a file into a map called orders, keyed on id:

class OrderLine {
def upc
def qty
def price
}

class Order {
def id
def lines = []
}

MetaBuilder mb = new MetaBuilder(getClass().getClassLoader())
mb.define {
order(factory: Order) {
properties {
id(req: true)
}
collections {
lines {
line (factory: OrderLine) {
properties {
upc(req: true)
qty(req: true)
price(req: true)
}
}
}
}
}
}

def orders = [:]
mb.build( { if(it.isRoot) { orders[it.node.id] = it.node }; it.node }, new File("orders.mb").toURL() )
A Hibernate Example
Andres Almiray's recent blog post on using ObjectGraphBuilder to create Grails fixtures inspired this next example, which extends the previous
example to insert each object into a database using Hibernate:

Session session = HibernateUtil.getSessionFactory().getCurrentSession();

session.beginTransaction()

mb.build( { if(it.isRoot) { session.save(it.node) }; it.node }, new File("orders.mb").toURL() )

session.getTransaction().commit()
Final Thoughts
One of the benefits of this feature is that it enables you to process large numbers of build objects without having to incur the overhead of building a list to contain them all.

2 comments:

Andres Almiray said...

Basically you are injecting a ResultCollector strategy which may be implemented with a Closure, clever. I guess the same can be done with OGB thanks to FBS.addPostNodeCompletionDelegate(Closure) (I believe you changed MB to be a child of FBS too, right?)

Just a matter of taste but I would prefer setting the strategy as the second parameter of build() that way it can be marked as optional

MB.build( Object src, Closure resultCollector = null )

Great work!

didge said...

@andres - Thanks! MetaBuilder itself is not a subclass of OGB. However, MB internally delegates to instances of MetaObjectGraphBuilder (MOGB), which is a subclass of OGB.

MOGBs are created every time the user starts a build, which is necessary if the user creates builds within builds, otherwise MB would get confused regarding which schema is being built. E.g.:
mb.build {
foo {
bar = mb.build {
something { ...}
}
}

I'll think about the optional parameter for version 1.2.

Content © didge

About Me

didge is my professional nickname, it's short for digital dave
Powered By Blogger