Programing Assignment 1 – Building Your First Grails Apps

The purpose of this assignment is to introduce you to Grails, including:

  • Setting up a development environment
  • Grails 5 Framework and the use of Domains, Controllers and Views
  • H2 Database and h2-console
  • Writing and running unit tests
  • Writing and running integration tests

You are to complete this programming assignment individually. In this assignment, you will build a simple web app for a book store’s administrative back end, meaning administrators will be able to add books and authors to the book store’s database.

Step 1: Introduction to Grails

Grails

What is Grails?

Grails is a Java Enterprise web framework built on top of the Spring Boot framework using the Groovy scripting language.

That is a mouthful and probably not very illuminating. We’ll dissect the above sentence.

  • A “web framework” provides structure and packages for developing web applications. Some web frameworks you might have heard of are WordPress and Rails.
  • “Java Enterprise” (Java EE) is the set of Java API for constructing enterprise applications which includes the technology for Servlets (the Java API for implementing a web server), Java Server Pages (JSP, the Java API for building web pages), Java Database Connector (JDBC, the Java API or interface for databases), and many more APIs.
  • “Spring Boot” is a Java EE web framework built using the Spring framework. The Spring Framework was the first popular framework implementing dependency injection for constructing large applications. Lectures in this course will describe Java Servlet and how Spring Boot uses them to construct a web server.
  • “Groovy” is a scripting language based on Java. A programmer can almost always write Java code in a Groovy script. Some of the differences between Groovy and Java are that Groovy is loosely typed and eliminates many of Java’s syntax.  Groovy scripts simplifies code. Groovy was originally developed to define Domain Specific Languages (DSL). An example of a DSL is Gradle which is a tool that you’ll get to use to describe how the web application should be built. There is also a Groovy lecture in this course.

That was a lot. Maybe it helps to explain the sentence describing Grails. Try reading the sentence again and see if it makes any more sense. The above explanations should make it clear that Grails is built on top of quite a few technologies. But what does Grails provide that Java EE and Spring Boot do not already supply?

Some Grails benefit:

  • GORM, which means Grails Object Relational Mapping (ORM). An ORM is an interface between the database and the application code. It simplifies schema descriptions and database queries. GORM is built on top of the Hibernate package. The Hibernate package provides an ORM, databases, and consoles for querying databases. In particular Hibernate provides a database instantiated in memory.
  • Model View Controller (MVC) structure for building the web application. MVC is a design pattern separating the concerns of information or data (the Model), preparing the information and responding to user interaction (the Controller) and presenting the information to the user (the Controller). In this programming assignment, you will learn Grails’ MVC pattern. Note there are many variations on the MVC design pattern implemented by different web frameworks.
  • Grails provides scaffolding or automatic code generation.  Grails commands will automatically write code for many of the components that you can build on. This saves a lot of coding labor.
  • Gradle is a tool for building your applications. Gradle uses groovy to specific the build. Using groovy the build description can mix procedural and declarative descriptions of the build. More important Gradle uses plugin, so Grails Gradle plugin can specify the build for your code and minimize your coding for the build. You will interact with Gradle in later programming assignments.
  • Grails plugins, which provides additional functionality to the framework. The most notable Grails plugins are GORM and Spring Security. Spring Security provides authentication and authorization for web application. In later programming assignments, you will get to use Spring Security.
  • “Convention over Configuration” is a technique for simplifying code without limiting flexibility of the framework. In essence, appropriate defaults are applied to the code. For example, where you place code and how you name the class file will automatically configure much of the code for you. But you can always override the defaults by coding configurations. In the programming assignments, you will use both convention and configuration. You should always use convention when you can because it will make the code easier to read and maintain.  
  • Much more.

To summarize, I’ll state again what is Grails.

Grails is a web framework. It use the Java EE and the MVC design pattern. Developers can write their code using the groovy scripting language. Grails provides many tools to simply the construction of your web application.

Hopefully, now you have a good idea of what is Grails and why this course uses it.

If you need more resources consult the Grails User Documentation

Grails Documentation

http://grails.org/doc/latest/

Please note that Grails’ documentation URLs always point to the latest version of Grails, as of November 18, 2023, Grails 6.1. This tutorial will use Grails 5.3.3. Because we are using a earlier version of Grails, we may want to read the documentation specific for the Grails version used in the project. The core features of Grails 3, 4, 5 and 6 generally agree, but you should be cautious. For example if you are using Grails 3.3.11, you’ll want to refer to

http://docs.grails.org/3.3.11/guide/single.html

Note the version number in the URL. The title of the page (appearing in the browser window tab) gives the Grails version number.

You will want to study chapter 7 about GORM and the dedicated GORM manuel:

http://grails.org/doc/latest/guide/GORM.html

http://gorm.grails.org/6.1.x/hibernate/manual/

For more details about Grails views and controllers, you will want to study chapter 8 about the web layer

http://docs.grails.org/latest/guide/single.html#theWebLayer

Also on the right hand side of all the documentation web pages is the Quick Reference, but you need to have a wide browser window to view the links in the Quick Reference.

