2012年11月27日 星期二

[宅] Grails的Scaffolding實作

在建立一個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"是會出現錯誤的。

[JS] function物件的call()&apply()

在javascript中,除了直接以function加上"()"來執行函式外,還可以呼叫function的call()或apply()來執行,但是"foo()"、"foo.call()"和"foo.apply()"其實是有差別的,接下來我們就來看看call()的用法:

function.prototype.call()

在browser上運行的javascript,其global scope是window,但當在不同地方呼叫global的function時,我也希望這個funciton能依據呼叫的所在而使用當時的scope,這時我們就能使用call。
以下是一段簡單的程式碼:
scope="window";
foo = function(){console.log(this.scope);};
cat={
    scope:"cat",
    foo:function(){console.log(this.scope);},
    bar:function(cb){cb();}
};

foo(); //"window"
cat.foo(); //"cat"

cat.bar(foo); //"window"
foo.call(cat); //"cat"
從這段簡短的程式碼中就可以看出call可以改變韓式執行的scope,這個功能常用在事件處理上。


function.prototype.apply()


apply()的功能跟call很像,差別在於參數的傳遞方式不同:
call(valueForThis, arg1, arg2, ...) //一個一個指定參數
apply(valueForThis, arrayOfArgs) //將參數放到陣列後傳入
真的說起來,apply()還比call()早出現(前者v1.2,後者v1.5)