over 5 years ago

還記得年初才發生的 Github 被入侵事件 嗎?

事件的起因是因為 Rails 內建的 mass assignment 機制,很容易被有心人利用這個漏洞入侵。

原本的 attr_accessible / attr_protected 的設計並不足夠實務使用。在該事件發生後,Rails 核心團隊在 3.2.3 之後的版本,預設都開啟了 config.whitelist_attributes = true 的選項。

也就是專案自動會對所有的 model 都自動開啟白名單模式,你必須手動對每一個 model 都加上 attr_accessible。這樣表單送值才會有辦法運作。

這樣的舉動好處是:「夠安全」,能強迫開發者在設計表單時記得審核 model 該欄位是否適用於 mass-assign。

但這樣的機制也引發開發者「不實用」「找麻煩」的議論。

問題 1: 新手容易踩中地雷

首先最麻煩的當然是,新手會被這一行設定整到。新手不知道此機制為何而來,出了問題也不知道如何關掉這個設定。更麻煩的是撰寫新手教學的人,必須又花上一大篇幅(就如同倒楣的我,想要幫 Rails 101 改版,結果內容無限追加)解釋 mass-assignment 的設計機制,為何重要,為何新手需要重視…etc.

問題 2: 不實用

手動一個一個加上 attr_accessible 真的很煩人,因為這也表示,若新增一個欄位,開發者也要手動去加上 attr_accessible,否則很可能在某些表單直接出現異常現象。

而最麻煩的還是,其實 attr_accessible 不敷使用,因為一個系統通常存在不只一種角色,普通使用與 Admin 需要的 mass-assignment 範圍絕對不盡相同。

雖然 Rails 在 3.1 加入了 scoped mass assignment。但這也只能算是 model 方面的解決手法。

一旦系統內有更多其他流程需求,scoped mass assignment 的設計頓時就不夠解決問題了…

癥結點:欄位核准與否應該由 controller 管理,而非 model

大家戰了一陣子,終於收斂出一個結論。原來一切的癥結點在於之前的想法都錯了,欄位核准與否應該由 controller 決定。因為「流程需求」本來就應該作在 controller 裡面。wycats 當時也起草了一份解法的 proposal。日後打算以 plugin 方式釋出。

plugin:strong_parameter

現在 plugin 出來了。(其實出來很久了,只是我一直沒寫文章...) 就是 strong_parameters 。strong_parameters 的想法與 DHH 當時扔出來的想法 相近。

DHH 當時的作法

是使用 slice 去把真正需要的部分切出來,所以就算 hacker 打算送其他 parameter 也會被過濾掉(不會有 exception)。

而 strong_parameters 的作法是必須過一段 permit,允許欄位。如果送不允許的欄位進來,會 throw exception。

class PeopleController < ActionController::Base
  def update
    person.update_attributes!(person_params)
    redirect_to :back
  end

  private
    def person_params
      params.require(:person).permit(:name, :age)
    end
end

安全多了,擴充性也比較大...

進階用法

當然,每一段 controller 都要來上這麼一段,有時候也挺煩人的。Railscast 也整理了一些進階招數

  • Nested Attributes
  • Orgngized to Class

大家可以研究看看…

 
over 5 years ago

今天要在這裡推薦一部影集:HBO 拍攝的關於新聞從業界的影集 The Newsroom 「新聞急先鋒」。

這部戲大概是在 6/24 左右才開始播的影集,原本是沒有去追的。直到幾天前有人作好了第一集開場的中文字幕,在 FB 流傳,因為氣勢過於震撼,所以才開始注意。

主角 Will McAvoy 是一個極受歡迎的新聞主播,專題 News Night 的風格溫和中立極受大眾喜愛。第一幕是 Will 參加西北大學的座談,一個女學生提問:為什麼美國是世界上最偉大的國家?」座談都在閑扯,直到 一個女學生提問:「為什麼美國是世界上最偉大的國家?」原本 Will 不想要得罪人,亂扯一些答案。但主持人不放過 Will,Will 就大爆炸開始…XD

看了就知道....

這個開場影片很讓人讚嘆,於是我就開始去挖這一部影集剩下的部分和劇集背景:

Will McAvoy 在該次爆發事件之後,雖然當時說了真話(美國並不是世界上最偉大的國家,但...),但影視圈大家對避之唯恐不及。他的老闆 Charlie 趁事件爆發後他去度假時,幫他找了以前的 Partner 準備作新型態的節目。

原本 Will 以為他老闆弄來這個 Producer 是認為西北大學那件事是出包,所以弄出這招搞他。後來才知道 Charlie 是欣賞他,欣賞他那番話找回良知,希望找來一個幫手,讓 Will 的節目轉型,也跳脫收視率的包袱扭曲,去作真的「新聞」,以良知報導「真相」。走出一番新路。

對比國內新聞節目亂象(特別是最近的旺中天走路工事件),看到這套影集真的很感慨。

第一集這一段 clip 並不是目前七集中最令人震撼的片段,第三集開場的陳詞才真正精彩:

因為實在太喜歡這一段的台詞,於是摘錄字幕如下:

晚安 我是 Will McAvoy 
這裡是 New Night
剛剛撥出的影片是 Richard Clarke
小布希總統的反恐中心主任
於 2004 年 3 月 24 號在國會前作證的影像
美國人喜歡那一刻,我喜歡那一刻
成年人要敢於為失敗負責
所以,今天節目的開始,我將加入 Mr. Clarke 的行列
向美國群眾,為我們的失責而道歉

因為在我負責期間這個節目時,我並沒有能夠有效地向傳遞資訊和教育美國選民

讓我先聲明 我並不代表…所有的新聞工作者道歉 並不是所有新聞工作者都需要道歉 我僅代表自己

我是這一系列複雜、重複、無知,且尚未被糾正的失敗的幫兇

我所領導的行業,錯報選舉結果、誇大恐慌事件、挑起政治辯論、隱瞞國家結構的改變 從經濟危機到國力的真實水準

到我們面對的真正威脅。

我所領導的行業如Harry Houdini一般 (知名魔術大師)

嫻熟地分散你們的注意力,同時缺乏審慎地將成千上萬的勇敢年青人送上戰場

我們失敗的原因顯而易見:我們過分重視收視率。