The documentation is a good reference but is very technical, so you will probably find it hard to understand when you first begin programming. There are good books that give easy explanation of grails, only a few books are about Grails 3, and no books for Grails 4 or 5.

https://grails.org/learn.html#books

There are also a large collection of Grails Guides:

http://guides.grails.org/#/index

Be careful of the Grails version used in the guides. Although for the most part, guides are written for Grails 4 and 5, some guides have not been update from Grails 3.

Step 2: Setup Development Environment

For both your programming assignments and your project, we need to set up the development environment because we are using advanced technologies (Grails and Gradle) to implement the web applications. The technologies have delicate dependencies to each other and Java. For example, specific Grails versions will only build properly with specific versions of Java and Gradle. In addition, when you are working as a team on your project, each member of your team needs to be using the same version of Grails, Gradle and Java. Also when you deploy your web app, the Java version on the server and development machine needs to agree.

Modern software development typically uses containerization to ensure that each team member’s development environment agrees with the production environment. But currently, MTU Information Technology (IT) does not support containerization. Also setting up a container environment that allows developers access to file while code is not easy. So we will manually assure that all development environments and the production environment agree. Because we will be developing using Java technologies this is not too difficult. 

0. Setup Bash Terminal (Windows only)

If your development machine is Linux or MacOS, you can skip this step.

Containerization is based on Linux environments. Also development is easier on a Linux machine. Consequently, if you are developing on a Windows machine, you need to emulate Linux. Modern Windows operating systems have Windows Subsystem for Linux (WSL) for emulating Linux and installing a Linux distribution. 

https://learn.microsoft.com/en-us/windows/wsl/about

But we do not need a virtual machine, we only need a minimal Linux environment, a terminal that will run basic Linux commands. We need what I call a “Bash terminal”. I use a MING64 terminal from “Git for Windows” for my “Bash terminal”.

https://gitforwindows.org/

Just click the download button and run the executable. This should set up a shortcut on the desktop. Also I believe that it will add a menu item to File Explorer’s context menu, “Git Bash Here”, which will open the Bash terminal in the directory. This is very convenient opening the Bash terminal in your project directory.

Unfortunately, “Git for Windows” does not have does not have the zip utility that we need to install SDKMAN, so we need to add it. Go to the link below and follow the instructions.

https://ranxing.wordpress.com/2016/12/13/add-zip-into-git-bash-on-windows/

Basically it explains to go to https://sourceforge.net/projects/gnuwin32/files/,

  1. download zip files for zip and bzip,
  2. unzip them,
  3. and copy the “zip.exe” and “bzip2.dll” into the “Git/usr/bin/” folder.

My “Git/” folder is in “C:\Program Files”, so the complete path is 

C:\Program Files\Git\usr\bin

That is it. You now have a very lightweight Linux environment.

1. Install SDKMAN

The best tool for managing Java versions and other Java packages is SDKMAN.

https://sdkman.io/

All development should install, Windows, Mac, or Linux machines, should install SDKMAN. If your using a windows machine, you need to install SDKMAN into your Linux environment provided by MINGW64, the bash terminal. If you are using Mac or Linux machines, the terminal provided by operating system is sufficient. Follow the instruction at:

https://sdkman.io/install

SDKMAN is easy to use. The usage manual is at

https://sdkman.io/usage

You should become familiar with “list”, “install” specific versions and “use” commands. The help option is sufficient to remind you of the usage.

sdk help

All developers should use SDKMAN to manage Java and Java package versions. 

NOTE: Some developers have issues installing SDKMAN using the git bash terminal. An alternative is to use Windows Subsytem for Linux (WSL). You will need to install WSL.

https://product.hubspot.com/blog/git-and-github-tutorial-for-beginners

After the Ubuntu WSL is installed you will probably also need to install zip in Ubuntu.

https://www.mysoftkey.com/linux/how-to-do-zip-and-unzip-file-in-ubuntu-linux/

2. Download Grails and Java

Downloading Grails and Java using SDKMAN is easy in your Bash terminal. To see the available Java version and builds, run

sdk list java

The list is long. I installed the build 8.0.392-zulu by running

sdk install java 8.0.392-zulu

To see the available grails version, run

sdk list grails

We will use Grails version 5.3.3, so run

sdk install grails 5.3.3

Now the Bash terminal will run these versions of Grails and Java. Confirm this by running

grails -v

You should also find where the Grails and Java packages reside in your machine. On my machine, the Grails 5.3.3 package is at

C:\Users\pastel\.sdkman\candidates\grails\5.3.3

Find where SDKMAN but the Java version 1.8.0.392 package .

We are almost done setting up our development environment.

3. Choose an Editor

You’ll want to choose an editor or Integrated Development Environment I’m not concerned about what Editor or IDE you use. I have used many different editors and IDEs.

Example editors:

  • Visual Studio Code
  • Atom 
  • Sublime
  • Notepad
  • Emacs
  • Vim

Example IDEs:

  • IntelliJ IDEA
  • Eclipse

I used to favor an IDE such as IntelliJ IDEA because they were very feature rich, but they are slow to load and difficult to learn. Also the many features of the IDE remain up to date, so now I lean to using editors. But at a minimal, the editor should do syntax coloring and basic refactoring tasks. Currently, I’m using Visual Studio Code. 

