Grails In Action-03.Modeling the domain

Grails In Action是使用Grails技术构建一个名叫Hubbub的博客网站,下面是第三章的代码。

第三章主要讲解构建hubbub博客需要的domain对象,重点是模型对象关系,涉及到的知识点如下:

  • User和Profile是一对一的关系
  • User和User自身有从属关系
  • User和Tag是多对多的关系
  • User和Post是多对多的关系
  • Post和Tag是多对多的关系
  • 验证
  • 排序
  • lazy
  • ApplicationUser和User的验证属性继承
  • spock测试,关于spock test的资料不多,不过非常值得一试。可以看看这个

构建模型

User.groovy


package com.grailsinaction

class User {

    String loginId
    String password
    Date dateCreated

    static hasOne = [ profile : Profile ]
    static hasMany = [ posts : Post, tags : Tag, following : User ]

    static constraints = {
        loginId size: 3..20, unique: true, blank: false
        password size: 6..8, blank: false
        profile nullable: true
    }

    static mapping = {
        profile lazy: false
    }
}

Profile.groovy


package com.grailsinaction

class Profile {
    User user
    byte[] photo
    String fullName
    String bio
    String homepage
    String email
    String timezone
    String country
    String jabberAddress

    static constraints = {
        fullName blank: false
        bio nullable: true, maxSize: 1000
        homepage url: true, nullable: true
        email email: true, nullable: false
        photo nullable: true
        country nullable: true
        timezone nullable: true
        jabberAddress email: true, nullable: true
    }
}

Post.groovy


package com.grailsinaction

class Post {

    String content
    Date dateCreated

    static belongsTo = [ user : User ]

    static hasMany = [ tags : Tag ]

    static constraints = {
        content blank: false
    }

    static mapping = {
        sort dateCreated: "desc"
    }
}

Tag.groovy


package com.grailsinaction

class Tag {

    String name
    User user

    static hasMany = [ posts : Post ]
    static belongsTo = [ User, Post ]

    static constraints = {
        name blank: false
    }
}

ApplicationUser.groovy


package com.grailsinaction

class ApplicationUser {

    String applicationName
    String password
    String apiKey

    static constraints = {

        importFrom User, include: ['password']
        applicationName blank: false, unique: true
        apiKey blank: false
    }
}

集成测试

UserIntegrationSpec.groovy


package com.grailsinaction

import grails.plugin.spock.IntegrationSpec

class UserIntegrationSpec extends IntegrationSpec {

    def "Saving our first user to the database"() {

        given: "A brand new user"
            def joe = new User(loginId: 'joe', password: 'secret')

        when: "the user is saved"
            joe.save()

        then: "it saved successfully and can be found in the database"
            joe.errors.errorCount == 0
            joe.id != null
            User.get(joe.id).loginId == joe.loginId
    }

    def "Deleting an existing user removes it from the database"() {

        given: "An existing user"
            def user = new User(loginId: 'joe', password: 'secret').save(failOnError: true)

        when: "The user is deleted"
            def foundUser = User.get(user.id)
            foundUser.delete(flush: true)

        then: "The user is removed from the database"
            !User.exists(foundUser.id)
    }

    def "Saving a user with invalid properties causes an error"() {

        given: "A user which fails several field validations"
            def user = new User(loginId: 'chuck_norris', password: 'tiny')

        when: "The user is validated"
            user.validate()

        then:
            user.hasErrors()

            "size.toosmall" == user.errors.getFieldError("password").code
            "tiny" == user.errors.getFieldError("password").rejectedValue
            !user.errors.getFieldError("loginId")

            // 'homepage' is now on the Profile class, so is not validated.
    }

    def "Recovering from a failed save by fixing invalid properties"() {

        given: "A user that has invalid properties"
            def chuck = new User(loginId: 'chuck_norris', password: 'tiny')
            assert chuck.save() == null
            assert chuck.hasErrors()

         when: "We fix the invalid properties"
            chuck.password = "fistfist"

            // 'homepage' is now on Profile so can't be set on the user.

        then: "The user saves and validates fine"
            chuck.validate()
            !chuck.hasErrors()
            chuck.save()
    }

    def "Ensure a user can follow other users"() {

        given: "A set of baseline users"
            def glen = new User(loginId: 'glen', password:'password').save()
            def peter = new User(loginId: 'peter', password:'password').save()
            def sven = new User(loginId: 'sven', password:'password').save()

        when: "Glen follows Peter, and Sven follows Peter"
            glen.addToFollowing(peter)
            glen.addToFollowing(sven)
            sven.addToFollowing(peter)

        then: "Follower counts should match following people"
            2 == glen.following.size()
            1 == sven.following.size()
    }
}

PostIntegrationSpec.groovy


package com.grailsinaction

import grails.plugin.spock.IntegrationSpec

class PostIntegrationSpec extends IntegrationSpec {

   def "Adding posts to user links post to user"() {

        given: "A brand new user"
            def user = new User(loginId: 'joe',password: 'secret').save(failOnError: true)

        when: "Several posts are added to the user"
            user.addToPosts(new Post(content: "First post... W00t!"))
            user.addToPosts(new Post(content: "Second post..."))
            user.addToPosts(new Post(content: "Third post..."))

        then: "The user has a list of posts attached"
            3 == User.get(user.id).posts.size()
   }

   def "Ensure posts linked to a user can be retrieved"() {

        given: "A user with several posts"
            def user = new User(loginId: 'joe', password: 'secret').save(failOnError: true)
            user.addToPosts(new Post(content: "First"))
            user.addToPosts(new Post(content: "Second"))
            user.addToPosts(new Post(content: "Third"))

        when: "The user is retrieved by their id"
            def foundUser = User.get(user.id)
            List<String> sortedPostContent = foundUser.posts.collect { it.content }.sort()

        then: "The posts appear on the retrieved user"
            sortedPostContent == ['First', 'Second', 'Third']
    }

    def "Exercise tagging several posts with various tags"() {

        given: "A user with a set of tags"
            def user = new User(loginId: 'joe', password: 'secret').save(failOnError: true)
            def tagGroovy = new Tag(name: 'groovy')
            def tagGrails = new Tag(name: 'grails')
            user.addToTags(tagGroovy)
            user.addToTags(tagGrails)

        when: "The user tags two fresh posts"
            def groovyPost = new Post(content: "A groovy post")
            user.addToPosts(groovyPost)
            groovyPost.addToTags(tagGroovy)

            def bothPost = new Post(content: "A groovy and grails post")
            user.addToPosts(bothPost)
            bothPost.addToTags(tagGroovy)
            bothPost.addToTags(tagGrails)

        then:
            user.tags*.name.sort() == ['grails', 'groovy']
            1 == groovyPost.tags.size()
            2 == bothPost.tags.size()
    }
}

ApplicationUserIntegrationSpec.groovy


package com.grailsinaction

import grails.plugin.spock.IntegrationSpec

class ApplicationUserIntegrationSpec extends IntegrationSpec {

    def "Recovering from a failed save by fixing invalid properties"() {

        given: "A user that has invalid properties"
            def chuck = new ApplicationUser(applicationName: 'chuck_bot', password: 'tiny', apiKey: 'cafebeef00')
            assert chuck.save() == null
            assert chuck.hasErrors()

        when: "We fix the invalid properties"
            chuck.password = "fistfist"
            chuck.save(failOnError: true)

        then: "The user saves and validates fine"
            chuck.validate()
            !chuck.hasErrors()
            chuck.save()
            println chuck.findAll()
    }
}

代码下载

地址

你可能感兴趣的:(Grails In Action-03.Modeling the domain)