Question

Mon problème est simple, mais je ne pouvais pas trouver la syntaxe GORM pour cela.

Considérez la classe suivante:

class Article {
  String text

  static hasMany = [tags: String]

  static constraints= {
    tags(unique: true) //NOT WORKING
  }

}

Je veux avoir un nom de balise unique par article dans mes contraintes défini mais je ne peux pas le faire avec la syntaxe ci-dessus. Il est clair que j'ai besoin dans le schéma DB quelque chose comme:

create table article_tags (article_id bigint, tags_string varchar(255), unique (article_id , tags_string))

Comment puis-je faire?

PS: Je suis également bloqué pour définir des contraintes sur minimum d'étiquette et la taille maximale

Était-ce utile?

La solution

Pour votre information, vous pouvez également utiliser un validateur personnalisé dans les classes de domaine:

    static constraints = {
    tags(validator: { 
        def valid = tags == tags.unique()
        if (!valid) errors.rejectValue(
            "tags", "i18n.message.code", "default message")
        return valid
    })

Au niveau de la base de données, vous pouvez customize génération DDL en ayant le code suivant dans Grails-app / conf / mise en veille prolongée / hibernate.cfg.xml :

<hibernate-mapping>
    <database-object>
        <create>
        ALTER TABLE article_tags
        ADD CONSTRAINT article_tags_unique_constraint 
        UNIQUE(article_id, tags_string);
    </create>
        <drop>
        ALTER TABLE article_tags 
        DROP CONSTRAINT article_tags_unique_constraint;
    </drop>
    </database-object>
</hibernate-mapping>

Autres conseils

Dans un premier temps je regardais joinTable mappage voir si elle soutiendrait une clé unique, mais il ne sera pas.

La meilleure solution que je peux penser est la combinaison suivante:

  • Exécuter manuellement l'instruction SQL pour ajouter la contrainte unique. Si vous avez une sorte d'outil de gestion de base de données (par exemple Liquibase), ce serait l'endroit idéale.

  • Explicitement déclarer l'association en tant que Set. Cela devrait éviter Hibernate jamais en cours d'exécution dans la contrainte d'unicité, de toute façon.

    class Article {
        static hasMany = [tags: String]
        Set<String> tags = new HashSet<String>()
    }
    

Une solution alternative serait de déclarer explicitement votre domaine de l'enfant (Tag) et mettre en place un grand nombre à plusieurs, en ajoutant la touche unique à la table de jointure en utilisant constraints il. Mais ce n'est pas vraiment une excellente solution, que ce soit. Voici un exemple primitif:

class Article {
    static hasMany = [articleTags: ArticleTag]
}

class Tag {
    static hasMany = [articleTags: ArticleTag]
}

class ArticleTag {
    Article article
    Tag tag
    static constraints = {
        tag(unique: article)
    }
}

Avec cela, cependant, vous devez gérer explicitement le nombre à plusieurs dans votre code. Il est un peu gênant de, mais il vous donne un contrôle total sur la relation dans son ensemble. Vous pouvez trouver le Nitty Gritty détails ici (la classe Membership dans l'exemple lié est semblable à la ArticleTag dans le mien).

Peut-être l'un des gourous plus familiers avec Gorm sonnera avec une solution plus élégante, mais je ne trouve rien dans les documents.

EDIT: Notez que cette approche ne pas envisager une contrainte unique(article_id , tags_id). Il soulève également un problème avec deux Articles ayant les mêmes balises. -. Désolé

Bien que ce ne soit pas officiellement documenté (voir les parties pertinentes de la documentation de référence Grails ici et ici ) les contraintes sur-à-plusieurs associations sont tout simplement ignorées par Gorm. Cela inclut les contraintes de unique et nullable, et probablement tout.

Ceci peut être prouvé par la mise en dbCreate="create" et ensuite, en regardant la définition du schéma de base de données. Pour votre échantillon Article et la base de données PostgreSQL, ce serait:

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
);

Comme on le voit ci-dessus, il n'y a aucune contrainte pour la colonne tags_string.

Contrairement aux contraintes sur les champs de l'association, les contraintes sur les champs d'instance « normaux » des classes de domaine fonctionnent comme prévu.

Ainsi, nous voudrons avoir une sorte de Tag ou TagHolder, classe de domaine, et nous aurions besoin de trouver un modèle qui fournit encore l'Article avec une API publique propre .

Tout d'abord, nous introduisons la classe de domaine TagHolder:

class TagHolder {
    String tag

    static constraints = {
        tag(unique:true, nullable:false, 
            blank:false, size:2..255)
    }
}

et l'associer à la classe Article:

class Article {
    String text

    static hasMany = [tagHolders: TagHolder]
}

Afin de fournir une API publique propre, nous ajouterons les méthodes String[] getTags(), void setTags(String[]. De cette façon, nous pouvons également appeler le constructeur avec des paramètres nommés, comme, new Article(text: "text", tags: ["foo", "bar"]). Nous ajoutons également la fermeture de addToTags(String), qui mimicks correspondant de Gorm « méthode magique ».

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)
        }
    }
}

Il est une solution, mais il n'y a pas trop de codage impliqué.
Un inconvénient, nous allons obtenir une table supplémentaire JOIN. , Ce schéma permet néanmoins d'appliquer des contraintes disponibles.

Enfin, un test pourrait ressembler à celui-ci:

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
    }
}

La seule façon que j'ai trouvé pour ce faire est d'écrire une contrainte personnalisée et faire une vérification de base de données pour la duplication. Je ne pense pas qu'il y ait un moyen intégré pour utiliser une contrainte GORM pour y parvenir.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top