Programing Assignment 1 – Building Your First Grails Apps

The purpose of this assignment is to introduce you to Grails and includes:

  • Grails 3 Framework including Domain, Controller and Views
  • IntelliJ IDEA integrated development environment (IDE)
  • H2 Database and dbconsole

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

Step 1: Watch Introduction to Grails Videos

Point your web browser to and watch

https://www.youtube.com/watch?v=PXHxo43hn34 – Getting Started with Grails Part 1, MVC including Domain and scaffolding.

https://www.youtube.com/watch?v=qNFksvLxZNU – Getting Started with Grails Part 2 – Custom UI.

https://www.youtube.com/watch?v=oL4yVtNU31E – Getting Started with Grails Part 3, Custom G-Tags.

Watch the part 1 and part 2 videos. Later, you can watch the part 3 video. Although the video uses Grails 2 and the GGTS IDE, it is still a good introduction to developing a web app using Grails. All the code introduced in the video will still run with Grails 3.

If you need more resources consult the Grails User Documentation

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

In particular, you will want to study chapter 7 about GORM and the GORM documentation to learn more about how to make and use Grails models

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

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

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

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.

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

There are also a large collection of Grails Guides:

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

The Grails Apprentice guide, Creating your first Grails Application,

http://guides.grails.org/creating-your-first-grails-app/guide/index.html

is a very detailed and technical introduction to creating a Grails app.

Step 2: Install IntelliJ IDEA and Grails on your home machine

The only Integrated Development Environments (IDE) that runs Grails 3 is IntelliJ IDEA. Although it is possible to develop with Grails 3 without an IDE, I recommend that your team use the IntelliJ IDEA. Then, your team and instructors can help you if there are IDE challenges.

Below is a detail instructions for IntelliJ IDEA installation.The instructions are specific to a Windows machine. If you are using Mac or Linux it might differ some.

1. Download the latest version of Grails 3 and Java to your home machine

i. Download latest version of Grails 3

You can find the latest version of Grails 3 (as of 8/28/2018 Grails 3.3.8 is the current stable version):

https://grails.org/download.html

There are two options for installing Grails. You can download a zip of the latest version of Grails 3 using the red “download” button on the upper right of the page or you can install SDKMAN on your machine and use SDKMAN to install different versions Grails.

SDKMAN is a command line tool for controlling the version of many Grooy and Java  development software.

http://sdkman.io/

It is useful if you are developings apps using different versions of Grails, but it is overkill for the purposes of this course. if you do use SDKMAN, note that it will make a “.sdkman” directory in the root of your user directory. In the .sdkman directory, there is a “candidate” directory. The candidate directory will contain the different software and versions of the software.

I assume that you download the latest Grails 3 version using the upper right red “download” button. That will download a zip file to your browser’s default download directory. You should move the zip file to the directory of your choice. I put my Grails download into

C:/grails/GrailsVersions/

After moving the download, you should unzip the Grails download. I use 7-Zip

http://www.7-zip.org/

It will unzip Grails into

C:/grails/GrailsVersions/grails-3.3.8/

Notes:

  1. You need to remember where you put Grails. You will need to specify it when you create your project.
  2. While working with a team, your team will need to use the same version of Grails.

ii. Locate Java JDK on your home machine and Download version

Grails 3 and IntelliJ IDEA will need JDK 1.7 or 1.8. You probably already have JDK on your machine, but to insure that the deployment app is successful your Java version should match the Java on the server. Currently (10/24/2017), the server is using Java openjdk version 1.8.0_111. Locate the JDK on your machine. On my windows machine, the JDK are located in

C:/Program Files/Java/

Remember this location, you will need it when you make your project in IntelliJ IDEA.

If you do not have server’s Java version (1.8.0_111), you should get and install it on your machine.

http://www.oracle.com/technetwork/java/javase/downloads/index.html

Download and install the JDK, not the JRE. You also do not need NetBeans bundled with the JDK.

2. Installing IntelliJ IDEA Ultimate

The Grails Developer Team now recommends using IntelliJ for your development environment. IntelliJ comes in two favors: Community and Ultimate. The community version is free for anyone and can run Grails 3, but it does not have many of the convenient features that the ultimate version has.  Jet Brains ofters an one year academic licences for the ultimate version, and our IT has installed the ultimate version on the school lab machines. I assume that you want to download and install the ultimate version on your home machine

