over 4 years ago

最近跟朋友一個討論的話題,就是台灣這麼多人在網路創業,尤其是台灣也存在著不少質優的 developer,但為什麼所謂的網路創業為什麼都「不算成功」或甚至失敗得很徹底。

因為我也在創業的這條路上,這一兩年來我一直在思考這件事情,到底問題在哪裡?

一直到最近的這幾個禮拜我大概理出一個頭緒:原因是太多人把「創業」當作是一個「目的地」,而不是一個職業、解決問題的過程,為想創業而創業,所以所謂的失敗率才這麼高...。

我知道這樣的結論可能會招致很多反對的炮火聲,但請先耐著性子繼續讓我把最近歸結出的想法說完:

「上班」到「創業」不是一個「打怪」「升等」路線

這一個想法是從 給下個年輕世代的殘酷真相得出來的靈感。這篇文章有一段是關於年輕人對於未來的憧憬:

「22到24歲時念個好的研究所。在25歲左右開始第一份工作然後住自己租的公寓。順利的話我想要在28歲左右自己開公司,大約30歲左右結婚。我想我多數的同學和我在財務上想像我們能夠在28歲時擁有自己的車子,30歲時開始存錢準備有天能夠買自己的房子。」

如果你已經出過社會幾年了,就會知道這真的如同原文作者鐘先生所說的,是一個非常天真的夢。而在這個年代,你可能很難在 30 歲前達到這樣的夢想。這卻是這個世代普遍受過大學教育年輕人所共同的夢。

而這段話也意外彰顯出一個迷思:「受一個不錯的教育,預備找到一個好的工作。工作一段時間,升到不錯的職位得到不錯職業的薪水,最後創業達到財務自由。」

yes。「最後」「創業」

我們把從上班到創業當成是一個「打怪」「升等」路線,問題是真實生活卻不是這麼回事。

「上班」的終點不是「創業」

「上班」的終點不是「創業」。這是我最後思索驗證出來的結論。(雖然乍看之下好像是廢話)

我一直以來有一個謎團未解:為什麼有人「沒什麼技術」創業卻會成功,有人功夫不錯,為什麼創業卻會去失敗?

我想這也是大部分創業者共同的疑惑。幾年來我一直思考,但沒有答案。

直到最近才意外的在一本書裡面得到了一個比較可靠的模型,找出了一點頭緒,這本書的書名是:川普、清崎點石成金

清崎由一個模型為這個問題做出了解釋:它把所有人分成四種類型:

  • Employee(受僱者、僱員):為他人工作而賺取薪金,追求安全、穩定,畏懼風險的一群。
  • Self-employed(自僱者、專業技能者、自由職業者、小企業主):擁有某一專長而能為自己工作而賺錢,重視完美,不輕易將職責交託予人,如:醫生、律師‧‧‧等等。
  • Business owner(企業所有人、僱傭者、僱主、老闆):擁有一個能夠良好運轉的企業系統,視風險為挑戰、歡迎問題並樂於透過解決問題而致富的一群,信奉以別人的時間(OTHER PEOPLE'S TIME,OPT)以及別人的金錢(OTHER PEOPLE'S MONEY,OPM)為他們工作,收入來源是企業的收益。
  • Investor(投資者):讓錢為他們工作,收入來源是各種投資,用錢來產生出更多的錢,即是「富爸爸」一書中所強調「讓金錢為你工作」。

在這本書中,他指出世界上最有錢的人都是 B。(而非大家以為的 I )

且清崎認為,身處 E、S兩種象限,無法令自己達至財務自由。我們應該透過成為B、I象限,才能達到目標。

無可厚非的大家都想要變成 B,或變成 I 。但有幾種身分轉變模式,很容易失敗,分別是(E -> I , E -> B , I -> B )。而比較容易成功的是 ( S -> B ) 或者是 ( B -> I )。

