2015年11月3日 星期二

[轉載] 為什麼分紅費用化對員工不好呢?

查資料時看到看到的,蠻好的解釋了分紅費用化的狀況。

重點:

  1. 過去股票紅利是照面額計算 (Ex. 分紅10萬,相當10張股票,但是股票市值往往遠超過面額)
  2. 依據上一點,「面額」及「市值」之間的成本便由小股東承擔了,也造成每股盈餘不明的情況
  3. 費用化後,因員工配股張數是以「分紅金額」除以前一年最後交易日的股價


2015年3月17日 星期二

如何寫出 User Story


什麼是 User Story?

User Story 是一段簡單的功能敘述,以 客戶或使用者的觀點 撰寫下有價值的功能(functionality/feature)。與其說它是規格文件(documentation),不如說它代表(represent)客戶的一個需求而已,因為實做細節將延後至開發時才會確定。

幾個 User Stories 的範例如下:

  • 使用者可以在網站上張貼履歷
  • 使用者可以搜尋有哪些工作
  • 公司可以張貼新工作
  • 使用者可以限制誰可以看到他的履歷


2015年2月18日 星期三

Deferred 與 Promise 的差異


在談Promise 之前,我們先來了解一下 CommonJS.org

CommonJS.org

CommonJS 是一個致力於將 JavaScript 生態圈標準化的計畫,尤其是針對瀏覽器以外的應用環境,也因此第一個版本的名字叫做 ServerJS。
CommonJS 的終極目標是制定一個像 C++ 標準庫一樣的規範,也就是介面,使得基於 CommonJS API 的應用程序可以在不同的環境下運行,就像用 C++ 編寫的應用程序可以使用不同的編譯器和運行時函數庫一樣。為了保持中立,CommonJS 不參與函式庫的實作,而是交給像 Node.js 之類的項目來完成。

而不少開發者向 CommonJS 提出 Promise 的規範草案,但是一直未被列入正式規範,有著 Promises/A, B, KISS, C, D 等版本。其中以 Promises/A 的提案最為簡單,也最常被大家提及。

值得一提的是:Promise/B, D 的提案人是知名流程控管套件 Q.js 的作者 Kris Kowal
滿有趣的一點是:Node 團隊在前年左右放棄繼續遵守 CommonJS 的規範。Isaac 指出 "Ryan basically always gave zero fucks about CommonJS anyway" ,並轉述了 Ryan 的一句重話 : "Forget CommonJS. It's dead. We are server side JavaScript."
有興趣可以看看這個Github的討論串:https://github.com/joyent/node/issues/5132#issuecomment-15432598
( Issac 是 NPM 的作者, Ryan 是 Node 之父 )

2015年2月4日 星期三

你還在為了UI-binding使用肥肥的Angular嗎?試試這些前端函式庫吧!

從 jQuery 到 Angular

從最早的 jQuery 以後,各種前端framework百花齊放:Backbone, Knockout, Ember, Angular....
其中 Angular 狹著 Google 的大名,可以說是在前端世界中呼風喚雨,但是許多人使用Anugular一段時間以後才發現入門容易但是深入難,而且隨著專案的複雜性增加,也開始踩到了各種地雷。因此也有人用這張圖來揶揄模擬Angular的學習曲線:



UI only

許多人開始發現,很多狀態下他們幾乎只需要 Data-UI binding 的功能,React.js 這個 facebook 御用、注重高效render 的 Library 就開始受到廣大的目光關注... React不是個完整的framework,只是專注於View的Library,因此更輕量、也更能與其他framework整合、相容。React官網是這麼說的:
Just the UI
Lots of people use React as the V in MVC. Since React makes no assumptions about the rest of your technology stack, it's easy to try it out on a small feature in an existing project.


但是!

React 推薦使用 JSX 在程式碼中勾勒 template,這點我就真的不行了,覺得髒髒的。所以我實在無法接受 React。

但是幸好這是一個前端技術大爆炸的時代,我們還有很多選擇....


2015年1月6日 星期二

手機網頁必會技巧!HTML meta viewport 屬性

什麽是Viewport

