(Quick Reference)

24 Scaffolding

Version: 8.0.0

24 Scaffolding

Scaffolding lets you generate some basic CRUD interfaces for a domain class, including:

  • The necessary views

  • Controller actions for create/read/update/delete (CRUD) operations

The way for an application to express a dependency on the scaffolding plugin is by including the following in build.gradle.

dependencies {
        // ...
        implementation "org.apache.grails:grails-scaffolding"
        // ...
    }

Dynamic Scaffolding

The simplest way to get started with scaffolding is to use the @Scaffold annotation on a controller:

import grails.plugin.scaffolding.annotation.Scaffold

@Scaffold(Book)
class BookController {
}

With this configured, when you start your application the actions and views will be autogenerated at runtime. The following actions are dynamically implemented by default by the runtime scaffolding mechanism:

  • index

  • show

  • edit

  • delete

  • create

  • save

  • update

A CRUD interface will also be generated. To access this open http://localhost:8080/book in a browser.

When the application is built, the Gradle plugin’s generateScaffoldedViews task expands the scaffolding templates for every domain class a controller scaffolds with @Scaffold, the application’s or a plugin’s, and compileGroovyPages compiles the resulting pages in the same compilation as the application’s views. The templates are expanded by the application’s own scaffolding library and Groovy, in a JVM on its runtime classpath, exactly as they would be expanded when the view is requested. With a grails-scaffolding the Grails Gradle plugin cannot work with, the build warns and compiles no scaffolded page, and the views are expanded on their first use. A packaged application then renders a scaffolded view without generating it on the first request, and a native image, which cannot compile a page at runtime, can render it at all.

The task covers the application’s scaffolded controllers and those its plugins provide, where the domain class a plugin’s controller scaffolds is on the classpath the pages are compiled against. A plugin the project has only at runtime, such as one that reaches it through another project’s implementation dependency, is not on that classpath. A page naming its domain class could not be compiled here, so its controllers are left to the plugin’s own build. For each controller it covers, it expands every copy of each template the view resolver could choose: the application’s own, in src/main/templates/scaffolding or src/main/resources/META-INF/templates/scaffolding, and every copy on the runtime classpath. Which copy the resolver finds is not something the build can know: on the JVM it looks beside the controller’s class first, then at a plugin that overrides the templates, then along the classpath, while a native image, which keeps no class files to look beside, goes by the order of the classpath it was built from. A namespace-specific template, such as src/main/templates/scaffolding/admin/show.gsp, is expanded only for the domain classes of controllers with a namespace.

A template of the application’s own is its code, as a view is: if it cannot be expanded for a domain class, or a page expanded from it does not compile, the build fails. A copy a dependency supplies is not, and because every copy is compiled, that includes a copy the application has replaced with its own. If such a copy cannot be expanded, or its page does not compile, the build leaves the page out and says so, and the view is expanded on its first use if that copy is ever chosen - which it is not while the application’s own copy comes first on the classpath, as it normally does.

Compiling the pages does not change which page a request gets. A view the application or a plugin declares still takes precedence, and the resolver chooses the template as it always has; the compiled page is used only when it was expanded from that template for the same domain class. Otherwise the template is expanded on its first use, as before, and the log notes it once. That is the case for a controller using the static scaffold property described below, for a controller of a plugin the application has only at runtime whose pages the plugin’s own build did not compile, and for a page the build left out because a template a dependency supplies could not be expanded or its page did not compile, which the build output reports.

A native image cannot expand a template, so there a view with no compiled page fails; where the application has compiled pages of its own, a warning naming the template and the domain class comes first. What to do depends on why the page is missing: declare the controller with @Scaffold rather than static scaffold; for a plugin the application has only at runtime, declare it as a dependency of the application; for a page the build left out, give the application a template of its own in place of the one it came from: the build output names the page, under build/generated/scaffolded-views, and the page ends with a comment saying which template it was expanded from. Otherwise, provide a concrete GSP view. A plugin the application has only at runtime needs declaring because its own build compiled pages only for the template copies it saw, so an application template, or a theme earlier on the application’s classpath, gives a page it never compiled.

If you prefer to keep your domain model in Java and mapped with Hibernate you can still use scaffolding, simply import the domain class and use it in the @Scaffold annotation.

Legacy Static Scaffold Property

The older static scaffold = Book syntax is still supported but the @Scaffold annotation is preferred:

class BookController {
    static scaffold = Book  // Legacy syntax - @Scaffold annotation is preferred
}
The static scaffold = true form is not supported in Grails 3.0 and above.
Scaffolded Services

In addition to controllers, you can also scaffold services using the @Scaffold annotation:

import grails.plugin.scaffolding.annotation.Scaffold

@Scaffold(Book)
class BookService {
}

This generates a service that extends grails.plugin.scaffolding.GormService and provides standard CRUD operations:

  • get(id) - Retrieve by ID

  • list(Map args) - List with pagination/sorting

  • count(Map args) - Count entities

  • save(instance) - Create or update

  • delete(id) - Delete by ID

Service-Backed Controllers

