Last updated by leet3lite 1 year ago
The Definitive Guide to Grails is a great book but like each book it contains a few errata. It is possible to submit errata on the book's page but these are not publicly available and therefore it is not possible to know if the errata you've found are new or have already been submitted many times. This page should offer a better opportunity to submit errata, addendum and small simplifications.
should be
Support for 'optionals' property will be completely removed in 0.6, i.e. this won't work in 1.0. Use:
{{d}} is useless as {{w}} is already {{[a-zA-Z_0-9]}}
should be
The following script can be helpful to execute code above to test for the last name error in the grails console:
Outstanding issue: unable to get arguments to resolve using the above script. If you examine the contents of the FieldErrror object the argument that is being passed from the constraint is not added to the FieldError arguments.
In the listing 4-31 and 4-32 the "?. operator is used". This operator is presented on page 102 for those who are not familiar with it.
should be
shouldn't it actually be
as it is in the rest of the book
should be
Page 15
- The book says: Grails created a test class called HelloTests.groovy for the HelloController, but it created a class HelloControllerTest.groovy
Page 21
- Assertions are not introduced in Java 5, but are also part of Java 1.4
page 28
- variable should be {{person}} and not {{fred}}
- {{17.<66}} should be {{17..<66}}
page 29
for(i in 0..<text[0..4]) {
println text[i]
}should be
for(i in 0..<4) {
println text[i]
}page 49
Since version 1.2.12 log4j has also a TRACE levelpage 64
The version column is missing from figure 4-2.page 65
static optionals = ['notes']static constraints = { notes (nullable:true) //.. }
pages 66-67
Given that add* is deprecated, the bottom of page 66 should read: "Not only that, GORM also automatically provides an implementation of an addToTags method that makes it easy to work with the association." The example at the top of page 67 should read in part://now add some tags b.addToTags( new Tag(name: 'grails' ) ) b.addToTags( new Tag(name: 'web framework') )
page 82
password(matches: /[wd]+/, length:6..12)
{{d}} is useless as {{w}} is already {{[a-zA-Z_0-9]}}
page 83
In table 4-2 maxLength login(maxLength:5) Sets the maximum length of a string or array property NOTE: maxLength has been deprecated (0.4) then removed (0.5), use maxSize, e.g. login(maxSize:5)page 86
return ['passwordEqualsLastName', lastName]should be
return ['EqualsLastName', obj.lastName] The following script can be helpful to execute code above to test for the last name error in the grails console:
def user = new User(login:'barry', password:'barry', firstName:'barry1', lastName:'barry', email:'barry@fake.com') if(user.save()){ println "User $user created" } else { users.errors.allErrors.each { println ctx.getBean('messageSource').getMessage(it, Locale.getDefault()) } }
Outstanding issue: unable to get arguments to resolve using the above script. If you examine the contents of the FieldErrror object the argument that is being passed from the constraint is not added to the FieldError arguments.
In the listing 4-31 and 4-32 the "?. operator is used". This operator is presented on page 102 for those who are not familiar with it.
page 103
notes(maxLength:1000) maxLength has been deprecated (0.4) then removed (0.5), use maxSize, e.g. notes(maxSize:1000)page 104-106
The code snippets for testing the login action fail because the cmd object is null immediately after the call to the controller action.cmd.validate() #cmd is valid and filled
controller.login(cmd)
assertTrue cmd.hasErrors() #cmd is null and assertion failspage 115
new Bookmark(title:"Canoo",url:"http://canoo.com").save()
should be
new Bookmark(title:"Canoo",url:"http://www.canoo.com").save()
shouldn't it actually be
new Bookmark(title:"Canoo",url:new URL("http://www.canoo.com")).save()
page 125,127
mock1.demand.render { Map params ->should be
ctrlMock.demand.render { Map params ->page 130
Main feature of property {{webtest_showhtmlparseroutput}} is to control if html parsing messages should be saved in the WebTest report or not page 144
It appears that as of Grails 1.0 the log4j configuration is now located at grails-app/conf/Config.groovy. Also, how logging is defined is quite a bit different than the book indicates and you are encouraged to reference the Grails 1.0+ User Reference Guide under section 3.1.page 162
Input fields should have an {{id}} otherwise the {{<label for="...">...</label>}} are useless. See GRAILS-540page 165
code: null<div class="errors">code: null
should be
code: null<div class="message">${flash.message}</div> <div class="errors">code: null
This allows the password mis-match error to actually appear.
code: null<g:renderErrors bean="${flash.user}"/>code: null
should be
code: null<g:hasErrors bean="${flash.user}"> <g:renderErrors bean="${flash.user}"/> </g:hasErrors>code: null
code: null<input type="confirm" name="confirm" />code: null
should be
code: null<input type="password" name="confirm" />code: null
page 166
code: nullif( user.save() ) { redirect( controller: 'bookmark', action: 'list' ) }code: null
should becode: nullif( ! user.hasErrors() && user.save() ) { session.user = user redirect( controller: 'bookmark', action: 'list' ) }code: null
Otherwise, without setting the session.user to the newly created user, we simply go back to the login page because of the security intercept. Also, the check for hasErrors() seems to be more in line with how Groovy 1.0 does things.
page 168
code: null<form action="upload" enctype="multipart/form-data">code: null
should be
code: null<form action="upload" method="post" enctype="multipart/form-data">code: null
page 174
code: null<p>${bookmark.title}</p>code: null
is correct in JSP too since version 2.0.
Note that this is not exactly the same than
code: null<p><c:out value="${bookmark.title}"/></p>code: null
as the {{<c:out.../>}} escapes xml special characters what is not done by ${bookmark.title} (neither in GSP nor in JSP 2.0). This is important to avoid Javascript Cross Site Scripting (XSS).
page 177
code: null<g:each in="${bookmarks.tags?}">code: null
should be
code: null<g:each in="${bookmarks.tags}">code: null
because
- the {{?}} is useless as null.each {} is a valid Groovy expression… that does nothing
- {{bookmarks.tags?}} is not a valid Groovy expression, therefore this example only works due to current implementation detail of the {{<g:each ...>}} tag.
page 182
In the Linking Tags paragraph… you are always linking to the write (sic) place in a consistent manner?should be… you are always linking to the right place in a consistent manner?
page 197
code: null<g:form action="search">code: null
should becode: null<g:form controller="bookmark" action="search">code: null
because the search exists on all pages, even the tag controller pages, therefore the controller needs to be specified, else you get an error attempting to use the search form from the controller rendered pages also
code: null<g:submit value="search"/>code: null
should be
code: null<g:submitButton name="search" value="Search"/>code: null
page 198
code: null12 I like("name", params.q)code: null
should be
code: null12 ilike("name", "%${params.q}%")code: null
also
code: null6 if(params.q && !params.q?.indexOf('%')) { {code}
should be
code: null6 if(params.q && !params.q?.contains('%')) { {code}
If the intention was to only perform the search if the user did not place a % in the search string. The way it is written now the user is forced to start all search strings with a %, though there is no indication that this must be done, and although the code appends a '%' character infront of the input string anyways. The bookmark controller in the current version of the example app (based on chapter 11) avoids this check altogether.
page 200
The new GSP should be placed in grails-app/views/bookmark/_bookmark.gsp That's bookmark singular.page 207
Listing 8-52 reads:
code: nullclass BookmarkTagLib { def repeat = { attrs, body -> attrs.times?.toInteger().times { n -> body(n) } } }code: null
But it should read:
code: nullclass BookmarkTagLib { def repeat = { attrs, body -> attrs.times?.toInteger().times { n -> out << body(n) } } }code: null
page 209
To make custom {{editInPlace}} tag working, Scriptaculous need to be added in the main layout {{grails-app/views/layouts/main.gsp}}:
code: null<g:javascript library="scriptaculous"/>code: null
Also, listing 8-55 reads:
code: null9 body() 10 out << "</span>" 11 out << "<script type='text/javascript'>" 12 out << "new Ajax.InPlaceEditor('${id}', '" 13 createLink(attrs)9 out << body() 10 out << "</span>" 11 out << "<script type='text/javascript'>" 12 out << "new Ajax.InPlaceEditor('${id}', '" 13 out << createLink(attrs) url="action:'updateNotes', id:id:bookmark.id (+)"\\ \\ \\ But on Grails 0.5, that won't output the results of body() or createLink(attrs). It should read:url="action:'updateNotes', id:bookmark.id (+)"\\ That should be: \\def updateNotes = { update.call() render( Bookmark.get(params.id)?.notes ) }\\ Listing 8-57 reads: \\def updateNotes = { def bookmark = Bookmark.get( params.id ) if(bookmark) { bookmark.properties = params if(bookmark.save()) render( Bookmark.get(params.id)?.notes ) else render( "Error saving bookmark" ) } } class BookmarkController { … void testEditInPlace() throws Exception { … } }\\ \\ \\ This depends on the *update* closure, which does more than just updating the record - it also redirects output to the show action. As a result, you'll end up with Show Bookmark page nested where the notes should be. An alternative is the following:code: null
That should be:
code: nullclass BookmarkTests extends GroovyTestCase { … void testEditInPlace() throws Exception { … } }class Bookmark { static belongsTo = User static hasMany = tags:TagReference (+) // a bookmark has 1 to many tag references... User user URL url String title String notes Date dateCreated static constraints = { url(url:true) title(blank:false) notes( nullable:true, maxLength: 1000 ) } String toString() { return "$title - $url" } }// \\ page 221
To continue on with the bookmark example, you will need to make other domain changes. Indeed, you will need to revisit many GSP pages, etc. In addition to adding the new domain class of TagReference, the author has also removed from the working Bookmark class two fields: rating and type. The current Bookmark domain class should look like the following: \\class Tag { String name String toString() { name } }\\ \\ Likewise, although not mentioned, the Tag domain element needs to change as well, since its clearly no longer belongs to the Bookmark domain.<g:remoteField action="suggestTag" update="suggestions${bookmark?.id}" name="url" value="${bookmark?.url}" />\\ \\ Notice the removal of the belongsTo declarative. \\ \\ \\ page 223
Due to a probably bug in Grails -- the code to use the remoteField, and specifically to generate the update= clause needs to be changed. \\code: null
Should be:code: null<g:remoteField action="suggestTag" update="suggestions${bookamrk?.id ? bookmark.id : '' }" name="url" value="${bookmark?.url}" />code: null
Why you ask? Currently (Grails 1.0.1 in any event) the phrase ${bookmark?.id} returns the string "null". This is one of those times. Later in the code when we actually declare the <div id="suggestions${bookmark?.id}" it does NOT return "null", rather it returns the empty string. Thus, for new bookmarks the two strings don't match ( "suggestionsnull" versus "suggestions") and you will never see the cool "Suggestions" area populate.
page 226
As of Grails 1.0, the code to add the tag and create a TagRefernece is incorrect.… b.addTagReference( ...
should be:… b.addToTags( ...
page 228
I believe the following code for suggestTag is much better than the original for various reasons. First, it works. The original code had a boundary condition that caused a 404 error to appear when the URL was empty. At least, the way I interpreted the code from the book, which had a missing } somewhere (from the trim() I think).Secondly, the code should be a little bit more efficient by not trying to find suggestions when there is no url.Also note the use of toURL and not toUrl, which doesn't exist.Finally, an important note -- its key to ALWAYS render something from this routine, else the dreaded 404 due to its trying to default the action to finding a gsp page of the same name as the method being invoked, in this case it was looking for "suggestTag.gsp".
def suggestTag = { def tags def bookmark = params.id ? Bookmark.get(params.id) : new Bookmark() if ( ! bookmark.url ) { if ( params.value?.trim() ) { if ( ! params.value?.startsWith("http://") ) { bookmark.url = "http://${params.value}".toURL() } } } // If we have a url -- try to get the bookmarks if ( bookmark.url ) { tags = getSuggestions( bookmark ) } // Must always render SOMETHING -- else the default action is to find a gsp page render( template: 'suggest', model: [tags: tags, bookmark: bookmark] ) }
page 232
… <div id="editButtons"> <g:submit name="save" value="Save" /> <g:submitToRemote url="[action: 'show', id: bookmark.id]" update="bookmark${bookmark.id}" name="cancel" value="Cancel" /> </div> ...
should be:
… <div id="editButtons"> <g:submitButton name="save" value="Save" /> <g:submitToRemote url="[action: 'show', id: bookmark.id]" update="bookmark${bookmark.id}" name="cancel" value="Cancel" /> </div> ...
Note the use of <g:submitButton> instead of <g:submit> which doesn't appear to exist anymore (if it ever did) as of 1.0.Also note do not use tabs (t) to pretty up your gsp within at least a <g:render>, it will cause it not to find / parse the tag properly.
i.e. <g:render template="blah" … /> using a tab between the <g:render and the "template" will not work. Must be a space (or one assumes multiple spaces).
page 236
The best solution for a real-world situation wouldn't be to perform caching but to avoid involving the server: the url is already available on the client side (in the bookmark link) and therefore the preview should be realised on the client side only.
page 245
The location to get the HTTP client jar files has changed within Apache. It appears the new home is http://hc.apache.org/.page 250
A tip section would be great to explain the trick for string conversion in:
bookmarks << new Bookmark(title:"${p.@description}", url:new URL("${p.@href}"))
page 254
The use of the bookmark template to render the results from del.icio.us isn't optimum since, in its current version, it produces Edit, Delete, and Preview actions, none of which are valid for remote links. Either the bookmark template should be modified to optionally not render those actions, or a new smaller and shorter template be created.
page 266
Listing 10-20 includes:
Should be:Add Tag: <g:textField name="tagName" /> <g:submitButton value="Add" />
Add Tag: <g:textField name="tagName" /> <g:submitButton value="Add" name="addButton" />
page 267
The create-job script and associated cool quartz stuff was moved to a plug-in as of Grails 1.0 and needs to be installed in the project via the
grails install-plugin quartz
command.
page 276
assert sw.toString().indexOf('<a href="http://grails.org/Download">Grails Download Page</a>')
should be
assert sw.toString().contains('<a href="http://grails.org/Download">Grails Download Page</a>')
because {{String.indexOf(...)}} returns {{-1}} when nothing is found and {{-1}} is not {{false}} according to the Groovy Truth.
page 278
{{contains(...)}} instead of {{indexOf(...)}} like on page 276page 288
boolean equals(obj) { if (this == obj) return true
should be
boolean equals(obj) { if (this.is(obj)) return true
otherwise a {{StackOverflowError}} will occur as {{==}} is the Groovy equivalent of {{equals()}} in Java.