手機瀏覽器是把頁面放在一個虛擬的「窗口」(viewport) 中,通常這個虛擬的「窗口」(viewport) 會比螢幕寬,這樣就不用把每個網頁擠到很小的窗口中(這樣會破壞沒有針對手機瀏覽器優化的網頁的布局),用戶可以通過平移和縮放來看網頁的不同部分。移動版的 Safari 瀏覽器最新引進了 viewport 這個 meta tag,讓網頁開發者來控制 viewport 的大小和縮放,其他手機瀏覽器也基本支持。舉例來說,一個常用於RWD優化過的頁面上,常會有這樣的程式碼:

<meta name=”viewport” content=”width=device-width, initial-scale=1, maximum-scale=1″>



Viewport的屬性


  • width:控制 viewport 的大小,可以指定的一個值,例如600或者其他的值,如 device-width 為設備的寬度(單位為縮放為 100% 時的 CSS 的像素)。
  • height:和 width 相對應,指定高度。
  • initial-scale:初始縮放比例,也即是當頁面第一次 load 的時候縮放比例。
  • maximum-scale:允許用戶縮放到的最大比例。
  • minimum-scale:允許用戶縮放到的最小比例。
  • user-scalable:用戶是否可以手動縮放


其中特別需要注意的是 width這個屬性,比較不容易搞懂。
舉例來說,當你的頁面沒有經過RWD優化,最適合的解析度是960px寬,因此有些使用者拿著螢幕800像素寬的手機時,會無法看到右方160px的區塊,必須進行橫移或縮放。
而你希望使用者無論拿著多大的手機,看到你的網頁時都能夠從左到右完整顯示的顯示網頁內容。於是你就需要設定width為960px,此時無論是480px, 640px, 800px 甚至是 1280px寬的手機,都會調整顯示比例至能完整顯示800px的寬度。
當然,手機的解析度是不可能變的,於是小手機上看到的內容就會比較模糊不清,畢竟他是試圖使用480px來顯示960px的內容。


Reference:

穆乙's Blog: http://www.cnblogs.com/pigtail/archive/2013/03/15/2961631.html

2014年12月21日 星期日

初探 Python Decorator

最近剛巧聽到朋友介紹 Python 的 Decorator。
一開始以為是指 design pattern,後來才發現原來是Pyhton特有的一種語法糖,其實有點合成函數的味道。

舉例來說,有一個取得商品價錢的函式:
def get_price(product_id):
    ...
    return price

但是為了慶祝聖誕節,有的商品打八折,有的商品則是大打五折
而該打幾折的程式是由另一個函式判斷:
def christmas_discount(func):
    discount = get_discount()
    def get_christmas_price(product_id):
        return func(product_id) * discount
    return get_christmas_price

這時我們可以用這兩個程式合成一個新的程式:
get_price(21) // ==> 200
get_price = christmas_discount(get_price)
get_price(21) // ==> '160'

但是我們可以用 decorator語法糖「掛上」合成的函式:
@christmas_discount
def get_price(product_id):
    ...
    return price

get_price(21) // ==> '160'

Functional Programming vs OOP:

在OOP中也有所謂的裝飾者模式,具體的講解可以參考 良葛格的文章
這裡就可以看出:原本許多在OOP需要特別實作的設計模式,在Python或JavaScript等函數式語言中都能夠很直觀、輕鬆的被實作出來。
常見的Factory模式也是一樣的道理,在JS中能夠被輕易的實作。

2014年12月19日 星期五

Git 小筆記

跟 git 太不熟了,在這邊堆放一些常用筆記

Branch:

git branch //看local branch(星號代表現在所在的branch)
git branch <bar> // 新增 "bar" branch
git branch -D <foo> // 刪除 "foo" branch


Checkout:

git checkout <bar> // 切換到 local 的 "bar" branch
git checkout -b <NewBranch> // 新創一個 branch 並同時 checkout 它
註:如需從origin拉下一個branch,需在本地創一個branch,checkout 後再拉 "origin/branchname"。eg. "git pull origin test"


回溯:

git log // 取得過往的commit,其中以 hex code 為 id
git checkout <hex code> // checkout 過往 commit
git show // 顯示當前所在
git checkout <branchname> // 回到 local 最新commit,eg. "git checkout master"


Tag:

