當你有多個設計選擇,不知道哪一種比較好的時候,A/B testing 可以幫助你測試哪一種比較有效果。例如: 你的網站註冊有兩種設計方式,哪一種讓比較多人註冊呢? 你的購物車按鈕有兩種設計,哪一種比較讓人順利結帳呢? 你的廣告版本有三種,哪一種比較吸引人呢?
別猜了!! 公說公有理,婆說婆有理。讓使用者的實際體驗跟統計結果來告訴你吧。a/b testing 的作法是: 1.輪撥這些選項 2. 設定追蹤點(goal) 3. 一段時間後,觀察哪個選項達成的 goal 比較多。
而 A/B testing 工具可以幫助你很容易做好這些事情: 1. 針對不同人提供不同選項 2. 但讓同一個人看到的都是同一個選項(這是一個重點,不然測試就不準了),例如透過 cookie 或使用者ID 3. 提供後台報表,並提供分析告訴你這些數據是否有統計學上的顯著差異 4. 方便安裝及使用 5. 夠快,不會對 production site 造成效能負擔
對 Rails 來說,目前有兩套可以考慮使用: A/Bingo 和 Vanity。A/Bingo 很容易安裝使用,功能比較陽春,使用 ActiveRecord 搭配快取(memcached) 來記錄資料。Vanity 功能比較豐富,願景也比較大,提倡了一整套的 Experiment Driven Development 開發方式。它使用了 Redis 來記錄資料。不過他的使用文件似乎沒有跟上程式的更新速度,所以不太好安裝,得去翻 source code。如果你現在就想試試,我會先推薦 A/Bingo 比較容易上手。
如果你不想改 server side 的程式(或是你是不會寫程式的行銷人員),也可以透過 Google Website Optimizer 這套工具來做 a/b testing。Google 的方式就單純用 JavaScript 來記錄: 你先告訴 Google 你有哪幾種頁面,例如 Original page、Variation page 1、Variation page 2 三種選項,接著提供 Conversion page 是指達成 goal 的頁面,然後將 Google 會給你 control script 貼到 Original page 去(讓使用者可以輪撥到其他頁面),以及 tracking script 貼到各個頁面。
最後,A/B Testing 當然也不是萬能的: Why A/B testing of web design fails。
ActiveSupport::Concern 是 Rails3 做 Modularity 的一個重要的小工具。他的任務是讓管理 modules 之間的 dependencies 變得容易。
假設我們有兩個 Modules 有依存關係,module Bar 依存於 module Foo,然後有一個宿主 Host 類別希望 include Bar 的功能,我們可以這樣寫:
module Foo
# self.included 這個函式會在 Foo 被 include 時執行
def self.included(base)
base.send(:do_host_something) # 對宿主做某些操作,例如增強功能等等
end
end
module Bar
def self.included(base)
base.send(:do_host_something)
end
end
class Host
include Foo, Bar
end
這有個討厭的缺點就是,我們必須在宿主中同時 include Foo 跟 Bar,也就是要把所有依存的 modules 都 include 進來。這很糟糕啊,為什麼我們需要在 Host 裡面知道這些 modules 的依存關係呢 :/
我們希望能夠將 modules 的依存關係寫在 module 中,而宿主 Host 就只要使用就好了。所以我們試著改寫成:
module Bar
include Foo # 因為 Bar 依存於 Foo,所以我們在這裡 include 它
def self.included(base)
base.send(:do_host_something)
end
end
class Host
include Bar # 只要 include Bar 就好,不需要知道 Bar 還依存哪些 modules
end
這樣乍看之下好像沒問題,但是卻有個嚴重的問題導致無法執行,因為 Foo 變成是由 Bar 所 include,所以對 Foo 的 self.included 來說,他的參數 base 變成了 Bar 了,所以他就沒辦法存取到宿主 Host 的任何函式及變數,do_host_something 時就會失敗。
Okay,ActiveSupport::Concern 就是來幫助解決這個難題,我們希望宿主可以不需要知道 modules 之間的 dependencies 關係。dependencies 關係寫在 module 裡面就好了。
require 'active_support/concern'
module Foo
extend ActiveSupport::Concern
included do
self.send(:do_host_something)
end
end
module Bar
extend ActiveSupport::Concern
include Foo # 因為 Bar 依存於 Foo,所以我們在這裡 include 它
included do
self.send(:do_host_something)
end
end
class Host
include Bar # 只要 include Bar 就好,不需要知道 Bar 還依存哪些 modules
end
如此就搞定了。One more thing,如果你有定義 module ClassMethods 和 module InstanceMethods 在裡面的話,它也會自動幫你載入到宿主裡面去,就不用自己寫 send(:include, InstanceMethods) 跟 send(:extend, ClassMethods) 了。用法舉例:
module Foo
extend ActiveSupport::Concern
included do
self.send(:do_host_something)
end
module ClassMethods
def bite
# do something
end
end
module InstanceMethods
def poke
# do something
end
end
end
想知道 ActiveSupport::Concern 到底怎麼實作的話,請看 /activesupport/lib/active_support/concern.rb,只有 29 行,而且 ActiveSupport::Concern 也沒有再依存其他東西了,嘿。
在 Rails3 中,處理 JavaScript 改採用 UJS 的方式,也就是以往 inline JS 的情況將不復見,link_to_remote 跟 remote_form_for 都沒有了:
例如:
link_to 'Logout', session_path, :method => :delete
產生出來的是:
<a href="/session" data-method="delete" rel="nofollow">Logoout</a>
例如
link_to 'Ajax delete', person_path(person),
:remote => true, :confirm => "Are you sure?"
產生出來的是
<a href="/people/1" data-confirm="Are you sure?" data-method="delete"
data-remote="true" rel="nofollow">Ajax delete</a>
# 點下去會向 Server 要求 JavaScript 內容執行
Rails3 預設還是使用 Prototype.js,要換成 jQuery 非常簡單。首先在產生 rails 專案時,可以輸入 rails your_project -J 就不會產生 Prototype.js 的檔案。
修改 /public/javascripts/rails.js 這個檔案,內容換成 http://github.com/rails/jquery-ujs/blob/master/src/rails.js 這個官方提供的 jQuery js driver。
在 Rails layout 的 HTML head 中加入:
<%= csrf_meta_tag %>
<%= javascript_include_tag "http://ajax.googleapis.com/ajax/libs/jquery/1.4.1/jquery.min.js" %>
<%= javascript_include_tag 'rails' %>
其中 csrf_mate_tag 產生的是做 CSRF 防禦的 authenticity_token,以往這也是 inline 在 HTML 中,現在則是先宣告在 HTML head 中。
另外要提醒你,如果你有使用 RJS 的話,Rails3 的 RJS 還是只會產生 prototype.js 的版本。所以如果非得用 RJS 的話,目前恐怕還是得裝以往 Rails2 用的 jRails,這好像有點蠢。也許是時候把 RJS 徹底揚棄,直接寫 jQuery 好了(?)
例如上面的 Ajax delete 例子,它的 controller 跟 js 可以這樣:
# people_controller.rb
def destroy
@person = Person.find(params[:id])
@person.destroy
respond_to do |format|
format.html { redirect_to(people_url) }
format.js # destroy.js.erb
end
end
# destroy.js.erb
jQuery("#<%= dom_id(@person)%>").remove();
裝了 Rails3 beta 之後啊,那個 rails 新建專案的指令就變成產生 Rails3 了,那要怎麼產生本來的 Rails2 版本呢? 今天 Ruby Tuesday 聚會有人問了我這個問題。
整理 Rails3 資料的時候就有看到解法,回答如下:
首先,如果還沒裝 Rails3,請先將本來 Rails2 版本的 rails 指令複製一份成 rails2 (可以打 which rails 可以找到位置,這個檔案跟 gems/rails-2.3.5/bin/rails 不一樣哩),然後將裡面的 version = “>= 0″ 改成 version = “~> 2.0″ 即可。之後執行 rails2 project_name 就可以產生 Rails2 版本的專案了。這個 rails2 的檔案長得如下(第一行的 ruby 位置你的可能跟我不一樣):
#!/usr/local/bin/ruby
#
# This file was generated by RubyGems.
#
# The application 'rails' is installed as part of a gem, and
# this file is here to facilitate running it.
#
require 'rubygems'
version = "~> 2.0"
if ARGV.first =~ /^_(.*)_$/ and Gem::Version.correct? $1 then
version = $1
ARGV.shift
end
gem 'rails', version
load Gem.bin_path('rails', 'rails', version)
如果你已經安裝了 Rails3,那 Rails3 的這個檔案跟 Rails2 的差異只在最後兩行而已。