i. Apply for JetBrains student License

Apply for a student licence at

https://www.jetbrains.com/student/

Clicking the “Apply Now” button in the middle of the page will take you to this form

https://www.jetbrains.com/shop/eform/students

Use the “University email address” tab to apply. Be sure to use your MTU email address. Click the “Apply for Free Products” button at the bottom of the page.

You should get an email from Jet Brains. The email will be from “sales.us@jetbrains.com”, and there should be a link in the email to activate your JetBrains account.

ii. Download and Install IntelliJ IDEA Ultimate

IntelliJ IDEA download website is at

https://www.jetbrains.com/idea/

Clicking the “Download” button in the middle of the page, takes you to the “chooseYourEdition” section of the page:

https://www.jetbrains.com/idea/#chooseYourEdition

Download the “Ultimate” version. For windows this will download an installer. Run the installer as administrator. Follow the wizard, you can use all the default options.

Jet Brain has good documentation for IntelliJ IDEA. You can find installation instructions at:

https://www.jetbrains.com/help/idea/2016.1/installing-and-launching.html

iii. Read “Getting Started with Grails 3” using IntelliJ IDEA

IntelliJ IDEA is a little different then the Eclipse IDE, so you should read a little about IntelliJ IDEA:

https://www.jetbrains.com/help/idea/2016.1/getting-started-with-grails-3.html

If you are accustomed to Eclipse or Netbeans IDEs then you should be interested in the link at the bottom of the page:

https://www.jetbrains.com/help/idea/2016.1/installing-and-launching.html#d1799863e445

In particular for Eclipse users, read

https://www.jetbrains.com/help/idea/2016.1/eclipse.html

Read at least the first couple sections of this page. Note that InelliJ IDEA does not have “workspaces” like Eclipse. This implies that ItelliJ IDEA will work with only one ‘project’ at a time. There are also no “perspectives” in IntelliJ IDEA, implying everything you need is in a single view. The left panel shows the project file directories much like Eclipse. Also the right panel is the editor view just as in Eclipse. Different from Eclipse, IntelliJ has a bottom panel. This may be hidden, but you can open different views using the buttons along the bottom of IntelliJ IDEA. The relevant view at the bottom occurs when you run your Grails app within IntelliJ IDEA. Running the Grails app will open a “Run” view that allows you stop and start your app and also clear and print the terminal output.

Step 3: Make the Grails project using IntelliJ IDEA

1. Make “workspace” and Project Directories

Although IntelliJ IDEA does not have “workspace,” I like to make a “workspace” directory in order to clearly indicate the location of my code and to keep it separate from other files, such as programming notes.

In a directory of your choosing, make the directories

C: .../cs4760progassign-Workspace/cs4760progassign/

Note that the directory “cs4760progassign” is a sub-directory of the “cs4760progassgin-Workspace” directory.  From experience, I have learned that the IntelliJ IDEA will automatically name the project and package of the app by the name of the directory where the project is created. So it is easier to properly name the project directory within the workspace directory first.

2. Use the wizard to make the Grails project

Run IntelliJ IDEA and point it to the “cs4760progassign-Workspace/cs4760progassign/” directory. If IntelliJ IDEA opens in the wrong directory do not worry. Within IntelliJ IDEA select “File” from the top menu. Then select “New Project …” and the wizard should open. In the left hand panel of the wizard, select “Grails”.

i. Specify JDK Location in the Wizard

In the right panel, enter the “Project JDK” by clicking the “New…” button. A file browser opens. Navigate to the location of you JDK and click the “OK” button at the bottom. Note that you do not want to use the JRE that ships with IntelliJ IDEA.

ii. Specify Grails SDK Home

Also in the right panel, enter the location of your Grails 3 by clicking the “…” button. Another file browser opens. Navigate to where you unzipped your Grails 3 and click the “OK” button at the bottom of the file browser.

Make sure that in the “Create” field that the “create-app” is clicked. No options are needed. Click the “Next” button at the bottom of the wizard window.

iii. Specify the Project Location