git tag -l // 列出所有 tags
git tag -a <tag name> <hex code> // 針對某一 commit 打 tag
git push origin --tags // 上傳所有本機 tags 到 遠端

2014年8月28日 星期四

[SQL] Common Table Expression (CTE)

CTE 簡單來說就是依據一定的查詢條件建立一個暫時的 table ,以便重複性的 Subquery 查詢。

舉例來說,我需要從 pc, laptop, printer 三個 Table 取最貴的產品出來,想像的寫法可能是如此:

SELECT model FROM (
    SELECT * FROM pc
    UNION
    SELECT * FROM laptop
    UNION
    SELECT * FROM printer
)
WHERE price=(
    SELECT MAX(price) FROM (
        SELECT * FROM pc
        UNION
        SELECT * FROM laptop
        UNION
        SELECT * FROM printer
    )
)

我們會發現三個 table 的 UNION 被重複使用,非常麻煩。
這時我們可以將這三個 table 的 UNION 暫存成 CTE,藉由以下的方式。

WITH product AS (
    SELECT * FROM pc
    UNION
    SELECT * FROM laptop
    UNION
    SELECT * FROM printer
)

SELECT model FROM product
WHERE price=(
    SELECT MAX(price) FROM product
)

TADA~~~是不是簡單明瞭又方便勒?

2014年8月9日 星期六

ES6 新玩意兒 - Generator

ㄜ....其實不太能算是新玩意了,畢竟小弟也好一陣子沒有繼續追JS的近況(廢)。
實在是因為前陣子臉書上太多好友在提Kao和CO,當初用的express好像過時了一樣....

「不行!我還沒老啊!」

身為一個技術潮人,臨時抱佛腳是必要的,就從ES6的新規範 - Generator(迭代器) 開始吧。


宣告方式

Generator 的宣告方式比較特別,有點像是 function ,基本上是長這樣:

function * gen(){
    console.log("start!");
    yield "hello";
    yield "I'm Kevin";
    console.log("end!");
}

這個 gen 是一個ㄜ.....函式?總之,使用他後會返回一個 generator,像這樣:

var g = gen(); // g 是一個 Generator


這時,就可以拿它來做這樣的事:

r = g.next(); // "start!"
console.log(r); // { value: "hello", done: false }
r = g.next();
console.log(r); // { value: "I'm Kevin", done: false }
r = g.next(); // "end!"
console.log(r); // { value: undefined, done: true }


Generator.next() 回傳什麼?Value & Done!

value的形式不拘,端看generator的yield後接著什麼,會被放到value中。
至於 done 則是個單純的 boolean,告訴你這個 generator 玩完了沒。

看到這邊,聰明的你應該可以得到一個結論,generator中的yield有點像是中斷點,當 next() 被執行時,generator內的程式碼開始被執行,直到下一個 yield 的那一行。直到 generator的最後一行,回傳 { done: true }。


Generator 也能接收資料

在迭代的過程中,我們也是可以將變數傳入next中的:

function * gen(){
    var got = yield "hello";
    yield got;
}

var g = gen();

g.next("Good morning!"); // "Hello"
g.next(); // "Good morning!"

TADA~~~很好玩吧?


Generator 的特性及用途

因為迭代器中斷函數時,是不會卡死其他程式碼的,這樣的特性非常適合拿來做同步處理。

舉例來說....

舉例來說....

舉例來說....

舉不出來,看看大大們怎麼說好了:


基本上主要是用作「非同步流程控管」及「解決回調地獄(callback hell)」用途使用。


2014年7月29日 星期二

[JS] nextTick() 介紹與應用


Node.js是單線程的,除了系統IO之外,在它的事件輪詢過程中,同一時間只會處理一個事件。你可以把事件輪詢想象成一個大的隊列,在每個時間點上,系統只會處理一個事件。即使你的電腦有多個CPU核心,你也無法同時並行的處理多個事件。但也就是這種特性使得node.js適合處理I/O型的應用,不適合那種CPU運算型的應用。在每個I/O型的應用中,你只需要給每一個輸入輸出定義一個回調函數即可,他們會自動加入到事件輪詢的處理隊列里。當I/O操作完成後,這個回調函數會被觸發。然後系統會繼續處理其他的請求。