https://code.visualstudio.com/

It appears to be a good compromise between an IDE and a text editor. You can install extensions on it to provide additional features, although access to the extensions’ features can be tedious.

I also recommend that your development team use the same IDE or editor, so that you share with each tricks using it.

4. Make Grails Project

I like to make a “workspace” directory in order to clearly indicate the location of my project code and to keep it separate from other files that I want to associate with the project, such as programming notes.

In a directory of your choosing, make the directories

C: .../cs4760-Programming-Assignment/workspace/

Open your bash terminal in the “workspace” directory, and run

grails create-app cs4760progassign

https://docs.grails.org/6.1.0/ref/Command%20Line/create-app.html

It takes some time, but eventually it will create a cs4760progassin subdirectory and download all the project files to get started. Move your bash terminal into cs4760progassign/ directory and view the content by running

cd cs4760progassign
ls -l

We will examine these files more, but first we should set up the git repository.

5. Setup Local Git Repository

You should at least setup a local repository for your programming assignment project. Grails has setup the project with an gitignore file. Editors and IDE do comes with version control system functionality, but I prefer to use the Bash terminal to run git commands. Open your Bash terminal at the root of the project file, cs4760prograssign/, and enter

git init
git checkout -b main
git add -A
git commit -m "initial commit"
git checkout -b progassign-1

We’re all done setting up development. Before starting development, we should learn more about Grails and study the project files that Grails provided us.

Step 3: Study Grails and the Project

Immediately after Grails has finished creating the app, the project directories and files are:

  • .gradle/
  • .idea/
  • build/
  • gradle/
  • grails-app/ <- This is the main directory that you will be coding in.
    • assets/ <- asset-pipeline plugin uses this directory. You will add css and JS files here.
      • images/
      • javascript
      • stylesheet/
    • conf/
      • spring/
      • application.yml <- Configuration file for the running app
      • logback.groovy
    • controllers/ <- Controllers talk to domains and views
      • cs4760progassign/
        • UrlMappings.groovy <- configure the url mapping for the app
    • domain/ <- domain models for interfacing with the data store
    • i18n/
      • Resource Bundle ‘messages”
        • messages.property <- English language files.
        • other language files
    • init/
      • cs4760progassign/
        • Application.groovy <- file that initialize the program
        • Bootstrap.groovy <- groovy code for initializing the app
    • services/ <- for groovy and Java code that run outside the controllers
    • taglib/ <- for app specific “g-tags”
    • views/ <- for gsp files, like html files.
      • Layouts/ <- gsp files for structuring views
        • main.gsp
      • error.gsp
      • index.gsp <- default home view
      • notFound.gsp
  • src/
    • integration-test/
    • main/
    • test/ <- Where unit tests are made
  • .gitignore
  • build.gradle <- configuration file for building the app
  • cs4760progassign.iml
  • gradle.properties
  • gradlew <- Command file for running gradle tasks
  • gradlew.bat
  • grails-wrapper.jar
  • grailsw <- Command file for running grails tasks
  • grailsw.bat
  • settings.gradle

I have marked a few important directories and files. For the most part, you will do most of your coding in the grails-app/ directory. You will make domain models in the domain/ directory to interface with the data store (database). You will generate controller files to act as the intermediary between the domain classes and the views.  Also you will make views, gsp (similar to html) files in the views/ directory. You will want to know the directory structure below well:

  • grails-app/
    • assets/
    • conf/
      • application.yml
    • controllers/
      • UrlMappings.groovy
    • domain/
    • i18n/
    • intit/
      • Bootstrap.groovy
    • views/
      • layout/
        • main.gsp
      • index.gsp

There are a few configuration files you will use:

  • application.yml in grails-app/conf/ – for configuring the running app including the data source
  • Bootstrap.groovy in grails-app/init/ – for setting initial values for the running app
  • UrlMapping.groovy in grails-app/controller – for configuring the URL
  • build.gradle in the project root directory – for specifying plugins and building the app

There are also two command files:

  • grailsw
  • gradlew

The grailsw file is a wrapper for Grails commands found in grails-wrapper.jar. It locates the project directory, determines what type of terminal you are running, finds the grails-wrapper.jar file, and then it runs the command. The gradlew is the wrapper for Gradle command in gradle/wrapper/gradle-wrapper.jar. It works very similar to the grailsw wrapper.

Note that the proper Grails and Gradle project files are in the project directory and should also be kept in the Git repository. This is so can you share your project file with your teammate, and team can be assured that everyone is running the same version of Grails and Gradle by running wrapper commands. Meaning that everyone on the team should run

./grailsw <some command>

instead of running

grails <some command>

as you did to originally make the grails app.

But note that the proper Java version is not included in the project files. So each team member will need to assure that they are using the proper Java version by manually setting it using SDKMAN in their bash terminal.

Run the App

The code generated by “grails create-app” will run. To run the basic app, you only need to run in the Bash terminal at the project root

./grailsw run-app

The command can take a while to run, but eventually you should see in the terminal.

