Thursday, March 12, 2009

Grails Tip of the Day: Always use packages



First of all I must say I'm lazy and I don't like overhead... That's also the reason why I don't use packages for Grails domain classes. Most Grails applications I've build so far consist of no more then 20 domain classes, so I had never the need to separate them in packages. In fact I like to have them in the top folder; no overhead of subfolders.

As Grails 1.1 was released I decided to restart Grails on Sakila. I recreated the domain classes including a "Category" domain class. I generated the default Controller and Views without problems and started the application. When browsing to the list page of the Category domain class I got (GRAILS-4233):


Welcome to Grails 1.1 - http://grails.org/
Licensed under Apache Standard License 2.0
Grails home is set to: D:\Grails\grails-1.1

Base Directory: D:\Grails\projects\grails-on-sakila
Running script D:\Grails\grails-1.1\scripts\RunApp.groovy
Environment set to development
[groovyc] Compiling 2 source files to D:\Users\moverdijk\.grails\1.1\projects\grails-on-sakila\classes
[groovyc] org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed, D:\Grails\projects\grails-on-sakila\grails-app\controllers\CategoryController.groovy: 40: You cannot create an instance from the abstract interface 'groovy.lang.Category'.
[groovyc] @ line 40, column 32.
[groovyc] def categoryInstance = new Category()
[groovyc] ^
[groovyc] D:\Grails\projects\grails-on-sakila\grails-app\controllers\CategoryController.groovy: 46: You cannot create an instance from the abstract interface 'groovy.lang.Category'.
[groovyc] @ line 46, column 32.
[groovyc] def categoryInstance = new Category(params)
[groovyc] ^
[groovyc] D:\Grails\projects\grails-on-sakila\grails-app\controllers\CategoryController.groovy: 40: You cannot create an instance from the abstract interface 'groovy.lang.Category'.
[groovyc] @ line 40, column 32.
[groovyc] def categoryInstance = new Category()
[groovyc] ^
[groovyc] D:\Grails\projects\grails-on-sakila\grails-app\controllers\CategoryController.groovy: 46: You cannot create an instance from the abstract interface 'groovy.lang.Category'.
[groovyc] @ line 46, column 32.
[groovyc] def categoryInstance = new Category(params)
[groovyc] ^
[groovyc] D:\Grails\projects\grails-on-sakila\grails-app\controllers\CategoryController.groovy: 40: You cannot create an instance from the abstract interface 'groovy.lang.Category'.
[groovyc] @ line 40, column 32.
[groovyc] def categoryInstance = new Category()
[groovyc] ^
[groovyc] D:\Grails\projects\grails-on-sakila\grails-app\controllers\CategoryController.groovy: 46: You cannot create an instance from the abstract interface 'groovy.lang.Category'.
[groovyc] @ line 46, column 32.
[groovyc] def categoryInstance = new Category(params)
[groovyc] ^
[groovyc]
[groovyc] 6 errors
Compilation error: Compilation Failed


First thing I thought I made some mistake but this wasn't the case. The problem is that Groovy resolves the Category class to groovy.lang.Category and not to my Grails domain class. This means if you have domain classes with the same name as a class in groovy.lang and don't use packages you will get into troubles.

So from now on I will create/put my Grails artifacts in a package, which is a good practice anyway. When running the Grails command line scripts to create artifacts you can specify the package so Grails automatically creates the package structure: e.g.:
grails create-domain-class org.company.Book


PS: I you want to read more about Groovy Categories have a look at the Groovy User Guide.

12 comments:

adwin said...

Thanks , I have bad habit like you too ... never using package ...

so how to solve ? did you need to import org.yourpackage.domainclass ?
or, grails will detect by himself ?

Marcel Overdijk said...

Yes, when you put your domain classes in a package you have to import them in controllers.

When using generate-all the import statements are automatically added for you.

Anonymous said...

I run the Grails on Sakila , But I don't know login username and password , Would you help me , thanks

Marcel Overdijk said...

