.connect{ follow ->

}

Grails Part 3 - Building a CRUD Application

Click here to view the full tutorial
Continuing series: Developing a Grails Web Application.
This application will use the Grails Framework to develop a web application using:
1. MongoDB
2. Spring Security
3. Apache CXF
4. Jetty Web Server
5. Maven build integration => Removed upon Request
Previous Post: Grails Part 2 - Configuring the Plugins
View Project on Github: Grails MongoDB Demo

These Instructions Will Walk Through:

  • Building a CRUD Application
    • Building the Domain Model
    • Controllers using Grails Scaffolding

When building out the application, we will be using a combination of grails on the command line, and IntelliJ. You can decide what works best for creating skeleton domain and controller classes. But for this tutorial, I'm trying not to be completely bound to IntelliJ IDE.

The Domain Model

* The initial goal of this application, is to create and update person address history *
To get started we are going to first build out some skeleton classes for our model.
  1. In IntelliJ, press command-alt-G or right click in project view -> Grails -> Run Target
    • If on command line, cd to your application directory
  2. On the command line type: create-domain-class org.mycompany.Person
    • In console append grails to the command.
  3. On the command line type: create-domain-class org.mycompany.Address
*Note: The names of database tables are automatically inferred from the name of the domain class.
Now we have skeleton classes for our two domain classes under: myapp/grails-app/domain
Open up the Address class, and edit it as follows
class Address {

    String streetAddress;
    String city;
    String state;
    String zipCode;

    //Date moveInDate;
    //Date moveOutDate;

    static belongsTo = [person: Person]

    static constraints = {
        streetAddress(blank: false)
        city(blank: false)
        state(blank: false, size: 2..2)
        zipCode(blank: false, size: 5..5, validator: {val, obj -> val?.isNumber()})
        /*moveInDate(nullable: false, max:  new Date())
        moveOutDate(nullable: true, validator: { val, obj ->
            val?.after(obj.moveInDate)
        })*/
    }
}
  • The static field belongsTo indicates that the class Person assumes ownership of the relationship. i.e. An Address belongs to only one person (in this case).
  • The static field constraints, handles the validation logic for each field. Most fields cannot be blank, but the move out date can be blank if a person hasn't moved out yet. moveOutDate also has a custom validator to ensure the date is after the move in date. The dates are disabled to keep the demo simple. Feel free to uncomment them, to see how the scaffold view handles dates.
Open up the Person class, and edit it as follows
class Person {

    String firstName;
    String lastName;

    static hasMany = [addresses: Address]

    static constraints = {
        firstName(blank: false)
        lastName(blank: false)
    }
}
  • The static field hasMany Defines a one-to-many association between two classes. i.e. a Person has many Addresses.
Important Note: We are using mongoDB, so using relationships with the hasMany definition will not be efficient in a production setting. The reason I chose to use this, is because scaffold views do not support lists within domain classes. The mongo GORM plugin does support this. So a better way to create Person addresses would look as follows:
class Person {

    String firstName;
    String lastName;
    List<Address> addresses; //We are going to store an array of addresses in the person collection.

    static constraints = {
        firstName(blank: false)
        lastName(blank: false)
    }
}
For the sake of simplicity, we will use the former case.
You can view descriptions on all domain class features on the Grails Quick Reference Page
back to top

CRUD application using Grails Scaffolding

Grails by default, has a decent UI for testing your new MVC components. Basic CRUD operations can be performed with one line
of code in the model's controller. These views (called scaffolding views) are generated by the compiler when calling grails run-app
To get started we are going to create controllers for our model classes.
  1. In IntelliJ, press command-alt-G or right click in project view -> Grails -> Run Target
    • If on command line, cd to your application directory
  2. On the command line type: create-controller org.mycompany.Person
    • In console append grails to the command.
  3. On the command line type: create-controller org.mycompany.Address
What this does is creates two controller classes named PersonController, and AddressController for our model.
Open up PersonController class.
  • remove the line def index() { } and replce it with def scaffold = Person
class PersonController {

    def scaffold = Person
}
Now do the same for the AddressController
class AddressController {

    def scaffold = Address
}
Finally, in IntelliJ click the run button. or from console mvn grails:run-app
Your application will be running on localhost. Play around with the default scaffolding UI and the validation.

Configure GRAILS-Boootstrap

The Configuration class: Bootstrap is useful because you can use it to add data to the application when the application starts. Copy the below Bootstrap class. It will add many People, with many associated addresses to the mongoDB instance, next time you run the application.
import org.keaplogik.Person
import org.keaplogik.Address

class BootStrap {

