null id in X entry (don't flush the Session after an exception occurs).
29/07/2013 14:17
1
Olá Senhores, estou entrando agora no mundo Grails e peguei um pequeno projeto para aprender melhor. Já conhecia Groovy e programo em Java há anos, mas Grails para mim ainda é um tanto novo. Por isso estou tentando fazer um exemplo que peguei nesse site http://omarello.com/2010/08/grails-one-to-many-dynamic-forms/ sobre uma página que consiga dinamicamente agregar filhos.

Exemplo: tenho um objeto Empresa que pode acomodar vários Sites. Na página de edição da empresa existe um pedaço que detalho aqui:

<!-- Multiplos Sites -->
<tr class="fieldcontain ${hasErrors(bean: empresaInstance, field: 'enderecosEletronicos', 'error')}" style="border-width: 1px; border-style: dotted; border-color: #B0E0E6;">
<td class="label">
<label for="enderecosEletronicos">
<g:message code="empresa.editar.endereco.eletronico" default="Sites_" />
<img src="${resource(dir:'images', file:'add.png')}" style="vertical-align:middle;" alt="${message(code: 'empresa.editar.adicionar.endereco.eletronico')}" onclick="addEnderecoEletronico();"/>
</label>
</td>
<td>
<g:render template="enderecosEletronicos" model="['empresaInstance':empresaInstance]" />
</td>
</tr>


Existe um render que é "enderecosEletronicos". Está aqui abaixo:


<script type="text/javascript">

var childCount = ${empresaInstance?.enderecosEletronicos.size()} + 0;

function addEnderecoEletronico() {
var htmlId = "enderecoEletronico" + childCount;
var deleteIcon = "${resource(dir:'images', file:'icon_delete.png')}";
var templateHtml = "<div id='" + htmlId + "' name='" + htmlId + "'>\n";
templateHtml += "<input type='text' id='enderecosEletronicosList[" + childCount + "].site' name='enderecosEletronicosList[" + childCount + "].site' />\n";
templateHtml += "<span onClick='$(\"#" + htmlId + "\").remove();'><img src='" + deleteIcon + "' /></span>\n";
templateHtml += "</div>\n";
$("#childList").append(templateHtml);
var str = "enderecosEletronicosList["+ childCount +"].site";
document.getElementById(str).focus();
childCount++;
}

</script>

<div id="childList">
<g:each var="enderecoEletronico" in="${empresaInstance?.enderecosEletronicos}" status="i">
<g:render template='enderecoEletronico' model="['enderecoEletronico':enderecoEletronico,'i':i,'hidden':false]"/>
</g:each>
</div>


Que também tem um render para um único endereço eletrônico, que segue aqui:

<div id="enderecoEletronico${i}" class="endereco-eletronico-div" <g:if test="${hidden}">style="display:none;"</g:if>>

<g:hiddenField name='enderecosEletronicosList[${i}].id' value='${enderecoEletronico?.id}'/>
<g:textField name='enderecosEletronicosList[${i}].site' value='${enderecoEletronico?.site}'/>
<input type="hidden" name='enderecosEletronicosList[${i}].deleted' id='enderecosEletronicosList[${i}].deleted' value='false'/>

<span onClick="$('#enderecosEletronicosList\[${i}\]\.deleted').val('true'); $('#enderecoEletronico${i}').hide()">
<img src="${resource(dir:'images', file:'icon_delete.png')}" />
</span>
</div>

No meu objeto empresa criei a referência para a classe EnderecoEletronico.groovy, que nada mais é que um POGO:

class EnderecoEletronico implements Serializable {

String site
boolean deleted

static transients = [ 'deleted' ]

static belongsTo = [empresa: Empresa]

static mapping = {
sort site: "asc"
}

static constraints = {
site blank:true, minSize: 4
}

def String toString() {
return site
}
}


No objeto Empresa tenho isso

List enderecosEletronicos = new ArrayList();
static hasMany = [ enderecosEletronicos: EnderecoEletronico ]
...
static mappedBy = [ enderecosEletronicos: 'empresa']
...
//Multiplos enderecos eletronicos
def getEnderecosEletronicosList() {
return LazyList.decorate( enderecosEletronicos, FactoryUtils.instantiateFactory(EnderecoEletronico.class))
}
...
static mapping = {
enderecosEletronicos cascade: 'all-delete-orphan'
}
...
static constraints = {
enderecosEletronicos nullable: true
}
...


Quando faço o submit do form e a função save(), dá um erro:

Message: null id in pacote.EnderecoEletronico entry (don't flush the Session after an exception occurs)

Imagino que não há ID mesmo para o EnderecoEletronico, mas esse pedaço do código não deveria gerar isso?

//Multiplos enderecos eletronicos
def getEnderecosEletronicosList() {
return LazyList.decorate( enderecosEletronicos, FactoryUtils.instantiateFactory(EnderecoEletronico.class))
}


Segundo o exemplo do link acima, era automágico
Tags: id null, one-to-many


1
Oi Rodrigo,

nossa, é bem complexo este seu problema. Mas pelo que pude entender, o problema é que você está tentando persistir um objeto filho cujo pai ainda não possuí um ID.

O código que você postou acima provavelmente não está persistindo o pai antes, o que gera este problema pra você.


0
Oi Henrique, obrigado pela resposta. Eu tinha uma ideia que seria isso, mas não consigo agora entender como fazer o Grails persistir primeiro o pai, porque lá é tudo mágicamente configurado. Pelo modelo de Classes o Grails deveria entender que 1) o pai já existe (pois estou na tela de edição e ele já pegou o pai na query) e após alterado o pai está dirty e 2) tem um filho a caminho. O problema é como fazer isso no Grails. Pelo que li no artigo do link do exemplo a mágica acontece naquela biblioteca da Apache da LazyList que teoriacamente criaria os objetos do tipo EnderecoEletronico.groovy.

