2012年9月30日 星期日

Grails 2.x 整合測試要注意的地方

2.x 版的整合測試 ( integration test ) 要注意的幾個地方 
  • class UsersIntegrationTests extends GroovyTestCase 記得要extends 這個類別,不然無法啟動orm。 
  • 在塞資料到資料庫時,要記得「所有的屬性 nullable 預設為 false」,也就是所有的值你都要塞入,如果不想要的話要自己明確指定 nullable:true
  • 在整合測試一直找不到原因的時候,例如當初 
def user = new Users( userId : 'joe') 
assertNotNull user.save(flush:true) 

最後一行一直出錯,錯誤訊息很模糊,一直不明其因。
在整合測試中,也請記得 

println user.errors 

很快的你就會看到原因了。印出來之後才發現我的屬性沒有全塞。
很快的再 Google 一下,才發現 2.x 預設的nullable屬性改了。
總之,希望有需要幫助的人剛好看到。 
* 在gsp/jsp 丟到controller的東西是 "blank" 喔! 沒填值的話,等於是丟空字串,並不是null。
 Grails 2.x integration test save not working failed gorm

Groovy與XML的相遇(三) : 如何找到節點

這一次就不寫 Java Code了,因為用傳統的寫法會發瘋。

一樣,以剛剛那個xml為範例

<root>
  <star age='22' blood='A'>布萊德彼特1</star>
  <star age='26' blood='C'>小勞勃道尼</star>
  <star age='24' blood='B'>基努李維</star>
</root> 

(明明他們就沒那麼年輕…)

問題1:  找到年紀小於25歲的男星,我只要他們的名字

println roots.star.find{  it.@age <=25 }.text() 

下底線又用了閉包傳入。很簡單吧! 可是執行失敗說!
因為xml讀進來都是字串啊,Groovy沒有人工智慧。
好,那怎麼辦…? 你如果有從我第一篇開始看,你應該記得轉型吧。

println roots.star.find{  it.@age as int <=25 }.text() 

你很快樂的按下去,不過結果還是錯的,你不要把Groovy當神好嗎。

println roots.star.find{  (it.@age as int) <=25 }.text() 

加個括號就行了。是那裡面的東西要轉成 int ,後面再來比條件。
問題1的解法,一行結束! 不過剛剛符合條件的男星有二個耶 ?
find 只會傳出第一個 match 的。
findAll 會傳出都符合條件的。於是我們再試一次

List list = roots.star.findAll{  (it.@age as int)  <=25 } as List
 

沒錯,還順便轉了型,裡面裝好了 groovy.util.Node。
List<groovy.util.Node> 啦。

問題2 : 把這些明星的年紀由小到大排序,呃,我要大於23歲的。然後裝成 List<Integer>。

當然以Groovy來說一定要秒殺解決的。


List list2 = (roots.star.@age.findAll{ (it as int ) >=23 } as List).sort()


我把這一行一個一個說明;
  • 取得root下的所有明星節點,但是我要他們的age而已,這時候已經是單純 age 的字串集合了。
  • 從這個結果中,幫我找 age 集合中年紀大於23 歲的,並轉型成 list。
  • 然後再排序。

是不是很方便呢? 其它的進階 xml 密技,就給你自己去研究了 !



Groovy與XML的相遇(二) : 解析文件

按照傳統,還是先來一段 Java code, 31行


import org.xml.sax.SAXException;
import org.w3c.dom.*;
import javax.xml.parsers.*;
import java.io.IOException;

public class ParseXml {
  public static void main(String[] args) {
    DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
    try {
      DocumentBuilder db = dbf.newDocumentBuilder();
      Document doc = db.parse("src/languages.xml");

      //print the "type" attribute
      Element langs = doc.getDocumentElement();
      System.out.println("type = " + langs.getAttribute("type"));

      //print the "language" elements
      NodeList list = langs.getElementsByTagName("language");
      for(int i = 0 ; i < list.getLength();i++) {
        Element language = (Element) list.item(i);
        System.out.println(language.getTextContent());
      }
    }catch(ParserConfigurationException pce) {
      pce.printStackTrace();
    }catch(SAXException se) {
      se.printStackTrace();
    }catch(IOException ioe) {
      ioe.printStackTrace();
    }
  }
}