在大眾通訊時代初始,新聞界的哥倫布和麥哲倫-William Paley和David Sarnoff (CBS之父及美國廣播通訊業之父) 

前往華盛頓,與國會簽署一份協議:

「國會允許初有雛形的電視台,免費使用屬於納稅人的廣播頻道, 

條件是這必須是公共服務,即每晚用一小時播報訊息, 

就是我們現在稱為晚間新聞的東西。」

國會未能預料到電視廣告對消費者的巨大影響, 

所以協議中沒有任何一條本能大大改善國家言論秩序的內容; 

國會忘了加上「在任何條件下,於新聞播報期間內都不能有付費廣告」,

他們忘了說「納稅人的廣播頻道是免費給你們使用, 

所以每天有23個小時,你們需要營利,但晚上那一小時,你們只能為國會服務」。

所以現在,那些絕對誠實的新聞人, 比如 Murror Reasoner 和 Huntley 還有
Brinkley 和 Buckley 和 Cronkite 和 Rather 和 Russert ...(皆為知名主播)

現在他們得和我這樣的人競爭

做為新聞主播面對的業界壓力,卻與澤西海岸(肥皂劇)製片人一模一樣 (收視率決定一切) 

那樣的方法對我們很有利,但本節目將不再這樣做。

你可能不相信,這個時代仍有一些偉大的新聞人,他們有卓越的頭腦跟多年的經驗, 和對新聞工作的真摯熱情

但現在他們只是少數人,當碰到馬戲團,他們變得無力競爭,被淹沒了...

我要辭掉馬戲團的工作,轉換隊伍,我要和那些被打擊的人站在一起,

他們仍有贏的信念 我很感動

我希望他們能使我受教。

從今天起,我們播出什麼新聞,如何呈現出來,

都只有一個簡單的原則:在民主制度中,沒有什麼比腦袋清楚的選民更為重要。

我們將努力將新聞放到更大的背景下,因為很少有新聞是獨立存在的;

我們將成為事實的承載者,成為那些含沙射影、投機炒作、言過其實,或胡言亂語的死對頭

我們不是餐廳服務員,只會用你喜歡的方式呈現你喜歡的新聞

我們也不是電腦,只會乾巴巴地說出事實;因為新聞只有在人文背景下才有用

我不會抑制我的個人觀點,但同時我也將不遺餘力展現出不同於我的觀點。

你也許會問,我們憑什麼做出這些決定?

我們是 Mackenzie MacHale 和 我自己

MacHale 是我們的執行製作人,他從超過百篇報導中整理出我們需要的資訊

他是製作人、分析師和技師。我們樂意提供他的資格證書。

我是這節目的總編輯,對於節目上出現的一切,我有最終決定權。

我們憑什麼作這這些決定?

我們是媒體中的菁英

稍後,我們將繼續播報新聞...」

非常非常深的反省。

The Newsroom 每一集處理的題材都很發人深省。不少題材的處理都可以讓你直接勾想起國內的媒體亂象。比如第四集在報導:眾議員Giffords被槍擊事件時。電視台高層一直施壓節目必須馬上報導 Giffords 已死亡(純屬謠言,只因其他台先報導,高層怕沒追到新聞會掉收視率),製作人卻力抗回了:「只有醫生才能宣告一個人死亡,新聞媒體不能」。那一個片段真的會讓看到起雞皮疙瘩。

看到第七集,集集都讓我嘆息,「這只是電視劇」。何時媒體才能夠醒過來與自清呢?

不過 The Newsroom 目前開播才七集,就已經掀起不小的震撼。我真的很推這部電視劇,希望更多人能看到這部影集,能重新思考新聞媒體的本質到底是什麼。

 
over 5 years ago

Bootstrap 是 Twitter 推出的一套 CSS Framework。相當受歡迎的原因是因為讓原本對設計苦手的程式設計師在開發產品早期原型時可以有個可以看的門面先擋著。

又因為 Bootstrap 在 2.0 版以後加入了 configuable 以及 responsive 的設計。所以有些開發團隊,不僅只在 development 階段當臨時門面,在 production 階段,也作為骨架使用。

不過作為 production 使用,就出現了一個不容忽視的課題:如何在一套已經有現成的 styling 的 CSS Framework 上「客製發揮」。

「客製」往往意味著「大幅修改」。不過既然也要「乾淨」,這也表示一個附加條件:「不能破壞 bootstrap 原始架構」。

How can it be possible?

以下是我平常使用 bootstrap 的方式:

利用 Bundler 掛上 Bootstrap 的 rubygems

Bootstrap 的原始版本是使用 LESS 撰寫,不過也有開發者修改成 SCSS 版本。我本身是使用 anjlab 的 bootstrap-rails

透過 Gemfile 把 Bootstrap 掛上來,不直接放進 Rails project 裡面。

Gemfile
gem "bootstra-rails"

利用 Asset Pipeline 以及 SCSS 機制,客製、覆寫

application.css 內容
//= require base
base.scss 內容
@import "bootstrap-config";
@import "bootstrap";
@import "bootstrap-customized";
@import "responsive";
@import "responsive-customized"
解說
  • bootstrap-config.scss 是用來修改「Bootstrap 預設的變數」
$navbarHeight: 50px;
$navbarBackgroundHighlight: white;
$navbarBackground: #F7F7F7;
$navbarSearchBackground: #EAECEF;
$navbarSearchBorder: #EAECEF;
$navbarSearchPlaceholderColor: #565E65;
  • bootstrap 則是 Gemfile 裡面掛上的預設 bootstrap 包。(不修改)

  • bootstrap-customized.scss 則是無法透過修改變數的效果,通通放這裡用 override 的方式覆蓋。

.navbar {
  .navbar-inner {
    background: url(/assets/bg_header.png);
  }
}
  • responsive 是 bootstrap 用來作 responsive 的 css
  • responsive-customized 是你想要針對 bootstrap 的 resposnive 版本做的客製。

小結

這樣你的 application 「理論上」就可以跟著 bootstrap 的小升級而升級,而不會被纏到這個纏那個...

 
over 5 years ago

角色判斷 current_ability

這是一段普通的 ability.rb 權限範例 code。

