2012年10月13日 星期六

如何在Grails 上實現多對多的關係(四) : 怎麼實作

寫了這麼多廢話,該說如何實作了。我們先從使用者開始。


class LoginUsers{
   static hasMany = [roleLink : RoleUsers]
   String userid
   static constraints = {
      userid(unique:true)  
   }

 List roles(){
    return roleLink.collect{it.role}
 }
 List addRole(Roles r){
    RoleUsers.link( this , r )
    return roles()
 }
 List addRoles(List roleids){
    for(rid in roleids){
       RoleUsers.link( this , Roles.get(rid))
    }
    return roles()
 }
 List removeRole(Roles r){
    RoleUsers.unlink(this,r)
    return roles()
 }
 void removeAllRoles(){
    RoleUsers.unlinkAllRolesFromUser(this)
 }
}
稍微解釋一下。還記得測試的程式嗎, aska.roles() 就可以得到角色,背後怎麼做呢?使用者的類別中我們這樣定義過…

   static hasMany = [roleLink : RoleUsers]

不用宣告一個 Set roleLink 在上面,GORM 自動幫我們加入了。所以呼叫 aska.roleLink() 就會馬上得到屬於aska的List<RoleUsers>

但我說過 RoleUsers 不算是一個真正的「物件」,我們並不關心它( 雖然它要做很多事),我們要的是群組。所以這一部份,我們要再加上collect後,封裝在 roles() 的方法裡丟出Roles。 

如果你還不知道collect的話,請看前面的介紹,這裡很簡潔的把 RoleUsers裡屬於 aska 的群組蒐集起來丟出去。

class LoginUsers {
    static hasMany = [roleLink : RoleUsers]
}
class RoleUsers {
    LoginUsers user
    Roles role
    static constraints = {  
    }
    static belongsTo = [user:LoginUsers , role:Roles]
}

這樣夠清楚吧。有這樣的宣告後,aska只要呼叫 roleLink.collect{it.role} 一行就完事了,非常給力。

可想而知 Roles的部份也是 userLink.collect{it.user} 來得到一個群組下的使用者們。這邊我就不再解釋一次了。

aska.addRole ,因為我們要新增一個 RoleUsers的物件,所以責任上我們就給RoleUsers去做。先定義方法叫做link。加多數的群組,就是呼叫link 多次。removeRole就先定義叫 unlink。 Roles的類別,可想而知也定義了類似的方法,不再解釋

class Roles {
        String name
        static constraints = {
             name(unique:true)
        }
        static hasMany = [ userLink:RoleUsers  ]
 List users(){
    return userLink.collect{it.user}
 }
 
 List addUser(LoginUsers u){
    RoleUsers.link( u , this)
    return users()  
 }
 List addUsers(List uids){
    for(uid in uids){
       RoleUsers.link(LoginUsers.get(uid) , this)
    }
    return users() 
 } 
 List removeUser(LoginUsers u){
    RoleUsers.unlink( u , this)
    return users()
 }
 void removeAllUsers(){
    RoleUsers.unlinkAllUsersFromRole(this)
 }
}

重頭戲在 RoleUsers 裡。

class RoleUsers {
 LoginUsers user
 Roles role
 static constraints = {
  
 }
 static belongsTo = [user:LoginUsers , role:Roles]
 
 static RoleUsers link(LoginUsers u , Roles r){
   def lk = RoleUsers.findByUserAndRole(u,r)
   if(!lk){
   //if not in db then we start to link
     lk = new RoleUsers()
     u.addToRoleLink(lk)
     r.addToUserLink(lk)
     lk.save(flush:true)   
   }
   return lk
 }
 static void  unlink(LoginUsers u , Roles r ){
   def lk = RoleUsers.findByUserAndRole(u,r)
   if(lk){
     u.removeFromRoleLink(lk)
     r.removeFromUserLink(lk)
     lk.delete(flush:true)
  }
 }
 static void unlinkAllRolesFromUser(LoginUsers u){
  def lks = RoleUsers.findAllByUser(u);
  if(lks){
   for(lk in lks){
      lk.user.removeFromRoleLink(lk)
      lk.role.removeFromUserLink(lk)
      lk.delete(flush:true)
   }
  }
 }
 static void unlinkAllUsersFromRole(Roles r){
  def lks = RoleUsers.findAllByRole(r);
  if(lks){
    for(lk in lks){
      lk.role.removeFromUserLink(lk)
      lk.user.removeFromRoleLink(lk)
      lk.delete(flush:true)
   }
  }    
 }
}

我假設你已經會基本的GORM了,所以只說重點。

在做鏈結與非鏈結時,記得先「解除關係」。

link 這個方法中透過


  • LoginUsers 的 hasMany 裡定義的變數名稱為roleLink,
  • 並且RoleUsers屬於 LoginUsers
  • 於是Grails就會在LoginUsers中,自動幫你合成方法 addToRoleLink



addToRoleLink 如此就變成是內建的方法了,不用再實作了。

記得要從「一」方去加「多」方,因為有主權的一方擁有責任。
aska.addToRoleLink(link),把自己加到鏈結當中。Roles也要做一樣的事。最後再save 那個 link (也就是RoleUsers) 則大功告成。

相反的,非鏈結要做的步驟也是一樣。把二邊的關聯去除後,再把 link 刪除。若你忘了先把關聯解除,就會得到exception。

再來是一個使用者想要把所有的群組刪掉。首先得到屬於他的 List<RoleUsers>後,一樣是要先解除關聯。要從「一」方來呼叫removeFrom,所以還得要先連回「一」方再分別解除,這也就是 lk.role.remove... 與 lk.user.remove....的意義。

程式是否少到令你難以置信?這中間沒有任何的 xml 任何的 annotation。類別的關聯性也不用再做文件說明,非常的明白易懂。定義好 RoleUsers裡的方法實作後,再來就沒有什麼事可以做了。呃,對,你也不用再管DB裡的表格了,Grails會自動幫你處理。

快速的做一些ui 的假想

有一個使用者的列表頁,點進去某個使用者之後,會出現基本資料維護,下方是他可以使用的群組。

在controller 裡我們快速的呼叫這二個方法塞成params丟到gsp中馬上就可以使用。


[role_list : Roles.list() , checked_role_list : aska.roles() ]

在維護頁裡,按下確認後回到controller,我們會先刪除aska的所有群組,然後再加有勾選的部份。

aska.removeAllRoles()
aska.addRoles(params.checkedRoleId)
二行搞定… 若前端頁面變成奇怪的新式UI,使用者點一下某個群組就會動態的Ajax往後端呼叫做更新,再按一下就刪除這個群組,那也是 aska.addRole(Roles.get(rid)) 與 aska.removeRole(Roles.get(rid)) 而已… 花了蠻多時間寫的,希望對大家有幫助。

沒有留言:

張貼留言