|Running application...
Grails application running at http://localhost:8080 in environment: development

Open a browser and navigate to

http://localhost:8080

You should see the default index page for a Grails basic app. Congratulations you made your first basic app.

A lot of useful information will appear on this page, including

  • Application Status <- Specifics Grails version
  • Artifacts <- number of controllers, domains and services.
  • Installed Plugins <- list of plugins and there versions.
  • Available Controllers <- List of controller links.

Currently, there are no controllers. The app is showing the view index.gsp. You can see the code by viewing grails-app/views/index.gsp in your editor. The code will appear in the edit window. For the most part the code is html, but it is accessing some app variables. They are written using ${…} syntax. More about that in other assignments and lectures.

Grails Commands

On the side, we should talk about the Grails commands for two reasons.

  • So you understand what you are doing while developing your applications.
  • Sometimes invoking a Grails command fails (especially when the command is invoked using the IDE), so you will need alternative ways to invoke the command.

Much of Grails functionality is provided by invoking a Grails command. You can see a list of all the Grails command at

https://docs.grails.org/latest/ref/Command%20Line/Usage.html

Be sure to look at the panel on the right side of the page to see the list of commands.

Grails commands can be categorized into one of two types.

  • Commands that build and run the web app
  • Commands that generate artifacts for the web application

Artifacts are components of the web applications, such as controllers, domains, services, html files, JavaScript files etc.  Generally, Grails commands that begin with “create” or “generate” will make artifacts for your project. Most of the other commands are for building or running the application. The “grails run-app” which you just ran is an example of a Grails build command.

https://docs.grails.org/latest/ref/Command%20Line/run-app.html

There are multiple ways to run a Grails command depending on the command types. I will discuss using the Bash terminal to run Grails and Gradle commands. Most IDE and many editors let you run Grails command using features of the IDE or terminals in the editor. I will not describe how you can use an IDE or editor to run the commands. I no longer recommend using an IDE to run Grails and Gradle commands because IDE extensions do not always stay up to date. Also it is hard for me to describe in text how to use the IDE.

Build and Run Grails Commands

Commands that build and run the applications can be invoke

  • in the IDE
  • by invoking the corresponding Gradle task
  • by invoking the Grails command in a Bash shell

We ran the app using the Grails command

./grailsw run-app

Alternatively, we could have ran the app by invoking the corresponding Gradle task, bootRun. There are two way to invoke a Gradle task

./gradlew bootRun

You can find the corresponding Gradle task for the grails build and run command at

https://docs.grails.org/5.3.3/guide/single.html#gradleTasks

Generate Artifacts Grails Command

There are only two ways to invoke Grails command that make application artifacts.

  • in the IDE
  • by invoking the grails command in a Bash terminal

To make a “domain” class and associated unit test and service files for your project, you would run in a Bash terminal.

./grailsw create-domain-class Author

By the way, there is yet another way to create Grails artifacts. You could just code them from scratch in the proper directory with the proper name. But in the case of creating domain classes, you would also have to create the unit test files from scratch. So there is a benefit to using the grails command.

In following steps of this assignments, I’ll illustrate different ways to invoke Grails commands.

Build Errors

Sometimes when adding a lot of artifacts for your project, the build fails. You will get an error similar to:

Execution failed for task ':bootRunMainClassName'.
> Unable to find a single main class from the following candidates [application, cs4760progassign.Application]

Gradle is confused. The solution is to clean the project by invoking the clean task using “./grailsw clean” or “./gradlew clean”. Sometimes “cleanning” does not work and you need to manually delete the “build/” directory and rebuild.

Step 4: Modify the Basic App: set the context path

Although the index view looks good, I’m not happy with the URL of the index or home view. The URL should be:

http://localhost:8080/cs4760progassign/

and NOT:

http://localhost:8080

The production server for your project will need the app URL to include the app name, so that the router (load balancer) knows where to route the request. So you will need to configure the server’s context path.

We need to edit grails-app/conf/application.yml by adding to the bottom of the file

server:
    servlet:
        context-path: /cs4760progassign

Note that there is a space between the colon and the quote. Without the space the YML parser will silently fail. An hint that the formatting is correct is that the “/cs4760progassign” will be a different color form “context-path” Also “server:”, “servlet:” and “context-path: /cs4760progassin” should be on separate lines. Finally, there must be a leading slash, “/”, or you’ll get an error.

The file application.yml is written in YML which is a markup language:

https://fdik.org/yml/

It is not a particularly complex markup language and more readable than XML. Gradle and Grails use YML. You’ll not need to learn too much of the language. For reference see:

http://stackoverflow.com/questions/32976039/how-do-you-change-the-application-name-in-grails-3

Now stop the server by entering “crtl-c” in the Bash terminal, and boot up the server by entering:

./gradlew bootRun

The output of the gradle command to the Bash

Step 5: Move the Controller List view

1. Generate the New Controller

The default home page, the controller list view, is useful but not appropriate for a home page, so we should move it. We would like the URL for the control list view to be:

http://localhost:8080/cs4760progassign/controllerList

Create a controller by entering the bash terminal at the project root directory,

./grailsw create-controller ControllerList