class Ability
  include CanCan::Ability

  def initialize(user)

    if user.blank?
      # not logged in

      cannot :manage, :all
      basic_read_only
    elsif user.has_role?(:admin)
      # admin

      can :manage, :all
    end

  end

  protected

  def basic_read_only
    can :read,    Topic
    can :list,    Topic
    can :search,  Topic
  end
end

一般開發者最有疑問的是 def initialize(user) 這一段程式碼中的 user 到底是怎麼來的?怎麼會沒頭沒尾的天外飛來一個 user,然後對這個 user 進行角色判斷就可以動了?

這一段要追溯到...lib/controller_additions.rb 中的這一段 current_ability。

cancan 裡面去判斷是否有權限的一直是 current_abibilty,而 current_abibilty initialize 的方式就是塞 current_user 進去。

def current_ability
  @current_ability ||= ::Ability.new(current_user)
end

所以 initialize(user) 裡的 if user.blank? 其實就等於 if current_user.blank?(若沒登入)。

這樣去解讀程式碼,看起來就好理解很多了…

權限類別解說 :manage, :all, ..etc.

cancan 裡面用了一堆自定義縮寫,如 :manage:read:update:all,讓人不是很了解在做什麼。

  • :manage: 是指這個 controller 內所有的 action
  • :read : 指 :index 和 :show
  • :update: 指 :edit 和 :update
  • :destroy: 指 :destroy
  • :create: 指 :new 和 :crate

而 :all 是指所有 object (resource)

當然,不只是 CRUD 的 method 才可以被列上去,如果你有其他非 RESTful 的 method 如 :search,也是可以寫上去..,只是要一條一條列上去,有點麻煩就是了。

組合技:alias_action

cancan 還提供了組合技,要是嫌原先的 :update, :read 這種組合包不夠用。還可以用 alias_action 自己另外再組。例如把 :update 和 :destroy 組成 :modify。

   alias_action :update, :destroy, :to => :modify
   can :modify, Comment
組合技: 自訂 method

要是你嫌每個角色都要一條一條把權限列上去,超麻煩。可以把一些共通的權限包成 method。用疊加 method 上去的方式列舉。比如把基礎權限都包成 basic_read_onlyaccount_manager_only, etc…

  def basic_read_only
    can :read,    Topic
    can :list,    Topic
    can :search,  Topic
  end

針對物件狀態控管

在 User story 中,使用者固然 can :update, Topic,但還是讓人覺得覺得哪裡有點怪怪的?

是的。使用者應該只能編輯和修改屬於自己的文章,can :update, Topic 只有說使用者可以「修改文章」啊(等於可以修改所有文章) XD

所以 ability.rb 就要這樣設計了

  can :update, Topic do |topic|
    (topic.user_id == user.id)
  end
  
  can :destroy, Topic do |topic|
     (topic.user_id == user.id)
  end

可以玩的更加進階:

can :publish, Post do |post|
  ( post.draft? || post.submitted? ) && !post.published?
end

其他

cancan 還有其他進階主題可以繼續探討,讀者可以自行研究:

不過關於「難懂」和「難用」的部分,我想我應該講的差不多了…

小結

在寫這一系列文章時,我發現 cancan 的作者,其實把大部分的文件與範例,都寫在 lib/ 下的 RDOC 裡面了,光看 code comment 其實就可以瞭解大半流程。

不過我覺得 cancan 讓人覺得難讀的最大原因,可能還是官方缺乏一個 example ability.rb,對於被隱藏的自動完成部分也缺乏解釋,所以才造成大家覺得 cancan 是個難用的 magic library。事實上如果你開始搞懂 cancan 怎麼撰寫的話,它是可以幫你把網站的權限 code 處理的非常漂亮又易懂的。

這系列就寫到這邊,如果你對 cancan 還有什麼使用上的問題,歡迎到 Rails Tuesday 來找我討論。

系列連結

 
over 5 years ago

使用Cancan 的限制:RESTful controller (resource)

一般新進開發者會被 cancan 這兩個 API 搞得七葷八素:load_and_authorize_resourceauthorize_resource

這是因為 cancan 並沒有明顯的在 README 上做出說明:cancan 在使用上是有架構的限制

* 必須為 RESTful resource

(cancan 直接假設了你一定使用 RESTful,畢竟這年頭誰還在寫 non-RESTful …?)

* resource 必須與 Controller 同名

(@article 與 ArticlesController)

使用過 cancan 的人,大概都「猜到」規則好像是這樣?

其實不必猜,source code 裡面就寫的很清楚。

load_and_authorize_resource

load_and_authorized_resource 做了兩件事:

   def load_and_authorize_resource
      load_resource
      authorize_resource
    end
  • load_resource
  • authorize_resource

load_resource 作什麼呢?: loard_resource => load_resource_instance

def load_resource_instance
  if !parent? && new_actions.include?(@params[:action].to_sym)
    build_resource
  elsif id_param || @options[:singleton]
    find_resource
  end
end

okay,這段的作用等於如果你在 Controller 裡面下了 load_resource,cancan 會自作聰明的幫你 自動 在每一個 action 塞一個 instance 下去

lass ArticlesController < ApplicationController
  load_resource
  
  def new
  end
  
  def show
    # @article is already loaded

  end
end

如果是 new 這個 action,效果會等於

   def new
     @article = Article.new
   end  

如果是 show 這個 action,效果會等於

   def show
     @article = Article.find(params[:id])   
   end

有好處也有壞處,好處是…你不需要自己打一行 code,壞處就是不熟 cancan 的人,找不到 @article 在哪裡會驚慌失措…

load_resource 還有一些其他進階用法,在 controller_additions.rb 裡面有不少說明...

authorize_resource

authorize_resource 就是對 resource 判斷權限(根據 CanCan::Ability 裡的權限表)。

而這個 resource 必定是與同名的 instance。

如果是 ArticlesController 對應的必然是 @article。

但是你會想說這樣慘了?萬一我在 ArticlesController 裡面要用 @post 怎麼辦呢?

你可以在 controller 裡面指定 resource instance 的 name 要用什麼名字: authorize_resource :post

lass ArticlesController < ApplicationController
  authorize_resource :post
  
  def new
    @post = Article.new
  end
  
  def show
    @post = Article.find(params[:id])
  end
