2012年10月13日 星期六

如何在Grails 上實現多對多的關係(三) : 三者的結構

首先你一定會先定義類別吧,我們一次要定義三個類別出來。

class LoginUsers {

}
class Roles{

}
class RoleUsers{
      //這個就是多對多的中間表,我們需要它來做很多事。
}
再來我們開始定義它們的關聯,也是很直觀。


// LoingUsers 1 <-> * RoleUsers * <-> 1 Roles

class LoginUsers {
    static hasMany = [ roleLink : RoleUsers ]
}
class Roles{
    static hasMany = [ userLink : RoleUsers ]
}
class RoleUsers{
    LoginUsers user
    Roles role
    static constraints = {
  
    }
    static belongsTo = [user:LoginUsers , role:Roles]
}
LoingUsers 1 <-> * RoleUsers * <-> 1 Roles 別說你看不懂喔,正規化是幼幼班的課程。
這邊比較特別的是…明明LoginUsers與Roles參照同一個表格,為何我取不同的名字,一個是roleLink一個是userLink ? 這是你使用上的自由…

從使用者的觀點來看,這是一個群組鏈結表。
從群組的觀點來看,這是一個使用者鏈結表。

所以我訂不同的名字。為了方便,你也可以都叫做 link:RoleUsers,這邊就隨你高興囉! 不過 Roles 不一定只和Users 做多對多,他也可以與功能做多對多,所以取名時還是多加幾個字比較好。

中間表裡的每一個record,每一個物件,都一定對到1個使用者與1個群組。

從使用者的觀點來說,他是一對多的,就像一個人可以寫很多本書,多方的書「屬於」作者。 所以這裡的多方 RoleUsers 是「屬於」使用者的。

從群組的觀點來說,也是一對多的,一個群組裡可以有多個使用者。

所以在這個中間鏈結表中,他同時「屬於」二者,中間表沒有什麼主權,所以他「屬於」別人。

用這樣的方式去理解應該就簡單了。

剛剛說,中間表裡的每一列都對應到一個使用者與一個群組,所以你當然要宣告他們才連的回去。 結構的部份搞清楚了,後面就不是很難理解了。內容請看下一章。

如何在 Grails 上透過 GORM 實現多對多的關係。(二) 我就是要這樣呼叫

二個類別,LoginUsers 和 Roles,還記得吧。

        LoginUsers aska = new LoginUsers(userid:'aska')
     LoginUsers gina = new LoginUsers(userid:'gina')
     Roles admin_role = new Roles(name:'admin')
     Roles normal_role = new Roles(name:'normal')
     aska.save(flush:true)
     gina.save(flush:true)
     admin_role.save(flush:true)
     normal_role.save(flush:true)
aska 是admin , gina 是一般使用者。簡單來說這個測試就是如此。 我們要做的測試是,從aska,gina的角度加入群組,以及從群組的角度加入使用者。 當然也要從aska與gina的角度來退出群組,以及從群組的角度來刪除使用者。 GORM 是「絕對直觀」派的東西。你先看我下面怎麼寫,就知道「絕對直觀」是指什麼。 我們先來測試從群組的觀點來加入使用者,並且倒過來從使用者的角度來得到群組。

        println 'add aska to admin_role' + admin_role.addUser(aska)
     println 'add gina to normal role '+ normal_role.addUser(gina)
     println 'add aska to normal role' + normal_role.addUser(aska)
     println 'aska should have two roles = '+aska.roles()
     println 'gina should have one role = ' + gina.roles()
     println 'admin user should be aska= ' + admin_role.users()
     println 'normal users should be aska and gina= '+normal_role.users()
我們實作的程式,就是這樣呼叫。也就是說,這樣寫一定是你最滿意的。得到一個群組的物件時,呼叫 addUser 就可以加入user,還有比這樣寫更簡單的方式嗎? 先說一下,這邊的程式一定都不會過,因為這些方法都還不存在。你可能有想到說,多對多不是有「中間」的表格嗎,怎麼都沒看到? 我們在實作的時候,會透過一個中間類別來做處理,這個中間類別對我來說,不應該出現在controller或是service 之中,它不是我們關心的物件。雖然等下他要做很多事! 再來我們測remove

        //test remove 
     normal_role.removeUser(gina)
     println 'remove gina from normal users, now normal role should be only aska = '+normal_role.users()
     normal_role.removeAllUsers()
     println 'remove all users from normal role, now normal role = '+normal_role.users()
     println 'aska should have no groups after unlink all roles' + aska.removeAllRoles()