For better separation of concerns, you can create controllers that delegate to a scaffolded service using RestfulServiceController:

import grails.plugin.scaffolding.annotation.Scaffold
import grails.plugin.scaffolding.RestfulServiceController

@Scaffold(RestfulServiceController<Book>)
class BookController {
}

This controller will automatically locate and use the corresponding BookService for all data operations.

Extending Custom Classes

You can specify a custom class to extend for your scaffolded controllers or services:

import grails.plugin.scaffolding.annotation.Scaffold
import com.example.MyCustomService

@Scaffold(MyCustomService<Book>)
class BookService {
}
Read-Only Mode

You can create read-only scaffolded controllers or services:

import grails.plugin.scaffolding.annotation.Scaffold

@Scaffold(domain = Book, readOnly = true)
class BookController {
}
Adding Custom Actions

You can add new actions to a scaffolded controller:

import grails.plugin.scaffolding.annotation.Scaffold

@Scaffold(Book)
class BookController {

    def changeAuthor() {
        def b = Book.get(params.id)
        b.author = Author.get(params["author.id"])
        b.save()

        // redirect to a scaffolded action
        redirect(action: 'show')
    }
}

You can also override the scaffolded actions:

import grails.plugin.scaffolding.annotation.Scaffold

@Scaffold(Book)
class BookController {

    // overrides scaffolded action to return both authors and books
    def index() {
        [bookInstanceList: Book.list(),
         bookInstanceTotal: Book.count(),
         authorInstanceList: Author.list()]
    }

    def show() {
        def book = Book.get(params.id)
        log.error("{}", book)
        [bookInstance: book]
    }
}

All of this is what is known as "dynamic scaffolding" where the CRUD interface is generated dynamically at runtime.

By default, the size of text areas in scaffolded views is defined in the CSS, so adding 'rows' and 'cols' attributes will have no effect.

Also, the standard scaffold views expect model variables of the form <propertyName>List and <propertyName>Count for collections, and <propertyName> for a single instance. The list view declares the count as Number, so it accepts the Integer or Long a controller supplies.

Static Scaffolding

Grails lets you generate a controller and the views used to create the above interface from the command line.

Generating Controllers and Views

To generate a controller type:

grails generate-controller Book

or to generate the views:

grails generate-views Book

or to generate everything:

grails generate-all Book

If you have a domain class in a package or are generating from a Hibernate mapped class remember to include the fully qualified package name:

grails generate-all com.bookstore.Book

A generated controller redirects a successful form save or update to its own show action, so it keeps working when it is renamed, for example to serve the domain class at a different URL.

Generating Scaffolded Controllers and Services

To generate a scaffolded controller (using @Scaffold annotation):

grails create-scaffold-controller Book

To generate a scaffolded service:

grails create-scaffold-service Book

To generate both a scaffolded service and controller together:

grails generate-scaffold-all Book
Command Options

The create-scaffold-controller command supports the following options:

  • --force - Overwrite existing files

  • --namespace - Set the controller namespace

  • --service - Use grails.plugin.scaffolding.RestfulServiceController instead of grails.rest.RestfulController

  • --extends - Specify a custom class to extend (default: grails.rest.RestfulController)

The create-scaffold-service command supports:

  • --force - Overwrite existing files

  • --extends - Specify a custom class to extend (default: grails.plugin.scaffolding.GormService)

The generate-scaffold-all command supports:

  • --force - Overwrite existing files

  • --namespace - Set the controller namespace

  • --serviceExtends - Specify a custom class to extend for the service (default: grails.plugin.scaffolding.GormService)

  • --controllerExtends - Specify a custom class to extend for the controller (default: grails.plugin.scaffolding.RestfulServiceController)

Examples:

# Generate a scaffolded controller that uses a service
grails create-scaffold-controller Book --service

# Generate with a custom class to extend
grails create-scaffold-service Book --extends=com.example.MyCustomService

# Generate both with namespace
grails generate-scaffold-all Book --namespace=api

Customizing the Generated Views

The views adapt to Validation constraints. For example you can change the order that fields appear in the views simply by re-ordering the constraints in the builder:

def constraints = {
    title()
    releaseDate()
}

You can also get the generator to generate lists instead of text inputs if you use the inList constraint:

def constraints = {
    title()
    category(inList: ["Fiction", "Non-fiction", "Biography"])
    releaseDate()
}

Or if you use the range constraint on a number:

def constraints = {
    age(range:18..65)
}

Restricting the size with a constraint also effects how many characters can be entered in the d view:

def constraints = {
    name(size:0..30)
}

The Fields Plugin

The Grails scaffolding templates make use of the Fields Plugin. Once you’ve generated the scaffold views, you can customize the forms and tables using the Taglib provided by the plugin (see the Fields plugin docs for details).

<%-- Generate an HTML table from bookInstanceList, showing only 'title' and 'category' columns --%>
<f:table collection="bookInstanceList" properties="['title', 'category']"/>

Customizing the Scaffolding templates

The templates used by Grails to generate the controller and views can be customized by installing the templates with the install-templates command.