The command creates several files. One of the files is ControllerListController.groovy in grails-app/controllers/ directory:

package cs4760progassign

class ControllerListController {

    def index() { }
}

The generated controller has only one method, “index” and the method is blank. This is all we need.

2. Move index.gsp

Also notice that Grails also made a new directory, /views/controllerList/, in the views directory. All the views for the ControllerListController should go into this directory. The names of the views should correspond to the method names in the controller. In this case, we want a view file called, “index.gsp.” To make this view all we have to do is move “views/index.gsp” to “views/controllerList/index.gsp. You can do this by dragginng “views/index.gsp” to the “views/controllerList/” directory. After dragging, IntelliJ IDEA asks if you want to perform any checks and change references. In this case, you don’t, so unclick, “check for references”. Then click “OK.”

Rerun the app using either “./grailsw run-app” or “./gradlew bootRun”

You should get a 500 navigating the browser to

http://localhost:8080/cs4760progassign

and a ServletException error message in the terminal. We should not be surprised, currently there is no root “index.gsp”.

In the browsers address bar, add “/controllerList” and you will get to the controller list view. Now the list of available controllers has one item in it:

cs4760progassign.ControllerListController

You can click on the link to go to the controller’s index view or action. In this case, clicking on the link will take you to this same view. You can notice that the URL has changed to

http://localhost:8080/cs4760progassign/controllerList/index

The part of the URL after the controller name is the controller action, in this case the “index” action. With an empty action all that the Grails app will do is display the index view, “index.gsp”.

3. Make Temporary Root Index View

We would like to eliminate the 500 error when the browser is pointed to the root of the app. We only need to make a new “index.gsp” file in the views root directory. Right click on the “views/” directory in the project panel, select “New” and then “GSP”. When the window pops up asking for the name of the file, enter “index”. After clicking the “OK” button, the editor window should show a template gsp file for “views\index.gsp.” Cut and paste the html code below into the editor window

<!DOCTYPE html>
<html>
<head></head>
<body>

 <h1>Base index.gsp </h1>
 <p>Under construction</p>
 <p><g:link controller="controllerList">Go to ControllerList</g:link></p>

</body>
</html>

The html code should look very familiar to you, if not study, html at w3schools

http://www.w3schools.com/html/default.asp

There is only one tag that should be unfamiliar to you, the g-tag, or more specifically the g:link tag. The g:link tag is a Grails tag that is similar to the html anchor tag. It will make portable links using controller names and actions. Whenever possible you should use g:link tags in your code rather then html anchor tags. This will make it safer to deploy your app. You can read more about the g:link tag at

http://docs.grails.org/latest/ref/Tags/link.html

You do not need to rerun the app when adding a new view. The development server will just try to serve the view again. This time it will find it. Now there is no longer a 500 error, and you can click the link that will take you to the controller list view.

4. Fix Unit Test

You should also notice notice that a new directory, test/, is made in the src/ directory. Navigate into src/test/groovy/cs4760progassign/ directory, and you should see the auto generated groovy file, ControllerListControllerSpec.groovy. This is a Specification file for specifying unit tests for the ControllerListController. Grails uses a Groovy testing platform, Spock:

http://spockframework.org/

The documentation is at

https://spockframework.org/spock/docs/2.3/index.html

You should read the primer

https://spockframework.org/spock/docs/2.2/spock_primer.html

https://docs.grails.org/latest/guide/testing.html

There are two ways to run the all the unit test. Open a second Bash terminal in root of the directory and run

./grailsw test-app

or

./gradlew check

For my Bash terminal, the outputs are similar.

$ ./grailsw test-app

...

> Task :test

ControllerListControllerSpec > test something FAILED
    org.spockframework.runtime.SpockComparisonFailure at ControllerListControllerSpec.groovy:16

1 test completed, 1 failed

> Task :test FAILED

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':test'.
> There were failing tests. See the report at: file:///E:/Data-2012/Classes/cs4760/s24/programming-assignments/workspace/cs4760progassign/build/reports/tests/test/index.html

...

So a test failed. This is not the easiest test report to read. The output gives a URL for better for formatted test report

file:///E:/Data-2012/Classes/cs4760/s24/programming-assignments/workspace/cs4760progassign/build/reports/tests/test/index.html 

Enter the above URL in a browser to view the report. The test report will appear in your web browser.

Inspect the unit test code at

src\test\groovy\cs4760progassign\ControllerListControllerSpec.groovy 

The test code is

package cs4760progassign

import grails.testing.web.controllers.ControllerUnitTest
import spock.lang.Specification

class ControllerListControllerSpec extends Specification implements ControllerUnitTest<ControllerListController> {

    def setup() {
    }

    def cleanup() {
    }

    void "test something"() {
        expect:"fix me"
            true == false
    }
}

The test, called a “feature” in Spock, is a method named by the string “test something”. It is attempt to test:

true == false

This test is certain to fail. Best practice in unit testing is not to test auto generate code because with 99% confidence we believe it to be correct, so we could remove the test or the feature. But a worthwhile feature/test might be to test that ControlerListController.index() returns status 200 OK. Then running the test assures us that action is still rendering. So replace the “test something” test with

