jueves, 10 de diciembre de 2009

Preparar una lista de Objetos que se almacenan en arbol para presentarla en un combo

Algoritmo (no se si eficiente, pero como mucho de un tiempo de computacion n^2) para dada una lista de objetos, en este caso Category, que se almacena en forma de arbol a través de la propiedad parentCagegory, poderse presentar en un combo ordernadas en arbol. Lo típico es recogerlas primero ordenadas alfabeticamente ( o como se quiera), despues pasar este algoritmo y presentarlas ya en el combo:

private static List<Category> getAllCategoriesAsTree(List<Category> l , boolean indent, String indentText) {

Set<Category> allChildCategories = new HashSet<Category>();
Map<Long,Integer> indentationLevels = new HashMap<Long,Integer>();
List<Category> result = new ArrayList<Category>();

for (Category c:l) {
if (c.getParentCategory()==null) {
result.add(c);
indentationLevels.put(c.getId(), 0);
}
else
allChildCategories.add(c);
}


while (!allChildCategories.isEmpty()) {
if (allChildCategories.iterator().hasNext()) {
Category childCategory = allChildCategories.iterator().next();

int s = result.size();
for (int i=0;i<s;i++) {
if (result.get(i).getId()==childCategory.getParentCategory().getId()) {
indentationLevels.put(childCategory.getId(),
indentationLevels.get(childCategory.getParentCategory().getId())+1);
result.add(i+1, childCategory);
s = result.size();
}
}
allChildCategories.remove(childCategory);
}

}

if (indent) {
for (Category c:result)
c.setName(StringUtils.repeat(indentText, indentationLevels.get(c.getId()))+c.getName());
}

return result;
}



Para probarlo

List<Category> l = new ArrayList<Category>();

Category c0 = new Category(0L,"Cero",null);

Category c1 = new Category(1L,"Uno",null);

Category c2 = new Category(2L,"Dos",null);

Category c01 = new Category(11L,"CeroUno",c0);
Category c02 = new Category(12L,"CeroDos",c0);

Category c11 = new Category(21L,"UnoUno",c1);
Category c12 = new Category(22L,"UnoDos",c1);

Category c21 = new Category(31L,"DosUno",c2);
Category c22 = new Category(32L,"DosDos",c2);


Category c021 = new Category(1021L,"CeroDosUno",c02);
Category c111 = new Category(1111L,"UnoUnoUno",c11);
Category c221 = new Category(1221L,"DosDosUno",c22);

l.add(c0);
l.add(c1);
l.add(c2);
l.add(c01);
l.add(c02);
l.add(c11);
l.add(c12);
l.add(c21);
l.add(c22);
l.add(c021);
l.add(c111);
l.add(c221);

List<Category> l2 = getAllCategoriesAsTree(l, true, "\t");
for (Category c:l2){
System.out.println(c.getName());
}


y el resultado es

Cero
CeroUno
CeroDos
Uno
UnoUno
UnoDos
Dos
DosUno
DosDos
DosDosUno

domingo, 15 de noviembre de 2009

Test de integración, recuperar las variables devueltas en el model

Para poder recuperar las variables que un Controller pone en el modelo desde un test de integración, puedes seguir el siguiente truco. Básicamente le dices que los valores que se rellenan en el render, los copie a un Map definido en el propio test de integración. Una vez invocado el controller, solo hay que acceder a dicho Map.

class FooController {
def bar = {
render(view:"baz", model:[foo:"bar"]
}
}
class FooControllerTests extends GrailsUnitTestCase {
def renderMap

protected void setUp() {
super.setUp()
FooController.metaClass.render = {Map map -> //Esto es para acceder al modelo cuando el view o template se renderiza
renderMap = map
}
}
void testBar () {
def controller = new FooController()
controller.bar()
assert renderMap.model.foo == "bar"
assert renderMap.view == "baz"
}
}

domingo, 1 de noviembre de 2009

Introspección y GroovyClassLoader

Varios truqitos en uno para cuando en algun sitio configureis el nombde de la clase con la que se ha de trabajar y tengais que usarla:



class CosasRarasTests extends GrailsUnitTestCase {

def sessionFactory

protected void setUp() {
super.setUp()
}

protected void tearDown() {
super.tearDown()
}

void testSomething() {
GroovyClassLoader classLoader = new GroovyClassLoader()


String domainClass = "User"
String enabledFieldName = "enabled"
String usernameFieldName = "username"
String passwordFieldName = "password"
def user = classLoader.loadClass(domainClass).newInstance()
user."$usernameFieldName" = "a"
user."$passwordFieldName" = "12345"
user."$enabledFieldName" = true

sessionFactory.currentSession.save(user)


println User.findAll().size()

}
}




Se usa la inyección del sessionFactory debido a que el classLoader parece que no carga la clase proxy creada por GORM, sino solamente la entidad mapeada.

viernes, 30 de octubre de 2009

Usando el Ajax.inPlaceEditor

He creado el taglib que viene en el libro "Grails - The definitive guide" :



class ProductTagLib {

def editInPlace = { attrs, body ->
def rows = attrs.rows ? attrs.rows : 0
def cols = attrs.cols ? attrs.cols : 0
def id = attrs.remove('id')
out << "<span id='productTags${id}'>"
out << body()
out << "</span>"
out << "<script type='text/javascript'>"
out << "new Ajax.InPlaceEditor('productTags${id}', '"
out << createLink(attrs)
out << "',{"
if(rows)
out << "rows:${rows},"
if(cols)
out << "cols:${cols},"
if(attrs.paramName) {
out << "callback: " +
"function(form, value) { " +
"return '${attrs.paramName}=' + encodeURIComponent(value) " +
"}"
}

out << "});"
out << "</script>"
}

}




En el show.jsp he usado el tag (usando también el toString con delimitador de la entrada anterior:



<g:editInPlace id="productTags${productInstance.id}" url="[action:'updateTags',id:productInstance.id]"
rows="1" cols= "40" paramName="tags">${productInstance?.tags?.toString(", ").encodeAsHTML()}</g:editInPlace>



y simplemente queda crear el método en el controller correspondiente



def updateTags = {
def productInstance = Product.get( params.id )
productInstance.parseTags(params.tags,",")
render productInstance.tags.toString(", ")
}


Mejorando el toString de un list

En una parte de mi aplicación estaba invocando al toString del List, con un resultado esperado pero no adecuado:
[tag1, tag2, tag3]
Esto es, el toString del List de toda la vida, y yo queria obtener esta lista sin los corchetes y con otro delimitador.
Lo he conseguido creando un plugin (grails create-plugin MisUtils). Edita el archivo gestor del plugin (Misutils/MisUtilsGrailsPlugin.groovy) y he modificado el método doWithDynamicMethods:


def doWithDynamicMethods = { ctx ->
List.metaClass {
toString { String delim ->
def sb = new StringBuilder()
delegate.each {
sb.append it.toString() + delim
}
sb = (sb.length() > 0 ? sb.substring(0,sb.length()-delim.length()) :sb )
}
}
}


Y lo he probado en un test de integracion


void testToStringDelim() {

def a = ['a','b','c']
println a
println a.toString('/')
assert a.toString('/') == 'a/b/c'
}



Solo queda


grails package-plugin


y en el proyecto destingo


grails install-plugin ../MisUtils/grails-mis-utils-0.1.zip


para poder usarlo