end

Ability 裡面要這樣下

  can :read, Post
  can :create, Post
  can :update, Post

resource 規則小結

所以 cancan 裡面的 resource 第一個會去吃 controller 的名稱當成 resource name,如果是 ArticlesController,instance 就會是 @article,而在 ability 裡面就會是 can :read, Article。這是在假設你已經使用同名設計 resource & controller 的情況下。

如果非同名。你可以做出指定:authorize_resource :post,雖然是 ArticlesController,但是這一組的 resource 名稱為 post,所以 instance 就會是 @post,而在 ability 裡面就會是 can :read, Post

一般開發者常會誤會的是

  • ability 會綁到 model,實際上不是
  • controller 名稱要與 @instance 名稱相同,實際上不一定
  • @instance 要與 model 同名,實際上不用
  • ability 吃的應該是 controller name,實際上不一定(吃的是 resource name,且可以被指定)。

Cancan 吃的是 resource,而且自作聰明的假設了大家「應該」都同名,而且 README example 也是使用「同名」,才會造成了這麼多的誤解…

如果你有更多疑問,可以直接看 source code 裡面的 這一支controller_resource.rb,相信會讓你對整個架構更加的清楚...

小結

這一節解釋了開發者認為最難懂的 load_and_authorize_resourceauthorize_resource。下一節我們要來講 ability 要如何設計…

系列連結

 
over 5 years ago

權限存取設計是在開發 Application 中相當讓人棘手的一個題目。

在一個網站開始建設的初期,通常這樣的問題並不會浮現,畢竟一般人的需求大半只會有 user 和 admin 兩種角色。但是隨著網站長大,更多的生意需求浮現,第三種角色的出現,通常就會把原本乾淨的 code 弄得骯髒不堪。

多種角色的權限設計難題

當只有 user 和 admin 的情況下,你可以在 view 裡面單純的做出這樣的設計

<% if user.is_admin ? %>
  <%= link_to("Admin Pannel", admin_panel_path ) %>
<% end %>

並且在 controller 裡面加上權限判斷

class Admin::ArticleController < ApplicationController
  before_filter :require_is_admin
end

但一段時間之後,User Story 被加進了這樣的需求:

  • 使用者可以被設定為「editor」
  • 擁有「editor」角色的使用者,可以進入 admin 後台發表、編輯文章
  • 擁有「edtior」角色的使用者,進入 admin 後台內的活動範圍僅限縮在文章後台內
  • 擁有「edtior」角色的使用者,進入 admin 後台內,不可以看到其他後台選項。

身為開發者的你,要如何在現有後台內加入這樣的設計?

不用實際動手寫也知道,若如以往使用 if / else 的設計,Helper / Controller / View 鐵定變成一團血肉模糊。

抱怨不能解決問題,但世界上是否存在乾淨的解答?

Rule-engine based authorization library: Cancan

答案就是:「Rule Engine」。

「針對多種條件執行多種動作」,此類的使用者需求,無論是使用 if / else,甚至是 case when,架構還是不免會一團混亂。與其承襲舊思路,不如啟用新想法「Rule Engine」實作:預先設計撰寫一套邏輯規則引擎,而後程式針對預設的規則進行邏輯判斷後執行。

而「角色權限」的設計需求上,正特別適合用 Rule Engine 這樣的觀念去建構。Rails 界知名的 authorization library cancan 正是以此作為基礎。

Cancan 可以做到的:介面單純化

cancan 希望做到的是,把權限判定的處理部分從 Helper / Controller / View 裡面,全部移到 app/models/ability.rb 進行判定。也因此可以做到

  • View 只需要判斷是否可以執行動作,而不必問是否有權限
<% if can? :update, @article %>
  <%= link_to "Edit", edit_article_path(@article) %>
<% end %>
  • Controller 不需要手動判斷是否具有權限
class ArticlesController < ApplicationController
  authorize_resource

  def show
    # @article is already authorized

  end
end

但驚人的是 ** view 的權限會是與 controller 的權限判定規則 ** 卻是一致的。(以往「自刻」權限判定,往往加了 view 卻會忘記 controller, 加了 controller 卻會忘記 view )

Cancan 希望做到的:權限中心化管理

而是否有權限存取,則全交給 app/models/ability.rb 去判斷處理。

class Ability
  include CanCan::Ability

  def initialize(user)

    if user.blank?
      # not logged in

      cannot :manage, :all
      basic_read_only
    elsif user.has_role?(:admin)
      # admin

      can :manage, :all
    elsif user.has_role?(:member)
      
      can :create, Topic
      can :update, Topic do |topic|
        (topic.user_id == user.id)
      end
      
      can :destroy, Topic do |topic|
         (topic.user_id == user.id)
      end
      
      basic_read_only
    else
      # banned or unknown situation

      cannot :manage, :all
      basic_read_only
    end


  end
  
  protected

  def basic_read_only
    can :read,    Topic
    can :list,    Topic
    can :search,  Topic    
  end
end

小結

cancan 是一套相當 powerful 的權限管理系統,但是它的文件卻相當不好讀,第一次想使用 cacan的 developer 很難從文件上找到自己想要的範例以及 api,或者了解其原理構造。如果沒有先給一些基礎範例,往往會是寸步難行。

下一篇我會深入頗析 Cancan 更深的設計原理,讓大家更看得懂 cancan 的 API 到底想幹什麼....。

系列連結

 
over 5 years ago

命名是 CS 中兩大難題,(ref: 在 DK 週一 Passion Bean 的 Talk 上聽到的 )。

今天在公司批票,剛好稍微教了一下同事如何設計寫出好讀的 Ruby method 名稱,覺得蠻有價值的,就隨手整理了一下貼上來。

場景

手機需要驗證,需要寫一個 Class 包裝一個手機驗證碼的寄送與儲存。原始的寫法是使用 「auth_gsm」欄位儲存。

視狀況使用「能表達情境」的名詞設計欄位 : gsm_authcode

  • auth_gsm 的缺點在於讓人看不出來這個字的主題是 gsm 還是 auth。是 gsm 嗎?看起來也不是,只能從字面知道這是跟 gsm 相關的 auth 行為。

  • auth_gsm 但這個 auth 到底是驗證的「狀態」,還是被驗證的「內容」?從名詞上看不出來。

  • 如果 User Story 是指這是應該傳送給使用者的驗證碼內容。應該被更具體的用名詞設計。

