Skip to main content

grails-developer

Comprehensive guide for Grails development, covering web applications, REST APIs, GORM, controllers, services, views, plugins, and testing with Spock and Geb

소스 정보

저장소
apache/grails-core
최근 소스 활동
2026년 9월 29일 12:42
감지된 SKILL.md 언어
영어
스타
2,937
포크
977

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
name
grails-developer
description
Comprehensive guide for Grails development, covering web applications, REST APIs, GORM, controllers, services, views, plugins, and testing with Spock and Geb
license
Apache-2.0
<!-- SPDX-License-Identifier: Apache-2.0 Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements; and to You under the Apache License, Version 2.0. --> ## What I Do - Provide detailed guidance for building Grails web applications and REST APIs. - Assist with GORM for data modeling, controllers for request handling, services for business logic, and views (GSP, JSON, Markup). - Support testing with Spock (unit/integration tests) and Geb for browser automation. - Guide plugin usage and development, security implementation, and deployment strategies. - Help with configuration, internationalization, async programming, and performance optimization. ## When to Use Me Activate this skill when developing with Grails, including: - Building CRUD applications, RESTful APIs, or full-stack web applications. - Working with GORM domain classes, constraints, and relationships. - Creating controllers, services, interceptors, or tag libraries. - Writing Spock specifications or Geb functional tests. - Configuring plugins, security, caching, or database migrations. - Deploying Grails applications to containers or cloud platforms. ## Technology Stack Grails is built on: - **Spring Boot**: 4.1.x - **Spring Framework**: 7.0.x - **Groovy**: 5.1.x - **Gradle**: 9.7 or later - **Spock**: 2.4-groovy-5.0 - **Jakarta EE**: 10 (migrated from javax.*) - **Micronaut**: Optional via `grails-micronaut` plugin ## Project Structure ``` myapp/ ├── grails-app/ │ ├── conf/ # Configuration │ │ ├── application.yml # Main config │ │ ├── logback-spring.xml # Logging config │ │ └── spring/resources.groovy # Spring bean definitions │ ├── controllers/ # Request handlers │ ├── domain/ # GORM domain classes │ ├── i18n/ # Message bundles │ ├── init/ # Bootstrap classes │ ├── services/ # Business logic │ ├── taglib/ # Custom GSP tags │ ├── utils/ # Utility classes │ └── views/ # GSP templates ├── src/ │ ├── main/groovy/ # Additional Groovy classes │ ├── main/java/ # Java classes │ ├── test/groovy/ # Test specifications │ └── integration-test/groovy/ # Integration test specifications ├── build.gradle # Build configuration └── gradle.properties # Project properties ``` ## Domain Classes (GORM) ### Basic Domain Class ```groovy class Book { String title String author Date datePublished Integer pages static constraints = { title blank: false, maxSize: 255 author blank: false datePublished nullable: true pages min: 1, nullable: true } static mapping = { table 'books' title column: 'book_title' datePublished type: 'date' } } ``` ### Relationships ```groovy class Author { String name static hasMany = [books: Book] static mapping = { books cascade: 'all-delete-orphan' } } class Book { String title static belongsTo = [author: Author] } // Many-to-Many class Book { static hasMany = [categories: Category] static belongsTo = Category } class Category { String name static hasMany = [books: Book] } ``` ### GORM Queries ```groovy // Dynamic finders Book.findByTitle("Grails Guide") Book.findAllByAuthorLike("%Smith%") Book.findByTitleAndAuthor("Guide", "Smith") Book.countByPagesGreaterThan(100) // Where queries Book.where { title =~ "%Grails%" && pages > 100 }.list() // Criteria queries Book.withCriteria { like('title', '%Grails%') gt('pages', 100) order('datePublished', 'desc') maxResults(10) } // HQL queries Book.executeQuery( "from Book b where b.author.name = :name", [name: 'Smith'] ) // Detached criteria (reusable) def criteria = Book.where { pages > 100 } criteria.list() criteria.count() ``` ### Transactions ```groovy // In services (default transactional) @Transactional class BookService { def saveBook(Book book) { book.save(failOnError: true) } } // Manual transaction control Book.withTransaction { status -> def book = new Book(title: "Test") book.save() if (someCondition) { status.setRollbackOnly() } } ``` ## Controllers ### Basic Controller ```groovy class BookController { BookService bookService static allowedMethods = [save: 'POST', update: 'PUT', delete: 'DELETE'] def index() { respond Book.list(), model: [bookCount: Book.count()] } def show(Long id) { respond Book.get(id) } def create() { respond new Book(params) } def save(Book book) { if (book.hasErrors()) { respond book.errors, view: 'create' return } bookService.save(book) redirect action: 'show', id: book.id } def edit(Long id) { respond Book.get(id) } def update(Book book) { if (book.hasErrors()) { respond book.errors, view: 'edit' return } bookService.save(book) redirect action: 'show', id: book.id } def delete(Long id) { bookService.delete(id) redirect action: 'index' } } ``` ### REST Controller ```groovy import grails.rest.RestfulController class BookController extends RestfulController<Book> { static responseFormats = ['json', 'xml'] BookController() { super(Book) } // Override to customize @Override protected Book queryForResource(Serializable id) { Book.where { id == id && active == true }.find() } } ``` ### Command Objects ```groovy class BookCommand implements Validateable { String title String author Integer pages static constraints = { title blank: false author blank: false pages min: 1, nullable: true } } class BookController { def save(BookCommand cmd) { if (cmd.hasErrors()) { respond cmd.errors return } // Process valid command } } ``` ### Content Negotiation ```groovy class BookController { def show(Long id) { def book = Book.get(id) respond book // Auto-negotiates based on Accept header } } // Or explicit def show(Long id) { def book = Book.get(id) withFormat { html { render view: 'show', model: [book: book] } json { render book as JSON } xml { render book as XML } } } ``` ## Services ### Basic Service ```groovy import grails.gorm.transactions.Transactional @Transactional class BookService { def findByTitle(String title) { Book.findByTitle(title) } def save(Book book) { book.save(failOnError: true) } @Transactional(readOnly = true) def list(Map params) { Book.list(params) } def delete(Long id) { Book.get(id)?.delete() } } ``` ### Service with Events ```groovy import grails.events.annotation.Publisher import grails.events.annotation.Subscriber class BookService { @Publisher def save(Book book) { book.save() // Automatically publishes 'book.saved' event } } class NotificationService { @Subscriber('book.saved') def onBookSaved(Book book) { // Handle event log.info "Book saved: ${book.title}" } } ``` ### Async Service ```groovy import grails.async.Promise import static grails.async.Promises.* class BookService { Promise<Book> findAsync(Long id) { task { Book.get(id) } } def processBooks() { def p1 = task { Book.findAllByActive(true) } def p2 = task { Author.list() } waitAll(p1, p2) // Both complete } } ``` ## Views ### GSP (Groovy Server Pages) ```html <!-- grails-app/views/book/list.gsp --> <!DOCTYPE html> <html> <head> <title>Books</title> <asset:stylesheet src="application.css"/> </head> <body> <h1>Books (${bookCount})</h1> <g:each in="${bookList}" var="book"> <div class="book"> <h2>${book.title}</h2> <p>By ${book.author}</p> <g:link action="show" id="${book.id}">View</g:link> </div> </g:each> <g:if test="${bookList.empty}"> <p>No books found.</p> </g:if> <g:paginate total="${bookCount}"/> <asset:javascript src="application.js"/> </body> </html> ``` ### Common GSP Tags ```html <!-- Links --> <g:link controller="book" action="show" id="${book.id}">View</g:link> <g:createLink action="list"/> <!-- Forms --> <g:form action="save"> <g:textField name="title" value="${book?.title}"/> <g:textArea name="description" value="${book?.description}"/> <g:select name="category" from="${categories}" optionKey="id" optionValue="name"/> <g:checkBox name="active" value="${book?.active}"/> <g:submitButton name="submit" value="Save"/> </g:form> <!-- Conditionals --> <g:if test="${condition}">...</g:if> <g:elseif test="${other}">...</g:elseif> <g:else>...</g:else> <!-- Iteration --> <g:each in="${items}" var="item" status="i"> ${i}: ${item.name} </g:each> <!-- Rendering --> <g:render template="bookRow" model="[book: book]"/> <g:render template="bookRow" collection="${books}" var="book"/> <!-- Security (with Spring Security plugin) --> <sec:ifLoggedIn>Welcome, ${sec.username()}</sec:ifLoggedIn> <sec:ifNotLoggedIn><g:link controller="login">Login</g:link></sec:ifNotLoggedIn> ``` ### JSON Views ```groovy // grails-app/views/book/show.gson import myapp.Book model { Book book } json { id book.id title book.title author book.author datePublished book.datePublished?.format('yyyy-MM-dd') _links { self { href "/books/${book.id}" } } } ``` ### Layouts ```html <!-- grails-app/views/layouts/main.gsp --> <!DOCTYPE html> <html> <head> <title><g:layoutTitle default="My App"/></title> <asset:stylesheet src="application.css"/> <g:layoutHead/> </head> <body> <nav><!-- Navigation --></nav> <main> <g:layoutBody/> </main> <footer><!-- Footer --></footer> <asset:javascript src="application.js"/> </body> </html> <!-- Use in views --> <html> <head> <meta name="layout" content="main"/> <title>Books</title> </head> <body> <!-- Page content --> </body> </html> ``` ## Interceptors ```groovy class AuthInterceptor { AuthInterceptor() { matchAll() .excludes(controller: 'login')
GitHub에서 보기
이 SKILL.md는 매우 커서 SkillsMP가 여기에는 첫 섹션만 미리 보여줍니다. GitHub에서 보기