About

Programmer
鼓手
人夫
孩子的爹

Posts Tagged “ geek ”

檢查 hash format 小幫手: Hash Police

在寫 Rails test 的時候,時不時就會要檢查 hash 的格式對不對,
好比說有一個 API 是會要 return

{
  "id": 123,
  "nickname": "Michael Jackson",
  "age": 50,
  "alive": false,
  "genres": [ "pop", "funk" ]
}

這個格式如果不小心被改壞了,那接這個 API 的 client 就有可能會壞掉。
於是在 controller test 裡面就會出現: (using RSpec)

response_body_hash = JSON.parse(response.body)
response_body_hash.should have_key('id')
response_body_hash['id'].should be_kind_of(Fixnum)
response_body_hash.should have_key('nickname')
response_body_hash['nickname'].should be_kind_of(String)
# balah balah balah...

如果我們的 API 有好多個 keys, 那簡直是檢查不完,而且 code 的可讀性也大大降低。
時間久了就會產生一種想吐的感覺。

Hash Police is at your service

於是我寫了一個叫作 hash_police 的 ruby gem,
功能就是你給他一個符合格式的 hash,
他就幫你 recursively 檢查格式有沒有錯。

所以上面的 code 就會變成:

response_body_hash = JSON.parse(response.body)
rule = {
  :name => '',
  :age => 1,
  :alive => true,
  :genres => ['']
}
police = HashPolice::Police.new(rule)
result = police.check(response_body_hash)

puts result.error_messages unless result.passed?
result.passed?.should be_true

還是太醜

沒錯,上面的作法雖然可以不用一個一個 key 去檢查,
但是如果錯了的話,沒辦法好好的用 RSpec 的格式 log 出來,
所以搭配RSpec custom matcher服用的話:

RSpec::Matchers.define :have_the_same_format_as do |expected|
  result = nil
 
  match do |actual|
    police = HashPolice::Police.new(expected)
    result = police.check(actual)
    result.passed?
  end
 
  failure_message_for_should do |actual|
    result.error_messages
  end
end
 
 
actual.should have_the_same_format_as(expected)

本來的 test case 就可以寫成:

response_body_hash = JSON.parse(response.body)
rule = {
  :name => '',
  :age => 1,
  :alive => true,
  :genres => ['']
}
respose_body_hash.should have_the_same_format_as(rule)

這樣就爽快多惹。

Notes:

  • 如果有什麼 bug 或者任何問題,請不吝指教 :D

Docker 初體驗

之前就有聽說過 Docker, 看了他的介紹和 documents 感覺超潮的阿!
最近試著把它加入日常生活的 workflow, 結論是只有找到 database 的 usecase 比較合用...

日常 workflow

我日常生活(?)的 workflow 是在 Ubuntu 的電腦上跑 Rails + database (MySQL/Postgresql) + RSpec。我預期 Docker 可以幫我做到的是讓我可以不用裝相關的 dependencies 在我 local 的機器上,這樣好像就可以無痛切換 dependencies (像是 ruby 升級之類的),或者是可以順利地讓 local 的環境複製到 server 上。
但是這樣的 settings 必須要讓我可以輕鬆地做:

  1. 跑 RSpec test case
  2. migration, bundle install 等等的東西

現在的解法

目前以 Rails(或其他 ruby 相關的 project)來說,bundler 好像解決了 project 之間 dependencies 的問題。然後用 Capistrano deploy 再搭配 bundler, 可以做到一鍵 deploy.

可能的施力點

這樣看起來好像沒有什麼太多的施力點可以讓 docker 來解決問題,但是我還是想了一些:

  1. project level dependencies (ex: 不同的 projects 需要同一個 gem 的不同版本)
  2. service level dependencies (ex: 不同的 projects 需要不同版本的 Postgresql / ngxin)
  3. 把環境加到 code 裡面一起 version control, 這樣就可以有系統地 upgrade 環境

結果

project level dependencies:

像上面提到的,這個問題 bundler 似乎就解決惹,用 Docker 的優勢好像沒有很明顯。

service level dependencies:

這個部份好像用 docker 就很不錯,目前有試著把 database 交給 docker 來管:
目前的作法是在 local 就不裝 db 了,而是直接 run 一個 Docker 的 container 當作 database, 然後再把相關的 data link 到 local 的檔案
這樣的話可以做到每個 project 有自己專屬的 database, 所以如果需要不同版本的話也可以做到。
Postgresql 來說,我的作法是:

  • $HOME/var/postgres/data 裡面放 Postgresql 相關的 data
  • run 一個 docker container:
    $ docker run --name postgres_svr -d -p 9527:5432 -v /home/mz026/var/postgres/data:/var/lib/postgresql/data postgres:9.3
    

這樣就可以在 0.0.0.0:9527 連到一個 Postgresql 的 server

把環境加到 code 裡面一起 version control, 這樣就可以有系統地 upgrade 環境:

這個野望後來沒有被實現 orz, 我想到的作法是在不要自己 maintain 一個 image host 又不要付錢的前提下:

  • 加入一個 Dockerfile 在 source code 裡面,用這個檔案來控制 project 所需要的環境,
  • deploy 的時候,在 server 的機器上 build 這個 docker image

這個作法遇到的問題就是,每次在 server 上 build 的時候,bundle install 這步要重來 orz, 所以每次根本都超慢 der。
這個問題的解法應該是只要自己 host 一個 image host (或者付錢給 docker hub),然後隨著每次的 code update, 把相關的 image update 也 check in 進去 image host。如此一來,在 deploy 的時候就只要在 server 上 pull image host 上面對應版本的 image 就好了。但是目前這個的 deployment 的作法 (Capistrano + bundler)好像也相安無事,所以等有遇到困難再來研究。

心得與感想:

感想就是這個 docker 真的是很潮阿,因為 run 起來相當的快,感覺等到有需要的時候應該會是很有力的解法。
又或者如果跳離 Rails 的環境需求的話,可能又會有更有趣的應用方式。

TODO:

要試一下用 Docker 在 continuous integration 的時候來 run test

然後我買了這本書 The Docker Book 但是還沒看 orz, 希望可以變成 docker 達人阿!如果有任何好用的 usecase 請告訴我!