void "test 200 response"() {
    when:
    controller.index()

    then:
    status == 200
}

Rerun the test, and you should see the terminal output that all tests have succeeded.

...

BUILD SUCCESSFUL in 18s
9 actionable tasks: 6 executed, 3 up-to-date
|Tests PASSED

This is not very informative, but we can guess that the test did not fail. You can refresh the browser window for old failed test to see the new successful report.

Step 6: Make Domain Classes

You will implement a simple database scheme. You need two tables in the database, an Author table and a Book table:

Database Scheme:

Author:

  • String name
  • has many books (list of entries in the Book table)

Book:

  • String title
  • Integer publishYear
  • belongs to author (entry in the Author table)

Read the chapter about GROM in the Grails and GORM documentation to learn how to specify the Domain classes, Author and Book.

http://docs.grails.org/latest/guide/GORM.html

http://gorm.grails.org/latest/hibernate/manual/index.html

Specifically, you’ll want to read carefully the section about GROM association:

http://gorm.grails.org/latest/hibernate/manual/index.html#gormAssociation

You do not need to understand the entire GORM Manual to complete this assignment. The assignment can be completed without deeply understanding GORM.

You should notice that the Author class references the Book class and the Book class references the Author class. This is a cyclic reference and can confuse an auto compiler in the IDE, if you just write the code for the classes sequentially. If you type up the Author class before there is a Book class then your code will have an error at the line referencing the Book class.

Open a Bash terminal in project root directory and enter

./grailsw create-domain-class Author

Then enter

./grailsw create-domain-class Book

These commands will make 4 files. Two domain groovy files in the grails-app/domain/cs4760progassign/ directory. One for domain/cs4760progassign/Author.groovy:

package cs4760progassign

class Author {

    static constraints = {
    }
}

Another file is made for domain/cs4760progassign/Book.groovy:

package cs4760progassign

class Book {

    static constraints = {
    }
}

Also the commands create two test files in the src/test/groovy/cs4760progassign/ which we will discuss later.

Assignment

You are on your own now to complete the domain classes. You will need to add the properties to the Author and and Book domain classes. Hint: Author should have a “hasMany” property and Book should have a “belongsTo” property to specify the associations between Author and Book.

https://grails.github.io/legacy-gorm-doc/6.1.x/hibernate/manual/#gormAssociation

Inspect the Database

You will want to inspect the database to verify that it has really made the tables. You can inspect the database directly to see if the SQL actions are preformed as you expect.

Rerun the Grails app. You will need to rerun run-app whenever you make changes to the Domain classes. Point your browser to

http://localhost:8080/cs4760progassign/h2-console

This will bring up the database login. Note in the login screen that the JDBC URL should match what is specified in the “grails-app/conf/application.yml” configuration file for the development data soruce entry. So if you running in a “development” environment the JDBC URL should be

jdbc:h2:mem:devDb

Note that “JDBC URL” in login screen is a link, click on it and you can read more explanation about the entry.

The user name should be “sa” and password should be blank. Click “Connect” button and data source browser will appear. You should see the Author and Book tables listed in the left panel. Expanding them you’ll see the fields for each table. Clicking the Author table will enter the SQL command:

SELECT * FROM AUTHOR

You can click the “Run” button to execute the command. Do so, and you’ll see that the table is empty because you have not add entries to the table.

Step 7: Add Entries to the Tables

1. Write Specification Class for AuthorBook

When we made the Author and Book domain classes, Grails auto generated the specification classes  AuthorSpec.groovy and BookSpec.groovy in the src/test/groovy/cs4760progassign/ folder. Inspect these specification files. If we run the tests they will fail. The Author and Book domain classes are very simple. If we made persistence test for Author and Book, we would just be testing the GORM code, which is not necessary.  If the domain classes had significant constraints specified in the constraint clause, the constraints would be worthy of unit testing. Grails has an excellent guide on unit testing constraints:

http://guides.grails.org/grails-test-domain-class-constraints/guide/index.html

We will save the AuthorSpec.groovy and BookSpec.groovy specification files just in case we add constraints worthy of testing. So comment out the feature tests in both AuthorSpec.groovy and BookSpec.groovy. Because creating Book entries belonging to an Author entry is non trivial, we could test the combined creation of Author and Book entries. This specification really only test our understanding of the syntax for creating Author and Book entries, but running the unit test is faster then running the entire app. Create a new Specification file in the src/test/groovy/cs4760progassign/ folder called AuthorBookSpec.groovy. Add the following code:

package cs4760progassign

import grails.testing.gorm.DataTest
import spock.lang.Specification

class AuthorBookSpec extends Specification implements DataTest {
    void setupSpec(){
        mockDomains Author, Book
    }

    void "test basic Author Book persistence"(){
        setup:
        new Author(name:"Stephen King")
                .addToBooks(new Book(title:"The Stand", publishYear:1978))
                .addToBooks(new Book(title:"The Shining", publishYear:1977))
                .save(flush: true, failOnError: true)

        new Author(name:"Mark Twain")
                .addToBooks(new Book(title:"Tom Sawyer", publishYear:1876))
                .addToBooks(new Book(title:"Huckelberry Finn", publishYear:1884))
                .save(flush: true, failOnError: true)


        expect:
        Author.count == 2
        Book.count == 4

        Author.findByName("Stephen King").books.size() == 2
        Author.findByName("Mark Twain").books.size() == 2
    }

}

