iberck
3/3/2017 - 10:17 PM

Grails-command-databinding-p4

Grails-command-databinding-p4

Alternativa 4

Vista con N paneles, un command object para cada panel.

Dónde aplicar

Un command object que tiene las mismas propiedades que su domain class y se desea sacar la lógica de validación de la capa vista del domain class.

Características:

En base al panel que envíe el usuario, se deben validar unas u otras propiedades. No tiene sentido implementar esta lógica en el domain class porque además de la vista los constraints aplicarían para la base de datos lo cual es incorrecto porque los datos en la bd no se guardan por "paneles".

Cuando hay errores de validación en un panel, los demás paneles conservan sus valores originales.

A pesar de modificar datos de otros paneles, sólo se actualizará el panel que actualice el usuario.

Ventajas:

  • Saca la lógica de validaciones de la vista del domain class y la pasa a un command object.
  • Define claramente qué operaciones de validación se harán en cada panel.
  • Cada command object define sus constraints dentro de static constraints {}.
  • Respeta principio open/close, bajo acoplamiento, alta cohesión.

Desventajas

  • Al ser una alternativa tan genérica, tiene problemas de simplicidad y legibilidad.
  • Command object y Domain class deben tener el mismo nombre de propiedades, de lo contrario esta alternativa fallará silenciosamente.
  • Es una alternativa MUY ENDEBLE a refactors, si se renombra una propiedad en el domain o command object, esta alternativa fallará silenciosamente.
class ParamsController {

    // envía a la página para editar los parámetros generales
    def edit() {
        return [paramsInstance: Params.getInstance()]
    }

    @Transactional
    def update(String panelClassName, Long id) {
        // Crear el panel indicado, asignar los valores del request y validar sus
        // datos de acuerdo a la lógica definida en cada panel concreto.
        def panel = GrailsUtils.newInstance(panelClassName)
        bindData(panel, params) // request(params)->command object
        panel.validate() // validar command object

        // Es necesario pasar los valores del command object hacia el domain object
        // porque el domain object se enviará de vuelta para mostrar los valores 
        // originales y los valores capturados incorrectamente junto con sus errores.
        def paramsInstance = Params.read(id)
        bindData(paramsInstance, panel)
        paramsInstance.errors = panel.errors//command.errors to domain.errors
        if (paramsInstance.hasErrors()) {
            render view: 'edit', model: [paramsInstance: paramsInstance]
            return
        }

        paramsInstance.save(failOnError: true)
        flash.message = message(code: 'params.edit.success')
        redirect action: "edit"
    }

}
<g:form method="post" class="form-horizontal" role="form" action="update">
    <g:hiddenField name="panelClassName" value="${us.incorpora.sigrem.PanelGeneral.name}" />
    <g:hiddenField name="id" value="${paramsInstance.id}"/>
    
    <div class="box-body">
        <g:render template="formGeneral"/>
    </div>

    <div class="box-footer">
%{--Actualizar--}%
    <t:onceSubmit name="submit1" class="btn btn-default pull-right">
        <i class="fa fa-save fa-2x"></i> </br>
        <g:message code="default.button.update.label"/>
    </t:onceSubmit>
    </div>
</g:form>
@Validateable
class PanelGeneral {

    String emailContactoHotel

    static constraints = {
        emailContactoHotel email: true
    }
}
@Validateable
class PanelConekta {

    boolean productionMode
    // obligatorio
    String sandboxPrivateKey
    // obligatorio
    String sandboxPublicKey
    String prodPrivateKey
    String prodPublicKey

    static constraints = {
        // (obligatorio cuando el modo producción es on, opcional cuando es off)
        prodPrivateKey(validator: { value, _this ->
            if (_this.productionMode && !value) {
                return "default.blank.message"
            }
        }, nullable: true)

        // (obligatorio cuando el modo producción es on, opcional cuando es off)
        prodPublicKey(validator: { value, _this ->
            if (_this.productionMode && !value) {
                return "default.blank.message"
            }
        }, nullable: true)
    }
}
@Validateable
class PanelFinanzas {
    // obligatorio
    BigDecimal tipoCambio
    // obligatorio
    BigDecimal costoPunto
}