在這種處理模式下,process.nextTick()的意思就是定義出一個動作,並且讓這個動作在下一個事件輪詢的時間點上執行。我們來看一個例子。例子中有一個foo(),你想在下一個時間點上調用他,可以這麽做:
function foo() {
    console.error('foo');
}
 
process.nextTick(foo);
console.error('bar');

執行結果將會是:
bar
foo

我們也可以使用setTimeout()函數來達到貌似同樣的執行效果:
setTimeout(foo, 0);
console.log('bar');

但在內部的處理機制上,process.nextTick()和setTimeout(fn, 0)是不同的,process.nextTick()不是一個單純的延時,他有更多的特性,這裡有兩者的效能比較
更精確的說,process.nextTick()定義的調用會創建一個新的子堆棧。在當前的棧里,你可以執行任意多的操作。但一旦調用netxTick,函數就必須返回到父堆棧。然後事件輪詢機制又重新等待處理新的事件,如果發現nextTick的調用,就會創建一個新的棧。


什麼時候會使用到 nextTick ?

因為JavaScript是單線程的Runtime,如果有需要長時間的運算,往往會卡死其他的程式,例如事件觸發的 function。
更多應用情境可以參考原出處

也可以參考Fred的這篇:探討 NODE.JS 的非同步機制

2014年2月19日 星期三

在development模式下,assets 與 precompiled assets 衝突的解決方法

因為自己的開發環境效能往往比production server 還好,有時候我們為了求快,會直接在local 將 assets precompile 過,上傳到 production。
但繼續在 development 模式下開發時,就有個很機車的情況出現了,在 public/assets 中已經 compile 過的 assets 也被 require 進來,造成某些 lib 被 require 兩次而造成不正常的情況,當有新的變更時也會被舊的蓋過去。

查了一下發現可以針對 development 模式對 asset 路徑加上prefix:
  config.assets.prefix = "/dev/assets"

這樣子 request 的路徑就會變成 /dev/assets/application.js ,避免被precompile的assets搞亂。



2014年2月12日 星期三

修正 PG::UniqueViolation: ERROR: Duplicate Key Value Violates Unique Constraint 'Your_table_name_pkey' 問題

PG::UniqueViolation: ERROR: Duplicate Key Value Violates Unique Constraint 'Your_table_name_pkey'

今天在新產品上遇到了這個 error,查了一下發現是因為 postgres 因為不明原因而把 seq 搞亂了,以致於新增資料時使用到了既有資料的 id 。

了解問題所在後,循以下作法順利解決了問題:
  1.  rails db production
  2.  SELECT setval('your_table_id_seq', (SELECT MAX(id) FROM your_table));
打完收工~


2014年1月8日 星期三

Rails 的 i18n fallback 問題

當在 config/application.rb 中指定了 config.i18n.fallbacks 語系順序後,別忘了要 comment 掉 produciton.rb 中的 config.i18n.fallbacks = true
production 下的語系順序才會乖乖實現。

2014年1月3日 星期五

Unicorn + Nginx 配置及設定

最近部屬公司的新產品 - flyTutor 的時候,嘗試了不同的架構,從以往的 passenger-plugin on Nginx 換成了Unicorn 搭配 Nginx 的「反向代理」架構

What's "Reverse Proxy Model" ?

在提起 Unicorn 及 Nginx 的配置之前,我們得先了解一下「反向代理機制」,下面這張圖應該可以幫助你了解:

(轉自: ROR實戰聖經)


在用戶端向 server 作請求的時候,不是直接與 app server 連結 (中間那三個),而是先連到 web server ,通常是 Apache 或 Nginx,再由 web server 指派 request 給 app server、或直接 serve static assets。
所以在這樣的架構下你可以想像你的系統需要常駐著以下的服務:
  1. web server
  2. 數個 app server
  3. DB server

這樣的架構是與常見的 Apache/Nginx + Passenger 架構有所不同的,需要作一點觀念上的調適。下面我們就來介紹一下如何做相關的配置。


Nginx 配置

以下是預設的 Nginx 設定檔 - nginx.conf:
user deployer; # 定義操作 nginx 的使用者
worker_processes 1; # 定義 worker 的數量

error_log  /var/log/nginx/error.log;
pid        /var/run/nginx.pid;

events {
  worker_connections  1024;
}