Note that AuthorBookSpec implements DataTest rather then DomainUnitTest. This is because we need to use mockDomains for Author and Book to create our database entries. In the feature test “test basic Author Book persistence”, we use the “setup” block to create the entries. While creating the entries note that the Book objects must be created before the Author object. The save for the Author object also saves the Book objects to the database. The “expect” block specifies the feature tests. Note that in

        Author.findByName("Stephen King").books.size() == 2
        Author.findByName("Mark Twain").books.size() == 2

the parentheses after “size” is necessary because otherwise Groovy interrupts “size” as a property of the Book object rather than a method of the list for books.

Run the tests.

2. Bootstrapping the Database

You can use the Bootstrap.groovy file to add entries to the table when the web app is installed on the server. Open “grails-app/init/cs4760progassign/BootStrap.groovy” in the editor, by double clicking the file in the project panel. Add the code to the init closure, by replacing the file content with below.

package cs4760progassign

class BootStrap {

    def init = { servletContext ->
        new Author(name:"Stephen King")
                .addToBooks(new Book(title:"The Stand", publishYear:1978))
                .addToBooks(new Book(title:"The Shining", publishYear:1977))
                .save()

        new Author(name:"Mark Twain")
                .addToBooks(new Book(title:"Tom Sawyer", publishYear:1876))
                .addToBooks(new Book(title:"Huckelberry Finn", publishYear:1884))
                .save()

    }
    def destroy = {
    }
}

Make sure that the grails app is running, you should not need to stop and rerun the app Grails recognizes that BootStrap.groovy has been modified and recompiles. Use the h2-console to inspect the tables. You should see the entries in both the Author and Book tables.

Step 8: Generate the Controller and Views

Grails will generate the basic controllers and all the views for the domain classes. To make the controllers and views for the Author domain class, enter in the Bash terminal

./grailsw generate-all cs4760progassign.Author

This will make several files for you:

  • grails-app/controller/cs4760progassin/AuthorController.groovy
  • grails-app/views/author/create.gsp
  • grails-app/views/author/edit.gsp
  • grails-app/views/author/edit.gsp
  • grails-app/views/author/index.gsp
  • grails-app/views/author/show.gsp
  • src/test/groovy/cs4760progasign/AuthorControllerSpec.groovy
  • services/cs4760progassign/AuthorService.groovy
  • src/integration-test/groovy/cs4760progassign/AuthorServiceSpec.groovy

You should inspect these files and learn from them. The generated controller uses Services to interact with the Domain object and make the model sent to the view. When interaction with domain objects run for extended time, consider making the Service transnational. See the documentation for Grails Services:

https://docs.grails.org/latest/guide/services.html

The file AuthorService.groovy is an interface for the service used by the AuthorController.

The file AuthorServiceSpec.groovy is an integration test specification for the implementation of AuthorService.

Generate the Book controller and all the views, and  inspect the generated files. Note you can also use the Bash terminal to generate the controller and views by entering

./grailsw generate-all cs4760progassign.Book

Step 9: Fix Integration Tests

The files in src/integration-test/groovy/cs4760progassign/ directory, AuthorServiceSpec.groovy and BookServiceSpec.groovy, are integration tests. Integration tests are used to test the communication between classes or components of the application. Because the roles of services are typically to interface between controllers and domain objects it is appropriate to use integration tests. Integration tests run the application before any of the tests are ran. All tests use the same application context. See the Grails documentation about integration testing:

https://docs.grails.org/latest/guide/testing.html#integrationTesting

There are several ways to run the integration test. In the Bash terminal you can run the Grails command

./grailsw test-app -integration

Run “integration test” now. It fails because of the integration tests specified in AuthorServiceSpec.groovy and BookServiceSpec.groovy. You can view the integration report by opening files:

  • build/test-results/integrationTest/TEST-cs4760prograssign.AuthorServiceSpec.xml
  • build/test-results/integrationTest/TEST-cs4760prograssign.BookServiceSpec.xml

The reports are in xml format, so they are not very readable. Nevertheless, you can determine the failing tests.

A more human readable report is available by opening build/reports/tests/index.html in a browser.

The first cause of the failure in the BookServiceSpec is in the setupData()

private Long setupData() {
    // TODO: Populate valid domain instances and return a valid ID
    //new Book(...).save(flush: true, failOnError: true)
    //new Book(...).save(flush: true, failOnError: true)
    //Book book = new Book(...).save(flush: true, failOnError: true)
    //new Book(...).save(flush: true, failOnError: true)
    //new Book(...).save(flush: true, failOnError: true)
    assert false, "TODO: Provide a setupData() implementation for this generated test suite"
    //book.id
}

Because the entire application will be running at the time of tests, we do not use MockDomains to make the Book entries. Instead we create the Books as we would normally in the program. The @Rollback annotation will treat each test method as a transaction that should be rolled back at the end of the test. Below is what we should write for the setupData method:

