jjc*_*pek 19 grails save grails-orm
有没有办法dateCreated在不关闭自动时间戳的情况下覆盖域类中字段的值?
我需要测试控制器,我必须提供具有特定创建日期的特定域对象,但GORM似乎覆盖了我提供的值.
我的课程看起来像这样:
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
}
}
Run Code Online (Sandbox Code Playgroud)
我正在使用控制台来缩短时间.当我写作时,我遇到的问题是Victor的方法:
Date someValidDate = new Date() - (20*365)
BlogMessage.metaClass.setDateCreated = {
Date d ->
delegate.@dateCreated = someValidDate
}
Run Code Online (Sandbox Code Playgroud)
我得到以下异常:
groovy.lang.MissingFieldException: No such field: dateCreated for class: pl.net.yuri.league.blog.BlogMessage
Run Code Online (Sandbox Code Playgroud)
当我尝试
Message.metaClass.setDateCreated = {
Date d ->
delegate.@dateCreated = someValidDate
}
Run Code Online (Sandbox Code Playgroud)
脚本顺利,但遗憾的dateCreated是没有改变.
我遇到了类似的问题,并且能够覆盖我的域的dateCreated(在Quartz Job测试中,所以没有对Spec,Grails 2.1.0进行@TestFor注释)
作为参考,我的测试:
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()
}
}
Run Code Online (Sandbox Code Playgroud)
获取ClosureEventListener可以暂时禁用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
}
}
Run Code Online (Sandbox Code Playgroud)
可能有一种更简单的方法来获取applicationContext或Hibernate配置,但在运行应用程序时这对我有用.它不适用于集成测试,如果有人知道如何做到这一点让我知道.
更新
对于Grails 2,使用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
}
Run Code Online (Sandbox Code Playgroud)
我通过简单设置字段来完成这项工作.诀窍是在首先保存域对象后执行此操作.我假设dateCreated时间戳是在保存时设置的,而不是在创建对象时设置的.
沿着这些方向的东西
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 )
Run Code Online (Sandbox Code Playgroud)
使用Grails 2.3.6
从 Grails 3 和 GORM 6 开始,您可以点击AutoTimestampEventListener执行一个Runnable暂时忽略所有或选择时间戳的函数。
以下是我在集成测试中使用的一个小片段,这是必要的:
void executeWithoutTimestamps(Class domainClass, Closure closure){
ApplicationContext applicationContext = Holders.findApplicationContext()
HibernateDatastore mainBean = applicationContext.getBean(HibernateDatastore)
AutoTimestampEventListener listener = mainBean.getAutoTimestampEventListener()
listener.withoutTimestamps(domainClass, closure)
}
Run Code Online (Sandbox Code Playgroud)
那么在您的情况下,您可以执行以下操作:
executeWithoutTimestamps(BlogMessage, {
Date someValidDate = new Date() - (20*365)
BlogMessage message = new BlogMessage()
message.dateCreated = someValidDate
message.save(flush: true)
})
Run Code Online (Sandbox Code Playgroud)
您可以尝试通过autoTimestamp = false在域类映射中设置来禁用它。我对全局覆盖表示怀疑,因为该值是直接从System.currentTimeMillis()(我正在查看org.codehaus.groovy.grails.orm.hibernate.support.ClosureEventListener.java)获取的。
所以我只能建议你重写dateCreated类中字段的设置器,并分配你自己的值。也许甚至元类访问也可以工作,比如
Date stubDateCreated
...
myDomainClass.metaClass.setDateCreated =
{ Date d -> delegate.@dateCreated = stubDateCreated }
Run Code Online (Sandbox Code Playgroud)