我不針對上面抄來的程式做修正了,反正大約都是一層的xml簡單文件而已。下面是 Groovy 的寫法:

def roots = new XmlParser().parse("D:\\test.xml")
roots.star.each{   
   println "age="+it.attribute("age") +" " +  it.text() 
}

處理的是這份文件

<root>
  <star age='22' blood='A'>布萊德彼特1</star>
  <star age='26' blood='C'>小勞勃道尼</star>
  <star age='24' blood='B'>基努李維</star>
</root> 

你應該已經知道 it 是預設的,如果你的閉包參數只有一個,那麼it 就是指這個物件。 唯一要注意的就是,如果要取 tag 裡的文字是用 text(),要取參數就是 it.attribute("名稱")。

( 取參數也可以用 it.@age ,如果你開始習慣之後,就可以改成Groovy提供的做法。  )

roots.star.each 這種語法或許 Java 的程序員還不是很習慣,但其實jQuery 裡也大量使用這種觀念啊。

$("#talbe1 tr)".each({

});
是否你覺得似曾相識呢? 我把它換行一下。

$("#talbe1 tr)".each(  {這裡就是一個活生生的閉包啊}   );

再注意一次相同性,都是把閉包 { } 丟到方法each 中做參數。 在這裡閉包就像是一個沒名字的方法。

你還是有一點意見對吧…

roots.star.each{   
   println "age="+it.attribute("age") +" " +  it.text() 
}

你說明明就有一點不同,和jQuery不同啊,少了圓括號與最後的分號。
那我們改一下再來跑一次。

roots.star.each({     
    println "age=" + it.@age +" " +  it.text()   
});
我連分號都加了,這樣你沒話說了吧,還是可以跑啊。 why!!
為何在Groovy 不用加 ( ) 呢? 這是Groovy 提供的一種簡潔式的寫法。好啦,我直接整理給你看三種不同寫法,都可以跑。

roots.star.each({     
     
});
roots.star.each(){     
     
};
roots.star.each{     
    
};
這三種都是一樣的意思。我很快的總結一下

  1. 真正的寫法是如此,閉包是each的參數。 
  2. 為了好看,閉包可以放在each()的後面,因為它太長了嘛! 
  3. 如果each只收一個閉包做為參數,那 ( ) 就可以省了,它是2 的再精簡寫法。

再感覺一下… Groovy 是不是沒那麼奇怪了呢? 是不是常覺得…如果jQuery的寫法可以用在 Java 多好? 如果有這種感覺的話,你才是真的是個程序員!

Groovy 與 XML 的相遇(一) : MarkupBuilder + XmlSlurper

用Groovy 來產生 xml 再簡單不過了。雖然以前的 dom4j也是用物件的方式操作,但你要做的功夫相當的多。 先貼Java要怎麼寫…大約51行

import org.w3c.dom.*;
import javax.xml.parsers.*;
import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.StringWriter;

public class CreateXml {
  public static void main(String[] args) {
    DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
    try {
      DocumentBuilder db = dbf.newDocumentBuilder();
      Document doc = db.newDocument();

      Element root = doc.createElement("root");      
      doc.appendChild(root);

      Element star1 = doc.createElement("star");
      Text text1 = doc.createTextNode("布萊德彼特");
      star1.appendChild(text1);
      root.appendChild(star1);

      Element star2 = doc.createElement("star");
      Text text2 = doc.createTextNode("小勞勃道尼");
      star2.appendChild(text2);
      langs.appendChild(star2);

      Element star3 = doc.createElement("star");
      Text text3 = doc.createTextNode("基努李維");
      star3.appendChild(text3);
      langs.appendChild(star3);

      // Output the XML
      TransformerFactory tf = TransformerFactory.newInstance();
      Transformer transformer = tf.newTransformer();
      transformer.setOutputProperty(OutputKeys.INDENT, "yes");
      StringWriter sw = new StringWriter();
      StreamResult sr = new StreamResult(sw);
      DOMSource source = new DOMSource(doc);
      transformer.transform(source, sr);
      String xmlString = sw.toString();
      System.out.println(xmlString);
    }catch(ParserConfigurationException pce) {
      pce.printStackTrace();
    } catch (TransformerConfigurationException e) {
      e.printStackTrace();
    } catch (TransformerException e) {
      e.printStackTrace();
    }
  }
}
雖然非常容易理解,一個一個物件生成,但這麼單純的事,要花51行來寫,覺得你快要死了嗎? (況且他們的屬性我還沒有加進去哩……) 來一點輕鬆的吧。先寫了一個第一版,15行,很棒了吧??

import groovy.xml.MarkupBuilder
import groovy.util.XmlSlurper
def file = new File("d:\\test.xml")
def objs = [
    [ age: 22, name: "布萊德彼特", blood: "A" ],
    [ age: 26, name: "小勞勃道尼", blood: "C" ],
    [ age: 24, name: "基努李維", blood: "B" ] ]
def b = new MarkupBuilder(new FileWriter(file))
b.root {
    objs.each { o ->
        star(age: o.age , blood:o.blood ,  o.name) {          
        }
    }
}
如果不要先把資料弄成集合 ( 雖然這樣比較容易看的懂 ),要寫的行數更少…

def xml = new groovy.xml.MarkupBuilder(new FileWriter("d:\\test.xml"))
xml.root(){
  star(age: 22, blood: "A" , "布萊德彼特")
  star(age: 26, blood: "C" , "小勞勃道尼")
  star(age: 24, blood: "B" , "基努李維")
}

對,你沒看錯,就是6行。 除了行數變少之外,易讀性完全破表。 root 並不是一個死的方法,你將它改成stars,那麼產出的xml的根節點就叫做stars了。裡面所有的東西都能改,只要你結構掌握住了,xml 某一個節點要加新的屬性,易如反掌! 維護程式的人,你覺得他看上面的51行容易理解,還是下面的6行呢?

我們都知道重構的重要。若只是為了把原來就繁複的語法歸類而重構,那為何不一開始就精簡語法? 

這樣精簡的xml 語法,很大的功勞一樣是用閉包做的。程式人員只要專心在資料面,如何產生xml 的部份並非你的價值所在。除非你是開發 xml parser 的人,否則不要沉醉在自己高深的50行程式中。這裡面並沒有價值。再一次強調,就像醫生的價值在於醫好別人的病,不在於你會用幾種手術刀。若你可以空手就取出腫瘤,30秒結束回家吃飯,沒有病人會想抱怨的。

再談 Closure

上一篇講了,閉包是物件,不是方法,雖然使用上真的很像,這裡我們直接做證明…



  Closure comparator = { String a, String b ->
    a.compareToIgnoreCase(b)
  }
這樣很明白了吧,Closure的確是物件。差別在這個物件把自己當參數傳入某個方法後,這個方法會執行 Closure裡的行為。 當然在宣告的時候和傳統的「方法」不同,你要用 { } 括起來,而且要寫 = 。 參數也不是方法一樣的宣告方式,不過概念是一樣的。 只要能接受語法後,就把它想成「動態的方法」就好了。 在書上的例子寫到


Closure comparator = { String a, String b ->
   a.compareToIgnoreCase(b)
}
List fruit = [ "apple", "Orange", "Avocado", "pear", "cherry" ]
fruit.sort(comparator)
println "Sorted fruit: ${fruit}"

這是一種寫法。先定義好有名有姓的閉包,當然 sort 也要接受這樣的閉包,不然你也是白寫的。 能夠變的地方在裡面實作的「比較方式」而已。 若還記得我們上篇文章,那是一種更直接寫的寫法。就像暱名的方法一樣。

 rpt_list.sort{o1,o2->
    o1.total<=>o2.total
 }
後面還會很大量的使用 Closure,所以目前暫時還不習慣也是無所謂的。

2012年9月29日 星期六

Groovy 的一些好用語法(二) << , 動態屬性 , collect , sort ,

Groovy 是一種動態語言,可以輕易的將方法加到原來死板的 Java 類別中,這樣可以帶來很多好處,有時候太有彈性,甚至用起來有一點頭暈腦脹,所以先整理一下一些好用的方法。

先來講 << 。


List rpt_list = new ArrayList()
rpt_list << new Report(name:"as1" , m1:10 , m2:20 , total:30)
rpt_list << new Report(name:"as2" , m1:40 , m2:40 , total:80)
rpt_list << new Report(name:"as3" , m1:20 , m2:30 , total:50)

<< 看起來很奇怪,但說起來也蠻好理解的,就是「塞過去」。 看箭頭的方向也知道,我要把右邊的塞到左邊。這個是 Java 沒有的語法。 你要是看起來很不習慣,一樣還是可以用 add ,這是 Groovy 的好處,你把他想成加強版的 Java 即可。

new Report 右邊的東西,是把屬性像 map 一樣傳進去。冒號的左邊是key右邊是value。還蠻容易理解的吧。

這邊有一個有趣的地方。一般來說 Java 要動態呼叫一個方法或是屬性,有一點麻煩,也就是說我們「執行時期」才知道要呼叫誰的話,在 Java 是比較不直觀的方式處理的。再一次強調,我的觀點仍然認為,程式的價值不在這些轉換上,所以務必要很容易做到

既然你可以把 map 當建構子傳入,那你可能會想到他是怎麼動態去mapping。
假設你要自己寫也不難,在 Groovy 來說只要寫

def dynamic_prop="total"

assert rpt_list[0]."${dynamic_prop}" == rpt_list[0]."total"
assert rpt_list[0]."total" == rpt_list[0].total

下面二個斷言都會是 true。相信你已經知道 ${ } 是像jsp 裡的變數,Groovy可放在字串中。
所以 rpt_list[0]."${dynamic_prop}"  => 會變成 rpt_list[0]."total"


而造成了可以簡單的動態呼叫的效果。相對於 Java 直接又容易理解。

好了你囉嗦了這麼久,該說 collect 了吧。我們已經準備好剛剛的 List 了

List rpt_list = new ArrayList()
rpt_list << new Report(name:"as1" , m1:10 , m2:20 , total:30)
rpt_list << new Report(name:"as2" , m1:40 , m2:40 , total:80)
rpt_list << new Report(name:"as3" , m1:20 , m2:30 , total:50)

再來你 GSP/JSP 的頁面,老闆要你最上面要先列出一個一個總和。
聰明的你應該想到上一集的招式了

List rpt_total_list = rpt_list*.getTotal()


事情有這麼簡單當然最好,但是老闆改口說,業務要看的是 / 1000 的單位啦,不然值太大。
你抓了抓頭,這樣 「星點」好像不能用了,其實還有更棒的招,就是collect,一行。

List rpt_total_list = rpt_list.collect{ it.total/1000 } 

我紅色標起來的是 Closure,大陸翻閉包,很難聽,就還是叫 Closure好了。
這樣說,閉包是一個物件,這個物件裡要寫一些邏輯,包起來當參數餵給 collect。
collect 裡頭,Groovy 自己幫你實做了,你要餵它的就是閉包 ( 好吧…叫閉包也行 )
collect 收到閉包後他做了什麼事 ?
其實 collect 在上例被呼叫了三次,因為rpt_list 裡有三個物件。
這邊依序取這三個物件的 total 後除 1000 ,然後add 到List 中,有多少加多少,加到完之後結束,把 List 傳出來這樣。
如果用傳統的 Java 來寫,又要容易閱讀的話 :

private List<Report> getTotalList(){
  List rpt_total_list = new ArrayList();
  for(Report r : rpt_list){
     rpt_total_list.add(r.getTotal);
  }
  return rpt_total_list ;
}
//煩死了…一行Groovy比七行Java

閉包使用起來像一個方法,只是不用再獨立寫出來。Groovy 提供了很多對 Collection 的瘋狂技巧。再下來我們講sort。 想到要排序剛剛的sort就很頭痛,因為在 Java 來說,你得這樣寫。你要實作compare來完成這個需求,剛剛的名言又要再講一次,這種東西沒有價值,越好寫越快。價值在客戶要用什麼排序,不是在你怎麼寫出這段程式。
public void sortPeopleByGivenName(List personList) {
  Collections.sort(personList, new Comparator() {
  public int compare(Person p1, Person p2) {
  return p1.getFamilyName().compareTo(p2.getFamilyName());
  }
  });
}
//很煩吧…
用 Groovy 的話
def sortIt(list, property) {
    list.sort { p1, p2 -> 
    p1."${property}" <=> p2."${property}" 
  }
}

長的有一點奇怪,不過你呼叫 sortIt ( rpt_list , "total") 就可以得到你想要的結果了。 上面講過了,那個${}會整個被替換成你丟入的參數。你也注意到了 sort 本身就接受閉包,所以不如改成

rpt_list.sort{o1,o2->
    o1.total <=> o2.total
}

<=> 是啥啦 ! 它就是


public int compare(int i1, int i2) {
 if (i1 == i2) return 0;
 else if (i1 < i2) return -1;
 else return 1;
}

的簡寫啦 ! 所以在閉包裡如果比的是數字,除非你的比法和人家不同,否則 <=> 就行了! 這樣子 sort 你開心了嗎 ? 今天講的就是 collect 與 sort 最後,其實 sort 還可以加條件,剛剛都白寫了 ! 沒錯,Groovy的寫法真的太有彈性了,你還可以寫成

rpt_list.sort{ it.getTotal() } 
是不是很瘋狂呢? 叫你「按照裡面每一個物件的total 給我排序!」 如果你要排的是數字,最後一個方案就是你要的! 一行搞定。

想哭嗎? 很正常。
如果不是單純的排序,你有一些奇怪規則的,留給你自己,在閉包裡動動腦筋囉!

Groovy 的轉型

Groovy 的轉型寫為 as .
為什麼 Java 已經有轉型了,Groovy 還要發明一個 ? 舉個例子


String s = "this,is,a,book"
String[] arr = s.split(",");

你這個陣列用一用之後,突然後面的邏輯變了,你需要再動態 add 一些元素到這個已經拆開的陣列中,你也知道陣列在 Java 是不能隨意改變大小的,所以你只能再寫一些程式,把這個陣子轉成 List 後再處理。
我網路上隨便查了一些寫法 :

1.

List myList = new ArrayList();
String[] myStringArray = new String[] {"Java", "is", "Cool"};

Collections.addAll(myList, myStringArray);
2.
List<String> list = new ArrayList<String>(words.length);  
for (String s : words) {  
    list.add(s);  
}  
3.
List<String> ret = Arrays.asList( 裡面放字串陣列) 

好,無論怎樣,都只是在表達一件事而已,而且這些事對真正的程式邏輯來說毫不重要,不是商業邏輯的一部份。在 Groovy 來說仍舊是一行解決。

String s = "this,is,a,book"
def ret = s.split(",") as List

( 當然第一行是字串陣列不算啦 )
這樣的好處很多! 而且中間甚至可以加上型態轉換,例如我們上一集的例子

def id_array = list*.getId() as Integer[]

我們假設 list 裡是一堆 Book ,而且他的id 是字串型態,我後面的程式需要他的 id 以數字的方式,包在整數陣列裡面。

現在你看的懂了,在第一個方法呼叫後,得到的是List<String> ,但是我們要的其實是整數陣列 ( 對不起你的同事就是要整數陣列 ) ,你打幾個字便已輕舟過萬重山。

如果你的同事突然發瘋,他決定還是List<Integer> 好了,那你就把 Integer[] 槓掉改成 List 就收工。

請記住,程式的重點都是在解決一個問題,中間轉型或是換集合物件都沒有價值。這些沒有價值的事,就是要快速處理,敏捷開發,也要有敏捷的語言搭配才可以的 !




Groovy 的一些好用語法(一) *.

 Groovy 可以和Java語法混用,這一點真的是很有趣的事。
在很不熟Groovy的時候,甚至可以把 Java 的寫法套進來。但是久了之後就會發現,Java 在一件簡單的事上,往往要很重覆的不斷寫上一樣的東西。

有些人會不屑的回應說,只是語法上的甜頭,不足掛齒。
其實這只看到表面。語法上的簡化、行數減少這是事實,不能否認,但好處為何?


  • 行數少,bug 就少。100行出錯的機率一定比10行來的高。
  • 行數少,程式的「自我描述性」就高。在 Java 中的五行,其實是在說一件事,但因為這5行會消耗你閱讀上的力氣,當行數越來越多的時候,你會很難抓住程式本身的重點。行數少,程式可以集中力氣在「表達」上,也就是易讀性會大大提升。
  • 等下的例子會十分明顯。


假設我們需要得到全部的「書」,然後把他的 id 傳到 jsp 供應用…不管你程式寫幾行,或用任何的程式語言,中文來說就是這二件事,以傳統的Java 來說:

List<Book> book = session.createQuery("from Book").list(); 
//do something....
List<Integer> list_id = new ArrayList();
for(Book b : book){
   list_id.add(b.getId());
}
request.setAttribute("id_list",list_id);

這個是我們每天在寫程式的時候做的事,雖然很囉嗦但是久了也習慣了。
Groovy 怎麼處理?

def book = Book.list()
//do something
["id_list", book*.getId() ]

啥?
對,就是你眼睛看到的。
*. 是什麼 ? 其實望文生義,就是「在這個集合裡的所有物件,都都呼叫這個方法,得到的值紀錄起來用List傳出來」。
這裡的 book 如 Groovy 第一行來說,已經是List  <Book> 了
book*.getId() 在這裡就是說「 book裡全部的物件都呼叫getId() 並塞到另一個新的List 中」
於是這個繁瑣的過程就這樣結束了。如果臨時 ui 又需要 book 裡所有的作者

["author_list" , book*.getAuthor()] 

馬上一行就可以解決並傳到前端去…
再一次說明,語法上的甜頭,並不是不重要的事,語法上的精簡,可以大大增加

  • 易讀性
  • 開發速度
  • 減少出錯機率







2012年9月24日 星期一

cygwin 在 win7 如何設定ssh

浪費了不少時間,請以這一篇做安裝,否則怎樣都會安裝不起來。

  • 在安裝cygwin時要選擇什麼。
這三樣套件記得裝。
cygrunsrv
openssh
vim

  • 環境變數
環境變數記得新增一個 CYGWIN=ntsec
在path 這個變數,最後加一個 c:\cygwin\bin

  • 如何安裝 ssh
 
*千萬記住,請按右鍵「以管理者的身份執行」來跑 cygwin 的terminal。否則最後會以沒有足夠的權限失敗做結尾。



a.先開權限
chmod +r /etc/group
chmod +r /etc/passwd   
chmod +rwx /var/

b.開始設定
輸入 ssh-host-config
Should privilege separation be used? (yes/no) yes 
Do you want to install sshd as a service? (yes/no) yes 
Enter the value of CYGWIN for the daemon: [ntsec] ntsec 
Do you want to use a different name? (yes/no) no
Create new privileged user account 'cyg_server'? (yes/no) no
Do you want to proceed anyway? (yes/no) yes

cygrunsrv.exe -S sshd 來試一下是否啟動成功。