I have just finished version 1.1.0 of Grails Quick Reference. For your download convenience, here's the zip file containing the latest source of the quick reference: http://bitbucket.org/sjtai/grailsqr/get/tip.zip. The PDF can be downloaded from here.
Grails Quick Reference by Tai Siew Joon is licensed under a Creative Commons Attribution 2.5 Malaysia License.
Monday, April 20, 2009
Thursday, April 16, 2009
The Y292M problem
In about 292 million years from now, we will face the Year 292 Million problem in all our Java/Groovy programs. Luckily it falls on a Sunday, so I think the impact on business will be minimum.
I use
I use
new Date(Long.MAX_VALUE)
to calculate that date.
Wednesday, April 15, 2009
Groovy's groupBy and inject methods
groupBy
Aman Aggarwal blogged about a powerful method from the
java.util.Collection
class. The groupBy
method converts a collection into a Map
with the keys being returned by the closure passed to the method.Example:
def map = invoices.groupBy { it.invoiceDate.format("MM/yyyy") }
map.each { k, v ->
println "There are ${v.size()} invoices in ${k}"
}
Incidentally, the example above uses another cool function from
java.util.Date
: format
. The method takes a date pattern (from the specification of java.text.SimpleDateFormat
) and returns a String
.inject
While writing about
groupBy
, I thought I might as well talk about the inject()
method too. It is a method that is best explained with an example. If I want to add all the numbers from 1 until 10, i.e. 1 + 2 + 3 + 4 + ... + 10, I can do it in one line:
def sum = (1..10).inject(0) { a, b -> a += b }
0 is passed as the initial value. In each iteration, the value from the last iteration is passed as
a
while the current value is passed as b
. If we add a println statement, e.g.
def sum = (1..10).inject(0) { a, b ->
println "${a} + ${b} = ${a + b}"
a += b
}
The output will be:
0 + 1 = 1
1 + 2 = 3
3 + 3 = 6
6 + 4 = 10
10 + 5 = 15
15 + 6 = 21
21 + 7 = 28
28 + 8 = 36
36 + 9 = 45
45 + 10 = 55
Another typical example is to calculate the product of a range of numbers. For example, to calculate the product of 1 to 10 (the factorial of 10):
def product = (1..10).inject(1) { a, b -> a *= b }
The result is the expected 3628800 (= 10!).
Friday, April 10, 2009
New find and findAll methods for String in Groovy 1.6.1
In Ted Naleid's blog, he describes the patches to the String class that he contributed to the Groovy community recently. The patches have been released as Groovy 1.6.1 on April 7, 2009.
He listed the following new methods:
In other words, the find/findAll methods now accepts a regular expression and the matching groups are passed to the closure.
Thanks for the patches, Ted.
He listed the following new methods:
- eachMatch(Pattern pattern) { fullMatch, group1, ... -> ... }
- find(String regex)
- find(String regex) { fullMatch, group1, ... -> ... }
- find(Pattern pattern)
- find(Pattern pattern) { fullMatch, group1, ... -> ... }
- findAll(String regex)
- findAll(String regex) { fullMatch, group1, ... -> ... }
- findAll(Pattern pattern)
- findAll(Pattern pattern) { fullMatch, group1, ... -> ... }
In other words, the find/findAll methods now accepts a regular expression and the matching groups are passed to the closure.
Thanks for the patches, Ted.
Thursday, April 09, 2009
Worst Usability Example
To create a Windows service from the command line, one would normally use the sc command. I wasted almost half an hour today while settings Subversion's svnserve service. So I thought I had better document my ordeal.
To start with, sc has the worst command line syntax that I have seen in my entire life as a software developer. The syntax to create a service for Subversion, type:
Notice there is whitespace after the "=" character? No, it's not "start=auto". If you type that, sc will print out the command line syntax and the service is not created. Add in the space, you will have svnserve running as a Windows service.
Can someone explain why the syntax is so weird?
To start with, sc has the worst command line syntax that I have seen in my entire life as a software developer. The syntax to create a service for Subversion, type:
sc create svn binPath= "c:\subversion\bin\svnserve.exe -r c:\repo" start= auto DisplayName= "Subversion Server" depend= Tcpip
Notice there is whitespace after the "=" character? No, it's not "start=auto". If you type that, sc will print out the command line syntax and the service is not created. Add in the space, you will have svnserve running as a Windows service.
Can someone explain why the syntax is so weird?
Sunday, April 05, 2009
A simple example of the power of polymorphism
I often see codes written by the less experienced programmers that contain a lot of if/else blocks where they do something differently with different types of objects. For example,
In the above codes, the current method is making all the coordination work as it determines what to do with different types of objects. By introducing a superclass or an interface, we can refactor the code to something more extensible, and you don't have to change the loop each time a new class is introduced.
First, declare an interface (or a superclass that all the different types of objects will inherit from):
Change types A, B, C, D, ... to implement this interface:
Change
You'll never have to change the loop again when you introduce new types. The Coordinator will invoke the
public class Coordinator {
public void doSomethingWithList(List list) {
for (Object o : list) {
if (o instanceof A) {
A a = (A) o;
doSomethingWithA(a);
}
else if (o instanceof B) {
B b = (B) o;
doSomethingWithB(b);
}
// and so forth for class C, D, E, F, ...
}
}
}
In the above codes, the current method is making all the coordination work as it determines what to do with different types of objects. By introducing a superclass or an interface, we can refactor the code to something more extensible, and you don't have to change the loop each time a new class is introduced.
First, declare an interface (or a superclass that all the different types of objects will inherit from):
public interface Foo {
public void workWith(Coordinator coordinator);
}
Change types A, B, C, D, ... to implement this interface:
public class A implements Foo {
public void workWith(Coordinator coordinator) {
// do something specific to the A class
}
}
Change
Coordinator.doSomethingWithList()
to:
public void doSomethingWithList(List<Foo> list) {
for (Foo o : list) {
o.workWith(this);
}
}
You'll never have to change the loop again when you introduce new types. The Coordinator will invoke the
workWith()
method polymorphically since all implementations of Foo
must define that method.
Saturday, April 04, 2009
Testing Controllers That Are Protected By JSecurity Plugin
It can be a challenge to write a test case to test an action that depends on the current user. For example, the list action only returns the records that is accessible by the current user.
If you use the JSecurity Plugin, chances are you will use
This is how I did it. In FooController, I define
and the test:
In line 1, just before the FooController instance is created, the
If you use the JSecurity Plugin, chances are you will use
SecurityUtils.getSubject()
to refer to the current user. If the user logs in through the login form provided by the plugin, the user name is stored as SecurityUtils.getSubject().principal
. What we want to do is to find a way to inject the "current" user name during the test.This is how I did it. In FooController, I define
getSubject
and list
like this:
class FooController {
def getSubject = {
return SecurityUtils.getSubject()
}
def list = {
def subject = getSubject()
// Restrict results in listing
...
[fooInstanceList: results, fooInstanceListTotal: total]
}
}
and the test:
void testList() {
FooController.metaClass.getSubject = { return [principal: 'user1'] }
def ctrl = new FooController()
ctrl.list()
...
}
In line 1, just before the FooController instance is created, the
getSubject
closure is modified to return a map that contains principal
as the key. When the list action is invoked, the getSubject()
method will return the map. That way, the listing will be restricted to the records which are accessible by the user user1
.
Wednesday, April 01, 2009
How to rollback a transaction in a service
The Grails Framework Reference Documentation only mentions that a method in a service class can be set as transactional. However, it fails to mention how one should rollback a transaction.
It is actually easier than I thought. If the method throws an exception, the transaction will rollback. Although you can perform multi-table updates in a controller, it is good practice to do it in a service method.
It is actually easier than I thought. If the method throws an exception, the transaction will rollback. Although you can perform multi-table updates in a controller, it is good practice to do it in a service method.
Difference between findAll and findAllBy
You may have already known the obvious difference between the domain class findAll() and findAllByXXX() method. findAll() expects a query as the first parameter, followed by the parameter list and the pagination parameters whereas findAllByXXX() expects variable list of objects, ending with an optional map for pagination purpose.
There is a small difference, and I got caught once by it.
As findAll allows you to write the query anyway you want, which may include the "order by ... desc" fragment. Therefore, findAll() ignores the "sort" and "order" entry in the pagination map. This won't return a list of countries in descending order:
To make it work, I did the following:
The params is still passed in to the function because the max and offset parameters are used.
If the query is simple enough like the query above, it is better to write:
There is a small difference, and I got caught once by it.
As findAll allows you to write the query anyway you want, which may include the "order by ... desc" fragment. Therefore, findAll() ignores the "sort" and "order" entry in the pagination map. This won't return a list of countries in descending order:
def list = Country.findAll("from Country c where c.continent = ?",
[ continent ],
[sort: 'name', order: 'desc']);
To make it work, I did the following:
def sort = params.sort ?: 'name'
def order = params.order ?: 'asc'
def q = "from Country c where c.continent = ? order by ${sort} ${order}"
def list = Country.findAll(q, [ continent ], params)
The params is still passed in to the function because the max and offset parameters are used.
If the query is simple enough like the query above, it is better to write:
def list = Country.findAllByContinent(continent, params)
Subscribe to:
Posts (Atom)