Nested NamedDomainObjectContainer

I’m trying to do something similar to Gradle User Guide at 53.6. Maintaining multiple domain objects. The difference is that I have a NamedDomainObjectContainer, that has another collection of objects. For example, here is a short DSL:

authors {
    gradle {
        quickStart {
            sourceFile = file('src/docs/quick-start')
        }
                  userGuide {
        }
                  developerGuide {
        }
    }
          sean_gillespie {
        greatMysteryNovel {
        }
    }
}

The rest of the code here: https://gist.github.com/sgillespie/5006107

And so my root class Author has a collection of Books, where the names quickStart, userGuide, developerGuide, and greatMysteryNovel all belong to a collection of Books belonging to an Author of either gradle, or sean_gillespie. Check out my domain objects @ line 46.

I think that makes sense, but I can’t figure out how to hook this up. The plugin apply, obviously not complete, is @ line 32. And I can’t figure out which steps I’m missing.

In any case, when I try what I have I’m getting:

* What went wrong:
A problem occurred evaluating root project 'dsl-test'.
> No signature of method: Author.quickStart() is applicable for argument types: (build_3socqihdnscdldni40777kinko$_run_closure1_closure3_closure5) values: [build_3socqihdnscdldni40777kinko$_run_closure1_closure3_closure5@584ba778]
  * Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.

It’s because ‘Author’ is not a container, but its ‘books’ property is.

You need…

authors {
    gradle.books {
        quickStart {
            sourceFile = file('src/docs/quick-start')
        }
        userGuide {
        }
        developerGuide {
        }
    }
    sean_gillespie.books {
        greatMysteryNovel {
        }
    }
}

That makes sense, but what if I wanted Author to be a container?

There’s no way to do this in the public API right now as you’d need to subclass one of the internal types.

Are you sure that’s what you want to do here though? Seems a bit awkward to say Author is-a container of Books. Seems like more of a has-a scenario.

Hi Luke,

Sorry to resurrect this, but your first answer seems to suggest that it is possible to nest NamedDomainObjectContainers. However, when I try the sample code from the gist, I still get errors.

I think at least the apply method is incorrect, because it does not set the “books” container on an “author” instance:

void apply(Project project) {

def authors = project.container(Author)

authors.all { author ->

books = project.container(Book) // This doesn’t quite work

books.all { book ->

book.sourceFile = project.file(“src/docs/$name”)

}

}

project.extensions.authors = authors

}

I don’t see how this should be written though. How do you make sure the “books” container gets created for every “author” instance?

Can you provide detail on the errors you get please.

Hi Luke,

Using the suggestion of your first reply I get:

C:\dev\projects\devops\gradle>gradle tasks
  FAILURE: Build failed with an exception.
  * Where:
Build file 'C:\dev\projects\devops\gradle\build.gradle' line: 4
  * What went wrong:
A problem occurred evaluating root project 'gradle'.
> Could not find method books() for arguments [build_7iqkhufc8mbvchv2coa8kkvjrb$_run_closure1_closure3@fae3852] on build
 'gradle'.
  * Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.
  BUILD FAILED

I suppose there should be something that configures “books” from a closure, and that “books” should be set on an “author”. The “books” task also looks wrong. So I tried this:

apply plugin: DocumentationPlugin
   authors {
    gradle.books {
        quickStart {
            sourceFile = file('src/docs/quick-start')
        }
                  userGuide {
        }
                  developerGuide {
        }
    }
          sean_gillespie.books {
        greatMysteryNovel {
        }
    }
}
      task books << {
    authors.each { author ->
         author.books.each { book ->
// --> author.books.each instead of author.each
            println "$book.name -> $book.sourceFile"
        }
    }
}
   class DocumentationPlugin implements Plugin<Project> {
    void apply(Project project) {
        def authors = project.container(Author)
           authors.all { author ->
            author.books = project.container(Book)
// ---> set books on author
            books.all { book ->
                book.sourceFile = project.file("src/docs/$name")
            }
        }
           project.extensions.authors = authors
    }
}
   class Book {
    final String name
    File sourceFile
       Book(String name) {
        this.name = name
    }
}
   class Author {
    final String name
    NamedDomainObjectContainer<Book> books
       Author(String name) {
        this.name = name
    }
      void books(Closure closure) {
        books.configure(closure)
 // ---> configure books from a closure
    }
}

But that gives me the same output as before. I use gradle 1.8.