In the next window, you specify the “Project location” by clicking the “…” button. Yet another file browser opens. Navigate to your workspace and project directory,  “cs4760progassign-Workspace/cs4760progassign/”. Click the “OK” button at the bottom of the file browser. Note that the “Project name” is automatically filled in the text field.

iv. Create the Project and the Basic App

Click the “Finish” button at the bottom in the Wizard. A notice appears asking if you want to open the new project in “This Window” or in a “New Window.” Click the blue “This Window” button.

IntelliJ IDEA will move to the new project directory, and IntelliJ IDEA begins building the basic Grail app directory structure and files within the project directory. This will take some time, 5 minutes or more. Be sure that you let IntelliJ build the complete directory and files. You can watch the build in the “Grails Console” at the bottom of IntelliJ IDEA.  In Windows, a “Windows Security Alert” window will pop up asking if you should allow Java access. Grant Java access by clicking the “Allow access” button.

v. Study the Project Directories

After Grails has finished creating the new app, the “Project” view shows the project directories files:

  • .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
  • out/ <- Will appear after making a war files, and the war will be in here
  • src/
    • integration-test/
    • main/
    • test/ <- Will appear when unit tests are made
  • build.gradle <- configuration file for building the app
  • cs4760progassign.ipr <- can click this file in file explorer to open IntelliJ IDEA in the project
  • cs4760progassign.iws
  • gradle.properties
  • gradlew
  • gradlew.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 model 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 (like 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

vi. Run the app

The code generated by IntelliJ IDEA will run. To run the basic app, you only need to click the right pointing green arrow at the top of IntelliJ IDEA. Make sure that the adjacent text field says “Grails: cs4760progassign”. This will open a terminal view at the bottom of IntelliJ IDEA. You can watch the build. When the build is done you should see this message:

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

Also your default browser window should open to http://localhost:8080. If it does not, just open a browser window and paste the URL into address bar. 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
  • Artefacts <- number of controllers, domains and services.
  • Installed Plugins <- list of pulgins and there versions.
  • Available Controllers <- List of controller links.

Currently, there are no controllers. The app is showing the index.gsp. You can see the code by clicking on grails-app/views/index.gsp in the Project panel of IntelliJ IDEA. 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.

vii. 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/

not

http://localhost:8080

The default context of the app is set to the root of the server. It is important that the context be set under the app name. This becomes important when you deploy your app to the public because all the apps will have the same root domain.

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

server:
    contextPath: '/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, the ‘/cs4760progassign’ should be green in IntelliJ IDEA.

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 uses YML. You’ll not need to learn too much of the language. The above line just sets the server context path to the app name. For reference see:

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

Now stop and rerun Grails. Note IntelliJ IDEA automatically saves edits to a file, so you do not have to save the file. Stop the app by clicking the red square either in the Run view or the tool bar at the top of IntelliJ IDEA. You can also restart the app clicking the small gray square with a  green arrow.

Step 4: 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

The part of the URL after the app-name is the controller name. We need to make a new controller. You can do this by right clicking on the controller directory in the project panel and select “New” and then select “Grails Controller.” Enter the name of the controller, “ControllerList”, in the text box. After clicking “OK”, Grails will generate files and directories. When it is done, it should show the “ControllerListController.groovy” file in the editor:

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 ask if you want to perform any checks and change references. In this case, you don’t. Click “OK.”

Rerun the app.

Note that when the browser opens, you get a “Error 500: Internal Server Error” saying “Could not resolve view with name ‘/index’ in servlet with name ‘grailsDispatcherServlet'”. 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

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 “File”. Now when the window pops up asking for the name of the file, enter “index.gsp”. Be sure to include the file suffix, “gsp”, because IntelliJ IDEA does not automatically associate “gsp” files in the view folder. After clicking the “OK” button, the editor window should show a blank 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

Rerun the Grails app. 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. In the source directory 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

http://spockframework.org/spock/docs/1.1/index.html

You should read the primer

http://spockframework.org/spock/docs/1.1/spock_primer.html

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

 

We can run all the tests for the app by running the grails command:

> grails test-app

To make the command in IntelliJ, click the field to the right of the green right arrow at the top (“Run/Debug” field) and select “Edit Configuration …”. In the window that pops up, click the green plus at the top and select “Grails”. In the right panel, name the test “test-app” and in the “Command line” field enter “test-app”. Be sure to select the “Application”, “cs4760progassign”. Click “OK”.

To run the tests, select “test-app” in the “Run/Debug” field then click the green arrow. A “test-app” console will open at the bottom which will show the program searching for tests. Eventually, you will see in the console displaying this message:

...

:test

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

1 test completed, 1 failed

:test FAILED

BUILD FAILED

Total time: 8.562 secs
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/s19/programming%20assignment/workspace/cs4760progassign/build/reports/tests/test/index.html

...

So a test failed. This is not the easiest test report to read. There several ways to get to the more attractive test report allured to in the message “There were failing tests. …”. One way is to navigate to build/reports/tests/test/index.html and right click and select “run index.html”.  The test report will appear in your web browser. At the same time “index.html” is added to the “Run/Debug” commands, so the next time you run the tests you can access the report by clicking the “Run/Debug” field and select “index.html” and finally click the green arrow.

Inspect ControllerListControllerSpec.groovy

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 feature in Spock, is a method named by the string “test something”:

 "true == false"

This test Is certain to fail. Good practice in unit testing is not test auto generate code because with 99% confidence we believe it to be correct, so we could remove the test or 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 repsonse"() {
    when:
    controller.index()

    then:
    status == 200
}

