outside grails save gorm

outside - grails views



Anulando fechaCreado para pruebas en Grails (10)

¿Hay alguna manera de que pueda anular el valor del campo dateCreated en mi clase de dominio sin desactivar la marca de tiempo automática?

Necesito probar el controlador y debo proporcionar objetos de dominio específicos con fecha de creación específica, pero GORM parece anular los valores que proporciono.

Editar

Mis clases se ven así:

class Message { String content String title User author Date dateCreated Date lastUpdated static hasMany = [comments : Comment] static constraints = { content blank: false author nullable: false title nullable: false, blank: false } static mapping = { tablePerHierarchy false tablePerSubclass true content type: "text" sort dateCreated: ''desc'' } } class BlogMessage extends Message{ static belongsTo = [blog : Blog] static constraints = { blog nullable: false } }

Estoy usando la consola para acortar las cosas. El problema que encontré con el enfoque de Victor es, cuando escribo:

Date someValidDate = new Date() - (20*365) BlogMessage.metaClass.setDateCreated = { Date d -> delegate.@dateCreated = someValidDate }

Me sale la siguiente excepción:

groovy.lang.MissingFieldException: No such field: dateCreated for class: pl.net.yuri.league.blog.BlogMessage

Cuando lo intente

Message.metaClass.setDateCreated = { Date d -> delegate.@dateCreated = someValidDate }

El script va bien, pero desafortunadamente dateCreated no se está modificando.


A partir de Grails 2.5.1, el método getMapping () de la clase GrailsDomainBinder no es estático, ninguno de los métodos anteriores funciona como está. Sin embargo, el método de @ Volt0 funciona con ajustes menores. Ya que todos estamos intentando hacerlo para que nuestras pruebas funcionen, en lugar de colocarlo en BootStrap, lo puse en una prueba de integración real. Aquí está mi ajuste al método de Volt0:

def disableAutoTimestamp(Class domainClass) { Mapping mapping = new GrailsDomainBinder().getMapping(domainClass) mapping.autoTimestamp = false } def enableAutoTimestamp(Class domainClass) { Mapping mapping = new GrailsDomainBinder().getMapping(domainClass) mapping.autoTimestamp = true }

Y simplemente llamar a estos métodos en pruebas como

disableAutoTimestamp(Domain.class) //Your DB calls enableAutoTimestamp(Domain.class)

El código anterior también se puede colocar en el directorio src y se puede llamar en las pruebas; sin embargo, lo puse en una prueba real, ya que solo había una clase en mi aplicación donde la necesitaba.


A partir de Grails 3 y GORM 6, puedes aprovechar AutoTimestampEventListener para ejecutar un Runnable que ignora temporalmente todos o selecciona marcas de tiempo.

El siguiente es un pequeño fragmento de código que uso en mis pruebas de integración donde es necesario:

void executeWithoutTimestamps(Class domainClass, Closure closure){ ApplicationContext applicationContext = Holders.findApplicationContext() HibernateDatastore mainBean = applicationContext.getBean(HibernateDatastore) AutoTimestampEventListener listener = mainBean.getAutoTimestampEventListener() listener.withoutTimestamps(domainClass, closure) }

Entonces en tu caso podrías hacer lo siguiente:

executeWithoutTimestamps(BlogMessage, { Date someValidDate = new Date() - (20*365) BlogMessage message = new BlogMessage() message.dateCreated = someValidDate message.save(flush: true) })


Conseguí este trabajo simplemente estableciendo el campo. El truco era hacerlo después de que el objeto de dominio se haya guardado primero. Supongo que la fecha de fecha y hora creada se establece en guardar y no en la creación de objetos.

Algo en este sentido

class Message { String content Date dateCreated } // ... and in test class def yesterday = new Date() - 1 def m = new Message( content: ''hello world'' ) m.save( flush: true ) m.dateCreated = yesterday m.save( flush: true )

Usando Grails 2.3.6


Estaba teniendo un problema similar, y pude sobrescribir dateCreated para mi dominio (en una prueba de trabajo de cuarzo, por lo que no hay una anotación de @TestFor en el Spec, Grails 2.1.0) por

  • Usar el complemento BuildTestData (que usamos regularmente de todos modos, es fantástico)
  • Doble toque en la instancia de dominio con guardar (flush: true)

Para referencia, mi prueba:

import grails.buildtestdata.mixin.Build import spock.lang.Specification import groovy.time.TimeCategory @Build([MyDomain]) class MyJobSpec extends Specification { MyJob job def setup() { job = new MyJob() } void "test execute fires my service"() { given: ''mock service'' MyService myService = Mock() job.myService = myService and: ''the domains required to fire the job'' Date fortyMinutesAgo use(TimeCategory) { fortyMinutesAgo = 40.minutes.ago } MyDomain myDomain = MyDomain.build(stringProperty: ''value'') myDomain.save(flush: true) // save once, let it write dateCreated as it pleases myDomain.dateCreated = fortyMinutesAgo myDomain.save(flush: true) // on the double tap we can now persist dateCreated changes when: ''job is executed'' job.execute() then: ''my service should be called'' 1 * myService.someMethod() } }


Estoy usando algo como esto para una importación / migración inicial.

Tomando la publicación de gabe como titular (que no me funcionó con Grails 2.0) y mirando el código fuente antiguo de ClosureEvent TriggeringInterceptor en Grails 1.3.7, se me ocurrió esto:

class BootStrap { private void changeTimestamping(Object domainObjectInstance, boolean shouldTimestamp) { Mapping m = GrailsDomainBinder.getMapping(domainObjectInstance.getClass()) m.autoTimestamp = shouldTimestamp } def init = { servletContext -> changeTimestamping(new Message(), false) def fooMessage = new Message() fooMessage.dateCreated = new Date("11/5/1955") fooMessage.lastUpdated = new Date() fooMessage.save(failOnError, true) changeTimestamping(new Message(), true) } }


La solución fácil es agregar un mapeo:

static mapping = { cache true autoTimestamp false }


No pude hacer funcionar las técnicas anteriores, la llamada a GrailsDomainBinder.getMapping siempre devolvió el valor nulo.

Sin embargo...

Puede usar el complemento de accesorios para establecer la propiedad dateCreated en una instancia de dominio

La carga inicial no lo hará ...

fixture { // saves to db, but date is set as current date :( tryDate( SomeDomain, dateCreated: Date.parse( ''yyyy-MM-dd'', ''2011-12-25'') ) }

pero si sigues con un controlador de correos

post { // updates the date in the database :D tryDate.dateCreated = Date.parse( ''yyyy-MM-dd'', ''2011-12-01'') }

Parte relevante de los documentos de accesorios aquí

Los dispositivos AFAIK no funcionan para pruebas de unidad, aunque los autores del complemento pueden agregar soporte de prueba de unidad en el futuro.


Obtener una retención de ClosureEventListener le permite deshabilitar temporalmente la marca de tiempo de Grails.

import org.codehaus.groovy.grails.web.servlet.GrailsApplicationAttributes import org.codehaus.groovy.grails.commons.spring.GrailsWebApplicationContext import org.codehaus.groovy.grails.orm.hibernate.cfg.GrailsAnnotationConfiguration import org.codehaus.groovy.grails.orm.hibernate.support.ClosureEventTriggeringInterceptor import org.codehaus.groovy.grails.orm.hibernate.support.ClosureEventListener class FluxCapacitorController { def backToFuture = { changeTimestamping(new Message(), false) Message m = new Message() m.dateCreated = new Date("11/5/1955") m.save(failOnError: true) changeTimestamping(new Message(), true) } private void changeTimestamping(Object domainObjectInstance, boolean shouldTimestamp) { GrailsWebApplicationContext applicationContext = servletContext.getAttribute(GrailsApplicationAttributes.APPLICATION_CONTEXT) GrailsAnnotationConfiguration configuration = applicationContext.getBean("&sessionFactory").configuration ClosureEventTriggeringInterceptor interceptor = configuration.getEventListeners().saveOrUpdateEventListeners[0] ClosureEventListener listener = interceptor.findEventListener(domainObjectInstance) listener.shouldTimestamp = shouldTimestamp } }

Puede haber una forma más fácil de obtener la configuración de ApplicationContext o Hibernate, pero eso me funcionó cuando ejecuté la aplicación. No funciona en una prueba de integración, si alguien descubre cómo hacerlo, hágamelo saber.

Actualizar

Para Grails 2 use eventTriggeringInterceptor

private void changeTimestamping(Object domainObjectInstance, boolean shouldTimestamp) { GrailsWebApplicationContext applicationContext = servletContext.getAttribute(GrailsApplicationAttributes.APPLICATION_CONTEXT) ClosureEventTriggeringInterceptor closureInterceptor = applicationContext.getBean("eventTriggeringInterceptor") HibernateDatastore datastore = closureInterceptor.datastores.values().iterator().next() EventTriggeringInterceptor interceptor = datastore.getEventTriggeringInterceptor() ClosureEventListener listener = interceptor.findEventListener(domainObjectInstance) listener.shouldTimestamp = shouldTimestamp }


Puede intentar desactivarlo configurando autoTimestamp = false en la asignación de clase de dominio. Dudo acerca de la invalidación global porque el valor se toma directamente de System.currentTimeMillis() (estoy viendo org.codehaus.groovy.grails.orm.hibernate.support.ClosureEventListener.java ).

Por lo tanto, solo puedo sugerir que anule un setter para el campo dateCreated en su clase y asigne su propio valor. Tal vez incluso el acceso a la metaclase funcione, como

Date stubDateCreated ... myDomainClass.metaClass.setDateCreated = { Date d -> delegate.@dateCreated = stubDateCreated }


Una solución más sencilla es utilizar una consulta SQL en su prueba de integración para configurarlo como desee después de inicializar su objeto con los otros valores que desee.

YourDomainClass.executeUpdate( """UPDATE YourDomainClass SET dateCreated = :date WHERE yourColumn = :something""", [date:yourDate, something: yourThing])