使用過去分詞 + "at" 或者是 "is" + 形容詞設計欄位

  • 被驗證的時間不應該使用 activated_time 而應該使用 activated_at。因為 time 是表示時間(時間是指什麼時候還是花多久時間?),不是「什麼時候被驗證」。

  • 是否已被驗證可以使用「is_activated」配合 boolean (true / false)。

使用 ? 表示這個 method 預期會傳回來的值只會是 true/false。

  • ruby 允許 method 名稱有 ?,若 method 名字內有 ?,開發者會預期回傳值是 true/ false
  • if post.is_hidden?if post.is_hidden 直觀
  • if user.is_activated?is_activated 直觀
  • 加上 ? 更強調了狀態,而非只是驗證欄位是否 true / false
  • Array 也有類似用法 array_a.include?(element_a)

通常會在 class 內再對作欄位作一層包裝

class Post < AR
   def is_hidden?
     is_hidden
   end
end

使用動詞與名詞如 generate_gsm_authcode 表示要做的事情

  • 不要在 controller 裡面直接使用
  user.gsm_authcode == "123456"
  user.save
  • 應該在 User model 內設計一個可以敘述要作什麼事的 method 包裝起來
class User < AR
  def genetate_gsm_authcode
    update_attribute(:gsm_authocode, rand(10))
  end
  • generate_gsm_authcode (動詞 + 名詞)只做事

名詞只是名詞

  • 如果 method 只是名詞,不要偷偷動作,而且預期傳回來的要是純量如 String, Array, Hash, Set, Object
class User < AR
  def full_gsm_number
    "#{area_code}-#{gsm_number}"
  end
end

使用 ! 表示這個 method會改變原先自身的狀態

  • String 的 gsub! 與 gsub 是不同的結果與作用。
  • 在 Ruby 中如果 method 加上! 通常會預期這個 method 會改變該 object 本身的狀態
  • ActiveRecord 的 save 與 save! 會發生的事其實是不同的。
  • save 遇到 validation 不過會儲存失敗,但不會 throw excecption。但是 save! 遇到 validation 會 throw exception。
  • dalayed_job 還是哪一套 background job 的 gem。method 後面若被加上 ! 表示立即執行不進 queue。
class User < AR
  def regenerate_authcode!
    # blah
  end

Library 的名稱應儘量為中性,並且貼近實際的責任。

如果這一支程式是拿來傳簡訊的函式,命名應考慮到這支程式應該要作什麼。

  • TWSMS (twsms.rb) 是比較好的。原因是這隻程式負責把 API 呼叫「包裝」了起來。

  • TWSMSWrapper (twsms_wrapper.rb) 是不好的。因為這隻程式並沒有實際「包裝」了什麼東西。它只是提供了一個介面讓其他人可以呼叫。

  • TWSMSSender (twsms_sender.rb) 是不好的。因為這隻程式並沒有實際自己去「呼叫」了外部程式。如果有 twsms_sender.rb 這支程式,裡面應該是一支負責實際去「呼叫」的 services wrapper。就跟 Mailer 的作用一樣。

module SMSsedner
  def send_to_customer
    # blah
  end
  
  def send_to_vendor
    # blah
  end
end

動作應該是中性的動詞,但不應該是保留字

這是安全的字

  • request
  • assign
  • ….

這是不安全的字

  • send
  • save
  • ….

worker 這個字被視為 background worker 。而不是作事的 dispathcher。

worker 通常會負責被視為是去 background job queue 裡面取出來的人。而非 job dispatcher。

Summary

這是我目前整理出來的一些比較大的方向。如果照著這樣的原則走去寫程式碼,大致上都會蠻乾淨好懂的…

 
over 5 years ago

前幾天貼了這一篇 3 招實用 Asset Pipeline 加速術

順便去 sass-rails 上面這個靠北 sass-rails 的 issue 回了一下 zurb/foundation 主要的問題…

我觀察 foundation 原先慢的原因是:幾乎所有的 library 都 @import "base";,然後 base 又 @import "compass";。所以導致只要 compile foundation 就無敵慢…

不過不知道解掉這個問題可以實際上快多少。今天正打算抓下來 benchmark 時,就發現這篇靠么 zurb 的人應該收到了。他們 patch 掉了,版本從 3.0.4 升到 3.0.5 。

主要變更就是把原先 @import "compass"; 改成 @import "compass/css3";。然後把所有的 @import "base"; 拿掉。

Benchmark

我就開了一個空的 Rails。實際用手上的機器去測 compile 時間。

iMac i5 2010 mid 款 8GB ram
  • Compiled application.css (9516ms) (pid 41169) # 3.0.4
  • Compiled application.css (2300ms) (pid 41483) # 3.0.5
Linode 4096
  • Compiled application.css (12518ms) (pid 12585) # 3.0.4
  • Compiled application.css (3201ms) (pid 12853) # 3.0.5

平均來說速度快了四倍...

結論

  • … 拜託不要偷懶直接用 @import "compass"; 啊 XD
  • 太慢有時候可能是 SCSS Framework 的問題
 
over 5 years ago

Asset Pipeline 最讓人詬病的就是 deploy 時花費速度過久。在社群聚會時發現大家都對這個主題非常不熟。所以把最近累積了的這方面技巧整理出來分享給大家。

1. Capistrano deployment speedup

使用 capistrano 內建 task 執行 assets:precompie

capistrano 內建了 'deploy/assets' 這個 task。只要在 Capfile 裡面

Capfile
load 'deploy/assets'

deploy 就會自動執行 assets precompile 的動作。由 原始檔 可以看到這個 task 實際執行的是

"cd /home/apps/APP_NAME/releases/20120708184757 && bundle exec rake RAILS_ENV=production RAILS_GROUPS=assets assets:precompile"

而執行的時機是

after 'deploy:update_code', 'deploy:assets:precompile'