http {

  include       /etc/nginx/mime.types;
  default_type  application/octet-stream;

  access_log    /var/log/nginx/access.log;

  sendfile on;
  tcp_nopush on;
  tcp_nodelay on;

  keepalive_timeout  65;

  gzip  on;
  gzip_http_version 1.0;
  gzip_comp_level 2;
  gzip_proxied any;
  gzip_vary off;
  gzip_types text/plain text/css application/x-javascript text/xml application/xml application/rss+xml application/atom+xml text/javascript application/javascript application/json text/mathml;
  gzip_min_length  1000;
  gzip_disable     "MSIE [1-6]\.";

  server_names_hash_bucket_size 64;
  types_hash_max_size 2048;
  types_hash_bucket_size 64;

  include /etc/nginx/conf.d/*.conf;
  include /etc/nginx/sites-enabled/*;

}

上面這段預設的設定檔有兩個地方需要注意:
第一行的使用者身分必須為負責 deploy 的用戶,否則常常會出現 403 權限問題,因為未經設定的話 nginx 會沒有權限存取 deployer 的檔案,如 js, css。
第二行的 worker 數量建議別設定超過 cpu 數目,多了也沒好處。

再來就是重頭戲了,我們要開始加入 app server 的設定,這段設定需要加在 http 的括號內:
  upstream unicorn {
    server 127.0.0.1:8080 fail_timeout=0;
  }

  server {
    listen 80 default deferred;
    server_name flytutor.com;
    root /var/www/flytutor/public;

    location ^~ /assets/ {
      gzip_static on;
      expires max;
      add_header Cache-Control public;
    }

    location / {
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header Host $http_host;
      proxy_redirect off;

      if (!-f $request_filename) {
        proxy_pass http://unicorn;
      }

    }

    error_page 500 502 503 504 /500.html;
    client_max_body_size 4G;
    keepalive_timeout 10;
  }


upstream:

首先我們先來講解一下第一部分的 upstream。首先,upstream 後的「unicorn」並非一定的,端看你希望怎麼命名,但是要注意的是 proxy_pass 也需作對應的設定。再來,upstream 內的 server 可以不只一個,如果你有數個 app server 在不同的 host 的話,你也可以做如下的設定:
upstream flytutor.com {
  server 192.168.0.111:8080 weight=3;
  server 192.168.0.222:8080 weight=2;
  server 192.168.0.333:8080 weight=3;
}
其中 weight 代表的是「被配發 request 的權重」,當 weight 越高的時候被分配到的機率越大,可以做 loading balance。

而除了走 TCP/IP 外,如果是在 web server 和 app server 都在同一個主機的情況下,你也可以使用 socket 來做設定,基本上效能會比 TCP/IP 好一點點(因為少了 header):
  upstream unicorn {
    server unix:/tmp/unicorn.sock fail_timeout=0;
  }

server:

接下來我們要介紹一下 server 的設定,基本上這邊指的 server 就是指 app server,一台 nginx server 可以依 server 設定派發 request ,所以可以做 vhost 的應用,例如判斷 request 的不同網域 (設定檔中的 "server_name")、或是同網域但是不同 port (設定檔中的 "listen"),來派發到不同的服務。
proxy_pass 很重要,許多網友都死在這個地方,切記要將他設置成 upstream 的命名,例如 "http://<upstream_name>"。
你可以想像當一個 request 經過 location(path), server_name(domain), listen(port) 重重篩選後,你卻不指派 proxy_pass 的目的地給他,讓這個 request 就此隱沒在數據流中,成了「digital phantom」,你於心何忍?


Unicorn 配置

unicorn 的配置通常是放在 rails project 中的 config 資料夾,長得像這樣:
app_root = "/var/www"
app_name = "flytutor"
listen "127.0.0.1:10101" #, :backlog => 2048 #這邊要跟nginx虛擬主機檔中upstream內定義的務必一樣
worker_processes 2 #看情況開
preload_app false
timeout 30
 
module Rails
  class <<self
    def root
      File.expand_path(__FILE__).split('/')[0..-3].join('/')
    end
  end
end

_working_directory = File.join(app_root, app_name)
working_directory _working_directory
logs_path = "#{_working_directory}/log"
pid "#{_working_directory}/tmp/pids/unicorn.pid"
stderr_path "#{logs_path}/unicorn.stderr.log"
stdout_path "#{logs_path}/unicorn.stdout.log"
 
GC.respond_to?(:copy_on_write_friendly=) and GC.copy_on_write_friendly = true
 
before_fork do |server, worker|
  defined?(ActiveRecord::Base) and ActiveRecord::Base.connection.disconnect!
  
  old_pid = "#{Rails.root}/tmp/pids/unicorn.pid.oldbin"
  if File.exists?(old_pid) && server.pid != old_pid
    begin
      Process.kill("QUIT", File.read(old_pid).to_i)
    rescue Errno::ENOENT, Errno::ESRCH
      puts "Send 'QUIT' signal to unicorn error!"
    end
  end
end
 
after_fork do |server, worker|
  defined?(ActiveRecord::Base) and ActiveRecord::Base.establish_connection
end

這邊要注意的地方有兩個:

  1. listen 的對象必須與 nginx.conf 中 upstream 設定的一樣,unicorn 與 nginx 就是靠這行設定連起來的。還有,如果是走 TCP/IP 的設定,盡量不要使用 1024 以下的 port 以免 deployer 沒有權限啟動 unicorn 導致失敗(你總不能老用 root 權限作 deploy 吧?)。
  2. unicorn 是採用 master-slave 機制,啟動 unicorn 後叫出  process 列表你就會了解的。而這裡的 worker_processes 的數目顧名思義就是指定 worker 的數目。開多少個才好?基本上沒有一定的標準答案,還是要依你的使用情況及環境做測試後才知道,如果懶得想的話就直接設定為「CPU 數目 + 1」吧。


操作 unicorn

許多網友在網路上搜尋到的啟動法是以 unicorn_rails 來啟動:/usr/bin/unicorn_rails -c config/unicorn.rb -E $RAILS_ENV -D。但現在 unicorn 官方已經不建議使用這種作法而是直接改用 unicorn 來啟動/重啟 unicorn

Hot restart with zero down time

許多人之所以選擇 unicorn 或許就是因為他的 hot restart 特性。如果對這有興趣的人可以參考這篇



2014年1月1日 星期三

vagrant 初體驗與故障排除

1. 30 秒認識 vagrant:

Vagrant 是一個 ruby 寫的工具,它是一個 DSL 讓開發者可以輕易控制 VirtualBox 的 VM 。用它可以輕鬆管理和制作我們理想中的開發環境。
  $ gem install vagrant #安裝 vagrant
  $ vagrant box add ubuntu http://cloud-images.ubuntu.com/vagrant/quantal/current/quantal-server-cloudimg-i386-vagrant-disk1.box #安裝新的 Vagrant Package。這裡的 ubuntu 是一個預先做好的空的 ubuntu 12.10 (intel-based)
  $ vagrant init ubuntu
  $ vagrant up
box 檔可以在 http://www.vagrantbox.es/ 下載


2. 設定:

此時就可以使用 vangrant ssh 連到vm了,預設使用者/密碼為 vangrant/vangrant
接著編輯本地資料夾中的 Vagrantfile,修改 config.vm.network :hostonly, "33.33.33.33" 將ip改成自己想要的ip,這裡以 "33.33.33.33" 為例。修改完後要執行 vangrant reload


3. 故障排除:


無法以 SSH 連接 VM:

[default] Failed to connect to VM!
Failed to connect to VM via SSH. Please verify the VM successfully booted
by looking at the VirtualBox GUI.
當出現上面的錯誤訊息時,可以照 http://vagrant.wikia.com/wiki/Usage 上的步驟來排除,基本上就是將 Vagrantfile 中的 config.vm.boot_mode = :gui 設定打開。再重新 vagrant up,在產生的 GUI 中輸入 sudo dhclient eth0


Guest additions not matchs:

[default] The guest additions on this VM do not match the install version of
VirtualBox! This may cause things such as forwarded ports, shared
folders, and more to not work properly. If any of those things fail on
this machine, please update the guest additions and repackage the
box.


Reference:

http://reality.hk/posts/2011/12/21/vagrant
http://gogojimmy.net/2013/05/26/vagrant-tutorial/


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了。