為什麼一些創業者,創業不久後會瞬間就 fail 是因為他們往往在 E 階段,就直接想往 B 階段跨過去,缺乏太多存活的技巧,所以就直接陣亡。清崎建議的路線是,如果你想成功的話,採取 (E -> S -> B) 的路線,機率是會比較高的。

這個模型解答一些疑惑(為什麼有人快速陣亡),但卻沒有回答到另外一些問題:為什麼有人直接走 E -> B 或甚至直接 B 卻成功了。

「解決問題」然後才「創業」

Max 就是典型的例子,他是直接就當 B 的人(還有一些創業成功的網路界朋友是 E-> B)。E -> B 成功不是不可能。那麼關鍵點在哪裡?Max 不會寫 code,但他事業成功了。我也問了 Max,Max 只跟我說了他「解決了問題」,「也許應該是這樣」。

很多創業文章的重點,都是勸創業者實際「踏出辦公室」「實際解決問題」。這些文章的道理是不錯,總是令我覺得說不出的哪裡怪。

最近還有一篇文章 互連網創業降級論。也是說不出的詭異。

後來我終於想清楚所謂邏輯的謬誤在哪裡。所謂的「創業」應該是創業者有一個問題,創業者為了解決它,而製造了方法,最後重新將此方法規模化,乃所謂「創造一個事業」。而目前的網路創業很多卻是所謂的「創業者」手上擁有了一堆技術,然後到處找問題,想把這些「技巧」變成「解決的方法」。難怪失敗率很高。

因為創業者不是真真切切擁有一個「很痛的」問題,然後製造一個方法解決它。而是賭自己的方法「能夠找到一個問題而恰好」解決它。能夠賭中的機會已經夠低了。而碰上這個「問題」恰巧「很痛」的機會又更低,這個問題不夠痛,造成利潤追不上成本,最後虧本。

這就解釋了為什麼那些「沒有技術」的人為什麼能夠網路創業成功,他們並非所謂的幸運。因為他們並非「沒有技術」,他們擁有珍貴的「解決問題的 Domain Knowledge」和其他建構事業所需的綜合技能(如 Leadership, Finace, Accounting, Sales),缺的是「網站建構技術」。但這不會阻止他們成為 B。因為這樣東西可以用資本買到,品質的高低並不會太大方面影響到能夠解決問題的核心能力。

而只有「網站建構技術」,卻無法單獨自己形成一個生意。因為這項技術並沒有解決任何問題。(除非你的事業就是販售前者製作好的網站或者是使用這些技術解決同領域的問題)

所謂的 ESBI 模型,缺陷的部分就在於:對於 S 的解釋過於單薄。癥結點在於若創業者的 S 與 B 的「領域」並不是同一個的話,S -> B 的成功機率是很低的。

這也解釋了為什麼很多網路創業最終以失敗收場。因為這些 S 者,專精的領域是軟體、行銷,而非 Bussiness Domain。而那些成功的 B 者,並非幸運,而是因為他們都是該領域的 solution provider。

小結

繞了這麼遠,才終於把這套想法梳理成一個脈絡。為什麼清崎的建議是走 E -> S -> B。這是因為不一定有人可以一次走 B 就能成功。而作為一個 B,所需要的技能也並不僅只於單一方面的技術純熟就可以達成。所以走 E -> S -> B 是一個可靠的路線。但這絕不保證你走了 E -> S,就能到 B。

而要成為所謂的 B ,重點也不在於之前累計多少技巧,而是在「解決核心問題」的能力。「解決問題」,然後最後會變成「創業」,然後就會得到「超額回報」。

但是為了有一個生意而去製造一個生意,而不是把精力放在解決問題之上,通常結局不是失敗就是繞了超長的遠路....

終於或多或少了解了一些這樣的來龍去脈...

 
over 4 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 4 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 4 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 4 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 4 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 4 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 4 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 4 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 4 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 書上的問題,請找小蟹。