許多開發者不知道有這一個 task 可以用。手動寫 task 去 compile,造成了兩個問題:

  1. 時機執行錯誤。Compile 時機錯誤會造成站上出現空白 css。
  2. 執行 compile 機器負擔太重。如果是手寫的 task 通常會是 load 整個 production 的環境去 compile。與只 load assets 這個 group 所吃的系統資源「有可能」差得非常多。

如果沒有變更到 assets 時,就不 compile

請把這裡面的內容貼到你的 deploy.rb 檔裡面

這是在 Railsconf 2012 的 Stack Smashing 上學到的一招。

如果你的 assets 檔案沒有變動的話,只要執行 copy 上一版本的 assets 就好了。這段 task 會偵測

  • app/assets
  • lib/assets
  • vendor/assets
  • Gemfile.lock
  • confir/routes.rb

是否有變動。基本上已經含了所有可能 assets 會變動的可能性。有變動才會重新 compile。

整體上會加速 非常非常的多

2. use @import carefully

避免使用 @import "compass"; 這種寫法

compass 是大家很愛用的 SCSS framework。大家寫 gradiant 或者 css spriate 很常直接開下去。

但是你知道

@import "compass";

@import "compass/typography/links/link-colors";

這兩種寫法。

前者 compile 的速度可能比後者慢到 9 倍以上嗎?

會這麼慢的原因,是因為 compass 本身即是懶人包@import "compass"; 會把

  • "compass/utilities";
  • "compass/typography";
  • "compass/css3";

下面的東西 通通 都掛起來(還跑 directory recursive)。

所以自然慢到爆炸。如果要用什麼 helper,請直接掛它單支的 CSS 就好了,不要整包都掛上來。

全掛其慢無比是正常的。

避免使用 partial

我知道 partial 是 SCSS 整理術的大絕招。但是若非必要,也儘量避免一直單檔一路 @import 到底。

common.css.scss
@import "reset";
@import "base";
@import "product";
common.css.scss
//= require "reset"
//= require "base"
//= require "product"

這兩個在 asset pipeline 輸出結果是一樣的。但後者會比前者快。

如果真的需要用到非得使用 partial 的技巧(如需讀變數用 require 讀不到,@import 才讀得到)再使用即可,因為只要一牽涉到directory recursive compile 就會慢…

3. don't require .scss & .coffee for no reason

避免使用 require_tree

使用 generator 產生 controller 時,rails 會自動幫忙產生

  • product.css.scss
  • product.js.coffee

然後 application.css 與 application.js 會利用

application.css
//= require_tree

這種技巧來把這些檔案掛上去。

但是你知道嗎?就算這些檔案裡面只寫了這幾行注解:

# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/

而且實際執行結果也等於空輸出。compile 一支大概也要 250ms。你可以想想,多 compile 10 支,就是 2.5 秒了。難怪超耗時。

可以使用 .js 或 .css 解決的不要用 .scss 與 .coffee 當結尾

Compiled jquery-ui-1.8.16.custom.css (0ms) (pid 19108)
Compiled jquery.ui.1.8.16.ie.css (0ms) (pid 19108)
Compiled jquery.js (5ms) (pid 19108)
Compiled jquery_ujs.js (0ms) (pid 19108)
Compiled custom.css (14ms) (pid 19108)

其中 custom.css 的檔名是 custom.css.scss

這樣應該知道為什麼不要亂用 scss 當檔名了吧?

小結

為了方便大家調整,我把具體加速 assets precompile 過程的步驟羅列在下面。

1. 換掉 deploy.rb 的 assets precompile tasks
2. 觀察 logs/product.log。
  1. 找出慢的 assets。
  2. 拿掉直接使用 import "comppass"; 的 SCSS,改用功能針對性寫法。
  3. 不需要使用 @import 寫法的改用 require
  4. 拿掉 require_tree,改用 //=require 一行一行掛上去
  5. 刪掉空的 scss 與 coffeescript
  6. 單純只是 CSS 的不要自作聰明幫忙加上 .scss 檔名。

====

如果有什麼問題,歡迎各位在底下留言討論。

也歡迎大家有空來 Rails Tuesday 坐坐。我很樂意幫大家解答問題。

P.S. 如果你是要問 Rails 101 書上的問題,請找小蟹。

 
over 5 years ago

上一篇。作者提到了 「New-product introduction model」有非常大的機率,會讓一個 Startup 從車站一開始出發,終點就注定是地獄。整理歸納了的九宗罪,多數 Startup 都是因為這九宗罪而死掉的。

1. Assuming “I Know What the Customer Wants”

第一條是創辦人盲目的認為自己:

  • 知道客戶是哪些人
  • 知道客戶要什麼
  • 知道怎麼把產品賣給客戶

任何冷靜的觀察者從 Day 1 就會觀察到一個客觀的事實:一個 Startup 一開始不會有任何客戶。除非這個 Founder 原先就是這個領域的專家。

不然創辦人往往只能用「猜」的去猜測「會有哪些客戶」、「存在哪些問題需要解決」、和「可能的商業模式」。

而使用 「New-product introduction model」會讓創辦人將猜測當作是事實,在跟第一個真正的客戶聊過之前,開始設計產品和花大錢。

想要成功,創辦人需要將假設與猜測設法僅快的轉變成「事實」。唯一的途徑就是走出室外真正的與客戶攀談了解需求。當初的假設是否正確,僅快修正自己錯誤的猜想。

2. The “I Know What Features to Build” Flaw

第二條跟第一條有點相關。

創辦人會自以為懂他的用戶,先行假設出一堆他覺得用戶會需要的功能。「閉門」使用傳統的產品開發流程,精心打造一整套完整功能的產品。

但…等等,這是 Startup 應該做的事嗎?

不。這種方法通常是「Company」在已經有客戶的情況下才可以這樣作。

傳統式瀑布開發法,通常一開始起頭,就需要 1-2 年的時間。進度的衡量方式是在推出前究竟寫了多少行 code 以及製造了多少硬體出來。

但問題是,在開發過程中。不跟用戶進行直接且持續的交流,是很難知道哪一項功能才真正的吸引人。

在產品完工之後才再進行修改,代價往往高昂且耗時。巨大的開發能量被浪費,無數小時的工作成果被當成垃圾。

但諷刺的是,很多 startup 常愛用這種傳統的方式去開發產品。

3. Focus on Launch Date