Rerun the “test-app”. Now the console returns

...

BUILD SUCCESSFUL

Total time: 8.746 secs
|Tests PASSED

Process finished with exit code 0

Test report is still generated and you can view it by selecting the “index.html” in the “Run/Debug” and clicking the right green arrow.

Step 5: 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/6.0.x/hibernate/manual/

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

http://gorm.grails.org/6.0.x/hibernate/manual/#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 cyclic references and can confuse the 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. Instead of writing the classes sequentially I suggest first making both empty Author and Book domain classes by right clicking on the project, select “New” and then “Grails Domain Class”. Fill in the name of the domain class: Author or Book, and finally “Finish.” After creating both the Author and Book domain classes, you can type the references to other class without error.

Step 6: Inspect the Database

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

Rerun the Grails app and point your browser to

http://localhost:8080/cs4760progassign/dbconsole

This will bring up the database login. Note that in the login screen 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;MVCC=TRUE

Note the login in screen has a link for “JDBC URL”, click on it and more explanation for the entry is given.

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 on 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/ folder. Inspect these specification files. If we run the tests in the feature/tests 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 persistence test the combined creation of Author and Book. This specification really only test our understanding of the syntax for creating Author and Book combined entries, but running the unit test is faster then running the entire app. Create a new Specification file in the src/test/ 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. You can run just this unit test by clicking the green arrows just to the left of the code. The test should be successful.

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 “init/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 = {
    }
}

Now stop the app and run it again. Use dbconsole 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 controller and views from the Author domain class, right click the project or select “tools” in the menu bar then select “Grails” and then “RunGrails Command”.  In the pop up window, type the command below in the command text field. You must type the command, IntilliJ IDEA does not recognize the command if it cut and paste in.

generate-all cs4760progassign.Author

Make sure the correct project is specified in the “Application” text field. You do not need any “VM options.”

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 use Services to interact with the Domain object and make the model sent to the view. Because interaction with domain objects can run for extended time, Services are 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 AuthorService.Spec.groovy is an integration test specification for the implementation of AuthorService.

Generate the Book controller and all the views.  Inspect the generated files.

Step 9: Fix Integration Tests

Run “test-app” 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.

Interaction tests are used to test the communication between classes or components of the application. Because services’s roles 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

The first cause of 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 can 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 can be running doing 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
//        assert false, "TODO: Verify the correct instances are returned"
        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 test-app. The BookServiceSpec does not fail any test.

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 dbconsole 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 show an author the list of books are referenced by the table (cs4760progassign.Book) and the id. Also showing a book references the authors by the table 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 model.

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 again 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, $, are 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 and id, and consequently the admin would not be confused.

Step 12: Make Screen Shots and Email Me

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.

Email me (pastel@mtu.edu) the screen shot. The subject of the email should be

“cs4760 Programming Assignment 1 Proof”