台灣日光節約時間之考據

· Comments

去年(2013)我寫過一篇文章是〈台灣時區變換的八卦〉,當時寄信去 IANA TZ Database 的 mailing list 提 patch,一年後終於改了,原因是我給的 link 裡面有 non-ASCII 文字(非英文數字的文字),為了這個,所以 Time Zone Database 必須修改成 UTF-8-compatible 的格式(可以容納非英文數字的文字),所以才拖到現在。

因為最近收到回信,所以我又去研究了一下台灣的時區,這次重點是放在日光節約時間。

在 JavaScript 實作 Mixin / Concern

· Comments

有這樣的需求:

  • 某兩個 class 要共用某一組 functions / variables
  • 發現到 JavaScript 只能單繼承,不支援 mixin (module)

CoffeeScript 也沒能很好解決這個問題。不過在 CoffeeScript Cookbook » Mixins for classes 這裡提了一個很有趣的做法,就是先混成兩個要繼承的 classes,再給新的 Class 繼承。缺點是 mixin 有更動的時候,不會反映在之前 include 過的 class 裡面。

土砲 Mixin

於是找到了 Addy Osmani 大師的 Learning JavaScript Design Patterns > Mixin Pattern 這篇文章,裡面是借用了 Underscore / Lo-Dash 的 _.extend 來實作的,看來非得這樣做不可了。

寫起來會像這樣:

concern.js.coffee
Function::include = (mixin) ->
  _.extend(this.prototype, mixin)
duck.js.coffee
this.Duck =
  quake: ->
    "quake quake"
  
  walk: ->
    "walks like a duck"
yazi.js.coffee
class Yazi
  @include Duck
kamo.js.coffee
class Kamo # "duck" in Japanese
  @include Duck
app.js.coffee
yazi = new Yazi()
kamo = new Kamo()

yazi.quake() #=> "quake"
kamo.quake() #=> "quake"

此外也可以在新的 class 裡面覆寫 function / variable 就是了。

然而這種做法跟第一種做法一樣,mixin 有更動的時候,不會反映在之前 include 過的 class 裡面。

要注意的是,因為我們專案用的是 CoffeeScript,所以寫起來像 @include 這樣子看起來很簡潔有力,如果換成 JavaScript 的話會變得比較醜一點,以下是 CoffeScript 編出來的結果:

kamo.js
var Kamo = (function() {
  var Kamo = function() {};

  Kamo.include(Duck);
  
  return Kamo;
})();

var kamo = new Kamo();
kamo.quake(); //=> "quake"

ActiveSupport::Concern 化

當然也可以弄得像 ActiveSupport::Concern,我是說 included callback:

concern.js.coffee
Function::include = (mixin) ->
  functions = _.omit(mixin, "included", "classFunctions")

  _.extend(this.prototype, functions)
  _.extend(this, mixin.classFunctions) # insert class functions in class itself

  # Call "included" callback function if available
  if typeof mixin.included is "function"
    # call included(mixin, base)
    # this = the mixin included, base = the class which called `include`
    mixin.included.call(mixin, this)
duck.js.coffee
this.Duck =
  quake: ->
    "quake quake"
  
  walk: ->
    "walks like a duck" 

  included: (base) ->
    console.log "mixing this class as a duck: #{base}"

  classFunctions:
    kindOfDuck: -> true
kamo.js.coffee
class Kamo
  @include Duck
app.js
// Console prints "mixing this class as a duck: function Kamo() { ..."
Kamo.kindOfDuck() //=> true

var animal = new Kamo()
animal.quake() //=> "quake quake"

至於怎麼弄到可以像 Rails 4.1 的 Concerning 我還沒想出來……。

第一次上 Amazon Japan 買 CD 就上手

· Comments

一般來說在台北要買日本進口 CD,通常是去西門町的九五樂府訂。不過這次我想要嘗鮮試試看 Amazon JP,一來是因為九五樂府網站沒有加密我不安心,二來是要跑去門市取貨我很懶,第三是有一張專輯在九五樂府的網站上沒有得訂。既然如此,就試試看 Amazon JP。

