在建立一個Grails專案後會發現,我們找得到controller和view,但卻找不到model。原來在Grails中,MVC的model是對應到grails特有的domain class。
domain-class - MVC中的Model
開發一個mvc應用,通常要先從model下手,現在我們就來製作一個model,在終端機輸入:
grails create-domain-class user
會產生User.groovy:
package <project_name>
class User {
static constraints = {
}
}
在Rails中產生model時,可以在指令後面加上一串參數來直接定義這個class的attributes。
Grails沒有這樣的機制,所以要在產生class之後手動進行
package gbookshelf
class User {
String uid
String name
String email
static constraints = {
}
}
constraints這個參數是用來做資料驗證的,可以限制存入資料的條件,例如:
static constraints = {
uid(size: 1..20, blank: false, unique: true)
name(size: 0..80, nullable: false)
email(maxSize: 40, blank: false, nullable: true)
}
Scaffold - 好用的crud"速成"工具
在Grails中,scaffold分成動態及靜態兩種產生方式,先講動態產生的方法。在domain class中寫入所需的attributes之後,輸入:
grails generate-views .User
這行程式碼的會自動幫你產生Scaffold的"views",有"_form.gsp", "edit.gsp", "create.gsp", "list.gsp", "show.gsp" ,這些都是CRUD操作所會用到的。
到了這裡程式還是跑不起來,因為我們還沒有controller,用這個指令產生一個controller:
grails create-controller user
這時候你會拿到一個很乾淨的UserController.groovy和UserControllerTests.groovy。
雖然這時我們有了一堆views,但是因為這個很乾淨的controller根本還沒定義action所以就算啟動了這個app也是找不到"user/list"的。
動態scaffold
從這裡開始就是"動態scaffold"和"靜態scaffold"不一樣的地方了:
在UserController中加入這一行程式碼,啟動scaffold功能:
static scaffold = true
變成
class UserController {
static scaffold = true
}
這行程式碼會自動幫你搞定以下這些action:
- list
- show
- edit
- delete
- create
- save
- update
靜態Scaffold
但是這樣的機制雖然方便,但卻像個黑盒子一樣,我們不清楚裡面的流程,更無從加入邏輯。所以我們可以用這個指令來產生controller:
grails generate-controller <project_name>.User
產生出來的controller會長這樣:
package <project_name>
import org.springframework.dao.DataIntegrityViolationException
class UserController {
static allowedMethods = [save: "POST", update: "POST", delete: "POST"]
def index() {
redirect(action: "list", params: params)
}
def list(Integer max) {
params.max = Math.min(max ?: 10, 100)
[userInstanceList: User.list(params), userInstanceTotal: User.count()]
}
def create() {
[userInstance: new User(params)]
}
def save() {
def userInstance = new User(params)
if (!userInstance.save(flush: true)) {
render(view: "create", model: [userInstance: userInstance])
return
}
flash.message = message(code: 'default.created.message', args: [message(code: 'user.label', default: 'User'), userInstance.id])
redirect(action: "show", id: userInstance.id)
}
def show(Long id) {
def userInstance = User.get(id)
if (!userInstance) {
flash.message = message(code: 'default.not.found.message', args: [message(code: 'user.label', default: 'User'), id])
redirect(action: "list")
return
}
[userInstance: userInstance]
}
def edit(Long id) {
def userInstance = User.get(id)
if (!userInstance) {
flash.message = message(code: 'default.not.found.message', args: [message(code: 'user.label', default: 'User'), id])
redirect(action: "list")
return
}
[userInstance: userInstance]
}
def update(Long id, Long version) {
def userInstance = User.get(id)
if (!userInstance) {
flash.message = message(code: 'default.not.found.message', args: [message(code: 'user.label', default: 'User'), id])
redirect(action: "list")
return
}
if (version != null) {
if (userInstance.version > version) {
userInstance.errors.rejectValue("version", "default.optimistic.locking.failure",
[message(code: 'user.label', default: 'User')] as Object[],
"Another user has updated this User while you were editing")
render(view: "edit", model: [userInstance: userInstance])
return
}
}
userInstance.properties = params
if (!userInstance.save(flush: true)) {
render(view: "edit", model: [userInstance: userInstance])
return
}
flash.message = message(code: 'default.updated.message', args: [message(code: 'user.label', default: 'User'), userInstance.id])
redirect(action: "show", id: userInstance.id)
}
def delete(Long id) {
def userInstance = User.get(id)
if (!userInstance) {
flash.message = message(code: 'default.not.found.message', args: [message(code: 'user.label', default: 'User'), id])
redirect(action: "list")
return
}
try {
userInstance.delete(flush: true)
flash.message = message(code: 'default.deleted.message', args: [message(code: 'user.label', default: 'User'), id])
redirect(action: "list")
}
catch (DataIntegrityViolationException e) {
flash.message = message(code: 'default.not.deleted.message', args: [message(code: 'user.label', default: 'User'), id])
redirect(action: "show", id: id)
}
}
}
這樣整個邏輯一目了然,也可以加入自己的邏輯了。
或是我們可以用這個指令,一步產生Scaffold的"view", "controller", "test unit"
grails generate-all gbookshelf.User
小結
在剛剛的範例中會看到generate-controller和create-controller兩種指令,差別在於"
create-controller"只會產生一個空白的controller,而"
generate-controller"必須依據一個現有的domain class來產生一個scaffold的controller。而"generate-all"、"generate-controller"、"generate-views"都是用來產生scaffold所需檔案的指令。
要注意的是,當在使用這些scaffold指令時,必須在指令後加上要依賴的"project_name.domain_class",例如:
grails generate-all myApp.User
而"grails generate-all User"是會出現錯誤的。