Mas deixando esse exemplo de lado, como você faria para adicionar filhos dinamicamente numa página pai e quando submetesse o form ele salvaria "n" filhos de uma vez?
30/07/2013 00:21


1
Oi Rodrigo, confesso que achei esta solução meio complicada demais.

Vou falar por alto ok? Aí a gente pode até mesmo desenvolver a idéia. Eu pensaria em alguma estratégia baseada em convenções.

Tipo: um controlador que recebe como parametro o id do pai, a classe do pai e, baseado em alguma convenção (ou configuração, sem problema (sei que falei convenções no início :) )) já buscasse os filhos por trás dos panos.

E pelo mesmo controlador "genérico", eu já colocaria também a parte de inclusão que fizesse a validação por trás dos panos pra poder fazer a inclusão correta. Por inclusão, entenda apenas verificar se o pai já foi salvo e, na submissão, definir o atributo que liga o filho ao pai atualmente exposto.

Não sei se ficou claro, mas a gente pode desenvolver o conceito nesta thread com a ajuda do pessoal da comunidade. :)


1
Henrique, obrigado pela dica, mas como eu tinha que resolver rapidamente, deixei meu exemplo mas removi a LazyList que vi que não funciona mesmo e peguei o que tinha criado com o jQuery no request, com o código abaixo:


//Primeiro remove todos endereços eletrônicos
empresaInstance.executeUpdate('delete from EnderecoEletronico e where e.empresa.id = ?', [empresaInstance.id])

//Depois adiciona todos os elementos que existem no request
params.entrySet().each {
if(it.key.startsWith("enderecosEletronicosList[") && it.key.contains("site")) {
def ee = new EnderecoEletronico();
ee.site = it.value
empresaInstance.addToEnderecosEletronicos(ee)
}
}


Não sei se é a melhor forma, mas funcionou.
31/07/2013 17:18



Ainda não faz parte da comunidade???

Para se registrar, clique aqui.


Aprenda Groovy e Grails com a Formação itexto!

Newsletter Semana Groovy

Assinar

Envie seu link!


Livro de Grails


/dev/All

Os melhores blogs de TI (e em português) em um único lugar!

 
Creative Commons
RSS Grails Brasil é mantido por itexto Consultoria.
Em caso de problemas contacte Henrique Lobo Weissmann (Kico) por e-mail: kico@itexto.com.br
Todo o conteúdo presente neste site adota o Creative Commons como licença padrão.
Ver: 4.14.0
itexto