    def init = { servletContext ->
        if (!Person.count()) {
            def johnDoe = new Person( firstName: "John", lastName: "Doe" ).save(failOnError: true)
            def joeReed = new Person( firstName: "Joe", lastName: "Reed" ).save(failOnError: true)
            def jimSmith = new Person( firstName: "Jim", lastName: "Smith" ).save(failOnError: true)
            def patrickHartwin = new Person( firstName: "Patrick", lastName: "Hartwin" ).save(failOnError: true)
            def steveGunther = new Person( firstName: "Steve", lastName: "Gunther" ).save(failOnError: true)
            def samWhiting = new Person( firstName: "Sam", lastName: "Whiting" ).save(failOnError: true)
            def sarahMathews = new Person( firstName: "Sarah", lastName: "Mathews" ).save(failOnError: true)
            def lisaPudock = new Person( firstName: "Lisa", lastName: "Pudock" ).save(failOnError: true)
            def karaWhiting = new Person( firstName: "Kara", lastName: "Whiting" ).save(failOnError: true)

            johnDoe.addToAddresses(
                    new Address(state: "NY", city: "Windsor", streetAddress: "117 W 2nd St", zipCode: "13865")
            ).addToAddresses(
                    new Address(state: "TX", city: "Alberta", streetAddress: "117 W 2nd St", zipCode: "55555")
            ).addToAddresses(
                    new Address(state: "NY", city: "Longely", streetAddress: "2 Sandy Creek", zipCode: "34009")
            ).addToAddresses(
                    new Address(state: "ME", city: "Ladly", streetAddress: "117 W 2nd St", zipCode: "55533")
            ).addToAddresses(
                    new Address(state: "KY", city: "Korba", streetAddress: "3 Apple St", zipCode: "40351")
            ).save(failOnError: true)

            joeReed.addToAddresses(
                    new Address(state: "KY", city: "Frankfort", streetAddress: "33 Main St", zipCode: "77625")
            ).addToAddresses(
                    new Address(state: "PA", city: "Scranton", streetAddress: "71 Kind Ave Apt 3", zipCode: "44567")
            ).addToAddresses(
                    new Address(state: "PA", city: "Scranton", streetAddress: "8559 Hard Rock", zipCode: "44567")
            ).addToAddresses(
                    new Address(state: "WV", city: "Charleston", streetAddress: "8233 Juniper Rd", zipCode: "33982")
            ).save(failOnError: true)

            jimSmith.addToAddresses(
                    new Address(state: "PA", city: "Blue Ridge", streetAddress: "780 Country Rd", zipCode: "44564")
            ).addToAddresses(
                    new Address(state: "TX", city: "Ft. Worth", streetAddress: "55 Holdem Dr." , zipCode: "77298")
            ).save(failOnError: true)

            patrickHartwin.addToAddresses(
                    new Address(state: "CA", city: "Sacramento", streetAddress: "1 Beach Rd", zipCode: "98765")
            ).addToAddresses(
                    new Address(state: "CA", city: "Sacramento", streetAddress: "53 Sinking Dr." , zipCode: "98765")
            ).save(failOnError: true)

            steveGunther.addToAddresses(
                    new Address(state: "CA", city: "Sacramento", streetAddress: "1 Beach Rd", zipCode: "98765")
            ).addToAddresses(
                    new Address(state: "CA", city: "Sacramento", streetAddress: "53 Sinking Dr." , zipCode: "98765")
            ).addToAddresses(
                    new Address(state: "CA", city: "Sacramento", streetAddress: "759 Sinking Dr." , zipCode: "98765")
            ).save(failOnError: true)

            samWhiting.addToAddresses(
                    new Address(state: "CA", city: "Sacramento", streetAddress: "1 Beach Rd", zipCode: "98765")
            ).save(failOnError: true)

            sarahMathews.addToAddresses(
                    new Address(state: "VT", city: "Burlington", streetAddress: "81 Lake Dr.", zipCode: "22183")
            ).addToAddresses(
                    new Address(state: "VT", city: "Burlington", streetAddress: "40 Shorten Ave Apt 33" , zipCode: "22183")
            ).addToAddresses(
                    new Address(state: "NY", city: "Plattsburgh", streetAddress: "1772 Lovely Lane" , zipCode: "22795")
            ).save(failOnError: true)

            lisaPudock.addToAddresses(
                    new Address(state: "VT", city: "Burlington", streetAddress: "81 Lake Dr.", zipCode: "22183")
            ).addToAddresses(
                    new Address(state: "VT", city: "Burlington", streetAddress: "40 Shorten Ave Apt 33" , zipCode: "22183")
            ).addToAddresses(
                    new Address(state: "NY", city: "Plattsburgh", streetAddress: "1772 Lovely Lane" , zipCode: "22795")
            ).save(failOnError: true)

            karaWhiting.addToAddresses(
                    new Address(state: "CA", city: "Sandiego", streetAddress: "9901 Shore Dr.", zipCode: "98741")
            ).save(failOnError: true)
        }
    }
    def destroy = {
    }
}
Click here to view the full tutorial