2013年12月31日 星期二

Rails query tips

Pluck - :

這個指令能讓搜尋結果的特定欄位獨立抽取出來放到 Array 裡,在很多情形下都很方便。
Ex: User.all.pluck :name # => ["Kevin", "Laura", "Yiya", "Diya"]


Subquery:

以第一個query的結果作為條件做搜尋,Ex:
Order.where( :product_id => Product.where("price<1000") )
(Reference: http://ruby-china.org/topics/10771)


Update with condition:

Ex: Model.where("state == 'decline'").update_all(:state => 'deny')


has_many relation with condition:

當兩個 model 彼此為一對多關係時,我們可以利用 condition 讓這個關係有所區分,我拿以下的情形為例:user 有很多訂單 (orders) ,有些是取消的、有些是處理中的、有些是已經結帳的。在這個情形下,用 user.orders 撈所有的訂單後再進行判斷顯然沒有效率、用 Order.where("{條件}") 的方法也就失去了 relation 的方便與直觀。這時我們可以做加上這幾個 relation:
has_many :cancled_orders, :class_name=>"Order", :conditions=>proc{ "status = 'Cancled'" }
has_many :handling_orders, :class_name=>"Order", :conditions=>proc{ "status = 'Handling'" }
has_many :paid_orders, :class_name=>"Order", :conditions=>proc{ "status = 'Paid'" }

這樣就可以透過如 current_user.cancled_orders 的方式取到「取消的訂單」了,相當直觀。


OR condition using Arel

Ex:
t = Post.arel_table

results = Post.where(
  t[:author].eq("Someone").
  or(t[:title].matches("%something%"))
)



2013年12月30日 星期一

動手寫一個 Rails plugin

因為開發上方便,需要一個能快速切換登入身分的小 widget,正好趁這個機會搞懂怎麼開發一個 Rails plugin。成品在這:https://github.com/kevin-shu/user_switch
以下就是步驟囉:

1. rails plugin new my_gem

首先執行 rails plugin new <plugin_name>,會產生 plugin 資料夾,結構為:
my_plugin
├── Gemfile
├── Gemfile.lock
├── MIT-LICENSE
├── README.rdoc
├── Rakefile
├── lib
│   ├── my_plugin
│   │   └── version.rb
│   ├── my_plugin.rb
│   └── tasks
│       └── my_plugin_tasks.rake
├── my_plugin.gemspec
└── test
    │
    ...(本篇不提到,忽略)
接下來就要介紹這些資料夾、檔案的用途


2. my_plugin.gemspec

這是一個 manifest 檔案,用來告訴 gem 該怎麼打包這個專案。


3. lib 資料夾

my_plugin.rb:

這就是 plugin 的核心,當裝上 plugin 後,被執行的就是這個檔案。你當然可以將所有 code 都寫在裡面,但是這樣很亂。一個功能稍微複雜且完整的 plugin 一定會有系統的做模組化。所以我們往往不在裡面寫很多 code ,而是將一個個寫好的模組 require 近來。

my_plugin 資料夾:

剛剛說的模組化的 code 通常會被分門別類放在這裡。

my_plugin/version.rb:

在初始化後,my_plugin 資料夾一開始只有這個檔案,用來記錄版本號。你如果有看一下 gemspec 的內容的話,會發現這個檔案會被 gemspec require 到。

Engine

如果你的 plugin 需要一些自定的 controller、view、assets,首先要加入這段程式碼:
module ZurbFoundation
  class Engine < Rails::Engine
  end
end
你可以將他獨立成一個檔案由 my_plugin.rb 載入,或是直接寫到 my_plugin.rb 裡。有了這段程式碼,Rails就會自動將 app 及 config 兩個資料夾下的所有檔案載入。
請注意,載入 engine 的 code 必須放在 my_plugin.rb 的最後,才能正常運作


4. app 資料夾

當你希望在安裝這個套件的專案中使用客製的 controller、assets 等檔案,就要在根目錄下建立一個 app 資料夾。結構基本上跟你熟悉的 rails 的 app 資料夾是一樣的。要注意的是,裡面的 controller 或 model 必須在 專案的 namespace 下,例如:
# CURRENT FILE :: app/controllers/my_plugin/my_controller.rb
module MyPlugin
  class MyController < ::ApplicationController
    def index
      ...
    end
  end
end


5. config

這個資料夾跟app資料夾一樣需要手動建立,最常見的用途是設定 routing,例如:
# CURRENT FILE :: config/routes.rb
Rails.application.routes.draw do
  get "team" => "team_page/team#index" , :as => :team_page
end


6. generators 資料夾

這個資料夾也是需要時才要自行建立。裡面放了一些設定檔等的 template 以及 install method,讓這個 gem 提供 install 功能,執行後會複製一份設定檔到專案中。這邊不會對 generators 多做介紹。


7. pack & push

當你的 plugin 完成後,就可以用以下指令將他打包起來,gem 會按照 gemspec 的設定做打包。
gem build /project_path/user_switch.gemspec
打包後的 gem 可以 push 到 rubygems.org:
gem push project_name-0.0.1.gem


8. 在 Rails 專案中掛上剛寫好的plugin push

我們有可以在 Gemfile 中依不同來源來做來源的設定,其中當來源是來自 local 或 git 時,是不用打包就可以直接掛上的,開發的時候來源通常是設定在 local。

(1) local: 

gem "my_plugin", :path=>"/path/to/your/plugin"

(2) git:

gem "my_plugin", :git=>"https://path/to/your/repo"

(3) rubygem.org:

gem "my_plugin"


Reference:

http://zurb.com/article/814/yetify-your-rails-new-foundation-gem-and-
http://coding.smashingmagazine.com/2011/06/23/a-guide-to-starting-your-own-rails-engine-gem/
http://guides.rubyonrails.org/plugins.html

2013年12月24日 星期二

Rails 的 Assets Convension

Rails 開發者應該都知道 assets 的路徑原則

  1. app/assets/ 放自己的 assets
  2. vendor/assetes/ 放第三方assets


但是在 app/assets 底下無法 require_tree vendor底下的資料夾,只能 require 單一檔案。要達到同樣的效果只要 require vendor的檔案,這個檔案再 require_tree 即可。

2013年12月17日 星期二

warning: toplevel constant Model referenced by NS::Model


今天遇到了這個問題,在網上搜一下找到一個滿詳盡的解釋,茅塞頓開:
Your User::File class is not loaded. You have to require it (e.g. in user.rb).
The following happens when ruby/rails sees User::Info and evaluates it (simplified; only User is defined yet).
  • check if User::Info is defined - it is not (yet)
  • check if Info is defined - it is not (yet)
  • uninitialized constant -> do rails magic to find the user/info.rb file and require it
  • return User::Info
Now lets do it again for User::File
  • check if User::File is defined - it is not (yet)
  • check if File is defined - it is (because ruby has a built in File class)!
  • produce a warning, because we've been asked for User::File but got ::File
  • return ::File

裡面提到了要手動 Require NameSpace 下的 Model,但是如果 NS 下的 model 本身有繼承到 NS 外的 Model,要確保在父類別宣告後建立才行。所以最好是在父類別檔案的結尾處 require。

2013年12月12日 星期四

以 STI (Single Table Inheritance) 及 NameSpace 提高專案維護及可讀性

正在開發的產品中,有個「個人資料維護功能」需要記錄使用者的學歷。我採用的架構是:
  • user has_many educations
  • education belongs_to school

而且 educations 和 schools 資料表中各有 type 和 stage 欄位記錄這個 學歷/學校 是屬於哪個階段 (ex. 國中、高中、大學)。(題外話,type 在 Rails 中是個欄位的保留名稱,專門給 STI 使用)

在這樣的情況下,當我使用 user.educations 取得使用者學歷後,還需要再進行 Array.detect 來找對應的階段來顯示「國中讀哪裡」、「高中讀哪裡」,這樣的邏輯實作出來相當冗餘也不好看。


用 STI 來讓多型別的 relation 更直觀


於是我便將架構做一點改變,新增幾個 education 的 sub-class,放在 "education" 的 NameSpace 下避免與既有的 model 撞名。並將 stage 改為 type,以建立 STI 的從屬關係,例如:
class Education::HighSchool < Education
  ...
end

這樣一來,我們只要再建立以下的 relation,就可以使用 user.high_school 和 user.junior_high_school 直接拿到對應學歷,相當直覺:
  • has_one :junior_high_school, :class_name=>"Education::JuniorHighSchool"
  • has_one :high_school, :class_name=>"Education::HighSchool"


STI name hack


但在這樣的情況下,因為 Rails 的 convention,他會預設抓取 type 為 "Education::JuniorHighSchool" 或 "Education::HighSchool" 的 education。但是這樣很醜,並不直覺。我希望在type中顯示 "JuniorHighSchool" 或 "HighSchool" 就好。這時我們可以在他們的父類別,也就是 education.rb 加入以下程式碼來 hack :
class Education < ActiveRecord::Base

  ...

  class << self
    def find_sti_class(type_name)
      ("Education::"+type_name).constantize()
    end

    def sti_name
      name.demodulize
    end
  end

end

find_sti_class 的用途是:在 table 中找到一個 entry 時,決定用什麼 model 來表達他,而傳入的 type_name 當然就是 type 欄位中的資料了。當我們如範例程式碼重寫 find_sti_class 後,如果傳入的 type_name 為 「HighSchool」,find_sti_class 會返回 Education::HighSchool 這個Class。

sti_name 則是在 new 一個繼承 STI 的物件時,將 return 的值塞進 type 欄位。經過範例程式法的改寫後,新增一筆 Education::HighSchool資料時,Rails 會在type欄位中存入經過 demodulize 的字串「HighSchool」,而不是 Education::HighSchool

這樣一來 Rails 就會乖乖找type為 "HighSchool" 的 Education::HighSchool model了。



2013年12月11日 星期三

Rails 的 db:migration 及 db:schema:load

相信 rails 開發者對 rake db:migrate 這個指令相當熟悉,但是可能比較少機會用到 rake db:schema:load,他是拿來幹嘛的?

db:migrate 的用途


在多個環境或多人開發的時候,我們會頻繁的使用 db:migrate ,為得是將各個環境的資料庫結構給同步起來,而不用使用同一個資料庫來開發。使用同一個資料庫除了會弄髒彼此的還境外,也會造成每改一次資料庫,就要確認其他開發者的程式碼是 workable 的,也就是「schema和程式碼不同步」的情形。

在這樣的情形下,將 migration 寫成腳本並加上 time stamp ,在各自環境的資料庫各自進行 migration 就成了一個最合理的 solution 了。而因為 migration 腳本就是整個專案程式碼的一部分,也就解決了schema 及程式碼的同步問題。每個開發者可以依照自己的進度 commit、pull、merge,再執行 rake db:migrate ,資料庫就同步了。

但是在開發過程,尤其是初期的 scratch 階段,常常會有一堆冗餘的動作,例如:
  1. 創建 users 資料表
  2. 加兩個欄位到 users 資料表
  3. 刪除 users 的其中一個欄位
  4. 再加一個欄位到 users 資料表

這時就會在系統中留下四個針對 users 資料表的 migration 腳本。這時如果有一個新加入的開發者,將程式碼拉到自己建立的新環境,使用 rake db:migrate 這個指令後, rake 就會乖乖的幫你分四個步驟做完,其實很沒必要也很笨。


db:schema:load 幫你直接從 schema.rb 初始化資料庫結構


其實在你進行migration的時候,rake 也產出了一份資料庫的架構檔案,叫做「schema.rb」,這個檔案記錄了這個專案的資料庫目前的架構。檔案中也會有一個 timestamp 記錄這是什麼時候的結構,用以區分先後順序。

而當有新的環境要被建立,或是重置資料庫的時候,我們可以利用 rake db:shema:load 這個指令讓資料庫「一步到位」,而不用一步步的照 migration 腳本來設置資料庫。



60fps scrolling using "pointer-events: none"



Reference: http://www.thecssninja.com/javascript/pointer-events-60fps

Eager & Lazy Loading

Eager Loading 就是事先將可能會用到的 resource 給準備好。
Lazy Loading 則相反,等到用到時才去fetch。

這兩個技術名詞聽起來沒什麼學問,但他代表了兩種不同的策略。何時該用哪一種是需要用經驗來判斷的,沒有孰好孰壞的問題。

2013年12月6日 星期五

用ngrok將公開網址指向你的localhost

在本機端開發網站的過程中,我們會遇到幾個情況:
要給夥伴看目前的進度
要接第三方的Oauth驗證
但如果是在家用adsl上網,會發生沒有實體ip可以接的窘境,這時我們可以使用 ngrok 的服務來產生一個公開的internet網址。

註冊Ngrok

當你沒有註冊會員時,只能隨機產生subdomain,例如:
ngrok 80 #http://580ef28f.ngrok.com

在你註冊過Ngrok的免費會員後,可以在後台取得authtoken,進而指定自己想要的subdomain:
ngrok -subdomain <your subdomain> -authtoken <your authtoken> 3000

2013年11月21日 星期四

[轉錄] 專訪引號科技團隊,十二年國教競爭力分析平台「國教通」上線 (轉自 TechOrange 橘子聚光燈)

「十二年國教」已經在今年六月由立法院三讀通過、拍板定案,2014 年正式上路。也就是說,現在就讀國三的學生,明年升高中時必須透過免試入學或特色招生的方式入學。
我們都知道,政府是在期望學生能夠全能發展的立意下推動十二年國教,然而,多如牛毛的規則始終讓其難以親近。入學方式分為免試入學與特色招生,但免試入學滿額後還要依超額比序、不同學區又各自有不同的比序,弄得家長、學生一頭霧霧水,許多家長從「搞不懂」十二年國教變成「不想搞懂」十二年國教,甚至直接把孩子丟到私立學校,一路直升,免除相關問題。自然而然大家也就感受不到政府的美意。
但大家有沒有想過,或許我們根本不必弄懂十二年國教多如牛毛的規則?
  • 十二年國教競爭力分析平台:《MilestoneS 國教通》
MilestoneS 國教通」(以下簡稱國教通)是一個幫助學生、家長、教師甚至補教業者更加了解學生學習狀況,並可以依照學生學習進度,規畫未來學習方向的平台。只要學生把想申請的學校、現有的學習進度填進系統,就能以清楚、明瞭的量化資料,了解到距離自己第一志願的不足之處,並能針對應加強之處給予建議,同時也可以一掃家長、學生對於十二年國教的一頭恐懼。
藉由國教通發表的機緣下,我們認識並訪問了其開發團隊引號科技的三個大男生:技術經理束凱文、軟體工程師劉軒銘、UX 經理劉大智。
十二年國教的規則有多複雜,我切身體驗過。在我為了瞭解國教通進而搜尋十二年國教相關資料時,也不免在幾個較為複雜的問題卡關,像是超額比序中的「就近入學」、「共同學區」等,而我相信,這些問題也同樣會困擾家長。
引號科技看見家長、學生的問題,推出台灣第一個整合全台灣 15 個不同學區超額比序資料與規則的平台。操作方式力求簡單、整潔、好上手,藉由第一次登入時的快速導覽,讓第一次使用的學生、家長可以很快地了解國教通的使用方法。
  • 國教通的優勢在哪裡?
1. 量化、圖片化(圖表化)完成程度,家長學生有目標
只需按表輸入學生的相關資料如幹部經歷、檢定證照、競賽表現等,再填入志願學區,國教通就會依照這些資料自動整理成易懂的量化模式,學生只需像完成遊戲關卡一般將表格慢慢填滿。當然填入的資料越齊全,在未來落點分析預測越準確。
2. 自主學習有方向,工欲善其事,必先利其器。
除了簡單易懂的模式與量化的圖表之外,學生還能透過國教通分析自己在十二年國教制度下的競爭力,並透過其中的客製化得分建議,清楚理解自己的不足之處,可以針對弱點處加強。
我們甚至可以預見有學生可以透過這個平台,在與家長共同規畫下,擁有清楚、明確的長短期努力方向,不必補習就可以進入自己的第一志願。這也許比一味的補習、填鴨更符合教育的本意。
3. 在未來國教通還可以 ……
隨著使用國教通的使用者越來越多,國教通可以帶來更多精準的預測,例透過越來越多人的區域、志願序甚至模擬考成績排名來做落點分析,使用的人越多,落點分析越精準。再加上引號科技也表示,未來政府若有相關政策修改,國教通也會馬上改版,調整到適合該年度政策的模式,家長與學生可以不用活在驚慌之中。
  • 規則過於複雜,家長學生卻步
訪問當天我們直接以「為什麼要做國教通?」做為開頭,這個問題由在訪問中話不多的軟體工程師劉軒銘(左)回答道:「我們一直有在關注教育方面的問題,同時也認為政府推動十二年國教是一件好事、美意,但,學生、家長卻因為相關計算的複雜性而感到卻步,這不是很可惜嗎?」
從「開始想要解決十二年國教問題」到「第一版國教通上線」,技術經理束凱文(中)向我們表示大約花了一個多月的時間,花費時間不算長,並不是他們特別「神」,而是他們之前就有過開發專案的經驗,但他們也坦承,在開發期間根本沒日沒夜,畢竟光是搞懂十二年國教的規則就花了不少時間,而他們還要把這些規則演繹成一套演算法,更要依據 15 個學區不同的計算方法來調整演算法。不過他們還是做出來了。
  • 要如何推廣國教通?
束凱文告訴我們,國教通最大的競爭對手,便是在地的相關服務。儘管國教通是第一個整合全台 15 個不同學區的系統,但部分學區已經有類似國教通的服務,外來的國教通想要打進各地早已自成一格的校務系統、補習班、學生網絡,不免有些困難。全面上線的同時,也與高雄、新竹地區校務系統商合作,整合兩地自有的校務系統直登入,為踏入各地學區的第一步。
國教通目前也積極地與提供各校模擬考的廠商洽談合作,將國教通當作模擬考的加值服務,讓考試分數直接匯入國教通系統、建檔在學生資料裡。這麼一來,除了可以蒐集到更多學生的模擬考成績,這些廠商也自然而然的成為推展國教通的業務。
  • 在我眼中,國教通還有什麼?
1. 資料的商機
我第一次了解到國教通時,除了覺得量化、圖片化複雜的十二年國規則是個非常好的主意之外,更覺得背後的商機不容小覷。如果今日國教通已經擁有部分學生的模擬考資料,是否可以針對某些科目較差的學生投放該科目的廣告?我相信使用國教通的使用者除了學生之外,家長也占了不少比例,如果有辦法進行精準行銷,非常有可能促使擔憂的家長打開錢包把小孩送去補習,這代表,補習班業者會願意打開錢包投放廣告。
除了這點之外,隨著使用者越來越多,國教通將掌握更多有價值的資料,這些資料都像是一座一座的金礦,等待挖掘。
當我們問起是否會以使用者的資料來投放不一樣的廣告時,束凱文說這還在未來的計劃中,目前首要的目標是增加使用國教通的人數。不過可以確認的是,他們絕對不會販賣、洩漏使用者的個資,而其他的廣告商機、資料商機,目前仍在規劃中。
2. 更瘋狂的填鴨式教育?
除了商機之外,使用國教通這類產品,會不會造成更瘋狂的填鴨教育?過往由於家長、學生沒有明確的目標,就是一味的補習補習補習,把每一個科目的分數衝高、追求第一志願,現在有了國教通,家長是否會按表操課,逼迫學生除了追求高分之外,還要當幹部、做志工服務以符合進入明星高中的門檻?
劉軒銘說,這絕對不是國教通的本意,也不會是教育的本意。十二年國教就是為了讓學生可以全能發展,不過擠入明星高中就是台灣目前的現況,只能期望學生們擠入明星高中之後可以好好省思一下自己未來的方向,而不是不斷地堆砌學歷。
  • 創業、工作,年經人如何抉擇?
看到這三個有理想、腦子動得快的年輕人,也不禁讓我們想了解國教通背後的開發公司,引號科技。在引號科技中負責網頁 UX 與設計的劉大智(上圖右)不厭其煩地向我們解釋了許多關於引號科技的小細節,包含引號科技目前的工作範疇:他們以由專案的 UX、流程規劃、App 開發、Data Mining 起家,曾與裕隆集團、華南銀行合作過,目前也有幾個的專案正在進行中,完全由內部主導開發的國教通便是其中之一。
引號科技目前成員只有五人,但他們不同的專案由不同的人主導,之後再向外尋找業務人選或是合作對象的方式,讓小小團隊也可以運行許多專案。例如此次的國教通專案由束凱文主導,除了來訪的軒銘與大智之外,在南部也有他們成大的學弟加入團隊運作。
引號科技的團隊人員都非常的年輕就走上創業之路,當我以「身為一個過來人,是否鼓勵年輕人自己創業」當作訪問的結尾時,束凱文語重心長地說:「最好在創業之前先工作過,才能了解到很多事情是怎麼運作的,不要抱著滿懷熱血最後卻得到一場空」。當然,這僅是他的答案,不過我相信有過不少經歷的他會這樣講,必然有一些道理。
最後,正如引號科技對自己的期許「希望可以回饋社會、為社會創造更大的價值與福祉」,這個理念我們在國教通身上看到了,也期望他們可以貫徹自己的理念,並懷抱著夢想不斷前進。
(圖片來源:人物圖片由圖片本人提供、Milestones 國教通

2013年10月20日 星期日

Riak實戰(4) - Secondary Indexes

NOSQL 不像 SQL,比較沒有 join 或針對某個 entry 的其中一個 attribute 做搜尋 (where) 的動作。通常都是以 key-value 的方式直接拿取資料,若遇到需要 join 的情況也往往是做多次的 query 拿到相關連的資料。反正概念上就是直接用 key 去對應 entry 或叫 document。
但是在使用 key 的過程中難免覺得彈性及可自定部分太少,使用起來不是這麼順手,於是 Riak 便有了 Second Indexes (2i) 的設計。

你可以將 2i 想成是 tag,讓你對特定的資料下標記,之後能更方便的 query 資料。例如我們可以從「engineers」這個 bucket 取出「language」設為「javascript」的資料。我們等等可以看看範例。


基本操作


新增並下 2i:

curl -X POST \
-H 'x-riak-index-twitter_bin: jsmith123' \
-H 'x-riak-index-email_bin: jsmith@basho.com' \
-d '...user data...' \
http://localhost:8098/buckets/users/keys/john_smith

然後這筆資料就有一個叫「twitter_bin」且值為「jsmith123」的secondary index;
以及一個叫「email_bin」且值為「jsmith@basho.com」的secondary index。


取資料 (Query):

取得特定 2i 的資料
curl http://localhost:8098/buckets/users/index/twitter_bin/jsmith123


進階操作


用範圍取資料 (Range) :

curl http://localhost:8098/buckets/users/index/field1_bin/val2/val4

範圍選取並回傳 2i (Range with terms):

curl 'http://localhost:10018/buckets/tweets/index/hashtags_bin/rock/rocl?return_terms=true'
當 return_terms=true 時,能夠連同被query到的 secondary index 一起返回


分頁 (Pagination):

相當好用,可以回傳一個token讓你再次取得下一頁的資料。
當 max_results 為 true 時,回傳結果會附上一個 continuation token,下一次的 query 只要附上這個 token,Riak 就知道要從什麼地方繼續提供資料給你。

curl 'http://localhost:10018/buckets/tweets/index/hashtags_bin/ri/ru?max_results=5&return_terms=true' | python -mjson.tool
{
    "continuation": "g2gCbQAAAAdyaXBqYWtlbQAAABIzNDkyMjA2ODcwNTcxMjk0NzM=",
    "results": [
        {
            "rice": "349222574510710785"
        },
        {
            "rickross": "349222868095217664"
        },
        {
            "ridelife": "349221819552763905"
        },
        {
            "ripjake": "349220649341952001"
        },
        {
            "ripjake": "349220687057129473"
        }
    ]
}

# Take the continuation value from the previous result set and feed it back into the query
curl 'http://localhost:10018/buckets/tweets/index/hashtags_bin/ri/ru?continuation=g2gCbQAAAAdyaXBqYWtlbQAAABIzNDkyMjA2ODcwNTcxMjk0NzM=&max_results=5&return_terms=true' | python -mjson.tool
{
    "continuation": "g2gCbQAAAAlyb2Jhc2VyaWFtAAAAEjM0OTIyMzcwMjc2NTkxMjA2NQ==",
    "results": [
        {
            "ripjake": "349221198774808579"
        },
        {
            "ripped": "349224017347100672"
        },
        {
            "roadtrip": "349221207155032066"
        },
        {
            "roastietime": "349221370724491265"
        },
        {
            "robaseria": "349223702765912065"
        }
    ]
}


效能問題

官網上有提到,當你的cluster超過512 partitions時,會有比較明顯的效能問題,這點必須注意一下。


2013年10月16日 星期三

台灣市場大?不如回來賣牛肉麵吧~

看了「台灣市場小,那市場多大才算大?」一篇文章,剛看完沒啥感覺。仔細想想後才發現自己浪費了五分鐘。

我用個簡單仿效一下這一篇的架構:
巷口的醬油很貴,多少才算便宜?

總是有人說:
「隔壁村的醬油比較好,而且又便宜」
「我們的阿婆年紀大,做出的醬油品質差」
「醬油醬油醬醬油」

……類似的觀點流傳太久、重複太多次,以致於「巷口醬油貴」幾乎成為不可質疑的觀點。但話說回來,每次聽到別人說「巷口醬油太鹹」之後,你好奇地追問:「那,醬油要多鹹才夠呢?」卻從來得不到明確答案。

總是有人說,一個村的醬油品質取決於村裡的勞動人口。我舉幾個反例吧:
隔壁村的醬油重600克,售200元。
城裡的醬油重400克,售140元。
我們的醬油1000克,300元。
你覺得呢?我不知道。

我只想當個打醬油的。 

其實大濕你的結論沒錯,因為你的文章根本沒結論

但是還是有一點營養價值的,

大概只比上面那一篇營養一點。

廢話不會錯,只是沒營養罷了。但是的確勾起了我一些想法。裡面提到統一和全家超商,說他們只靠內需,也做的很好。我同意,但這是大家都同意的廢話。那你要不要對王雪紅說,
「你們外銷做不好沒關係,你還能在台灣賣牛肉麵啊!我們內需很夠。」

這是個可行的solution,但可能不是最好的選擇。


我們來提提台灣內需市場,真的很大嗎?

統一超商夠大吧?
王品集團夠大吧?
潤泰集團夠大吧?
義美食品夠大吧?

大不大其實見人見智,如果你待的產業環繞著民生需求,也覺得上述產業做的不錯,那我想對你來說夠大了。
但就像古時候的望族或土豪一樣,大多數環繞民生需求的產業都是早期進入者,早就卡了柴米油鹽醬醋茶等等需求,口袋鼓鼓後繼續想要怎麼進一步買下這個國家人民的生存權,例如土地之類的。這個情勢在發生什麼巨大的天災人禍之前不可能逆轉。

「好吧,我們活該出生晚」

於是年輕人這麼想,他們想跳脫紅海,開始創新、開發前所未有的需求。但是問題來了,「台灣市場好像有點小,新玩意的早期使用者太少,養不起團隊」。而且就算如預期成長了,上了年紀的投資人們也會說,「你們東西很有趣,但是只做台灣賺不了幾個錢」。於是一個在美國、大陸或許能大放異彩的點子、產品就這樣在台灣胎死腹中。
市場不夠大,所以我們也不可能創造出另一個facebook、twitter或paypal。

你說我看到這篇四不像的廢文氣不氣?


廢話,內需一直都在,只是拜託不要再故步自封了

台灣一直以來都是外銷國家,從香蕉、雨傘到IC,台灣沒有太多資源但是還是能創造許多經濟奇蹟,靠的就是外銷。
內需一定一直都在,但有限,尤其是人口和產能沒有明顯增加的情況下。舉超商或餐廳來說,一家開了、一家就倒,放眼整個台灣,這樣的平衡大致是成立的。如果以目前的市場自滿,那就.....


「雪紅,趕快收收回台灣賣牛肉麵吧」


2013年10月9日 星期三

Riak實戰(3) - Link Walking

Riak中有個頗強大的功能,叫做link walking。但是在介紹這個功能之前先讓我們來談談graph DB。


什麼是Graph DB?

在台灣或許不多人接觸過graph db,但是希望大家能多少了解一下他的設計概念及使用情境。
Graph DB主要的訴求就是要解決傳統關聯式資料庫所無法處理的關係搜尋。在Graph DB的架構下,每筆資料(document形式)就是一個node,node之間可以存在edge,這個edge儲存的就是node間的關係,如下圖:

(來源:http://readwrite.com/2011/04/20/5-graph-databases-to-consider)
這樣的架構不只直覺,也克服了某些特定架構下的搜尋效能問題。舉例來說,如果你希望搜尋的是「A的朋友的朋友的親人」,這樣的架構在傳統的關聯式資料庫的實作就很複雜,因為人與人之間的關係是多對多的,一定會有個table去儲存這些關係。在我們所提到的「A的朋友的朋友的親人」的使用情境下,我們至少會做 6 次的join!( A->關係->朋友->關係->朋友->關係->親人)
這樣操作並不直覺,效能也不甚理想,所以因應這種需求的Graph DB就出現了,例如:Neo4jTitanInfinite。當然除了這編列的三種以外,還有許多graph db,甚至有些是為了生物學或特定科學領域而誕生的。



Riak 的 Link Walking

我個人覺得 Riak 為這個機制的命名實在非常生動,怎說呢?
Riak在各筆資料之間可以建立一個「單向」的 Link,透過許多link的彼此連結,我們可以再這些資料節點上「散步(walking)」,例如:
GET localhost:8091/riak/people/kevinshu/_,friend,_/_,friend,_/_,relative,_/

從上面的例子中做的事是:

  1. 我們先進入 kevinshu 這個節點
  2. 再「走」到 kevinshu 的朋友
  3. 再「走」到 kevinshu 的朋友的朋友
  4. 再「走」到 kevinshu 的朋友的朋友的親人

於是這樣傳回的結果就是「 kevinshu 的朋友們的朋友們的所有親人們」,一個看似複雜的關係就這樣透過 Link Walking 找出來了。



Link Walking 的建立與query


建立Link:

$ curl -v -XPUT http://127.0.0.1:8091/riak/people/timoreilly \
  -H 'Link: </riak/people/dhh>; riaktag="friend"' \
  -H "Content-Type: text/plain" \
  -d 'I am an excellent public speaker.'
以上的範例除了建立了 davethomas 和 timoreilly 的link外,也用 "riaktag" 來定義這段關係,如 "friend", "relative", "employee"


Query:

$ curl -v http://127.0.0.1:8091/riak/people/timoreilly/{{bucket}},{{tag}},{{Keep}}

  • Bucket: 指定某個bucket,如果這個值是 _ (underscore),則比對所有的 bucket
  • Tag: 指定 riaktag 的值,如果這個值是 _ (underscore),則比對所有的 tag
  • Keep: 0 或 1,是否回傳這個階段的搜尋結果。以一開始提到的使用情境來說,如果我只也想知道 kevinshu 的「朋友」和「朋友的朋友」是誰,我就必須在這兩個query的  Keep 值定為 1。
    如果這個值是 _ (underscore),除了在最終階段的 query 視為 1 外,中間層的query則使用預設值 0 (不回傳)。這樣講不容易懂,直接看例子:
    /riak/people/kevinshu/_,friend,_(視為0)/_,friend,_(視為0)/_,relative,_(視為1)/



Reference


  • http://docs.basho.com/riak/latest/dev/using/link-walking/
  • http://neo4j.tw/understand_graph


2013年10月6日 星期日

Riak實戰(2) - MapReduce in javascript

原理

在接收到client端來的MapReduce要求後,會將request中的process散佈給cluster中的所有node。再將inputs所指定的資料從vnode中取出,再經過「Map」、「Reduce」兩個階段的處理。一一傳給Map function,再由Reduce function給收斂起來。


處理階段


Map

這個階段會依 inputs 所指定條件中算出所有符合的 bucket-key,vnode會依bucket-key找出這些data並將他丟到Map function中做運算,回傳結果(通常是Array)。


Reduce

這個階段所拿到的input就是在Map階段算完的結果的集合。舉例來說:A data 及 B data 經過Map function運算之後的結果分別是 [1,2,2,4] 和 [3,3,4,5],在進入Reduce階段前會先被結合成 [1,2,2,4,3,3,4,5] ,再跑Reduce function。
請注意,Reduce function通常會被重複的執行,以Riak來說至少會被執行兩次,也就是再各個node reduce一次,再將各個node reduce後的結果reduce起來,成為最終的輸出。




HTTP Query Syntax


由RESTful介面向Riak下mapreduce指令的方式是POST到cluster主機的 "mapred" path,例如:
POST http://localhost:8098/mapred

而POST過去的data大約長這樣:
{"inputs":[...inputs...],"query":[...query...],"timeout": 90000}


Inputs (String or Array):

我們在下query時,在inputs這個欄位可以輸入兩種資料格式:
1. 字串:代表我要對哪個bucket做mapreduce
2. 二維陣列:對某個bucket的某筆資料做mapreduce,例:
[ ["bucket1","key1"],["bucket2","key2"],["bucket3","key3"] ]
或是:
[ ["Bucket1","Key1","KeyData1"],["Bucket2","Key2","KeyData2"],["Bucket3","Key3","KeyData3"] ]


Query (Array):

這個參數內會放一個或多個分別屬於不同階段的Mapper、Reducer或Link,格式大約有這幾種:

  • {modfun, Module, Function} where Module and Function are atoms that name an Erlang function in a specific module.
  • {qfun,Fun} where Fun is a callable fun term (closure or anonymous function).
  • {jsfun,Name} where Name is a binary that, when evaluated in Javascript, points to a built-in Javascript function.
  • {jsanon, Source} where Source is a binary that, when evaluated in Javascript is an anonymous function.
  • {jsanon, {Bucket, Key}} where the object at {Bucket, Key} contains the source for an anonymous Javascript function.

如果想把Riak的MapReduce玩熟,上面這五種格式可能需要好好看過一遍。


先來看Mapper的簡單範例:
{ "map":{"language":"javascript","source":"function(v) { return [v]; }","keep":true} }
首先會用key註明這個是map()、reduce或link,在key對應的object中則記錄了是使用「什麼語言(language)」、「執行式(source)」以及「是否在最後的結果中顯現(keep)」。language和source應該不用特別解釋,keep是個布林值,代表在這個階段運算的結果要不要呈現在最終的運算結果裡面。通常只有在最後一個階段,才會將keep設為true。

TimeOut (Integer, optional):

Map/Reduce queries 預設 timeout 為 60000 milliseconds (60 seconds),當運算超過timeout時間的時候會回傳error。我們可以依需求覆寫他(in milliseconds)。


Key filters

Key filters 可以在MapReduce之前先對input資料做預處理(pre-process),在載入資料之前事先檢查是否通過檢查條件。

使用方法

將inputs的key_filters對應到一個array,riak將會依陣列中的條件順序進行篩選,如:
{
  "inputs":{
     "bucket":"invoices"
     "key_filters":[["tokenize", "-", 1],["eq", "basho"]]
   },
   // ...
}
以上面這個例子來說,riak會先將invoice這個bucket中每筆資料的key先以"-"符號做分割(tokenize),取得「第一個」字串區段,再將這個字串與"basho"進行「完全比對」(eq)。
我們接下來就來看看在key_filter中有哪些方法可以用:

tokenize

將key依傳入的符號進行分割,再取出某個區段,通常還會接著其他條件做篩選。
"key_filters":[["tokenize", "-", 1]]

to_lower

將key的每個字母字元都轉為小寫,通常還會接著其他條件做篩選。
"key_filters":[["to_lower"]]

eq

將key與傳入字串進行完全比對。
"key_filters":[["eq", "kevin"]]

between

提供一個區段進行比對
"key_filters":[["between", "20100101", "20101231"]]

matches

篩選key中包含傳入字串的資料,這樣的比對方式叫做「wildcard」,通常會有效能上的問題。
"key_filters":[["matches", "kevin"]]

ends_with

篩選key結尾為傳入字串的資料
"key_filters":[["ends_with", "riak"]]


範例

以下的範例就是做SQL中的distinct動作,Mapper單純將自身的資料以array的形式傳回,多個map的結果concate在一起後,由reducer做distinct的動作。

{ "inputs":"goog",
  "query":[{"map":{
                "language":"javascript",
                "source":"function(value){ return [ value ];}"}
           },
           {"reduce":{
                "language":"javascript",
                "source":"function(values){ 
                              var i=0, max=values.length, r=[];
                              for(i=0;i++;i<max) {
                                  if( r.indexof(values[i])==-1 ) {r.push(values[i]);}
                              }
                              return r;
                          }"
           }]
}

2013年10月5日 星期六

JavaScript 1.7 老梗新炒 - generator, iterator, array comprehension, let

ECMAscript 1.7 在2006年年底就已經release了,我是到了最近才碰到這個版本的一些「新功能」,分別有:
  • 產生迭代器(Iterator) 的 Generator
  • 陣列簡約式(Array comprehension)
  • Let表達式

這些功能在這篇Mozilla的技術文章中已經講的很清楚了,就不多做介紹。

現在才搞懂這些....

我果然還得多練練啊!


Riak實戰(1) - 3分鐘安裝與基本操作

在Mac OSX環境安裝Riak


首先,確認你的mac已經安裝了brew,然後執行:
brew install riak

此時我們可以試著啟動他看看:
riak start

riak預設啟動在8098 port,我們可以連到以下網址看看:
http://localhost:8098/
在啟動Riak時,你可能會看到這樣的警告:
 WARNING: ulimit -n is 256; 1024 is the recommended minimum. 
要怎麼改善呢?.....嗯.....還是自己看吧~


利用RESTful介面做CRUD

Riak的儲存機制是key-value的模式,在Riak底下有多個Bucket,可以把它想像成是SQL的Table。在Bucket下就是你要儲存的的資料了,用Key-Value的方式來做對應。
操作資料的方式如下:

讀取

GET http://localhost:8098/riak/bucket/key

儲存

PUT http://localhost:8098/riak/bucket/key

新增(隨機key)

POST http://localhost:8098/riak/bucket

刪除

DELETE http://localhost:8098/riak/bucket/key


各種環境的Client

除了使用curl,你也可以使用Riak的client端套件來操作Riak。官方以及相關社群已經提供了許多不同語言的client套件,可以在這裡找到:
http://docs.basho.com/riak/latest/dev/using/libraries/

2013年10月3日 星期四

Riak介紹及原理

Hadoop在台灣最近很HOT(一如往常的,台灣在資訊技術上總是慢了國外五年以上的時間),但是在survey相關資料時,發現Hadoop的學習曲線實在異常陡峭,而且如果我只想要分散式儲存及map/reduce機制,對HDFS這種專為大檔案設計的儲存機制沒有特別需求,但是HBase是depend on it的。如果考慮使用Hadoop作為解決方案的朋友可以先參考這篇文章再做決定:「你不需要 Hadoop 做数据分析的 10 个理由
如果需求相當簡單只需要做分散式儲存,很多NOSQL都能做得相當好而且非常容易做擴充,甚至可以提供MapReduce的機制,如MongoDB、CouchDB以及今天我要介紹的Riak


Riak的優點


1. 很容易架設cluster,做擴充
2. 支援 HTTP/RESTful 介面,操作方便
3. 支援Map/Reduce (JavaScript 或 Erlang)
4. 可以下Secondary Index,幫助搜尋(可以想像成是下tag)
5. 社群支援度高,各種語言的client套件齊全
6. Document詳細


Riak的儲存機制

Ring 環

Riak是採用buckets和keys來記錄和分類資料,舉例來說:「student(bucket) / kevin(key)」,這是我們表面看到的,但是在底層的實作上,Riak是將bucket和key做hash以後得到一個新的key(160-bit binary),再將所有的新key聚在一起,成為一個「Ring」。這個Ring會依你的設定被分割為多個Partition,每個partition都由一個vnode負責。

什麼是vnode?

Riak主打的就是他的延伸性,也就是能輕鬆的結成一個cluster。在這個cluster裡面每個Riak instance都是一個node,每個node上又跑了好幾個virtual node,也就是vnode,每個node都有差不多數量的vnode。舉例來說,我有一個4個node所組成的riak cluster,當我設定這個cluster的ring需要被切割為16個partitions時,整個cluster也會需要產生16個vnode來對應這些partition,在這個情況下,每個node會平均開啟8個vnode來認領(claim)這些partition。

資料備份數目

每個Bucket都可以設定他所儲存的資料要被保存幾份(N),以確保整個cluster的容錯能力。所以當有新資料要被插入或更新到這個cluster時,會看這筆資料所在的partition是屬於哪幾個vnode,將這個新增或更新的request PUT到這些vnode。
當要取得資料時,這個request也會被送到所屬的nodes。特別的是,這個request可以要求這個cluster必須在R個node都回應以後才將response吐回去。(R<=N)


Reference:


2013年9月27日 星期五

Rails 的 Counter Cache 機制

雖然很基本,但是一定要做一下記錄。
我們先想像一個情況:你目前的專案中,有user及comment兩種model:

class User < ActiveRecord::Base
  has_many :comments
end

class Comment < ActiveRecord::Base
  belongs_to :user
end

如果在一個頁面需要將使用者列表,並同時顯示他所擁有的回應數時,我們直覺上會在迴圈中,一個個進行user.comments.count,並同時產生一個db query,這樣的情況就是經典的「N+1」問題。


讓我們把counter cache住吧!

在系統架構上還滿常用到的一個手法是,將「讀取機會比寫入機會大很多」的數值記錄起來,例如這次會講到的counter cache,就是乾脆在user資料表上開一個comments_count欄位,在新增或刪除comment的同時,記錄他目前所擁有的comment數。
如果你對rails有一定程度的了解,應該會想到使用ActiveRecord callback的機制在新增或刪除comment後進行user.comments_count的更新。但是也因為這是一個還算常見的作法,Rails本身就內建了這個功能,能夠讓我們所做的工減少很多。


進入正題

1. 新增counter欄位

首先我們要先在user資料表中開一個專門儲存comment數的欄位,由於rails「Convention over configuration」的核心精神,這個欄位應該被命名為 [resource複數]_count ,否則rails可能會認不得他。以這個例子來說,就是要新增一個叫 comments_count 的欄位,並且預設值為0。新增欄位的過程就不贅述了。


2. 修改relation選項

接下來要做的是在comment.rb中的relation上加上一個選項::counter_cache ,告知Rails這個relation是要做counter的同步動作的。程式碼變成:

class Comment < ActiveRecord::Base
  belongs_to :user, :counter_cache => true
end

如果目前的專案增加這個欄位時,並沒有存在任何資料,也就是所有的user所擁有的comment是0,這樣的情況下其實這樣已經ok了。但如果之前就已經有一些user有創造comment,這時我們就要做counter的更新。


更新counter cache

可能有些人會想說,直接遍尋所有user,讓他們的 comments_count = user.comments.count 就好。
很不幸的,Rails對於counter欄位有保護機制,我們不能這樣直接修改他,而必須使用 update_counters(id, counters) 或 reset_counters(id, *counters) 兩種方法,以reset_counter來說,migration的程式碼就像這樣:

User.find_each do |user|
  User.reset_counters user.id, :comments
end



PostgreSQL 新增使用者及設定「唯讀」權限

新增user

以下是新增使用者之指令及相關選項:

Usage:
  createuser [OPTION]... [ROLENAME]

Options:
  -c, --connection-limit=N  connection limit for role (default: no limit)
  -d, --createdb            role can create new databases
  -D, --no-createdb         role cannot create databases
  -e, --echo                show the commands being sent to the server
  -E, --encrypted           encrypt stored password
  -i, --inherit             role inherits privileges of roles it is a
                            member of (default)
  -I, --no-inherit          role does not inherit privileges
  -l, --login               role can login (default)
  -L, --no-login            role cannot login
  -N, --unencrypted         do not encrypt stored password
  -P, --pwprompt            assign a password to new role
  -r, --createrole          role can create new roles
  -R, --no-createrole       role cannot create roles
  -s, --superuser           role will be superuser
  -S, --no-superuser        role will not be superuser
  --help                    show this help, then exit
  --version                 output version information, then exit

Connection options:
  -h, --host=HOSTNAME       database server host or socket directory
  -p, --port=PORT           database server port
  -U, --username=USERNAME   user name to connect as (not the one to create)
  -w, --no-password         never prompt for password
  -W, --password            force password prompt

If one of -d, -D, -r, -R, -s, -S, and ROLENAME is not specified, you will
be prompted interactively.

其中比較重要的是 -P 這個選項,在建立使用者的同時為他設定密碼。如最後一行(紅字)提到的,「創建DB」(d)、「創建user」(r)、「是否為最高權限」(s)。


為使用者設定唯讀權限


在postgres中,並沒有所謂的「唯獨權限」,所以我們必須透過修改使用者的預設參數來達到這個目的:

alter role digoal set default_transaction_read_only=true;



開放 Tables 和 Sequences 權限給使用者


特定 Table:
GRANT SELECT ON table_name TO role_name;

所有 Tables:
GRANT SELECT ON ALL TABLES IN SCHEMA schema_name(通常是public) TO role_name;

所有 Sequences:
GRANT SELECT ON ALL SEQUENCES IN SCHEMA schema_name TO role_name;

Reference

德哥@Digoal的博格:http://blog.163.com/digoal@126/blog/static/1638770402011111274336235/

2013年9月21日 星期六

Ajax跨域請求 - jQuery.getJSON

從事前端開發的工程師應該或多或少都知道ajax無法做跨域請求,除了JSONP這種形式以外:
$.getJSON( 
    "http://localhost:3030/parse?callback=resHandler",
    { q: queryText }
);

但是這樣的用法是不會觸發success callback的,因為從server side回傳的資料會被當做script直接執行,假設從server side回傳的訊息為:
{
    name: "kevin",
    age: 18
}

如此一來在收到這個訊息後會直接執行這段程式碼,但是因為這段訊息並不遵守javascript的文法,因此會fail掉。聰明的程序猿一定知道,我們必須在server side回傳的資料上加點料,讓我們要的資料能適合的函式處理。
從第一段程式碼中看到,我們在請求的url上加了一個callback參數,通知server side 我們要用什麼函式來對資料做處理,假如這段函式叫resHandler好了,我們期待的運算是這樣:
resHandler(data);

data為server應該要回傳的資料。所以在server side我們就應該要這樣處理:
var data = '{msg: "success!"}' //主機回傳資料
var callbackFunction = req.query.callback;

res.send(callbackFunction+"("+")");

這時client端接收到這段資訊後會直接執行他:
resHandler({msg: "success!"})

假如resHandeler為 function(data){alert(data.msg);} ,user便會看到視窗跳出"success"的提示。

2013年9月8日 星期日

還22k一個清白吧,請先檢視自己的價值及定位

聽到太多人罵22k,到底22k是怎麼來的?


「大專畢業生企業實習方案」,俗稱22k方案

當初政府在推行這個政策的心態是好的,教育部補助26,190元讓企業聘請「實習生」,扣除勞健保後,實習生實領22k,這也是22k的由來。
這個政策的推出使得了三萬八千人能夠投入職場,而即便這個方案結束了,當年的38,000人中也有六成五的留職率。

但如果沒有22k方案,那些人能這麼順利找到工作嗎?

老實說當年的政策讓受惠者能得到「寶貴的第一份工作」,有了經驗後總比白紙一張好找工作。所以我覺得這個政策本質上是利大於敝的。


22k只是個數字罷了

當然有人說,從此以後22k成了企業的標準薪資。這可能是當初政策所造成的定錨效應沒錯,所以很多人看到這個數字就「高潮」了。但是問題真的不在這個數字,而是供需法則。當大家通通爭著讀大學、研究所,甚至博士班,但相應的職缺又沒有增加。所以大家只好開始往待遇相對較差的職位找工作,然後就有「你看隔壁的那個小孩唸到碩士班結果薪水還不到四萬,你大學畢業後一定要念個研究所不然一定活不下去」這種說法。

看到同儕讀研究所,於是也跟著讀研究所,發現碩士供過於求於是又讀博士,然後跟弟弟妹妹說至少要讀個研究所不然找不到工作.....於是就進入了惡性循環,大家永遠在跟風、讀書、嚇自己。


改變心態吧,教育不是為了讓你找個好工作,而是希望你適性發展

在台灣,高等教育是分一般、技職、跟師範體系的,為的就是因材施教。但大家往往對技職體系不屑一顧,搞到最後技職體系也沒守住貞操,紛紛改稱「科技大學」,放棄了最重要的技職訓練。搞到最後大家讀出來都一樣,科大出來的不會修水電不會車床不會修馬達,也不想進生產現場學習,人人都想坐辦公室。
但退一百步來說,其實這樣也ok,因為這是你的選擇,喜歡讀書就讀吧~但是從什麼時候開始你有了「讀好書就可以拿高薪」的錯覺?現在的文憑很貴,但對職場來說一點都不值錢,為什麼?因為「好學校」是教你怎麼讀書,而不是怎麼「和職場接軌」
其實當初政府希望教育部去推動這個22k專案時,很多學校是覺得很莫名其妙的,「我們的目的是要『作育英才』,這乾我屁事?」
所以這也是為什麼我覺得大家要從心態開始改變,認真考慮自己為了什麼讀書。大家都想大學畢業後坐辦公室,才符合自己所受的「高等教育」,每月看到薪水條上的數字後就大嘆學歷不值錢。但是偏偏自己的工作可替代性太高,你不做還有一堆人等著做,「唉,還是待著吧」。
相較之下,很多藍領的薪水不知道比22k高多少了,更別提三到五年的「老手」,薪水可能隨便都是22k的3~4倍。到你家修馬桶、冷氣的可能比你的薪水還要高。

嫌薪水低?承認吧,你只是不想把手弄髒而已。


假設你是老闆,你會.....?

案例一

如果我今天要雇一個應屆畢業生當工程師,一樣的錢我寧願雇學士,而不是碩士甚至博士生。Why?很簡單,因為學校絕對沒教我需要的技能,反正我還是要從頭訓練,學士、碩士、博士對我來說有差嗎?

真的沒差嗎?ㄟ.....讓我想想......

有!
一,大學畢業的比碩士少操兩年、比博士少操2+n年,別講熱情,光講體力就比你不知道好多少。
二,一樣的薪資,「學士感激、碩士無感、博士嫌少」。

所以你說我幹嘛不請個學士就好?


案例二

如果你當了老闆,今天要找一個行政助理,門檻超低。現場有10位畢業生搶著應徵,學歷都差不多,私立xx大學xx管理系之類的,個性或積極度也看不出有什麼不同,而期望待遇從2萬到4萬都有,你會願意花多少錢?


結論勒?

  1. 問題不會全都在政府,如果你自覺被虧待,也請好好檢討一下自己
  2. 如果你真的覺得賺錢最重要,那別急著念研究所,多賺兩年的薪水再加上年資絕對比得上一個碩士學位
  3. 想賺錢切忌眼高手低
  4. 如果你真的覺得辦公室就是你的歸屬,而且念了一堆書也拿不出什麼可以說嘴的專長,薪水的部份就別太要求了吧

2013年8月17日 星期六

Rails排程工作套件 - whenever

專案中或多或少會需要排程的功能,不免俗的,Rails強大的生態圈中當然也出現了幾套好用的排程gem,其中最多人使用的就是「whenever」。

安裝

gem 'whenever', :require => false

其中":require => false"的變數是為了不讓這個gem被專案require,因為其實他本身並不會被專案所呼叫,那也就沒有必要被載入專案,佔用記憶體。

原理:
linux中本身就有排程的功能(參見鳥哥的linux私房菜),whenever只是以更親切的方式來提供開發者使用,但實際上還是由os來執行cronjob。


初始化

在專案底主目錄下:
wheneverize .
這樣會產生config/schedule.rb,我們需要在裡面設定隔多久執行特定的job。


設定cronjob

上一個步驟中產生的schedule.rb中其實已經有一些範例可以做參考,基本上格式就是:

every 1.day, :at => '4:30 am' do
  runner "MyModel.task_to_run_at_four_thirty_in_the_morning"
end

以上述程式碼來說,要執行的method就寫在my_model.rb中,這樣應該是足夠明瞭了。


最後一步

但可不是這樣就搞定了,因為對os來說,crontab中仍然沒有任何排程工作。可以用以下指令來檢視:
crontab -l
所以我們要先執行whenever的一段指令讓我們寫在schedule.rb的排程工作實際加到crontab中才行:
whenever --update-crontab store
這時我們再執行 crontab -l 就可以看到剛剛加入的行程了。


2013年7月28日 星期日

如何在Rails中重置所有user的session

前陣子因為遇到devise更改了他的session配置,使得專案中一個功能出了點問題(改版前登入使用者的session配置與改版後登入的使用者不同),所以最近申請的user會引發一個bug。為了讓所有的使用者統一session格式,我必須強制清除所有user的session,讓他們登出才行。

重設secret_token

要做到這樣的目標很簡單,只需要將環境中的secret_token更新在重啟即可,方法如下:

  1. 在terminal中,專案資料夾下輸入 rake secret,會拿到一串亂碼
  2. 將上一步驟中得到的亂碼替換掉「config/initializers/secret_token.rb」中的MyApp::Application.config.secret_token
  3. 重啟伺服器

這樣就完成了

2013年6月27日 星期四

利用formData來達成ajax上傳檔案

最近手邊有個案子用到canvas來做圖片的合成,並且要將輸出圖片存到facebook相簿中,一般來說這種要將圖片合成的功能都在server-side完成,這次用canvas除了為了省時以外,也想盡量減輕server-side的負擔。就這個專案的需求來說,操作canvas不是難事,但是以canvas.toDataURL輸出的dataURL要怎麼以file的形式post到fb呢?


首先,要把 dataURL轉成blob

下面這四點是我在stackoverflow上查到的四個步驟:
  1. Take a bytearray or any other data
  2. Convert it to base64
  3. Convert it to blob
  4. Send it as a normal file with Ajax/xhr/FormData using your favorite method
下面這段程式碼就是上述的1~3個步驟

function (dataURI) {
    // convert base64 to raw binary data held in a string
    // doesn't handle URLEncoded DataURIs
    var byteString = atob(dataURI.split(',')[1]);

    // separate out the mime component
    var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];

    // write the bytes of the string to an ArrayBuffer
    var ab = new ArrayBuffer(byteString.length);
    var ia = new Uint8Array(ab);
    for (var i = 0; i < byteString.length; i++) {
        ia[i] = byteString.charCodeAt(i);
    }

    var blob = new Blob([ab]);
    return blob;
}

不要問我上面的code怎麼來的,我抄來的!(挺)
接下來我曾經好傻好天真的認為可以把blob塞進form的欄位中,然後把它submit出去,但最後發現是不可以的...


用Ajax將檔案post出去吧


查了一下發現最方便的方式是使用formData物件,只要只用他的append方法就能把blob或string append上去:
var fd = new FormData();
fd.append("source", blob);
接下來只要將他post出去就好囉:
var xhr = new XMLHttpRequest();
xhr.open("POST", url);
xhr.send(fd);

2013年6月26日 星期三

Rails的db:seed小技巧

在開發Rails專案時,在db的sync上,常常會遇到下面兩個情況:

1. 開發環境與正式環境的初始資料
2. 多人開發時的local DB同步問題

第二個問題其實只要開一個遠端的伺服器來共用就解決了,但是在使用sqlite或是沒有網路的情況下,這麼做實在很麻煩,而且有時候我們也不希望別人的資料弄髒資料庫。這時候我們可以使用seed.rb來初始化資料庫中的資料,我們來看看怎麼做:
# seed.rb

areas = [
  {name:"台北"},
  {name:"高雄"}
]
Area.create areas
然後下rake db:seed這個指令就行了


進階需求


但是需要初始化的資料往往都是系統的固定選項(如縣市等不應被隨意增加、減少的資料),這時常常會有需要指定id的需求,這時我們就要稍微修改一下作法:
areas = [
  {id:1, name:"台北"},
  {id:2, name:"高雄"}
]
areas.each do |area|
  s = Area.new area
  s.id = area[:id]
  s.save
end

如果是確定此資料表中只會出現seed中所寫入的資料的話,我們可以加上delete_all來先清空資料表再做插入資料的動作:
Area.delete_all

使用csv匯入資料


在只有少量資料需要被處理時,直接將資料寫在seed.rb十分方便,但是當資料量很大時,我們希望能以報表的形式事先整理好再做匯入。而csv是一個格式簡單,也能被Excel, Numbers等報表軟體支援的檔案格式,因此我選擇它作為外部資料匯入的媒介。

但是在Excel中比較難去設定成我們想要的utf-8編碼,比較建議使用Apple的Numbers軟體,可以直接輸出成utf-8的csv檔。

來一段簡單的程式碼大家應該就能看懂了:
# seed.rb

require "csv"
SysSchool.delete_all
CSV.foreach("#{Rails.root}/db/school.csv") do |row|
 school = {
  id:row[0],
  name:row[1],
  sys_area_id:row[2]
 }
 s = SysSchool.new school
 s.id = school[:id]
 s.save
end

這段程式碼也是要貼在seed.rb中的,這樣一來在執行"rake db:seed"時,就會先刪除資料表中的資料,並將db/school.csv裡的資料一列一列插進資料表中。

2013年6月8日 星期六

Middleman 產生 sitemap 的方法

middleman除了前兩篇提到的好用功能外,當然也可以產生sitemap囉,我們來看看要麼做:

1. 安裝builder

首先在Gemfile中加入這一行:
gem "builder"
然後在config.rb中加入:
require "builder"
接著bundle install


2. 在根目錄下新增data/site.yml

內容:
url: <你的網址,ex. http://google.com>


3. 在source資料夾中新增sitemap.xml.builder

xml.instruct!
xml.urlset "xmlns" => "http://www.sitemaps.org/schemas/sitemap/0.9" do
  sitemap.resources.each do |resource|
    xml.url do
      xml.loc "http://dash.tw/#{resource.url}"
    end if resource.url !~ /\.(css|js|eot|svg|woff|ttf|png|jpg|psd)$/ 
  end
end

然後每次build的時候就會產出sitemap到build/sitemap.xml了

2013年6月1日 星期六

Middleman奇技淫巧大揭密

上一篇我們介紹到了middleman的基礎知識以及slim的安裝,這一篇我們會介紹更多好用的工具,我們先接續上一篇講到的slim:

使用slim作為project預設的template engine


上一篇教到的slim安裝方式其實不能算是很方便,因為安裝過後還必須手動將所有的erb附檔名換成slim,檔案內容也要改成slim的格式,並不是太親民,我們希望一開始產生project時裡面的頁面就全部是以slim的格式呈現的。
幸好middleman-slim這一款gem可以幫我們做到這件事。來,簡單兩步驟:
  1. gem install middleman-slim 
  2. middleman init PROJECT_NAME --template slim

ok,這樣就搞定了。


懶得按F5沒關係 - LiveReload幫你自動刷新瀏覽器頁面


LiveReload真的可以稱得上是究極懶人功能,目前市面上最有名的是這個:http://livereload.com/,但他是要花錢買的。現在使用middleman讓你不花半毛錢,而且幾乎零設定,一樣是簡單的三步驟:
  1. 在Gemfile中加入這行:gem "middleman-livereload"
  2. 在config.rb中加入這行:activate :livereload
  3. 在終端機輸入:bundle install,重啟伺服器
現在只要在檔案中做了任意修改並存檔後,瀏覽器的頁面就會自動刷新,搭配外接螢幕真的可以說是神兵利器啊~XD


高效率撰寫css - sass&compass


sass是一種可以被編譯成css的語言,他的出現是為了補足css的諸多限制,例如:無法使用變數、無法進行數學運算、沒有函式。習慣了sass之後,你可以更有效率的撰寫css,只是在寫完以後會需要多一個編譯的動作罷了,但幸好在middleman中,這些事情都會自動幫我們做好。
在開始教學之前,還要在講解一個東西,那就是compass。compass之於sass就好像jquery之於javascript,他提供了許多常用的函式及模組讓我們能更有效率的撰寫sass。
好,那我們就開始安裝sass及compass吧~
  1. 在Gemfile中加入這兩行:gem "sass"、gem "compass"
  2. bundle install
記得將附檔名.css改成.css.sass,還要注意一下縮排、分號、大括號喔。


用Lorem產生假文字、假日期、假圖片


middleman之所以好用,不只是因為可以整合上面提到的plugin,最大的原因就是他能嵌入ruby程式碼,幫你搞定許多routine work,例如:迴圈產生假的list、產生假文字。

在這裡我們就介紹一下如何使用ruby code來產生迴圈及假資料

Loop:

<% 5.times do%>
    <!-- HTML code -->
<% end %>

假文字:

<%= lorem.words 5 %>

假句子:

<%= lorem.sentence %>

假圖片:

<%= lorem.image("300x400", :background_color=>"333", :color=>"111", :text=>"YEAH") %>

<%= lorem.image("300x400", :random_color=>true %>


Middleman快速入手

之前就時常聽到Middleman這個東東,但是一直沒有機會去試他,因為其實自己在刻html上還沒遇到什麼太大的產能問題。今天為了教設計師怎麼迅速產生靜態網站,自己便下海來學了一下。

why middleman?

當然是因為他好吃好看又好玩
  1. 可以內嵌partial頁面
  2. 可以導入haml、slim加快layout速度
  3. 可以導入sass加快寫css的效率及管理
  4. 可以livereload,瀏覽器即時更新目前頁面


How to start?

首先,你得安裝ruby環境及gem,這部份不贅述。
安裝好ruby環境後,一行指令輕鬆安裝:
gem install middleman

好了,這樣就裝好了,接著我們來介紹三個實用指令:
  • middleman init [project_name]
  • middleman server 
  • middleman build


產生middleman project - middleman init


很簡單,在你要產生project的路徑下打這段指令即可:
middleman init [project_name]

這時middleman會幫你產生一些檔案:
source(資料夾)、
config.rb、
Gemfile、
Gemfile.lock、
.gitignore

我們主要要修改的html,css,javascript等檔案,都在source這個資料夾裡面。


啟動伺服器 - middleman server 


如果還要先安裝apache那也太累了,一樣在專案資料夾底下一行指令:
middleman server

輕鬆啟動你的服務~預設是跑在localhost:4567上


輸出你的專案 - middleman build


一樣在專案資料夾底下一行指令:
middleman build

就會在專案資料夾中產生build資料夾,裡面就是輸出後的靜態頁面了。
看到這裡你可能會很好奇,為什麼我還要多跑這道手續呢?
因為在middleman裡我們預設是使用erb來寫html,雖然跟html幾乎一模一樣,但是我們還是會多少使用到一些嵌程式碼或局部頁面的功能,而這樣的東西還是要經過compile成一般的html檔案,瀏覽器才看得懂。如果你是用sass來寫css,在這個步驟也會同時將scss檔轉成css檔案。


安裝其他套件讓你的專案建立更快速


在middleman中管理套件的方式跟rails是一樣的,只需要把需要安裝的gem加到Gemfile中即可,安裝時只要打這個指令:
bundle install

拿slim這個套件做舉例好了,slim是個跟haml和jade有著異曲同工之妙的html簡化工具,能讓你用更加簡潔的方式撰寫html,例如原本要寫:
<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    
    <title>Welcome to Middleman</title>
    
  </head>
  
  <body class="index">
    <div class="welcome">
      <h1>Middleman is Watching</h1>
      <p class="doc">
        <a href="http://middlemanapp.com/">Read Online Documentation</a>
      </p><!-- .doc -->
    </div><!-- .welcome -->
  </body>
</html>

但用slim的話只要這樣寫就夠了,有夠簡單:
| <!doctype html>
html
  head
    meta charset="utf-8"
      title Welcome to Middleman
  body.index
    .welcome
      h1 Middleman is Watching
      p.doc
        a href="http://middlemanapp.com/"
      /! .doc
    /! .welcome

那要怎樣在middleman中使用這個簡潔的撰寫方法勒?只要簡單三步驟:

  1. 在Gemfile中加入gem "slim"
  2. 在config.rb中加入require "slim"
  3. 在終端機輸入:bundle install


OK,接著你就可以開始體驗這種簡潔快速的撰寫方式了!

更多關於middleman的奇技淫巧請看下一篇:Middleman奇技淫巧大揭密

2013年5月16日 星期四

[JS] 『返樸歸真』讓jQuery慢慢淡出你的網站吧 (1) -- Basic part

其實兩三個月以前就想寫這篇文章了,直到最近看到越來越多的文章都在提jQuery的pros & cons,想想也是時候做個彙整,順便重新整理一下自己在前端設計的思維。下面這些連結都有很多不錯的建議及教學:
最後一個連結 - vanilla.js算是一個對前端framework重度使用者開的玩笑,「維護團隊」還煞有其事的做了一個客製化選單來幫你"build"需要的模組,但是下載下來的檔案中當然是一行程式碼都沒有。
官網中舉的一些case應該都能讓你反思「我們到底有沒有必要這麼依賴jquery?」

從事web開發以來,在前端開發時一直與jQuery脫不了關係,但隨著對javascript了解的增加,發現自己已經不知不覺陷入了jQuery的設計模式,很多普通的功能其實在原生的javascript中一點都不難做,效能也比較好,我們就一步步從簡單的部份開始,慢慢學習怎麼設計出jquery-free的程式。


$(document).ready()

通常需要載入一些比較肥大的js檔時(例如jquery)而且不考慮相依性時,很多人會將他塞進頁面的尾巴,最後載入。因為頁面的render是synchronise的,所以在最前面塞入一堆肥大的js檔會使得接下來的DOM物件的render被block住,而產生「很慢」的錯覺。
所以如果你是遵照這樣的best practice,在js程式碼中,只要將需要ready後執行的code放在最後即可,就用不到$(document).ready()了。


query dom物件

在使用jQuery的時候,我們已經很習慣使用$(".class #id tag")這樣的方法來query出我們要的dom物件,但這樣的query方式並不是jquery獨創的,而是從css那邊學來的。且在w3c也早有規劃去實作這個api(http://www.w3.org/TR/selectors-api/),那就是querySelectorAllquerySelector
querySelectorAll及querySelector的差別只在前者會query出所有符合條件的element,回傳nodeList;後者只會回傳第一個符合條件的element。兩著的使用時機如下:
  • document.querySelectorAll
  • document.querySelector
  • nodeList.querySelectorAll
  • nodeList.querySelector
  • element.querySelectorAll
  • element.querySelector
支援ie8以上及其他瀏覽器

dom物件的操作

dom物件本身就有相當豐富的method可供使用,了解他們之後你會發現很多情況下我們真的沒有必要用到jquery,這邊舉幾個例子:

Style:

$(elem).css("color","red"); 
vs
elem.style.color="red";

Class:

$(elem).addClass("active"); 
vs
elem.className+=" active"; 
or
elem.classList.add("active"); //ie10+


$(elem).removeClass("active"); 
vs
elem.className= elem.className.split("active").join(""); 
or
elem.classList.remove("active"); //ie10+

HTML、text

$(elem).text("content");
$(elem).html("<p>TEST</p>"); 
vs
elem.innerText="content";
elem.innerHTML="<p>TEST</p>";

Append:

$(elem).append(child);
vs
elem.appendChild(child);

Remove:

$(elem).remove();
vs
elem.remove();


Event-binding:

$(elem).click(function(){});
vs
elem.onclick = function(){};

It sucks, I know. 因為只能綁一個function,第二次綁定時就會把前一個給覆蓋掉了。

$(elem).bind("click", function(){});
vs
elem.addEventListener("click", function(){});


這種方式要bind幾次都ok,要注意的是在unbind的時候必須在第二個參數傳入相同的reference才行,例如:
var foo = function(){};
elem.addEventListener("click", foo);
elem.removeEventListener("click", foo);

2013年5月8日 星期三

[ROR] ActiveRecord效能調校

雖然在使用Rails的過程中感受到orm的方便,但是ActiveRecord用爽爽的同時,許多對sql不是很瞭解的開發者可能會犯下了許多問題導致無謂的浪費了sql資源。在系統剛做好,資料量不大的時候不太是問題,但在使用者及資料量多了以後,問題就會一一浮現了,我們先來看一下一些常見的問題:


Resource.find(id).child_resources


這樣的寫法很直覺,也很方便,但是會造成兩個query:
Resource Load (x ms)  SELECT "resource".* FROM "resource" where id = n
ChildResource Load (x ms)  SELECT "child_resource".* FROM "child_resource" WHERE "child_resource"."resource_id" = n

如果改成這樣的寫法只需要一次的query:
ChildResources.where("resource_id=#{id}")

雖然只少了一次的query,但最多能減少50%的sql操作時間



Resource.all (沒有加select)


當你可能只是很單純的要Resource的id及name,而不需要肥肥的"content"欄位,使用select只拉出需要的欄位,可以幫以省下不少傳輸時間和記憶體。
雖然可以使用slim_scrooge這種gem,不過別這麼懶,自己養成好習慣吧!


利用joins將需要的資料一次拿完

剛開始開發rails的時候,常常會拿出model後就直接往view丟,相關的relation到時候再拿就好,例如這樣:
<%= @category.products.counts %>
但是這樣常常會產生無謂的sql query,少量還好,當你在前台對一堆@catgories跑each的時候會發生很可怕的情形....也就是所謂的 "N+1 Query"
所以盡量養成一個習慣,就是要用到的資料一次拉進來就好,最嚴謹的檢查方式就是將產出的資料用attributes轉成hash,所有要拿的資料都在裡面了,沒有query進來的relation絕對拿不到。不過這樣的作法可能有點矯枉過正就是XD
要一次拿完所需資料最簡單就是用join+select操作,例如:
@products=Category.joins(:products=>:photo).select("categories.name cat_name, products.name pro_name, photos.url")
<% @products.each do |product| %>
  <%= product.cat_name %>
  <%= product.pro_name %>
  <%= image_tag product.url %>
<% end %>
而不是
@products=Product.all
<% @products.each do |product| %>
  <%= product.category.name %>
  <%= product.name %>
  <%= image_tag product.photo.url %>
<% end %>


適時使用counter_cache

相信這種情境一定很常見:
進入網站後,螢幕顯示出你有幾個朋友、幾個相簿、幾篇部落格
這時,在controller裡面....

Bad code:

  @friend_count = current_user.friends.count
  @album_count = current_user.albums.count
  @blog_count = current_user.blogs.count


這時我們該使用counter cache把他cache起來:
在users增加friends_count、albums_count、blogs_count等欄位
在friend.rb、album.rb、blog.rb中的
belongs_to :user
改成
belongs_to :user, :counter_cache => true
如果之前已經有資料了,記得把user.xxx_count給update到目前的count數,但為了保護counter的正確性,counter是read-only的,我們只能使用Base.reset_counter來設定他。
而counter的reset程式碼最好是寫在migration.rb裡,讓各種環境下migrate時順便reset counter:
class AddCounterCacheToUsers < ActiveRecord::Migration
  def self.up
    add_column :users, :comments_count, :integer, :default => 0
    User.find_each do |user|
      User.reset_counter user.id, :friends, :albums, :blogs
    end
  end

  def self.down
    remove_column :users, :comments_count
  end
end

Good code:

@counts = User.find(current_user.id).select(:friends_count, :albums_count, :blogs_count).attributes



善用Transaction


利用transaction


find.each

2013年5月2日 星期四

[RB] class, instance, class instance variable

Instance variable

在程式碼中我們常能看到單一個@開頭的變數,但是可別一味把它當成是instance variable喔,如果是在instance method中被創造出來的那肯定是instance變數,但要小心這種情況:
class Med

  @@c='This is class variable'
  @c='This is class instance variable'

  def self.class_instance_c
    @c
  end
  def class_c
    @@c
  end
  def instance_c
    @c
  end
end

m = Med.new
puts m.class_c #->'This is class variable'
puts m.instance_c #->''
puts Med.class_instance_c #->'This is class instance variable'
上面的@c其實是class instance variable,是不能被物件實例存取的。
而當物件實例想要存取的@c是instance variable,並沒有被定義出來,所以才會是nil,印不出東西。


Class instance & Class instance variable

相較Class instance variable,Class variable比較好辨認,"@@"開頭的就是了。
這兩種變數的差別在於,Class variable是共用於繼承鏈上的,當Class variable被改動時,所有繼承鏈上的其他類別的此變數也會被改動。
可以想像成是直接改動javascript中的prototype
但是Class instance variable是各個類別獨立的,「可以被繼承,但是繼承給子類別後就沒有任何關係了」



cattr_accessor

就像是attr_accessor一樣,只是他提供的是對Class variable的讀寫,而不是instance variable



參考資料

[ihower] 深入Rails3: ActiveSupport 的 class_attribute
[ihower] 一些 Ruby Dynamic Features 記事 (1) OOP

2013年4月25日 星期四

[RB] Ruby中的Proc及Block

Block:

在寫ROR時,大家或多或少會碰到這樣的語法:
posts.each do |post|
  puts post.tiltle
end

posts.each {|post| puts post.tiltle}
上面兩段程式碼中用do-end和大括號{}包起來的就是block,但用{}包裹程式碼的方法只適用於程式碼是一行的情況。
學ROR以來有好長一段時間我都不太清楚他的妙用,只覺得這麼普通的東西為什麼要寫的那麼拐彎抹角,但是在看了code school的教學影片後發現他跟javascript的callback機制有異曲同工之妙,這樣的設計讓我們在做一些enumeration操作的時候能將block中的code傳入,之前之所以對這個概念一直搞不清楚就是因為很難具體的想像在each中視怎麼call這段code,現在我們就用這個demo來剖開這個黑盒子:
class Child
  def initialize
    @toys=['gun','hammer','puzzle']
  end
  def each_toy
    for toy in @toys 
      yield toy
    end
  end
end

mark = Child.new
mark.each_toy do |toy|
  puts "I'm playing #{toy}~"
end

在這個範例中block像參數一樣被傳入each_toy這個method,並在裡面的loop內被多次呼叫。這樣的設計除了使用起來很靈活、有彈性以外,也能減少重複的程式碼。如果你對javascript比較了解的話可以看這一段程式碼來幫助了解:
function Child() {
    var toys = ['gun','hammer','puzzle'];
    this.eachToy = function (cb) {
        for (var _i in toys) {
            cb(toys[_i]);
        }
    }
}

mark = new Child();
mark.eachToy( function (toy) {
  console.log("I'm playing " + toy + "~");
});

但是上面的程式碼中,block看起來一點都不像是物件,也不能被重複使用。如果要像是javascript一樣能將函數像物件一樣被宣告、保存起來,並傳入其他函數作為callback function,我們還少學了一些東西,那就是接下來要介紹的Proc物件。

Proc:

proc物件可以將ruby的程式碼保存起來,在需要的時候再執行他,或當做block傳入其他函數。proc有兩種實作方法:

By Proc.new

routine = Proc.new {|variable| Do something... }

routine = Proc.new do |variable| 
  Do something...
end

By lambda

routine = lamda {|variable| Do something... }

routine = -> {|variable| Do something... }

直接執行

routine.call value


把proc轉成block

要把proc物件轉為block時,我們要在這個物件的前面加上"&",例如:
posts.each &routine

在method中判斷是否有block傳入

要知道有沒有傳入block,我們可以使用block_given?來判斷:
if block_given?
  yield
else
  puts "No block past in"
end


小結

現在我們就來改寫一下一開始的範例:
class Child
  def initialize
    @toys=['gun','hammer','puzzle']
  end
  def each_toy
    if block_given?
      for toy in @toys 
        yield toy
      end
    else
      puts "No block past in"
    end
  end
end

mark = Child.new
routine = lambda { |toy| puts "I'm playing #{toy}~" }
mark.each_toy &routine

2013年4月13日 星期六

[JS] javascript OO設計 - 繼承

接觸javascript有一定的人應該都知道在js中,你所看到的幾乎都是物件。
函數是物件的第一型,Function和Object都是物件,也是函數的實例,而所有的function都是Function的instance。

關於原形(prototype)

prototype是函數物件特有的屬性,使用函數建構出來的物件會藉由原型鏈(prototype chain)而存取得到建構函數的prototype的屬性,用文字說清楚很難,不如我們直接看範例:
function Person(name, age) {
    this.name = name;
    this.age = age;
}
Person.prototype.nation = "Taiwan";
var kevin = new Person("Kevin", "18");
var laura = new Person("Laura", "17");
alert(kevin.nation); // "Taiwan"
alert(laura.nation); // "Taiwan"
雖然kevin和laura本身並沒有被定義"nation"這個屬性,但是當使用這些不存在的屬性時,會找他們的建構函數中的prototype,再沒有的話還會看這個prototype物件的建構函數中有沒有這個屬性,直到最高層,也就是Object建構函數的prototype,這就是所謂的prototype chain。為了證明這點,我們再次修改一下上面這個例子:
function Person(name, age) {
    this.name = name;
    this.age = age;
}
Person.prototype.nation = "Taiwan";
Function.prototype.whatTypeAmI = "Funciton";
Object.prototype.whatTypeAmI = "Object";
var kevin = new Person("Kevin", "18");
alert(kevin.nation); // "Taiwan"
alert(kevin.whatTypeAmI); // "Object"
alert(Person.whatTypeAmI); // "Function"

從上面可以看出雖然kevin這個物件沒有whatTypeAmI這個屬性,但是他會往上找Person.prototype物件有沒有這個屬性,再找不到就會找Person.prototype的建構函式,也就是預設的Object函式,這時我們會在他的prototype中找到whatTypeAmI這個屬性是"Object"。
而我們在呼叫Person的whatTypeAmI時也會發生找不到的情形,這時就會往上找到他的建構函數也就是Funciton的prototype,如範例所示,這時回傳的是"Function"。


利用prototype來實踐javascript繼承模式

我們可以利用上面所提到的prototype chain來實踐多層祖孫繼承的模式,我們用範例來解釋一下這點:
// 哺乳綱
function Mammals() {
    this.blood = "warm";
}

// 靈長目
function Primate() {
    this.tail = true;
    this.skin = "hairy";
}
Primate.prototype = new Mammals();

// 人科
function Homo() {
    this.skin = "smooth";
}
Homo.prototype = new Primate();

var human = new Homo();
human.name = "Kevin";

alert(human.name); // "Kevin", from self
alert(human.skin); // "smooth", from Homo
alert(human.tail); // "true", from Primate
alert(human.blood); // "warm", from Mammals
上例應該很清楚的顯示出以原型實踐繼承模式的原理了。


原型設計模式的漏洞

雖然原型設計模式能夠很方便的實踐物件間的共用屬性及繼承模式,但是在操作上要對prototype chain有一定的了解,再加上細心的邏輯驗證,才不會出現如下的錯誤:
function Human() {}
Human.prototype.blood = "red";
Human.prototype.body = ["foot","hand"];

var john = new Human();
var kevin = new Human();

john.blood = "purple";
john.body.push("wing");

alert(kevin.blood); // "red"
alert(john.blood); // "purple"

alert(kevin.body.toString()); // "foot, hand, wing"
alert(kevin.body.toString()); // "foot, hand, wing"
從上面的例子可以看到,john因為不明原因而突變以後,不只血變成紫色的,也長出翅膀來了!但是在john突變之後,kevin的血雖然沒有變色,但是卻莫名其妙長出了翅膀。很明顯的,我們不小心改動到了Human的prototype。
原來在我們為john的blood指定顏色時,javascript會為john這個物件增加一個屬於自己的"blood"屬性,這種情況就跟為物件增加屬性的方式一樣。於是在後來的呼叫時,會先找到john自己的blood屬性。但要john的body屬性執行push函式時,會發生在john中找不到body的狀況,於是就往上找到了Human.prototype的body屬性,並由他來執行push函式,此時改動到的便是Human.prototype.body了,也就連帶的影響到了無辜的kevin。

其他繼承模式設計

javascript是個很活的語言,除了prototype的實踐方式以外,我們也可以使用別的方式來實現繼承:
// 哺乳綱
function Mammals() {
    this.blood = "warm";
}

// 靈長目
function Primate() {
    Mammals.call(this); // 記得放前面,不然會蓋掉重複的屬性
    this.tail = true;
    this.skin = "hairy";
}
Primate.prototype = new Mammals();

// 人科
function Homo() {
    Primate.call(this); // 記得放前面,不然會蓋掉重複的屬性
    this.skin = "smooth";
}

var human = new Homo();
human.name = "Kevin";

alert(human.name); // "Kevin", from self
alert(human.skin); // "smooth", from Homo
alert(human.tail); // "true", from Primate
alert(human.blood); // "warm", from Mammals
這種方式是將父類別的建構函式放在子類別的建構函式中以「this」的身分來執行,為自己建置父類別的屬性。這樣的作法有個好處,就是不會因為不當的操作,改動到別的物件的屬性,但是相對的也失去了共用屬性的便利性。

這種方式也能讓我們很方便的實作多重繼承,只要在子類別的建構函數中呼叫多個父類別的建構函式即可。

2013年2月19日 星期二

[SQL] SQL tuning 奇技淫巧 (in Oracle)

雖然真的很討厭SQL,但是只要從事web開發的多多少少都會碰到,實在是無法避免。最近碰到了SQL從一個table取資料去update另一個table的效能問題,最初的寫法是這樣的:
UPDATE NEW_DATA N
SET N.A_COLUMN =  (   
    SELECT O.A_COLUMN
    FROM OLD_DATA O
    WHERE O.ID = N.ID) 
)
這樣的寫法跑了大概半個小時都還沒跑完(一共4萬多筆資料),最後受不了把他停下來,改成以下寫法:
UPDATE (
    SELECT  N.A_COLUMN NEW_A, 
            O.A_COLUMN OLD_A
      FROM  NEW_DATA N, OLD_DATA O
     WHERE  O.ID = N.ID
)
SET NEW_A = OLD_A
結果不到兩分鐘就跑完了~原因是第二種寫法會先將兩個table依select條件join成一個暫存的view,這個view再用自己的資料對自己做update,而不是像第一種寫法一樣每寫一筆資料之前都要先查詢一次。

Partition

Select ID, GROUP
    From (
        Select  ID,
                GROUP,
                Row_Number() Over (Partition By GROUP Order By Abs(ID) Desc) Sequence
                -- 針對同一個GROUP的資料依ID大小排序,並給定序號
        From DATA
    ) TEMP
WHERE TEMP.SEQUENCE = 1
-- 只取出每個GROUP最大ID的資料

2013年2月16日 星期六

[JS] function - 函數物件

前言

javascript引擎要執行javascript時,會先"預編譯",即對所有宣告的變數及函式進行處理,但要注意的是,在函數中被宣告的local變數只有在函式被呼叫時才會被處理,函數return之後這些變數就會被釋放。


函數的定義

函數有三種定義方式:
1. 以function宣告(ex. function abc(){})
2. 匿名函數(ex.function(){})
3. Function建構函數(ex. var fn = new Function("return true;"))
前面有提到在預編譯時會先處理被宣告的函數及變數,所以第一種的宣告方式或是將匿名函數指定給一個變數時....
var fn = function(){/*Do something*/};
會在預編譯時期先被處理,但在直接執行匿名函數....
(function(){/*Do something*/})();
或是用Function建構函數來建立函數時都會在執行到該行程式碼的時候才會被處理。因為這樣的特性使得我們能用Function來比較動態的宣告函數,但因為必須以字串方式傳入函式的敘述內容(函式中要執行的程式碼),所以相對不好維護,而且因為是動態編譯的,效率也會較差,我們可以用一段程式碼來證明:
var startTime = (new Date()).getTime();
for(var i=0; i<100000; i++){
    function fn(){}
}
var total = (new Date()).getTime() - startTime;
alert("function*100,000 takes "+total); // 62 secs

var startTime = (new Date()).getTime();
for(var i=0; i<100000; i++){
    new Function();
}
var total = (new Date()).getTime() - startTime; // 18991 secs
alert("new Function*100,000 takes "+total);


函數的作用域(scope)

就像上一段所說,因為宣告function會在預編譯時期就被處理,所以函數的作用域是取決於宣告時所在的位置,立即執行函式(IIFE,直接執行匿名函數)也是。但是以Function建構函式所建立的函數作用域是Global。
var a = 1;
function fn(){
    var a=2;
    var fn1 = function(){alert(a);}
    var fn2 = new Function("alert(a)");
    fn1(); //"2"
    fn2(); //"1"
}
fn();


arguments物件

在function中可以使用arguments這個array物件,其中包含了所有被傳入此函數的參數,即使這個函數並不需要任何的輸入,例:
function fn(){
    for(var i in arguments){alert(arguments[i]);}
}
fn(1,2,3,4,5,6,7,8,9);
順帶一提,要如何取得一個函數預定接收幾個參數呢?可以使用function.length


.call() & .apply()

之前剛好寫了一篇關於這兩個方法的文章
這兩個方法能讓function很靈活的視作不同object的屬性來使用,切換不同的scope,用完之後馬上消失,不會真的成為該object的屬性。


別忘了function物件也有屬性

function也是個物件,當然也有自己的屬性。下面的這段程式碼想用迴圈來依次輸出1~10,但這樣的寫法用到了全域變數,缺乏封閉性。
function fn(){
    return x++;
} 
var x = 1;
for(var i=1; i<=10; i++){
    alert(fn());
}
所以我們可以利用函數自己的屬性來暫存這個值
function fn(){
    return fn.x++;
} 
fn.x = 1;
for(var i=1; i<=10; i++){
    alert(fn());
}


閉包(Closure)

在C、JAVA等語言中,當呼叫函數並傳回後,所有的區域變數就會被釋放,即刪除他們所在的堆疊。但在Javascript裡,如果宣告了一個內嵌函數,區域變數將在函數傳回後仍能被存取。我們可以利用這種特性來設計具有封閉及包裹性的閉包架構:
var mod = (function(){
    var d=0;
    var foo=function(){return d;};
    return foo;
})();
alert(mod()); // "0",還是存取得到閉包中的變數
關於Javascript的模組化可看一下之前的這篇文章


2013年2月12日 星期二

[JS] deferred.promise()的應用

今天在看jQuery的.animation()程式碼的時候注意到的。

之前就有稍微研究一下jQuery的deferred物件,今天看到他被用在animation的程式碼裡面,而且是我之前沒學過的用法,於是在這邊筆記一下。
var obj = { speak: function(name){alert(name);} }
    deferred = $.Deferred();
deferred.promise(obj);
obj.done(function(name){obj.speak(name)});
deferred.resolve("Kevin");

After being promised, the object would would be extended with some of the deferred object's function like "done", "fail", "always".

As the above, though the promised object can't resolve it self, but the deferred object who promise it can. And it can even pass an argument on resolving or rejecting it.

The deferred object really helps dealing with asyncronise problems in javascript.


2013年2月11日 星期一

[前端] Responsive Web Design - 自適應網頁設計技巧整理

自己在開發網站前端時往往會希望在任何device上的UX都能呈現到最好,又不想為每種顯示模式特別再寫一份layout,就開始研究RWD,畢竟很多實驗性或是新創網站是沒那麼多成本去特別寫一個mobile版的。

"Responsive Web Design",或"自適應網頁設計"其實比較像是個設計概念,而不是什麼特定技術,但是在實踐他的過程中我們還是需要一些小技巧,以下是到目前為止累積的一點小心得:


1. CSS: width:X%

一般在設計網頁常用的兩欄式、三欄式設計,往往是用固定的欄寬來對side area做定義,但當螢幕寬度小於網頁的總寬度的話就會出現橫向的scroll-bar,這是在行動裝置上所不樂見的,最好的方法是利用比例來做切割,例如1:3的兩欄式就可以利用個別設定他們的寬度為"25%"、"75%"
(但事實上你往往會需要為他們留一點margin,所以比較實際的配置應該是25:1:74之類的)
Fluid 960 GridBootstrap fluid layout 等都是可以拿來現成使用的樣板。


2. CSS: max-width: X%

雖然直接用比例來定義欄位或container的寬度十分方便,但有時候會遇上「寬度100%在小螢幕剛剛好,但在寬螢幕上看就是大的很奇怪」或是「寬度60%在大螢幕剛剛好,但在小螢幕就太小」的這種尷尬情況。這種時候可以用接下來會講到的Media Query來解決,但畢竟要多寫一種css配置實在有點麻煩,畢竟有的時候這只是局部性的小配置罷了。這種令人尷尬的小壞蛋只要用max-width就可以輕鬆的切換寬/窄螢幕下適合的寬度設定!解決的方式如下:
.ding-dong-box{
    width: 480px;
    max-width: 100%;
}
這樣一來只要這個box的container的寬度大於480px,ding-dong-box會維持原樣,而當縮到比480px窄的時候,ding-dong-box也會跟著一起縮小。實際應用可以參看這個網頁試算表格


3. CSS: Media Query

Media Query就是讓瀏覽器判定目前的devise寬度套用不同的css設定,很多前輩都已經寫得很棒了,小弟在這邊就恭敬的附上幾篇連結:
用CSS3的Media Queries進行自適應網頁設計(Responsive Web Design)
Mobile Web 前端技術筆記(二): Media Queries 與 CSS
比較要注意的是,在行動裝置上的配置不能用@media  screen and (max-width: 480px)來達成,
針對行動裝置的偵測必須寫成:@media screen and (max-device-width: 480px)
而Media Query 的最終目的就是要讓layout能在大型裝置的橫向配置和行動裝置的縱向配製作切換。至於這些配置間的不同,可以看這篇文章的介紹:"Responsive Design with CSS3 Media Queries" 或是參考 "Bootstrap fluid layout" 這個template,總之概括來說就是要讓兩欄(ex. 30:70)或三欄(ex. 20:60:20)這種配置可以在行動裝置上切換成縱向的配置(每個區塊的寬度都是100%)


4. align="center" VS style="margin: 0 auto;"

很常見的水平置中問題,除了在外面套一層align屬性為center的container以外,你也可以設定margin為"0 auto",利用margin來平均補齊左右空間達到水平置中的結果。
總而言之,拜託不要再用<center></center>了.....


5. Javascript

放大絕,特殊情形不知道該怎麼解決的時候就用$(window).resize();和$("").css()吧!



2013年1月21日 星期一

[Ubuntu] 安裝PostgreSQL9.1步驟

雖然一開始Ubuntu就安裝了postgresql,但是在搞不清楚狀況的一開始我還是把它給砍了= =
然後再重裝一遍:
sudo apt-get install -y postgresql-9.1 postgresql-client-9.1 postgresql-contrib-9.1

相關的執行檔會被放在這個資料夾:
/usr/lib/postgresql/9.1/bin

在安裝時一直遇到找不到放置PGDATA的錯誤訊息,所以我們要在9.1資料夾下創建一個"data"資料夾,並更改擁有者為postgres:
mkdir /usr/lib/postgresql/9.1/data
chown postgres /usr/lib/postgresql/9.1/data
此時再執行su postgres,切換為postgres身分,在/usr/lib/postgresql/9.1/bin的路徑下執行:
./initdb -D mkdir ../data
會出現以下的訊息:
The files belonging to this database system will be owned by user "postgres".
This user must also own the server process.

The database cluster will be initialized with locale en_US.UTF-8.
The default database encoding has accordingly been set to UTF8.
The default text search configuration will be set to "english".

fixing permissions on existing directory ../data ... ok
creating subdirectories ... ok
selecting default max_connections ... 100
selecting default shared_buffers ... 24MB
creating configuration files ... ok
creating template1 database in ../data/base/1 ... ok
initializing pg_authid ... ok
initializing dependencies ... ok
creating system views ... ok
loading system objects' descriptions ... ok
creating collations ... ok
creating conversions ... ok
creating dictionaries ... ok
setting privileges on built-in objects ... ok
creating information schema ... ok
loading PL/pgSQL server-side language ... ok
vacuuming database template1 ... ok
copying template1 to template0 ... ok
copying template1 to postgres ... ok

WARNING: enabling "trust" authentication for local connections
You can change this by editing pg_hba.conf or using the -A option the
next time you run initdb.

Success. You can now start the database server using:

    ./postgres -D ../data
or
    ./pg_ctl -D ../data -l logfile start


接著我們就可以執行
./postgres -D ../data

./pg_ctl -D ../data -l logfile start
來啟動服務了。

Postgresql這時候已經可以接受連線,我們來執行createdb指令來新增一個資料庫:
postgres@li440-29:/usr/lib/postgresql/9.1/bin$ createdb test

然後執行psql -d test來進入console,如果直接執行psql是會直接進入預設的postgres資料庫的,快樂的打指令之前請先確認一下所在的DB。

而pg_ctl是用來對postgres做操作的控制台,可以執行"pg_ctl --help"來查看它的功能,這邊先列出一部分:
Usage:
  pg_ctl init[db]               [-D DATADIR] [-s] [-o "OPTIONS"]
  pg_ctl start   [-w] [-t SECS] [-D DATADIR] [-s] [-l FILENAME] [-o "OPTIONS"]
  pg_ctl stop    [-W] [-t SECS] [-D DATADIR] [-s] [-m SHUTDOWN-MODE]
  pg_ctl restart [-w] [-t SECS] [-D DATADIR] [-s] [-m SHUTDOWN-MODE]
                 [-o "OPTIONS"]
  pg_ctl reload  [-D DATADIR] [-s]
  pg_ctl status  [-D DATADIR]
  pg_ctl promote [-D DATADIR] [-s]
  pg_ctl kill    SIGNALNAME PID

此時執行netstat -ap應該能看到postgres正在listen

但因為每次啟動PG都要輸入-D參數實在太麻煩,我們是先將PGDATA加入環境變數中,打開~/.bashrc,在最後一行加上:
export PGDATA=/usr/lib/postgresql/9.1/data

後記

在重啟服務時,常常會有失敗的情況,這時只要切換終止模式即可:
./pg_ctl -D ../data restart -m fast