傳統的開發流程會出現:Engineering、Sales 和 Marketing 的時程綁死在一個不能修改的「上線日」。

Engineering 的開發流程中通常會有 alpha / beta / release 三個階段,確保產品能夠有時間空間能被改善到能 deliver 的程度。

好笑的是,往往上線日,真正上線的產品的品質和進度卻往往都是「剛做完」而已。而不是到公司已經知道怎樣去行銷或者販售這個產品的程度。

但幾乎在每一間 startup,不管到底準備好沒有,不能修改的「上線日」卻往往跟「first customer ship」綁在一起。甚至更慘的是,有些投資者的財務計畫甚至跟這個時間也綁在一起。

投資者往往會說:「Why, of course that's what you do. Getting the product to market is what sales and marketing people do in startups. That's how a startup make money.」

這是 絕對致命的建議 ,千萬別理他們。(這句話是書上講的,不是我講的)

每一家 startup 或者是 company 當然都想要能夠一開始就順利的販售出新做出來的產品,並且能夠執行極佳的行銷策略。但這個夢只有在公司知道「誰會負責賣」以及「為什麼客戶願意買」的前提下才會達成。

但是多半的情形是,大家一廂情願的只會認為:有著良好的「Engineering Execution」,客戶就會買單…

一次又一次的,只有在上線之後,Startup 才會發現沒有足夠多的用戶會使用他們的網站、轉化成有效的訂單。早期用戶數不夠提升到主流市場。不能解決 high-value 的問題。更或者是配送成本過於昂貴。

發現這些事情已經夠慘了。

更慘的是情況變成騎虎難下的局面,已經花了很多錢卻沒有期待的效益。只好開始找問題在哪裡,看看還有沒有機會修正…

Webvan 的情況是,當初他們更身處在 dot-com 狂燥症熱錢到處是的年代,又加重了這樣的情形。公司在前半年只有 400 人,後半年就補了 500 人進來;在初期只開了一間值 4000 萬美金的配送中心,不久之後,又瞬間開設了 15 間相同等級的配送中心。

你問他們為什麼要這樣作?喔。因為這是 Business Plan 上寫的。不管真實狀況是不是需要。

4.Emphasis on Execution Instead of Hypotheses, Testing, Learning, and Iteration

Startup 的文化通常強調「get it done,and get it done fast」。

所以很自然的:「the heads of engineering, sales and marketing all believe they are hired for what they know to do, not what they can learn 」

這些頭頭們直接假設他們的經驗跟現在的事業有強烈的正相關,而且他們需要做的事就是在這個新事業「重製」他們在前單位所作的事。

問題是,Company 跟 Startup 是不同的。Company 需要的是「執行」business model,它們的客戶、需要解決的問題,和產品需要的功能都處於「已知」。

但 Startup 所需要的運作方式卻是必須開啟「Search」模式,然後測試和證明所有當初的猜想。從每次測試的結果中學習,提煉出猜想,然後再繼續測試一遍。目的就是要找尋出一種可以重製(repeatable)、規模化(scalable)以及能夠獲利(profitable)的 business model。

書上特別 highlight 了一段話,我覺得很棒: 「Relentless execution without knowing what to execute is a crime」

實務上,startup 是由一連串的原始猜想所組成,它們可能最後都是錯的。所以,如果專注在執行和產出一個全由這些原始猜想組成的產品,絕對是一個自殺策略。

而傳統的「product introduction model」的想法通常是直接假設建立一間 startup,是一個 一步一步來、有順序的、執行導向的過程。

每一個階段都可以被 PERT chart (PERT 圖是一個項目管理工具,用於規劃,組織和調整項目內的任務)所描繪。根據里程碑投入相對應的資源。

所以想搞 startup 還用「product introduction model」,難道不是有計畫的自殺嗎!?

5. Traditional Business Plans Presume No Trial and No Errors

傳統的產品開發模式對董事會和創辦人來說有一個很大的好處:它能提供一條看似不模糊的道路和前面還有哪些里程碑需要完成。

在這種模式中,財務進度也是用收入現況、資產負債表和現金流等實際指標來追蹤。

但是在 Startup 真實的狀況中,這些指標沒有一個適合。這些財務指標是用來衡量已有存在客戶、市場的大公司用的。

它們沒有一個可以用來追蹤 Startup 唯一目標的進度:那就是「找到一個可以重複、規模化的 business model」。

6. Confusing Tradition Job Titles with What a Startup Needs to Accomplish

多數的 Startup 會借鏡一般的公司所給的 Job Title。但記得,這些都是從已知生意模式借來的玩意。在這些公司中,所謂的「Sales」指的是重複的銷售一個已知的產品給一些已經理解這個市場運作規則的客戶。

但是 Startup 怎麼可能會有這些「已知」的這些玩意??

因為目標用戶、產品規格和產品介紹極可能可能每日一變,Startup 的早期成員要是能夠非常能夠適應混沌的人。他們要對學習和發現抱持著極大的開放態度、殷切找到「可以重複、規模化」的 business model」。

Webvan 的執行長和 VP 都是從大公司挖來的一幫人。他們對於這種 startup 的混沌都非常不適應。對於混沌,他們的解決手段就是:趕快讓公司急速長大,以為這樣就能解決問題。

7. Sales and Marketing Execute to a Plan

公司真的缺人的時候經應該補人。要補人當然要開出正確的缺補到適合的人。但,你確定你真的補進了正確的人嗎?

補進一個正確職稱的 VP,但是他卻用了錯誤的技能以及錯誤的經驗在作事,這也是對 Startup 的一場災難。

在一般公司裡,往往走的是依循著傳統的 Business Plan 和「product introduction model」。也就是讓董事會和創辦人對即將展開的這個新生意,設出 一個上線日、估算 burn rate、制定獲利計畫和一狗票里程碑。

這對已存在生意模式的公司當然是合理的。但大部分的 Startup 都不適用這樣的情形,

現實生活中 Startup 通常一開始小有成績,接下來就會想補業務拓展團隊。這時候的業務拓展團隊適合的方向往往該是跟 Product Development 部門摸索出可以重製而且可規模化的生意。

但問題是你挖來的 Sales VP 和 Marketing VP 可能往往不管這些,他只懂的做的是接著董事會這些假想計畫,假想一個狀況自顧自的進行舖天蓋地的銷售計畫以及行銷手段。

