Grails Webapplication
This article explains how to build a Grails application. This article is based on Java 1.6 and Grails 1.2.x.
Table of Contents
Grails is a web framework based on Groovy and Java which can be deployed into existing Java web servers, e.g. Tomcat or Jetty.
Grails allows to create quickly web applications; its scaffolding capabilities let you create a new project within a new minutes. Grails is based on the "convention over configuration" idea which allows the application to auto-wires itself based on naming schemes (instead of using configuration files, e.gl XML files).
The Grails framework allows instance development without requiring any configuration. Just download Grails and you are ready to start. Grails accomplish this by automatically providing the Tomcat webcontainer and the HSQLDB database during development. If you deploy you Grails application later you can use another webcontainer or database.
Grails uses GORM (Grails Object Relational Mapping) for the persistence of the domain model. GORM is based on Hibernate. You can test with the HSQLDB and run in production against another database simply by changing the configuration file (DataSource.groovy).
Grails uses JavaEE as the architectural basis and Spring for structuring the application via dependency injection.
Grails is plug-in based and provides its own build system (Gant). The Grails homepage provides several pre-defined plugins which extend the Grails framework.
During the start of a new development with Grails you mainly use the command line to generated new user interfaces.
Grails is based on the programming language Groovy .
Groovy is (almost) a superset of Java, e.g. most valid Java constructs are also valid Groovy constructs. Groovy has several advanced features in additonal to the standard Java features, e.g. closures, native support for lists and maps, a shorter syntax and much more. Please see Groovy Tutorial in case you want to get to know Groovy.
Download Grails from the Grails Homepage http://grails.codehaus.org/. Unzip the download to a directory of your choice.
Setup your GRAILS_HOME environment variable pointing to your installation directory of Grails. Add also the $GRAILS_HOME/bin to the PATH variable.
Lets develop a guestbook for a website.
Lets create the application with the name "de.vogella.grails.guestbook". Create a new directory which should contain your Grails application. Open a command shell, switch to this new directory and type in the following command.
grails create-app de.vogella.grails.guestbook
This command creates the directory structure and the basis configuration of your new web application.
This created already a full webapplication.
The created application can already run. Switch in the shell into your directory "de.vogella.grails.guestbook". You can then start your application with the following command.
grails run-app
This should start the Grails internal web container and you should receive the message "Server running. Browse to http://localhost:8080/de.vogella.grails.guestbook". Open a browser and open the URL "http://localhost:8080/de.vogella.grails.guestbook"
Congratulations! Your first running Grails application.
Stop the server via "Ctrl + C". We need the command line to create more elements.
Our application does not do anything. Lets create the a domain model. For your feedback system we would like to have:
- Class Feedback: The feedback itself
- Class User: The person who gives feedback
- Class Comment: A remark to the feedback
Grails can created templates (empty classes and prepared unit tests for your domain model. Create the domain model scaffolds via the following commands:
grails create-domain-class de.vogella.grails.guestbook.Feedback
grails create-domain-class de.vogella.grails.guestbook.User
grails create-domain-class de.vogella.grails.guestbook.Comment
This will create a groovy classes for your domain model in the directory ".\grails-app/domain". In the directory ".\test\unit" you find empty files for your unit tests.
Use a text editor to change the classes to the following:
package de.vogella.grails.guestbook
class Feedback {
String title
String feedback
Date dateCreated // Predefined names by Grails will be filled automatically
Date lastUpdated // Predefined names by Grails will be filled automatically
// Relationsship to the other classes
User user
static hasMany=[comments:Comment]
// Contrains are defined as static
static constraints = {
title(blank:false, nullable: false, size:3..80)
feedback(blank:false, nullable:false,size:3..500)
user(nullable:false)
}
}
package de.vogella.grails.guestbook
class User {
String name
String email
String webpage
static constraints = {
name (blank:false, nullable:false, size:3..30, matches:"[a-zA-Z1-9_]+")
email (email:true)
webpage (url:true)
}
String toString(){
return name;
}
}
package de.vogella.grails.guestbook
class Comment {
String comment
Date dateCreated // Predefined names by Grails will be filled automatically
Date lastUpdated // Predefined names by Grails will be filled automatically
User user;
// This will make sure that all comments for a feedback are deleted in case the feedback item is deleted
static belongsTo=[feedback:Feedback]
static constraints = {
comment (blank:false, nullable: false, size:5..500)
user (nullable: true) // Comments are allowed without a user
}
String toString(){
if (comment.size()>20){
return comment.substring(0,19);
} else
return comment;
}
}
Grails allows to define constraints for the domain model via a static method. Some of these constraints, e.g. nullable are reflected in the database others are only used to validate this value via the user interface, e.g. the url constraint.
Grails support dynamic and static scaffolding for the user interface. If you use dynamic scaffolding then a user interface for the domain class is dynamically generated by the Grails runtime. This user interface allows the operations Create, Read, Update and Delete (CRUD).
To use dynamic scaffolding create controllers for your domain class via the following commands:
grails generate-controller de.vogella.grails.guestbook.Feedback
grails generate-controller de.vogella.grails.guestbook.User
grails generate-controller de.vogella.grails.guestbook.Comment
This will create controller classes in the directory "\grails-app\controllers".
Activate the dynamic scaffolding in each controller by replacing the line which starts with def index with "def scaffold = true ". For example for the FeedbackController.
package de.vogella.grails.guestbook
class FeedbackController {
// only change here
def scaffold = true
static allowedMethods = [save: "POST", update: "POST", delete: "POST"]
def index = {
redirect(action: "list", params: params)
}
def list = {
params.max = Math.min(params.max ? params.int('max') : 10, 100)
[feedbackInstanceList: Feedback.list(params), feedbackInstanceTotal: Feedback.count()]
}
def create = {
def feedbackInstance = new Feedback()
feedbackInstance.properties = params
return [feedbackInstance: feedbackInstance]
}
def save = {
def feedbackInstance = new Feedback(params)
if (feedbackInstance.save(flush: true)) {
flash.message = "${message(code: 'default.created.message', args: [message(code: 'feedback.label', default: 'Feedback'), feedbackInstance.id])}"
redirect(action: "show", id: feedbackInstance.id)
}
else {
render(view: "create", model: [feedbackInstance: feedbackInstance])
}
}
def show = {
def feedbackInstance = Feedback.get(params.id)
if (!feedbackInstance) {
flash.message = "${message(code: 'default.not.found.message', args: [message(code: 'feedback.label', default: 'Feedback'), params.id])}"
redirect(action: "list")
}
else {
[feedbackInstance: feedbackInstance]
}
}
def edit = {
def feedbackInstance = Feedback.get(params.id)
if (!feedbackInstance) {
flash.message = "${message(code: 'default.not.found.message', args: [message(code: 'feedback.label', default: 'Feedback'), params.id])}"
redirect(action: "list")
}
else {
return [feedbackInstance: feedbackInstance]
}
}
def update = {
def feedbackInstance = Feedback.get(params.id)
if (feedbackInstance) {
if (params.version) {
def version = params.version.toLong()
if (feedbackInstance.version > version) {
feedbackInstance.errors.rejectValue("version", "default.optimistic.locking.failure", [message(code: 'feedback.label', default: 'Feedback')] as Object[], "Another user has updated this Feedback while you were editing")
render(view: "edit", model: [feedbackInstance: feedbackInstance])
return
}
}
feedbackInstance.properties = params
if (!feedbackInstance.hasErrors() && feedbackInstance.save(flush: true)) {
flash.message = "${message(code: 'default.updated.message', args: [message(code: 'feedback.label', default: 'Feedback'), feedbackInstance.id])}"
redirect(action: "show", id: feedbackInstance.id)
}
else {
render(view: "edit", model: [feedbackInstance: feedbackInstance])
}
}
else {
flash.message = "${message(code: 'default.not.found.message', args: [message(code: 'feedback.label', default: 'Feedback'), params.id])}"
redirect(action: "list")
}
}
def delete = {
def feedbackInstance = Feedback.get(params.id)
if (feedbackInstance) {
try {
feedbackInstance.delete(flush: true)
flash.message = "${message(code: 'default.deleted.message', args: [message(code: 'feedback.label', default: 'Feedback'), params.id])}"
redirect(action: "list")
}
catch (org.springframework.dao.DataIntegrityViolationException e) {
flash.message = "${message(code: 'default.not.deleted.message', args: [message(code: 'feedback.label', default: 'Feedback'), params.id])}"
redirect(action: "show", id: params.id)
}
}
else {
flash.message = "${message(code: 'default.not.found.message', args: [message(code: 'feedback.label', default: 'Feedback'), params.id])}"
redirect(action: "list")
}
}
}
Do this also for UserController and CommentController.Run the application again via:
grails run-app
Browse to http://localhost:8080/de.vogella.grails.guestbook". You should have a full CRUD (create, retrieve, update, delete) application available. To start the app, click on FeedbackController.
Grails allows to simulate example data (this is called bootstrapping). To create example data you can use the class BootStrap.groopy from the directory "./grails-app/conf" with some data. This class is automatically executed whenever the server is started and can be used to create some example data for testing.
Change the code to the following.
import de.vogella.grails.guestbook.*
class BootStrap {
def init = { servletContext ->
User user = new User(name:'lars', email:'muster@muster.com', webpage:'http://www.vogella.com')
User otherUser = new User(name:'jim', email:'jim@muster.com', webpage:'http://www.vogella.com')
if (!user.save()){
log.error "Could not save user!!"
log.error "${user.errors}"
}
if (!otherUser.save()){
log.error "Could not save otherUser!!"
}
Feedback feedback = new Feedback(title:'First feedback', feedback:'This is my first feedback', user:user)
feedback.save()
Comment comment = new Comment(comment:'Hello, my name is Jim', user:otherUser)
comment.feedback = feedback
comment.save();
}
def destroy = {
}
}
The grails server should pickup the change automatically. Wait a while (or re-start the server to be sure) and check if you get an error message.
By default the data maintained in the web application are not stored. If you entries should be saved to the database after server shutdown use the following command to start it.
grails prod run-app
To switch from dynamic scaffolding to static scaffolding you need to have view. Grails can generated them for you.
Type in the following to create a scaffold for the controller and the view.
grails generate-views de.vogella.grails.guestbook.Feedback
grails generate-views de.vogella.grails.guestbook.User
grails generate-views de.vogella.grails.guestbook.Comment
Remove the "def scaffold = true " in your controller to use your generated views.
This creates the GSP (Groovy Server pages) for your actions in the directroy "grails-app\views". GSP are standard HTML code with Groovy mixed in. Have a look at the coding. The controller defines several actions (list, show, delete,edit). For all these actions corresponding views have been created under grails-app/views.
If you not satisfied with the order of the fields you can change the views directly. For example in the following view I did change the order of the fields so that name is displayed before comment.
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<meta name="layout" content="main" />
<title>Create Feedback</title>
</head>
<body>
<div class="nav">
<span class="menuButton"><a class="home" href="${createLinkTo(dir:'')}">Home</a></span>
<span class="menuButton"><g:link class="list" action="list">Feedback List</g:link></span>
</div>
<div class="body">
<h1>Create Feedback</h1>
<g:if test="${flash.message}">
<div class="message">${flash.message}</div>
</g:if>
<g:hasErrors bean="${feedback}">
<div class="errors">
<g:renderErrors bean="${feedback}" as="list" />
</div>
</g:hasErrors>
<g:form action="save" method="post" >
<div class="dialog">
<table>
<tbody>
<tr class="prop">
<td valign="top" class="name">
<label for="name">Name:</label>
</td>
<td valign="top" class="value ${hasErrors(bean:feedback,field:'name','errors')}">
<input type="text" id="name" name="name" value="${fieldValue(bean:feedback,field:'name')}"/>
</td>
</tr>
<tr class="prop">
<td valign="top" class="name">
<label for="feedback">Feedback:</label>
</td>
<td valign="top" class="value ${hasErrors(bean:feedback,field:'feedback','errors')}">
<input type="text" id="feedback" name="feedback" value="${fieldValue(bean:feedback,field:'feedback')}"/>
</td>
</tr>
</tbody>
</table>
</div>
<div class="buttons">
<span class="button"><input class="save" type="submit" value="Create" /></span>
</div>
</g:form>
</div>
</body>
</html>
Grails created the default CSS style sheets under the directory web-app/css/main.css
You can directly change the main.css to make your application look different. For example if the feedback field should be larger then the name field add then following to main.css.
#feedback {
height: 80px;
width: 160px;
}
Type the following command to create a war archive. This archive can be deployed to a web container, for example Tomcat.
grails war
The command "create-domain-class" create also automatically a test for the domain-class. Go to your directory "test\integration" and open the class "FeedbackTests.groovy".
You can run your test via the following command.
grails test-app
The following requires that Eclipse with the Groovy plug-in is installed. Please see Groovy Eclipse Plugin for more information.
After running "grails create-app" you may notice the .project and .classpath file. These Eclipse related files are created automatically and allow you to import the project into Eclipse. Import your project via File -> Import -> General -> "Existing Project into Workspace".
The environment variable "GRAILS_HOME" may not be set in Eclipse. Select your project, right-click on it and select properties. Select the Java Build Path and here the tab "Libraries. Press "Add Variable" and then "Configure Variables". Press New and add GRAILS_HOME.
A tighter integration of Grails is archived by http://www.grails.org/STS+Integration .
Hey,
ReplyDeleteVery nice and detailed blog. Really appreciate that. Keep it up sharing new post!
Groovy on grails development companies use Groovy as a fairly innovative language that is compatible with JVM and can speed up project delivery. groovy on grails development company in India