almost 5 years ago

這是我今天在 Ruby Tuesday #21 所寫的投影片。以下文章是寫投影片前已經擬好的草稿。可以配著服用

Rails Developer 在使用內建工具開發網站時,若是由小做起,通常不會踩到這些雷。但是若一開始開站資料就預計會有 10 萬筆、甚至 100 萬筆資料。執行 db 的 rake task 通常往往會令人痛苦不堪。

在這篇文章內,我整理了一些處理巨量資料必須注意的細節,應該可以有效解決大家常遇到的問題:

1. 盡量避免使用 ActiveRecord Object

ActiveRecord 當初的設計目的是為了框架內「商業使用」。它的工作是將純資料化為具有商業邏輯的 Ruby Object,並且配合框架,設計了多層 callbacks。

簡單來說,它並不是為了「處理 raw data」而設計。如果開發者只是要作一些簡單的資料操作,建議的方式請直接下 SQL,不要沾到任何 ActiveRecord。

(但大多數開發者直覺都是會開 ActiveRecord 下了條件就直接跑迴圈,忘記 MySQL 是可以直接拿來下指令的)

當然,沒有 ActiveRecord 這麼抽象化的工具,下純指令也是蠻痛苦的一件事,我推薦可以換用 sequel 這套工具試看看。

再者,在實務操作上我也建議避免使用 ActiveRecord + 內建的 rake task 操作巨量資料。原因是,開發者會順帶會把整套的 Rails 環境都載進來跑,其慢無比是正常的…

2. 有 update_all 可以用,少用 for / each。

通常會出問題的 code 是長這樣的:

posts = Post.where(:board_id => 5)

post.each do |post|
  post.board_id = 1
  post.save
end

這段 code 非常直觀,但會造成許多的問題。如果符合的條件有 10 萬筆,大概放著跑一天都跑不完....

先提供快速解法。Rails 提供了 update_all 可以下。可以改成這樣

Post.update_all({:board_id => 1}, {:board_id => 5})

基本上就是等於直接幫你下 update 的 SQL 啦。同樣資料量跑下去大概只要 10 秒秒以下左右吧。

3. 不要傻傻的直接 Post.all.each,可以用 find_in_batches

直接叫出所有符合的資料(Array) 是一件危險的事。如果符合條件的資料是 10 萬筆,全拉出來有高達 10G 的大小,嗯…我想機器沒個 10 G 以上的記憶體,指令下去機器直接跑到死掉有極大的可能性…

Rails 提供了 find_in_batches

Post.find_in_batches(:conditions => "board_id = 5", :batch_size => 1000) do |posts|
  posts.each do |post|
    post.board_id = 1
    post.save
  end
end

如果沒下 batch_size 預設一次是拉 2000 筆。可以一次指定小一點的數目,如一次 500 筆去跑。

4. 使用 transaction 跳過每次都要 BEGIN COMMIT 的過程,一次做完 1000 筆,然後再 COMMIT。

打開 Rails 的 development.log,這樣的 LOG 應該對你不陌生。

   (0.3ms)  BEGIN
   (0.5ms)  COMMIT

Rails 開發時,為了確保每比資料正確性,儲存的時候都會過一次 transaction,於是即使已經照 3 這樣的解法,還是要過 10 萬次 COMMIT BEGIN。很浪費時間。

Post.find_in_batches(:conditions => "board_id = 5", :batch_size => 1000) do |posts|
  Post.transaction do 
    posts.each do |post|
      post.board_id = 1
      post.save
    end
  end 
end

如果你只是要 update 少量欄位,而且資料處於離線狀態,可以使用 Transactions 搭配 find_in_batches,做完兩千筆再一次 commit,而不是每次做完就 commit 一次,在資料量很大的狀況下也可以節省不少時間。

5. 使用 update_column / sneacky-save 而非原生 save

用原生 save 會有什麼問題呢?原生的 save 在資料儲存時,會經過一堆 validatorcallbacks,即使你只是要簡單 update 一個欄位,會觸發到一狗票東西(假設 10 道 hook),那 10 萬筆就是過 100 萬道 hook 了啊。天啊 /_\,機器死掉不意外。

如果你想要閃掉 hook 的話,可以使用 update_column,