這是 Startup 所需要的嗎?不,這是大災難...

8. Presumption of Success Leads to Premature Scaling

傳統的 Business Plan 往往將公司發展的每個步驟敘述的完美無暇,天衣無縫。這使得在這樣的模式中,能夠犯錯、從中學習、根據客戶意見回饋修正的空間,被壓得很小。

從沒有人規定說「Stop or slow down hiring until you understand customers」或者是「pause to process customer feedback」。

即便是最有經驗的執行者也會被被迫根據進度一直補人。跟著這就會引起下一場 startup 災難:「premature scaling」

明明網站目前每天只有 5000 訪客。但 Bussiness Plan 上面寫的是,認為下半年每天應該衝到 50 萬訪客。這樣的規模就需要大買機器,大肆雇用人,擴張新 feature,於是就開始花大錢衝刺這些部分。

時間慢慢的過去,這些東西都沒有如預期般的用上,當然也沒達到成績。但是東西、人,都已經到位了。放著閒置也不是辦法,只好再找一些「不是事情」的事情給他們作。或者是拼命假想一些情境製作 feature。賭看看可否衝刺到當初的目標。

聽起來熟悉嗎?

書中很酸的舉了 Google 的 Orkut, Wave, Dodgeball. Microsoft 的 Zune, PocketPC 等等作為例子。這都是用了「on rigid schedules driven by the models and the presumption of success]」搞出來的災難。

雇用人和灑錢應該根據產品銷售狀況和市場反應是否能夠進入「可預測、可重複、可規模化」的狀態,而不是根據「它們應該按照原定計畫被執行」。

9. Management by Crisis Leads to a Death Spiral

通常董事會和創辦人會在事情已經發生了,「該做的都已經做」了卻沒有起色,之後才檢討到底出了什麼問題。

什麼是「該做的都已經做了」?

就是明明都已經請厲害的 PM、程式設計師打造這個產品了。行銷計畫也聘請了好的公關公司舖天蓋地的宣傳了,當中也請了不少 focus group 來對談。但是就是沒有多少用戶想來使用,更別提留下來變成長期用戶了。

他們往往檢討的原因不會在這段時間到底做了什麼錯事,結論往往會導向:當初的某個 VP 是否適任,他的策略有很大的問題。接著董事會會作一件事,就是再從外面挖來一個高手,換掉這個人,「修正」當初的錯誤。

而這個「高手」,一進來也會直接給一個結論:那就是「前一個人有問題,之前的策略通盤皆錯,於是我們必須這樣那樣。」他會說出這樣的話不意外。

因為這就是他被『雇用來的原因:前人有錯,之前策略有問題』。不然你期待他要說什麼?

但事情本質並不是這樣的。Startup 本來就是對『假設』一連串的『試誤』、『驗證』與『頓悟』。而非是一堆被『Bussiness Plan』和『Milestone』驅動的『怪物』。

後記感想

這一章節只有短短的 10 頁。但是卻讓我讀起來冷汗直流,腦海裡一直衝上不少真實場景、真實例子。

一直以來,我對一些實例百思不得其解。有些企業挾著原先的資源優勢和招募優勢,風風火火的搞了一個偽 Startup。最後卻慘敗收場。但是毫無資源的個人或單純只是優秀的程式設計師,卻赤手空拳自己蓋了一座雄偉堡壘。

你說這世界一定是這樣嗎?也有大企業投入資源最後取得有效的成功(美國、德國的大型山寨集團),而個人陣亡的更是不計其數。

每個創業家都在思考這個問題:『成功』到底跟『資源』有沒有正相關,還是只跟『團隊』與『創辦人』有關?

這些年來我也一直在思考這個問題的答案:只是每當以為自己稍微想通一點脈絡,另一個實例就突然打了我一巴掌。最後我也只好這些例子收起來,因素歸諸成『Luck』與『God』。

這本書,最吸引我的就是作者寫的前言和導讀。作者在這本書一開始的部分就寫,這一本書就是一本 step-by-step,教人怎樣建造一間成功、獲利、可規模化 startup 的指南。他認為這樣的公司不是神話而是可重製的。這本書就是答案。

他也不希望讀者一口氣就讀完這本書(而且他也認為讀者一口氣讀不完)。還寫了長達六頁的指南教大家怎麼讀。

還沒正式閱讀本書時,光看到這幾頁,我心中只有一個想法:「這個作者真狂妄」。這本書再厲害,也不可能有你講的這麼誇張吧?我就是要一次讀 200 頁,不可以嗎?

讀了 30 頁以後,我的想法徹底改變了。我開始認為他說的一切都是真的。而在這本書裡面勸告讀者的話,都是真心的。(竟然好心的寫了六頁教你怎樣讀這本書)

我開始對一些懵懂不解的問題有了答案:

至少我開始理解原來一直以來,大家習慣用的作事的方法,就是製造業一來使用的方法。只適合在有確定用戶,確定市場,確定 bussiness model 的情況下才能使用。也只有在這樣的情況下才有機會成功。

這很大程度了解釋了為什麼:個人、大公司要『新創』一個事業很容易失敗。而一些『大公司』要『山寨』一個服務也有機會取得成功。

Webvan 盛大開場,悲慘結束。也是因為盲目 follow business plan,沒有摸索出 customer 的樣貌,也沒有針對 customer 的 feedback 中調整產品,沒有從構築公司裡面學習並修正。錢花光,於是就破產收場了。

第一章通篇在講的是如果你想要『新創』一個事業,你絕對不能掉進這個傳統製造業的公式裡。而且作者認為,在 21 世紀的現在,網路與生活緊密接軌,新創公司、新創服務可以套用傳統製造業的公式的機會越來越小。大家都是往未知前進。於是你必須改用另外一種模式探索、構築你的 Startup 才行。

而這個模式就是作者頓悟出的另一個模式『Customer Development Model』。

Customer Development Model 分為四個階段:

  1. Customer discovery
  2. Customer validation
  3. Customer creation
  4. Company-building

我會在下一篇讀書心得中,整理這四個階段的內容。

====

(待續…一個禮拜,已經讀完了但是寫出來要花很久的時間)