РЕДАКТИРОВАТЬ: Обратите внимание, что этот подход не учитывает ограничение unique(article_id , tags_id)
.Также возникает проблема с двумя Article
с одинаковыми тегами.- Извините.
Хотя это официально не задокументировано (см. Соответствующие части Справочной документации 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
, или TagHolder
, класса домена инам нужно найти шаблон, который все еще предоставляет Article
чистый публичный API .
Сначала мы представляем класс TagHolder
domain:
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
}
}