5

圣杯 2.2.0

我正在尝试创建一个自定义约束来强制用户只有一封主电子邮件。这是导致错误的简化代码:

用户域类

class User {

    static hasMany = [emails: Email]

    static constraints = {
    }
}

电子邮件域类

class Email {

    static belongsTo = [user: User]
    String emailAddress
    Boolean isMaster

    static constraints = {

        emailAddress unique: ['user']
        isMaster validator: { val, obj ->
            return !val || Email.findByUserAndIsMaster(obj.user, true) == null
        }

    }
}

集成测试

class EmailTests {

    @Before
    void setUp() {

    }

    @After
    void tearDown() {
        // Tear down logic here
    }

    @Test
    void testSomething() {
        def john = (new User(login: 'johnDoe')).save(failOnError: true, flush: true)
        assert (new Email(emailAddress: 'john@gmail.com', user: john, isMaster: true)).save(failOnError: true)
    }
}

运行“grails test-app -integration”会导致:

| 失败:testSomething(webapp.EmailTests)
| org.hibernate.AssertionFailure: org.grails.datastore.gorm.GormStaticApi$_methodMissing_closure2.doCall(GormStaticApi.groovy:105) 的 webapp.Email 条目中的空 id(发生异常后不刷新会话)webapp.Email $__clinit__closure1_closure2.doCall(Email.groovy:13) at org.grails.datastore.mapping.engine.event.AbstractPersistenceEventListener.onApplicationEvent(AbstractPersistenceEventListener.java:46) at webapp.EmailTests.testSomething(EmailTests.groovy:21)

如果我将唯一约束更改为自定义约束之后,则不会发生问题。这里发生了什么?我想了解这里的任何相关约束的顺序如何?

需要明确的是,这不会导致问题:

static constraints = {
        isMaster validator: { val, obj ->
            return !val || Email.findByUserAndIsMaster(obj.user, true) == null
        }
        emailAddress unique: ['user']
    }
4

1 回答 1

4

我想我明白了……一对多的关系被打破了。

让我解释

  • 测试中的第一行创建User john被保存和刷新的。
  • 第二行创建一个Email并将 john 设置为用户属性。

一旦你尝试保存Email实例,GORM 就会抱怨。那是因为您将 john 分配给Emailwhich 是关系的反面。拥有方并没有意识到这一点,并且在这一点上什么也没有。简单的说。在添加到用户之前,您无法保存和通过电子邮件发送实例。

这是一个应该有效的测试方法。

void testSomething() {
   def john = new User(login: 'johnDoe')

   john.addToEmails(new Email(emailAddress: 'john@gmail.com', isMaster: true))
   john.save(flush:true)

   assert false == john.errors.hasErrors()
   assert 1 == john.emails.size()  
}

addToEmails()方法将电子邮件实例添加到集合中,并将用户设置在关系的反面。现在关系已经满足,保存 john 也应该保存所有电子邮件。


另一条路线

由于问题似乎是对Email验证器中用户实例的引用,我虽然可能还有另一条路线可以采取。

class User {
    static hasOne = [master: Email]
    static hasMany = [emails: Email]
}

这将消除对有问题的验证器的需求,这使您Email根据User验证进行分类。您可以让用户对他拥有哪些电子邮件地址以及应该应用哪些规则负责。您可以添加验证器来User验证您是否拥有电子邮件列表中不存在的主地址,并验证分配的所有地址是否都是唯一的。例如:

static constraints = {
    master validator: { master, user, errors ->
        if (master.emailAddress in user.emails*.emailAddress) {
            errors.rejectValue('master', 'error.master', 'Master already in e-mails')
            return false
        }
    }

    emails validator: { emails, user, errors ->
        def addresses = emails*.emailAddress
        if (!addresses.equals(emails*.emailAddress.unique())) {
            errors.rejectValue('emails', 'error.emails', 'Non unique e-mail')
            return false
        }
    }
}

我做了一些测试,他们以这种方式做得很好。

于 2013-03-19T06:53:06.490 回答