結果一試之下不得了了!算一算加運費還跟九五樂府差不到 100 塊錢台幣,而且到貨超快!

2014-05-28 00.53.59 HDR.jpg

向 RailsGirls Taipei 社群的致歉

· Comments

本人莊育承,Twitter 代號 @yorkxin ,在此向 RailsGirls Taipei 主辦單位以及主辦人之一的 Manic (@ManicLF) 致歉。

我在上一篇文章〈RubyConf Taiwan 2014 「女性歧視」事故再掀 Code of Conduct 風波〉裡面,拋出了這樣的疑問:

RailsGirls 在全球遍地開花幾乎都是歡迎男性的(只要你帶一個女生來,名額不足則女士優先),而 RailsGirls Taipei 包括進階班 LTRT - Let's Try Rails Together 至今公開的活動都只限女性,男兒當自強,女生就來手把手帶練功,是否也算性別歧視?在台北「你想跟我們學 Rails 就切雞雞」?

並且刻意在 Twitter 上四處質疑這樣的正當性何在。

致歉的原因,是因為這種事情,其實我可以直接問 RailsGirls Taipei 主辦人 Manic ,我就可以得到這樣的答案:

的確是很合理的原因。

於是,先前我大聲質疑這點的正當性,還和社群朋友在 Twitter 上吵架,現在看來,都是可以避免的。

本人在此為我的魯莽道歉,也希望相關人士可以瞭解我的疑慮、我為何質疑,並諒解我。


瞭解這個原因之後,我便曉得,我能做的就是另外開一個 Workshop ,這個 Workshop 將不會以開拓特定族群的程式設計視野為優先,而是讓想學 Rails 的人可以直接加入。畢竟人多好辦事,平行運算跑得快。我希望 Ruby 社群可以成長,可以成為對新手友善的社群。

並且在這裡要先透露,我已經和幾位朋友開始規劃 Workshop ,具體細節確定之後,近期會公開。

Update: Ruby on Rails Outreach Workshop Taipei #01 現在已經開放報名,除了台北場之外,台南場和高雄場也會陸續展開。

RubyConf Taiwan 2014 「女性歧視」事故再掀 Code of Conduct 風波

· Comments

我知道寫這篇文章會得罪很多人,不過做為一個台灣的 Ruby 程式設計師,我認為我有必要記錄這件事。

RubyConf Taiwan 2014 剛結束,應該要是和樂融融大拜拜,卻在最後一天的 ligntning talk 掀起了一陣不小的風波。

而這風波是西方人最討厭的「女性歧視」。

事件經過

Lightning Talk 其中一場由 Boris Stitnicky 演講的 Executable Systems Science in Ruby,講到一半的時候,電腦不小心啟動了 Mac 的 Exposé 顯示桌面的功能,於是投影幕上就顯示了他的桌布,很不巧,是一張「辣妹照」。

這裡我要特別聲明:我當時低頭在看自己的電腦,所以沒有親眼看到「辣妹照」長什麼樣子,而是旁邊的人跟我說是這樣子的照片。我無法用我(保守的)道德標準評斷這張照片。後來問了朋友,說是比基尼泳裝照,[Update] 根據 @sheseee 在本文下方的留言,是女性內衣廣告的照片,以我(保守的)道德標準,我也會覺得不舒服。

接下來 Twitter 上就有人發難了。

這裡我不認得一個單字, "misogyny" ,查了字典才知道是不好的字,叫做「厭惡女性」。

Microsoft Sculpt Mobile Mouse and Mac

· Comments

tl;dr: Everything works properly, except the Windows Button. But you can remap it to Mouse Button 4 using KeyRemap4MacBook.

I just got a new mouse: Microsoft Sculpt Mobile Mouse. And because I only use Mac OS X, I didn't expect that all the features are available on OS X. The reason I choose Microsoft's mouse over Logitech's is because that many people reported that horizontal scrolling, or "spin", is not working at all on OS X.