posts.each do |post|
  post.update_column(:board, 1)
  post.save
end

但 update_column 的缺點是一次只能 update 一個欄位,如果你有 update 多個欄位的需求,可以用sneacky-save 這套 gem。

如其名,sneacky-save 偷偷儲存不會勾動任何天雷地火。

6. 可以的話使用 Post.select("column 1, colum2").where

很多人會忽略一件事,Post.where("id < 10"),其實是把這 10 個 object 拉出 database。Post 裡面有什麼呢?會有幾千字的 content 啊。所以當你下了這道 comment 後,拉出來的是這些內容

Post Load (18.8ms)  SELECT `posts`.* FROM `posts` WHERE (id < 10)

拉出 10 萬筆會發生什麼事呢?(炸)

所以這也是我建議如果你沒有複雜操作(相依高度 model 邏輯)需要的話,千萬別碰 ActiveRecord,因為你不會知道會按下哪一顆核彈按鈕。

7. 使用 delegate 把大資料搬出去

ActiveRecord 裡面有 delegate 這個 API。如果你嫌要 Post.select("column 1, colum2").where 這樣東閃西閃很麻煩,還是希望使用 SELECT post.*。那麼不妨可以換一個思路,把肥的 column 丟到另外一個 table,再用 delegate 接起來。

class Post < ActiveRecord::Base
  has_one :meta
   
  after_create :create_meta
  
  deleage :content, :to => :meta
end

8. 操作資料前,別忘記打 INDEX

舉凡操作資料,多半是至少會先下個 condition。再看是直接用 SQL 處理掉還是跑迴圈。不過一般開發者最會中地雷的部分就是

  • 忘記打 index

忘記打 index 下 condition ,就會引發 table scan,這當然會很慢啊 /_\

  • 對 varchar(255) 直接打 index

使用 Rails 產生的 varchar,多半是 varchar(255),很少有人會直接去改長度的。而且使用 Rails 直接打的 index,也就是全長的 index 打下去了。效率爛到炸掉。

可以用這招 index 可以指定只取前面 n chars 的方式增進效率

ALTER TABLE post DROP INDEX PTitle, ADD INDEX(PTitle(13));

Percona 前幾天也有一個 talk 是 MySQL Indexing Best Practices,值得參考。

9. delete / destroy,刪除很昂貴。確保你知道自己在幹什麼。

首先第一件事要分清楚 delete 和 destroy 有什麼不同。

  • destroy 刪除資料並 go through callbacks
  • delete 刪除資料,不過任何 callbacks

所以要刪除資料前,請確認你用的是何種「刪除」。

destroy_all 和 delete_all 也是類似的原則。

找到符合特徵的紀錄,然後呼叫 destroy method。在這個動作中會引發 callbacks ….orz

找到符合特徵的紀錄,刪掉,但不觸發 callbacks

不過如果你真的要「清空 DB」。不要用 delete_all,MySQL 提供了:TRUNCATE 給你用。請用這個...

  • TRUNCATE TABLE
ActiveRecord::Base.connection.execute("TRUNCATE TABLE #{table_name}")

雖然 delete 不觸發 callbacks,但是「刪除」DELETE 真的很慢,因為
DELETE 涉及到會 update index,所以會…很慢。http://stackoverflow.com/questions/4020240/in-mysql-is-it-faster-to-delete-and-then-insert-or-is-it-faster-to-update-exist

如果你的資料要作大量的刪除動作,有兩種思路可以繞。

一個是使用軟性刪除 soft_delete,也就是加上標記標示已刪除,但實質上不從資料庫刪除資料,只 update 會比 delete 快一點。有 acts_as_archive 可以用。

另外一個想法是:與其用刪的 (DELETE) 不如用 塞的 (INSERT)

開一個新的 Table,用倒的,把 match 的 record 塞到新的 DB 去。INSERT 比 DELETE 快太多了。

10. 無法避免的昂貴操作丟到 background job 去操作。

使用 posts.each 是一個昂貴的線性操作。這個 process 會無限的膨脹及 block 資源。

所以可以改用一個作法,使用 background job 如

把昂貴的操作包成獨立事件。塞進 queue 裡面,丟到背景跑,然後開 10 支 worker,十箭其發,速度可以快不少。