好,當我們在刪除的時候,要是你是從使用者的角度出發,你只想呼叫removeRole(role) , removeAllRoles()這樣吧。我所謂的「絕對直觀」,就是程式員的本能,如果可以這樣寫就下班有多好,從這個角度來設計就對了。從群組的角度來說,就是 removeUser(user) , removeAllUsers()這樣,反之亦然,找不到更短的寫法了。 在得到使用者的群組亦是 aska.roles() , 得到管理員群組下的人也該是admins.users(),這就是直觀導向的寫法。 下面再從使用者的角度,加入一堆群組。這個在維護畫面中常常見到啊,管理者勾一勾這個人能加入的群組,將群組id送到controller等待寫入db,所以我們也是需要這樣的功能。

        aska.addRoles([1,2])
     println 'add aska to admin and normal group, now aska join : ' +aska.roles()
     println 'add gina to normal role' + gina.addRole(normal_role)
     println 'remove gian from both roles'+gina.removeAllRoles()
     println 'gina should have no roles now'+gina.roles()
     println 'add gina to admin and normal roles '+ gina.addRoles([1,2])
     println 'clear all user from role'
     normal_role.removeAllUsers();
     admin_role.removeAllUsers();
     println 'try to add people to roles.'
     println 'add aska and gina to normal role.'+normal_role.addUsers([1,2])
     println 'add aska to admin role.'+admin_role.addUsers([1])
     println 'test remove user from roles'
     aska.removeAllRoles()
     gina.removeAllRoles()
     println aska.roles()
     println gina.roles()

我不用解釋你也看的懂,這就是我們要的程式,再來下一章要講背後我們要如何實作,以得到這直觀寫法。

如何在 Grails 上透過 GORM 實現多對多的關係。(一) 測試先行

當我完成 GORM 多對多的測試時,真的覺得很酷。坦白說,我是SQL 派的人,物件導向的Query Language 背後其實沒啥效率,不管是新增或是刪除常常多做了幾筆SQL。在複雜的程式中,其實直接透過SQL 來達成任務,在效能上是比較能接受的。

不過反過來思考,多對多的維護功能其實很常見。如果一直在程式中SQL 來SQL 去的,「程式的可讀性」會大大的降低。Hibernate出現後,程式員透過物件的方式來操作資料庫,反倒成了顯學。這邊的爭論可以寫在另外一篇文章了。

這顯學蔚然成風,但我畢竟是散淡的人,多寫幾個字總覺得麻煩,但是又無處可去。

Grails 的做法非常的另人驚艷。
不講理論,先講結果,當多對多完成的時候,我可以怎樣去得到我要的東西?
首先我們的  domain class 為 LoginUsers 和 Roles。
這個很單純,就是一個使用者可以加入多個群組,而一個群組裡也可以包含多個使用者。
Grails 講的是結果。所以你想怎麼做,可以先寫在「整合測試」中。
寫在整合測試,在跑的時候,就有db連線的功能。

整合測試

我以前一直不太懂,先寫測試有什麼好處。在傳統 Java 開發中已經夠慢了,想到要寫測試就頭大。但後來終於理解「測試先行」的意思了。

那就是先決定「我就是要這樣呼叫」。
而後再把程式的肉慢慢做出來。

這樣的差別在於,你若是「呼叫」的人,一定是想寫最少的程式來得到你要的結果。而這樣的導向,會讓程式易於理解。若先寫類別與方法,寫完了有時候發現外部程式呼叫起來很不順,這也是因為沒有從別人的觀點來開始的原因。

不過我們不是要講整合測試

不囉嗦,開始寫我們想要的呼叫方式。請看下一篇。




2012年10月9日 星期二

在Grails 中如何寄 email

首先,先在Command mode輸入

 grails install-plugin mail
一般應該是用公司的mail server發送吧 ? 第二步你要設定email 的相關資訊。打開

/your_project/grails-app/conf/Config.groovy
加入 

grails{
 mail {
      host = "78.78.78.78" 
      port = 25     
    }
}
grails.mail.default.from="itdep@aska.com" 

再來要怎麼做呢… 在 傳統的 Java 來說,你要 import 一堆 Java Email 相關的的類別,中間的辛酸就不用再提了。在Grails 的話,在程式任何一處只要寫這樣就可以寄出了 !