Here is the test result for those who want to get one but don't know whether it works on your Mac.

關於 Ruby 的 autoload 與 Rails 的 autoload_paths 以及 reopen module / class

· Comments

最近在實作一個特別的需求,做了一個 gem 搞這種事:

  • 在 Gem 裡面, lib/models/post.rb 定義 Post < ActiveRecord::Base
  • 在 App 裡面, app/models/post.rb 打開 class Post 多寫一些 app-specific methods

然後就搞了三天搞不定。

具體的現象是:

  • 在 Gem 裡面,不論是使用 Kernel#autoload 還是 Rails 的 config.autoload_paths << 來做到自動載入,都無法在 App 改寫 Post class 。
  • 如果在 Gem 裡面不做 autoloading ,則 Rails 會去抓 App 裡面的 app/models/post.rb, which is not inherited from ActiveRecord::Base 。

之後試了繼承(很難搞)和 module ,最後是用 ActiveSupport::Concern 包了 module ,把 association 之類的東西寫在 included do 裡面,解決。

今天讀到這篇文章 Rails autoloading — how it works, and when it doesn't ,對於 Ruby 和 Rails 的 "autoload" 有粗略的瞭解了。簡單整理如下:

  • Ruby 的 Kernel#autoload 是告訴 Ruby runtime 「要找某個 constant 的時候,可以載入某檔案」,比較像是「登記」,在登記之後, Ruby runtime 若發現程式裡面有要用某個 const ,但沒有定義,就會載入該檔案,這是發生在「第一次使用」的時候,用第二次就不會觸發。
  • Rails 的 autoloading 跟 Ruby 的 Kernel#autoload 完全不一樣,實作方式是用 Module#const_missing :抓不到(const 在 runtime 沒定義)的時候才自動根據 constant 找檔名,例如 Taiwan::Taipei::SungShan 就是會找 taiwan/taipei/sung_shan.rb
  • 承上,「要去哪裡找檔案」這件事,是在 config.autoload_paths 設定的,這個 array 就是「要自動從檔案載入缺失的 const 的時候,就去依序搜尋哪些路徑」,類似 shell 的 $PATH 。如果檔案不存在,就會 raise NameError ;如果檔案存在,但 const name 跟所要找的不同,就會出現「Expected app/models/user.rb to define User」這種錯誤。
  • 承上,第一次載入完成以後,就可以在 Runtime 裡面找到,所以不會再度觸發 const_missing 來自動搜尋。

所以:

  • Kernel#autoload 不應跟 Rails 的 autoload_paths 混淆,要視為兩個完全不同的功能
  • 誰第一次載入誰算數, Rails 只在找不到該 const 的時候才會去 autoload_paths 搜尋
  • 所以,如果某個 const (class / module) 已經在 runtime 裡面定義了,那麼要在 Rails 裡面 reopen 它,就必須確定它一定會執行,例如 initializers 裡面,或是手動 require 它。如果是放在某個 autoload paths 裡面,例如 app/models/ ,則 Rails 並不會執行之,因為同名的 const 已經在 Runtime 裡面了。

這也就是為什麼會有「在 gem 和在 app 裡面,同名的 model class 是 mutually-exclusive,除非手動 require 才能改寫其內容」。也就是說,想要在 gem 裡面定義一個 model ,然後在 rails app 裡面 reopen 它,是不可能的,必須要手動載入它的 reopening。

說得更 general 一點就是:如果該 class / module 已經在 Gem 裡面載入,則要在 Rails 裡面 reopen 它,就必須放在 autoload_paths 以外的地方,並且手動 require 之。


該文很推薦一讀,除了詳細說明了 Ruby 和 Rails 的 autloading 機制,還提到一些陷阱,例如說 Rails 的 autoloading 其實不會理 Module.nesting (lexical context of current line) ,這樣子某些情況下會變成「第一次可以成功 autoload ,但第二次卻說 NameError 找不到 const」這種問題。