You can login with username Mike and password 12345. However the sample application is not finished yet. Not even close....

小k said...

thank your reply , I still have question , would you tell me , how to make _menu.gsp , I want to make the some effect menu , It's cool

Lance said...

Marcel said
>Yes, when you put your domain classes in a package you have to import them in controllers.

But if you put your controllers in the same package, you won't have to explicitly import your domain classes.

Jose said...

What about the gsp's? For example, if you use .list() on a domain object it gives a missing method exception. If you prefix the domain object with it's package name it works fine.

I tried importing the package name at the top of the main.gsp file but that doesn't work.

Alvin Lim said...

Hi Marcel,

Need some help here. I'm a newbie in Grails + Groovy and I'm having problem with this domain package thingy.

This works fine.

--> grails create-domain-class org.company.Category

And it'll create the necessary folder structures but when I tried to do generate-all, it failed.

--> grails generate-all org.company.Category

and here's the error message

--> Domain class not found in grails-app/domain, trying hibernate mapped classes...

--> No domain class found for name org.company.Category. Please try again and enter a valid domain class name

It seems like Grails cannot recognize anything other than those in the default package, to be domain classes. Is there a workaround to this or maybe I've made a mistake here?

Marcel Overdijk said...

@Alvin,

This worked for me:

D:\Grails\projects\test-packages>grails create-domain-class org.company.Category

Welcome to Grails 1.1 - http://grails.org/
Licensed under Apache Standard License 2.0
Grails home is set to: D:\Grails\grails-1.1

Base Directory: D:\Grails\projects\test-packages
Running script D:\Grails\grails-1.1\scripts\CreateDomainClass.groovy
Environment set to development
[mkdir] Created dir: D:\Grails\projects\test-packages\grails-app\domain\org\
company
Created DomainClass for Category
[mkdir] Created dir: D:\Grails\projects\test-packages\test\unit\org\company
Created Tests for Category
D:\Grails\projects\test-packages>grails generate-all org.company.Category
Welcome to Grails 1.1 - http://grails.org/
Licensed under Apache Standard License 2.0
Grails home is set to: D:\Grails\grails-1.1

Base Directory: D:\Grails\projects\test-packages
Running script D:\Grails\grails-1.1\scripts\GenerateAll.groovy
Environment set to development
[mkdir] Created dir: D:\Users\moverdijk\.grails\1.1\projects\test-packages\c
lasses
[groovyc] Compiling 7 source files to D:\Users\moverdijk\.grails\1.1\projects\
test-packages\classes
[mkdir] Created dir: D:\Users\moverdijk\.grails\1.1\projects\test-packages\r
esources\grails-app\i18n
[native2ascii] Converting 11 files from D:\Grails\projects\test-packages\grails-
app\i18n to D:\Users\moverdijk\.grails\1.1\projects\test-packages\resources\grai
ls-app\i18n
[copy] Copying 1 file to D:\Users\moverdijk\.grails\1.1\projects\test-packa
ges\classes
[copy] Copied 2 empty directories to 2 empty directories under D:\Users\mov
erdijk\.grails\1.1\projects\test-packages\resources
[groovyc] Compiling 1 source file to D:\Users\moverdijk\.grails\1.1\projects\t
est-packages\classes
Generating views for domain class org.company.Category ...
Generating controller for domain class org.company.Category ...
Finished generation for domain class org.company.Category
D:\Grails\projects\test-packages>

Which version of Grails are you using?

Alvin Lim said...

Okay, I came across one article which says that I need to move everything away from the default package. If there's one domain in a package, I cannot have anything sitting in the default package. I've tried that by moving everything to 1 package (not default) and it works.

Sorry for the trouble.

Alvin Lim said...

Marcel, I'm using Grails 1.1. It seems like I can't have any other domain classes sitting in the default packages if I were to use this org.company.Category. As stated in my comment(still in moderation I believe), it worked when I've moved all the domain classes in default package to org.company

Horst Krause said...

Additionally I had to clean the controller dir and to do a clean before the moved domain classes were found.