之所以把 delayed_job 列出來又不推薦的原因是因為 delayed_job 清 queue 的方式是用 DELETE,在第九點我們談過了,在有大量資料的情況下,「刪除」這件事會非常昂貴。使用 delayed_job 無異是拿汽油澆火。

結論

十點列下來。我的建議是,如果你手上的資料量大到一個程度,能儘量回歸基本(SQL command)就回歸基本。因為使用 ActiveRecord ,開發者永遠不知道自己什麼時候會按下核爆彈的按鈕啊…

其他

目前我們固定在禮拜二,都會在 松江路的田中園 上舉辦 Taipei Rails Meetup。我自己本身也會固定在這裡免費幫大家解答 Rails 與 Web Operation 相關的問題。而坦白說,最近一些比較經典的 Post 也是從聚會裡的問答集裡面萃取出來的。

如果你對 Rails 有濃厚的興趣又住在台北,歡迎每週加入我們,謝謝!

 
almost 5 years ago

朋友的公司最近遇到一些公司成長上的問題。這些問題搞的他很頭大,於是也請我幫他想想辦法。看問題到底是出在哪裡…

公司裡面的員工,在當初面試時都沒什麼異狀,但進來之後有一些工作上的問題搞得他頭很大。

原因是有些人的職能明顯有落差(也就是當初面試進來時,是希望他補上這個工作的缺乏產能,但本身技能在進入這個團隊,跟著運轉時跟不上)。而他在幫忙修正 Coach 這些同事時,發現了一個大問題。

有人被指正一講就聽,馬上有顯著的進步。有些同事卻出現明顯的認知落差,明明知道自己已經出問題了,卻還是想要照自己的那一套「發揮」。類似的「發揮」病,也出現在一些菜鳥身上。菜鳥很明顯的基本功夫打沒打穩、團隊目標也沒摸清楚,就想要自作主張的改一些方向,造成白工。老是挑一些不是問題的問題反應,希望反應證明自己的才能。就算被電了,過沒多久還是會重複發生同樣的事情…

「發揮」病讓他一個頭兩個大。希望我幫他把脈解決…

本來這是別人的事情,加上這種事情其實在大公司也時常發生(以前我也遇過...),所以我也隨口安慰他:「這些事情本來就會遇到,不是你特別倒楣啦...」想要打發掉他 XD

他還是一直拜託我幫他找問題,因為這個問題讓他很痛。再來是,這些同事都是他「找進來」的...,要是真的救不起來,在「處理」上會非常麻煩…

共同特徵:「發揮病」

聽到「找進來」這個關鍵字,讓我燃起了興趣。於是我就追問他更多詳細的關鍵,員工組成成分…

然後才發現這些「發揮病」的患者有一件共同的驚人特徵,那就是他們真的都是「被從外面找進來的」。都是他透過「私人關係」從外面找來的「朋友」或「熟人」。

OK。找到問題了。Hiring 101:「千萬不要找朋友來當同事,否則通常出差錯了以後 90% 機率連朋友都做不成。而且很多朋友,是只有當朋友的本事而已,技術和職場相處上能不能當同事很難說。」

但是他反駁,當中這些人的技術或潛質是他自己驗證過,也算很不錯的。但進來之後不知為什麼也禁不起考驗。而且不約而同的,「發揮病」很嚴重。個人喜好隱隱約約的似乎遠遠重於團體目標…

「反而」那些是從網路上應徵來的員工,或自己主動投遞履歷進來工作的,幾乎都沒有這種問題。個個都是積極學習,主動了解團隊目標,樂於修正成長的好夥伴。

他很懊惱的抱怨,他不是不願意讓人「發揮」的 Leader,只是部分這些同事的「角色」,第一優先的不是發揮,而是「打穩基礎」(紮實做好手頭工作補上團隊缺乏產能,了解共同目標)。達到了,才能談所謂的「發揮」啊?

為什麼這些人不能學學那些正常的同事呢?

不是每一個人的位子上面都有「客卿」標籤

聽到這裡,我腦袋快速倒轉過幾遍以前遇過的 case。大概知道這個團隊出了什麼問題了。(其實斷斷續續也討論了不少次,一點一點拼起來...)

最根本原因就在於所謂的「客卿心態」。