sendMail {     
          to "aska@aska.com.tw"     
          subject "test , post created."     
          body "${params.content}" 
       }
透過動態語言的好處,每一個類別都「突然會」 sendMail 了!
也就是「動態的」他們都擁有了這個方法。
grails email plugin 的好處在於,你不需要特別再寫一個工具類別,或是繼承,或是任何方法來達成寄信的功能。
讓程式從無到有學會寄信,不到一分鐘就可以完成。
更多的細節,請參考 http://grails.org/plugin/mail

2012年10月7日 星期日

Grails 如何取得 hibernate 的 session ?

有的時候還是忍不住想要下native sql ,在 grails 中要如何取得  session 呢?
答案簡單到不行…

首先在controller 宣告一個  def sessionFactory。

在你要執行的方法中,寫

def ss = sessionFactory.getCurrentSession()

這個 ss 就是hibernate 的session ! 等下,那怎麼取得 sessionFactory ? 別忘了Grails 內建Spring的注入功能,該注的他都幫你注入進去了。


def ret = ss.createSQLQuery("select * from post").list()
println ret.get(0)[0]

這種方法回傳的就是 List<Object[]> !
Grails 幫我們做掉太多事了! 邊學邊拍桌子啊 !

2012年10月1日 星期一

Grails 的ORM 關聯(一) belongsTo, hasMany

我們直接舉1對1,或是1對多的例子。

一對多來說,一個作者可以發表多篇文章。
那這個關聯我們要從哪邊開始加?我們可以先在「文章」(多)中先加 belongsTo。
文章「屬於」作者。這個應該沒有疑問。

class Post {
  String content
  Date dateCreated
  static constraints = {
   content(blank: false)

  }
  static belongsTo = [ user : User ]
}

最後的 static belongsTo = [ user : User ] 當然是我們注意的重點。
這個 [ user : User ] 其實是一個map,不過一個小寫的user ,一個大寫的 User有何用處?
這個user是給我們用做參考關聯的。透過這個寫法,Post 也可以反著查詢 User了。

例如有一個畫面是閱讀某人寫的文章,最上面需要出現那個人的id。因為我們在這裡已經建立好倒回去關聯 User 時的變數名稱 「user」,所以只要 post.user.userId 便很快的可以得到我們要的結果。

你可能也看過
static belongsTo = User 
的寫法,如果這樣寫的話,post 就不能反過來查到對應的 user了。如果沒有必要從 Post 反查 User 時,這樣寫當然有一點效能上的優勢。

belongsTo 還有要注意的一點,既然都說自己「屬於」別人了,所以當 User 從資料庫上被幹掉的同時,Post 也沒有任何存在的必要了。這個就是「屬於」的觀念。
當然在 User 這個類別上,也要記得加上
static hasMany = [ post : Post ]
如此一來關聯就正式接上了。接上之後有很多預設的神奇招式,在下一章會講到。

Grails 中 Domain class 如何自訂驗證

Groovy 彈性的語法,總是讓人有一點不習慣,但其實抓住幾個原則就行。
先來看這個自訂驗證的範例。

  static constraints = {
     userId (unique:true , size : 3..20)
     password(
       nullable:true , 
       size : 6..8 ,
       validator :{ password , user ->
          return password != user.userId
       }      
     )
     homepage(nullable:true , url : true)
   
  }


  1. 這個例子要達到的目的就是,使用者的密碼不要和帳號一樣即可。最大的限制式本身就是一個閉包。一般的驗證常用的就是 nullable , size 啊等等的。它有一點像 Java 後來加入的動態數量參數。所以你可以想到什麼就臨時加什麼 ( 當然缺點就是編譯時期無法知道是否正確 )。
  2. 我們主要看的是在 password裡的「自訂驗證」,這個時候要加 validator : value。在 value 的部份,我們傳入一個閉包,這邊我用藍色框起來。從key :value -> validator : value -> validator : 閉包。這樣就不會覺得 {   } 的出現很突兀了。
  3. 這個validator 所用的閉包,可以接受二個參數。password 當然就是使用者傳進來的參數囉。第二個參數是user 物件,這是方便你做比較用的。簡單來說,閉包的參數就是二個,一個是password ,一個是user 物件。若你不需要用到user 物件,那只要指名 password -> 即可。
  4. 後面要回傳true or false,所以你要告訴他怎樣是成功,怎樣驗證是失敗。