private Long setupData() {
    // Need to remove all data from tables just in case hibernate is already running. 
    // https://stackoverflow.com/questions/8160296/grails-delete-all-data-from-table-domain-class-i-e-deleteall
    // Note even the deletes will be rolled backed.
    Book.executeUpdate('delete from Book')
    Author.executeUpdate('delete from Author')
 
    // Add to the tables
    Author testAuthor = new Author(name: "Test Author").save(flush: true, failOnError: true)
    Book book0 = new Book(title: "Test Book 0", publishYear: 1999, author: testAuthor).save(flush: true, failOnError: true)
    new Book(title: "Test Book 1", publishYear: 1999, author: testAuthor).save(flush: true, failOnError: true)
    new Book(title: "Test Book 2", publishYear: 1999, author: testAuthor).save(flush: true, failOnError: true)
    new Book(title: "Test Book 3", publishYear: 1999, author: testAuthor).save(flush: true, failOnError: true)
    new Book(title: "Test Book 4", publishYear: 1999, author: testAuthor).save(flush: true, failOnError: true)

    book0.id
}

The integration test will use the same application context that run-app uses. So the app should not be running at the time of the test. Even if the app is shut down by the “stop” command, hibernate is still running in the background. Consequently, we need to clear all the tables. The fastest way to do this is to use HQL directly. See

https://stackoverflow.com/questions/8160296/grails-delete-all-data-from-table-domain-class-i-e-deleteall

The Rollback will even rollback the deletes from the table, so in fact, the app could be running during integration tests as long as the app is inactive, its state will be restored.

Because the Book domain is defined as “belongsTo” to an Author domain object, we need to create an Author first and include it in the constructor call for the new Book. Note that the save methods includes flush and failOnError true. We need to flush to assure that the Book object is made immediately, and we want failOnError because this is a test and should fail on the save rather than later.

The “test get” feature test was written assuming that no other operations were performed on the database. But if you have added to the database, for example using Bootstrap and running the program, then the first id will not be 1. So we patch the “test get” feature test

void "test get"() {
    Long bookId = setupData()

    expect:
    bookService.get(bookId) != null
}

We also need to patch the “test list” feature test:

    void "test list"() {
        setupData()

        when:
        List<Book> bookList = bookService.list(max: 2, offset: 2)

        then:
        bookList.size() == 2

        bookList[0].title.contains("Test Book")
        bookList[1].title.contains("Test Book")
    }

Also, we need to patch the “test save” feature test:

    void "test save"() {
        when:
//        assert false, "TODO: Provide a valid instance to save"
        Author author = new Author(name: "Author").save(flush: true, failOnError: true)
        Book book = new Book(title: "Test Book", publishYear: 1990, author: author)
        bookService.save(book)

        then:
        book.id != null
    }

Run “integration test” command. The BookServiceSpec tests will succeed, but the AuthorServiceSpec will fail.

You are to patch AuthorServiceSpec.groovy on your own. HINT: an Author object does not need to have any Books to be created.

Step 10: Use the Web App

You can run the web app. Stop and start the app again. The controller list page should show the Author and Book controllers. Click on these controller links. Inspect the index views for the Author and Book controllers. You should navigate and play with the interface.

Use the app to create a new author and add a book to the author. View the Author and Book list to see that it is in database. Also go to h2-console to inspect the tables directly.

Delete the author “Stephen King” and then view the book list. The books “The Stand” and “The Shining” should be gone from the list because of the static property “belognsTo” in the Book domain class.

Step 11: Change Cross Referencing List Displays

Using the web app, you should have noticed that when you view the list of authors that the list of books are referenced by cs4760progassign.Book and the id. Clicking a book reference will show the authors by the cs4760progassign.Author and the id. This is not an error, but it might not be what you want. You probably expect that the book reference ought to be the book title and the author reference should be the author’s name. We can change to this behavior by adding a toString method to the domain class.

In the Author domain class, add the lines below:

String toString(){
 "${name}"
}

In the Book domain class, add the lines

String toString(){
 "${title}"
}

You’ll need to stop and run the web app for the changes to the domain to take place. Now try the web app, you notice that Author is referenced by name and Book by the title. The code in the toString method tells the view how to render the view. The dollar sign, $, is a grails tag that alert the view that within the curly bracket is groovy code, in this case a property of the class.

In general, you should be careful making these changes. The problem with our change is that the name of the author may not be unique, meaning two authors may have the same name. So, this will cause ambiguity when associating a book with an author. In the next assignment, you’ll see that my general strategy is to use the generated views only for the administrated backend of the website and that we’ll make public views by hand coding. The admin should understand the references to book and author are table id, and consequently the admin would not be confused.

Step 12: Make Screen Shots and Submit on Canvas

After creating a new author, adding a book and deleting the author “Stephen King”, make a screen shot of the book list. The URL is

http://localhost:8080/cs4760progassign/book/index

Also make a screen shot of AuthorServiceSpec.groovy that you have patched.

Submit the screen shots in canvas for the “Programming Assignment 1 – Building Your First Grails App”.