別誤會,「客卿」並不是不好的詞。

「找來的人」之所以跟那些「主動應徵」的人,其根本心態不同點就在於,他是被「找來」的。既然是被「找來」的就是要來「發揮所長」的,這些同事認為他是「卿」。

這就造成美麗的誤會。有時候,公司「找」員工,並不一定是要找他「發揮」,而是要缺一個位子,希望「找合適技能的人」來補上這個位子「貢獻」。

所謂能夠讓人「發揮」的位子,多半是個獨立的職缺,或者是組織主管的位置。而讓人「發揮」的位子,身為主管,多半也會先幫他掃除一些障礙,比如說先跟團隊成員提醒這位是新主管,請大家多多配合。或者是這位新同事,有特殊的才能,但做事也有自己獨特的規矩…etc.(讓他融入團隊時比較順暢)

但是如果只是一個一般的職缺,公司怎麼可能特別為這個位子特別作這些事。但問題是,被「找來」的同事不會這麼想,他會認為他是來「發揮」的,也就是在一開始就會跟那些主動來應徵的人心態上就有根本的不同,潛意識上他的優先權不會是先去管別人的 rule 是什麼,甚至還會產生自己是卿所以自己比較厲害的心態,認為新同事應該「讓」他。

他自己會趕快想把私人壓箱寶拿出來,「作一些事」。然後就造成悲劇了…

當然,能夠發現自己有錯誤心態而趕快修正的人,不是沒有,而是相對比例並沒有那麼多…

延伸閱讀:The Startup Owner's Manual 讀書心得(2): New-product Introduction Model 致命的九宗罪 # 7. Sales and Marketing Execute to a Plan

相反地,來應徵的人或主動投遞,就通常沒有這一狗票問題。因為這些同事的出發點,是來「加入一個他想要工作的團隊」的。他們通常會假設這個團隊已經有自己獨立運作的一套 rule 了。他會想要先搞清楚這個 rule 在哪裡,如何將自己 fit in。而不是讓團隊來 fit in 他…

小結

Startup 最珍貴的資產就是同心一致的團隊,最害怕的是搞不清楚狀況的團隊成員各行其是…

不少 Startup 團隊在一開始草創時,缺乏自信或管道,找到戰力。因此都會想透過熟悉的渠徑「找」到人補上空缺,繼續往目標前進。

只是現在看起來,這個「找」,看起來還是要好好的看狀況使用啊…

 
almost 5 years ago

睡前看到 DK 在 twitter 講 Percona 有一場 Webniar 在講 MySQL index 的 best practices。

結果連過去看時發現已經結束了。不過還是不死心的註冊了一下…

大概半小時後系統通知影片和 slide 在 http://percona.tv 已經可以抓了…

內容講的蠻紮實。教了不少讓開發者判斷 Index 如何下、下得好不好的準則…

有時間應該整理成講義的…

===

打完這篇要去睡之前,又在 twitter 上看到 confreaks 丟出了新的 pry 影片 http://confreaks.com/videos/959-mwrc2012-prying-into-your-app-s-private-life。(前幾天寫過的 Pry :新一代 Debug 利器 )

 
almost 5 years ago

提起 Pry,一般 Ruby 開發者幾乎對這套 Gem 沒有很深的印象。比較有在追社群新聞的人,會知道這是一套新的 IRB 取代方案,但僅此於如此。事實上在近一年前,Pry 被 Ruby5 報導的原因 也是因為很炫的 console。

比如說 Pry 允許開發者在 console 這樣幹:

