第六章主要讲解使用controller实现页面之间的跳转。在系统中实现了Post信息提交及显示功能。主要的知识点有:
默认访问index,重定向给timeline/params,如果没有params,默认给一个.
......
def index() {
if (!params.id)
params.id = "jeff"
redirect action: 'timeline', params: params
}
def timeline() {
def user = User.findByLoginId(params.id)
if (!user) {
response.sendError(404)
} else {
[ user : user ]
}
}
......
增加一个post,因为post需要指定user,所以需要user登录,前面的index指定了一个user params,可以直接使用.后续增加登录功能
......
def addPost() {
def user = User.findByLoginId(params.id)
if (user) {
def post = new Post(params)
user.addToPosts(post)
if (user.save()) {
flash.message = "Successfully created Post"
} else {
flash.message = "Invalid or empty post"
}
} else {
flash.message = "Invalid User Id"
}
redirect(action: 'timeline', id: params.id)
}
......
访问index,重定向给timeline,timeline.gsp负责提交post并显示post信息,所以,timeline分这两个部分.
<html>
<head>
<title>Timeline for ${ user.profile ? user.profile.fullName : user.loginId }</title>
<meta name="layout" content="main"/>
</head>
<body>
<div id="newPost">
<h3>
What is ${ user.profile ? user.profile.fullName : user.loginId } hacking on right now?
</h3>
<g:if test="${flash.message}">
<div class="flash">${flash.message}</div>
</g:if>
<@!-- 提交(addPost) -->
<p>
<g:form action="addPost" id="${params.id}">
<g:textArea id='postContent' name="content" rows="3" cols="50"/><br/>
<g:submitButton name="post" value="Post"/>
</g:form>
</p>
</div>
<@!-- 显示(timeline/params) -->
<div class="allPosts">
<g:each in="${user.posts}" var="post">
<div class="postEntry">
<div class="postText">${post.content}</div>
<div class="postDate">${post.dateCreated}</div>
</div>
</g:each>
</div>
</body>
</html>
这本书在测试上下了一些功夫,讲解的也很到位.
单元测试:test/unit/com.grailsinaction.PostControllerSpec.groovy
package com.grailsinaction
import grails.test.mixin.Mock
import grails.test.mixin.TestFor
import spock.lang.Specification
@TestFor(PostController)
@Mock([User,Post])
class PostControllerSpec extends Specification {
def "Get a users timeline given their id"() {
given: "A user with posts in the db"
User chuck = new User(loginId: "chuck_norris", password: "password").save(failOnError: true)
chuck.addToPosts(new Post(content: "A first post"))
chuck.addToPosts(new Post(content: "A second post"))
and: "A loginId parameter"
params.id = chuck.loginId
when: "the timeline is invoked"
def model = controller.timeline()
then: "the user is in the returned model"
model.user.loginId == "chuck_norris"
model.user.posts.size() == 2
}
def "Check that non-existent users are handled with an error"() {
given: "the id of a non-existent user"
params.id = "this-user-id-does-not-exist"
when: "the timeline is invoked"
controller.timeline()
then: "a 404 is sent to the browser"
response.status == 404
}
def "Adding a valid new post to the timeline"() {
given: "A user with posts in the db"
User chuck = new User(loginId: "chuck_norris", password: "password").save(failOnError: true)
and: "A loginId parameter"
params.id = chuck.loginId
and: "Some content for the post"
params.content = "Chuck Norris can unit test entire applications with a single assert."
when: "addPost is invoked"
def model = controller.addPost()
then: "our flash message and redirect confirms the success"
flash.message == "Successfully created Post"
response.redirectedUrl == "/post/timeline/${chuck.loginId}"
Post.countByUser(chuck) == 1
}
def "Adding a invalid new post to the timeline trips an error"() {
given: "A user with posts in the db"
User chuck = new User(loginId: "chuck_norris", password: "password").save(failOnError: true)
and: "A loginId parameter"
params.id = chuck.loginId
and: "Some content for the post"
params.content = null
when: "addPost is invoked"
def model = controller.addPost()
then: "our flash message and redirect confirms the success"
flash.message == "Invalid or empty post"
response.redirectedUrl == "/post/timeline/${chuck.loginId}"
Post.countByUser(chuck) == 0
}
@spock.lang.Unroll
def "Testing id of #suppliedId redirects to #expectedUrl"() {
given:
params.id = suppliedId
when: "Controller is invoked"
controller.index()
then:
response.redirectedUrl == expectedUrl
where:
suppliedId | expectedUrl
'joe_cool' | '/post/timeline/joe_cool'
null | '/post/timeline/chuck_norris'
}
}
集成测试:test/integration/com.grailsinaction.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()
}
}