編集:このアプローチは制約を考慮していないことに注意してください。unique(article_id , tags_id)
またArticle
、同じタグを持つ2つの問題も発生します。- ごめん。
これは公式に文書化されていませんが(こことここのGrailsリファレンスドキュメントの関連部分を参照)、1対多の関連付けに対する制約は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
ドメインクラスを紹介します。
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
}
}