pry(main)> cd Article
pry(#<Class:0x1022f60e0>):1> self
=> Article(id: integer, name: string, content: text, created_at: datetime, updated_at: datetime, published_at: datetime)

Article.first

pry(#<Class:0x1022f60e0>):1> first
=> #<Article id: 1, name: "What is Music", content: "Music is an art form in which the medium is sound o...", created_at: "2011-08-24 20:35:29", updated_at: "2011-08-24 20:37:22", published_at: "2011-05-13 23:00:00">

cd name

pry(#<Article:0x102300c98>):2> cd name
pry("What is Music"):3> upcase
=> "WHAT IS MUSIC"

允許了開發者利用 console 進行程式的進階探索。當然這樣的 feature 是很炫。但是不算很大幅解決了開發者的問題,所以只被當作是一套還不錯的 shell。就這麼被大家靜悄悄的撇在身後了…

killer feature: binding.pry

但是大家比較沒有注意到的是,Pry 真正強大的地方不在於它的 console,而是在後面接著演化出的 binding.pry

binding.pry 做的是 Runtime invocation。也就是可以在執行時攔截呼叫。這樣講你可能沒有感覺。

真正厲害的用途是: 例如搭配 Rails 使用,在程式碼裡面插入 binding.pry。打開 rails s

class CourseController < ApplcationController
  def show
    @course = Course.find(params[:id])
    binding.pry
  end

當 browser 打開 http://localhost:3000/courses/30,pry 會自動攔下 request,跳出 console 供開發者 debug 。

From: /Users/xdite/Dropbox/projects/mentorhub/app/controllers/courses_controller.rb @ line 20 CoursesController#show:

    20: def show
    21:   @course = Course.find(params[:id])
 => 22:   binding.pry
    23: end

開發者可以在 console 直接就拉出 @course 這個 object 出來看

[1] pry(#<CoursesController>)> @course
=> #<Course id: 30, name: "voluptas", user_id: 1, course_topic_id: 2, plan: "Laboriosam labore soluta debitis excepturi consequa...", hourly_rate: 822, location: "Taipei", course_type: nil, created_at: "2012-08-12 09:41:21", updated_at: "2012-08-12 09:41:21", video_link: nil, video_link_html: nil>

也可以繼續追下去看裡面的東西

[2] pry(#<CoursesController>)> cd @course
[3] pry(#<Course>):1> plan
=> "Laboriosam labore soluta debitis excepturi consequatur et eos et et praesentium doloremque. qui debitis ab est rerum aut velit fuga ut nemo omnis eum praesentium voluptatem ut. eum fugit rerum fuga error architecto quod nesciunt assumenda in. dicta "

binding.pry 可以 Runtime 攔截呼叫物件,這讓開發者在寫一些複雜 Library 或者是 API 交涉資訊時,頓時就變得如虎添翼。因為每次在解決這類需求時,狀況都很像被綁黑布蒙著眼開發,最討厭的就是每次還要不斷的執行「印出」 debug,效率低落的驚人。

pry-nav

也因為 binding.pry 太好用。社群也基於 Pry 繼續做了其他的 pry 的 plugin。最 killling 的就是 pry-nav

pry-nav 做的就是可以讓你在 binding.pry 的攔節點前後,作 nextstep。直接一行一行的逐一 debug。

相信我,如果你在寫通訊交涉的 Library,或者是正在改複雜的 Rails View。用到 pry + pry-nav 鐵定會感動到哭出來 XD

pry-remote

Pry 搭配 Rails,在往常的作法只有 rails s 可以叫出 debug console 而已。但很多人實際上是使用 Pow 作為開發用 HTTP Server。

這樣的需求可以用 pry-remote 解決。pry-remote 的作法是把原本的 bindig.pry 改成 binding.remote_pry

binding.remote_pry 會開一支 DRb 起來,開發者再用 pry-remote 連到 debug console。

小結

Pry 在短短一年間,已經默默的演化出一個龐大的生態圈,只是這當中的過程並沒有大張旗鼓,所以很多開發者並沒有發現 Pry 其實已經默默從 console shell 進化到超強 Debugger 了。

Pry 的 wiki 上有著相當大的相關資源,相當值得各位繼續探索下去…

同場加映

rack-webconsole 一樣是 pry 的應用,可以在 webpage 裡面直接開 console 改東西…超酷的

追加

Confreaks 最近又釋出了 Moutain West Ruby conf 的 Pry talk http://confreaks.com/videos/959-mwrc2012-prying-into-your-app-s-private-life

 
almost 5 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 ,重點也不在於之前累計多少技巧,而是在「解決核心問題」的能力。「解決問題」,然後最後會變成「創業」,然後就會得到「超額回報」。

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

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

 
almost 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

大家可以研究看看…

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

 
almost 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 的小升級而升級,而不會被纏到這個纏那個...

 
almost 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 來找我討論。

系列連結

 
almost 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 要如何設計…

系列連結