编辑:请注意,这种方法不考虑约束unique(article_id , tags_id)
。它还引发了两个Article
s 具有相同标签的问题。- 对不起。
虽然这没有正式记录(请参阅此处和此处的 Grails 参考文档的相关部分)对一对多关联的约束被 GORM 简单地忽略了。这包括unique
和nullable
约束,可能还有。
这可以通过设置dbCreate="create"
和下一步通过查看数据库模式定义来证明。对于您的Article
示例和 PostgreSQL 数据库,这将是:
CREATE TABLE article_tags
(
article_id bigint NOT NULL,
tags_string character varying(255),
CONSTRAINT fkd626473e45ef9ffb FOREIGN KEY (article_id)
REFERENCES article (id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION,
CONSTRAINT article0_tags_article0_id_key UNIQUE (article_id)
)
WITH (
OIDS=FALSE
);
如上所示,列没有约束tags_string
。
与关联字段的约束相反,域类的“普通”实例字段的约束确实按预期工作。
因此,我们需要某种Tag
, or TagHolder
,域类,并且我们需要找到一个仍然提供Article
干净的公共 API的模式。
首先,我们引入TagHolder
域类:
class TagHolder {
String tag
static constraints = {
tag(unique:true, nullable:false,
blank:false, size:2..255)
}
}
并将其与Article
类关联:
class Article {
String text
static hasMany = [tagHolders: TagHolder]
}
为了提供干净的公共 API,我们添加了方法String[] getTags()
, void setTags(String[]
. 这样,我们也可以使用命名参数调用构造函数,例如,new Article(text: "text", tags: ["foo", "bar"])
。我们还添加了addToTags(String)
闭包,它模仿了 GORM 的相应“魔术方法”。
class Article {
String text
static hasMany = [tagHolders: TagHolder]
String[] getTags() {
tagHolders*.tag
}
void setTags(String[] tags) {
tagHolders = tags.collect { new TagHolder(tag: it) }
}
{
this.metaClass.addToTags = { String tag ->
tagHolders = tagHolders ?: []
tagHolders << new TagHolder(tag: tag)
}
}
}
这是一种解决方法,但没有涉及太多编码。
一个缺点是,我们得到了一个额外的 JOIN 表。然而,这种模式允许应用任何可用的约束。
最后,一个测试用例可能如下所示:
class ArticleTests extends GroovyTestCase {
void testUniqueTags_ShouldFail() {
shouldFail {
def tags = ["foo", "foo"] // tags not unique
def article = new Article(text: "text", tags: tags)
assert ! article.validate()
article.save()
}
}
void testUniqueTags() {
def tags = ["foo", "bar"]
def article = new Article(text: "text", tags: tags)
assert article.validate()
article.save()
assert article.tags.size() == 2
assert TagHolder.list().size() == 2
}
void testTagSize_ShouldFail() {
shouldFail {
def tags = ["f", "b"] // tags too small
def article = new Article(text: "text", tags: tags)
assert ! article.validate()
article.save()
}
}
void testTagSize() {
def tags = ["foo", "bar"]
def article = new Article(text: "text", tags: tags)
assert article.validate()
article.save()
assert article.tags.size() == 2
assert TagHolder.list().size() == 2
}
void testAddTo() {
def article = new Article(text: "text")
article.addToTags("foo")
article.addToTags("bar")
assert article.validate()
article.save()
assert article.tags.size() == 2
assert TagHolder.list().size() == 2
}
}