高見龍

iOS app/Ruby/Rails Developer, 喜愛非主流的新玩具 :)

Ruby 語法放大鏡之「有時候會看到有兩個冒號寫法是什麼意思?」

Ruby 語法放大鏡」系列短文主要是針對在大家學習 Ruby 或 Rails 時看到一些神奇但不知道用途的語法介紹,希望可以藉由這一系列的短文幫大家更容易的了解到底 Ruby 或 Rails 是怎麼回事。

隨便打開你 Rails 專案裡的 model 檔案,它可能會長得像這樣:

1
2
3
4
class Post < ActiveRecord::Base
  belongs_to :user
  belongs_to :category
end

第一行後面那個 ActiveRecord::Base 裡面的 2 個冒號 ::,各位知道是什麼意思嗎?

菜市場名

也許你也叫金城武(或自己覺得自己長得像金城武),然後我也剛好也很巧叫金城武,這樣在大街上大叫一聲帥哥就一堆人回頭了,不太好。

寫程式偶爾也會遇到這樣的狀況,舉例來說,如果大家剛好都有一個叫做 Animal 的類別或是 Flyable 的模組,放在同一個專案裡就會打架了。所以 Ruby 有設計 namespace 來解決這個問題。

在 Ruby 裡,因為 moduleclass 可以有巢狀結構(類別裡可以包模組,模組裡面可以包類別,就是大腸包小腸的概念啦),剛好可以拿來做 namespace 的包裝,例如:

1
2
3
4
5
6
7
module Utility
  module Flyable
    def fly
      # ....
    end
  end
end

這樣一來,如果要使用 Flyable 這個類別的話:

1
2
3
class Cat
  include Utility::Flyable
end

要 include 那個 Flyable 模組,就必須連名帶姓的 include,也就是「樹林::金城武」、「三重::劉德華」的概念。

除此之外,其實你也可把常數包在 namespace 裡:

1
2
3
4
5
6
7
8
9
10
11
12
module Utility
  TaxRate = 10
  module Flyable
    Speed = 20
    def fly
      # ....
    end
  end
end

puts Utility::TaxRate          # => 10
puts Utility::Flyable::Speed   # => 20

要使用這些常數,也是一樣要提供完整的路徑給才行。(如果你知道的話,其實不管是類別或模組,它們的名字基本上也就是常數而已)

所以,原來這段程式範例:

1
2
3
4
class Post < ActiveRecord::Base
  belongs_to :user
  belongs_to :category
end

它的意思就是說有一個叫做 Post 的類別,它是繼承自某個放在 ActiveRecord 的模組(或類別)裡的 Base 類別。如果去 github 上翻 Rails 的原始碼的話(連結),應該可以看到這段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
module ActiveRecord
  # .....
  # 中略
  # .....
  class Base
    extend ActiveModel::Naming
    extend ActiveSupport::Benchmarkable
    extend ActiveSupport::DescendantsTracker

    extend ConnectionHandling
    extend QueryCache::ClassMethods
    # ... 略
  end
end

的確是有一個 ActiveRecord 的模組,裡面包了一個 Base 的類別沒錯 :)

Ruby 語法放大鏡之「類別跟模組有什麼不一樣?」

Ruby 語法放大鏡」系列短文主要是針對在大家學習 Ruby 或 Rails 時看到一些神奇但不知道用途的語法介紹,希望可以藉由這一系列的短文幫大家更容易的了解到底 Ruby 或 Rails 是怎麼回事。

在網路上常看到一些介紹 Ruby 裡類別(class)跟模組(module)的差別,有人說他們兩個差別很大,是完全不同的東西。事實上,類別跟模組是很像的。可能很多人不知道,類別跟模組是有血緣關係的,如果你試著這樣做:

1
puts Class.superclass

你會得到 Module 這個類別。也就是說,在 Ruby 裡,「類別」其實就是「模組」的後代,類別根本就是繼承自模組來的。即然是同一個體系,自然沒有太多的差異。

那差別是什麼?

最明顯的差別,當然就是一個是用 class 來定義,一個是用 module 來定義。至於前、後代功能上的差別,我們只要寫幾行簡單的程式就可以看得出來:

1
2
3
4
5
# 前、後代的類別方法差異:
p Class.methods - Module.methods

# 或直接這樣寫:
p Class.methods(false)

上面這段會得到 [] (空陣列),表示他們在類別方法上並沒有差別。再來看看實體方法的差別:

1
2
3
4
5
# 前、後代的實體方法差異:
p Class.instance_methods - Module.instance_methods

# 或直接這樣寫:
p Class.instance_methods(false)

會得到以下結果:

1
[:allocate, :new, :superclass]

其中,allocatenew 跟產生實體(instance)有關,superclass 跟繼承有關。

也就是說,類別比模組多了可以產生實體的能力以及繼承能力。

那用途上有什麼差別?

請大家先思考一個問題:「如果我有一個小貓類別,我希望這個小貓會飛,你會怎麼做?」

  1. 直接寫一個有飛行功能的小鳥類別,然後再叫小貓類別去繼承它?
  2. 直接把飛行功能寫在小貓類別裡?

第 1 種做法的設計有點怪怪的,好好的貓不當,為什麼要去當鳥? 為了想要有飛行功能就去當別人家的小孩…

第 2 種做法看來似乎可行,但如果之後又有個「我希望我的這個小狗類別也會飛!」的需求,那這樣又得在小狗類別裡寫一段飛行功能,程式碼沒辦法共同。

這時候,模組就可以派上用場了。

1
2
3
4
5
6
7
8
9
10
11
12
module Flyable
  def fly
    puts "I can fly!"
  end
end

class Cat
  include Flyable
end

kitty = Cat.new
kitty.fly        # => I can fly!

在上面這段範例中,我做了一個飛行模組(Flyable),然後小貓類別不用特別寫什麼功能,就只要把這個飛行模組掛上去(include)就搞定了。

如果之後小狗類別也想要會飛的話,只要這樣:

1
2
3
class Dog
  include Flyable
end

小狗也會飛了。

要用繼承還是要用模組?

基本上,如果你發現你要做的這個功能,它可能在很多不同體系的類別裡都會用得到,那你可以考慮把功能包在模組裡,然後在必要的時候再 include 進來即可。

如果你還是不知道到底類別跟模組有什麼差別,我再舉二個例子。

不知道大家有沒看過火影忍者這部漫畫,漫畫裡的主人公之一,宇智波佐助,因為他們家族血統的關係,他寫輪眼這個功能是天生就有的,這個功能是從他的家族「繼承」來的。而佐助的老師,旗木卡卡西,他雖然也有寫輪眼功能,但他的寫輪眼並非繼承來的,事實上是他在年輕時候 include 了某個寫輪眼模組,所以才有這個效果。

另一個例子,海賊王漫畫裡,魯夫本來是普通人,但在偶然的機會下,他 include 了橡膠果實之後,他就有了橡膠人的能力了,並不是因為他老爸是橡膠人所以他才是橡膠人。

以上,希望能讓大家在使用類別跟模組時有更進一步的認識 :)

Ruby 語法放大鏡之「Ruby 的參數」

Ruby 語法放大鏡」系列短文主要是針對在大家學習 Ruby 或 Rails 時看到一些神奇但不知道用途的語法介紹,希望可以藉由這一系列的短文幫大家更容易的了解到底 Ruby 或 Rails 是怎麼回事。

大家在寫 Rails 專案的時候,一定都有用過 link_to 這個 view helper。問大家一個簡單的問題:

1
link_to '刪除', user, method: :delete, data: { confirm: 'sure?' }, class:'btn'

請問,上面這段範例中,link_to 方法共有幾個參數?

如果你的答案是 5 個,那你就需要繼續往下看了 :)

這篇文章中提到,Ruby 很愛省略東西,像是方法的小括號,所以原來上面的 link_to 語法:

1
link_to '刪除', user, method: :delete, data: { confirm: 'sure?' }, class:'btn'

原本應該長這樣:

1
link_to('刪除', user, method: :delete, data: { confirm: 'sure?' }, class:'btn')

最後一個參數如果是 Hash,大括號可省略

在 Ruby 省略小括號大家都知道,不過逗點分開的參數不是 5 個嗎?

事實上,在 Ruby 中如果最後一個參數是 Hash 的話,它的大括號是可以省略的。舉個例子來說:

1
2
3
def say_hello_to(name, options = {})
  # do something
end

如果要使用這個方法,可以這樣寫:

1
say_hello_to "eddie", {age: 18, favorite: 'ruby'}

又,因為最後一個參數是 Hash,所以 Hash 的大括號也可省略:

1
say_hello_to "eddie", age: 18, favorite: 'ruby'

如果你了解有什麼東西被省略的話,一開始的那段 link_to 的範例:

1
link_to '刪除', user, method: :delete, data: { confirm: 'sure?' }, class:'btn'

還原之後會變成:

1
link_to('刪除', user, {method: :delete, data: { confirm: 'sure?' }, class:'btn'})

所以,其實參數個數只有 3 個,最後一個參數是一個 Hash。

小結

Ruby 的語法可以適時的省略小括號、大括號或 return,程式碼寫起來的確會更像文章,對新手來說可能會容易混淆,不過看久了應該也會慢慢習慣了。

以上,希望對大家有幫助 :)

Ruby 語法放大鏡之「attr_accessor 是幹嘛的?」

Ruby 語法放大鏡」系列短文主要是針對在大家學習 Ruby 或 Rails 時看到一些神奇但不知道用途的語法介紹,希望可以藉由這一系列的短文幫大家更容易的了解到底 Ruby 或 Rails 是怎麼回事。

先說結論:

attr_accessor 會幫你在 Ruby 的類別裡產生一對 getter 以及 setter 方法。

不過我想這結論對新手來說有講跟沒講一樣,讓我們繼續往下看。

跟別的程式語言比起來,Ruby 可以省略很多東西,像是呼叫方法的時候可以不用小括號,回傳資料的時候有時候不用特別加 return

我們先來看一段範例:

1
2
3
4
5
6
7
class Girl
  def initialize(age)
    @age = age
  end
end

mary = Girl.new(20)

這是一個簡單的 Ruby 類別,我用 Girl 類別建一個名為 mary 的實體(instance),並且在初始化的時候就設定她的年紀為 20。Girl 類別裡有一個 @age 這個實體變數(instance variable),也許你會猜說如果要知道 mary 的年紀的話,只要:

1
puts mary.age

就行了,但一執行就會發現錯誤訊息:

undefined method `age' for #<Girl:0x007f93a609fa10 @age=20> (NoMethodError)

怪了,我是想要存取 age 這個屬性,為什麼錯誤訊息卻是 undefined method?

在解釋之前,要先說明幾個 Ruby 這個程式語言跟別家程式語言在設計上的不同:

一、Ruby 的方法呼叫,常常會適時的省略小括號:

舉個例子來說:

1
2
3
4
5
def say_hi_to(name)
  puts "hi, #{name}"
end

say_hi_to("eddie")

但我們通常會寫成:

1
say_hi_to "eddie"

在呼叫方法的時候省略小括號,這在 Ruby 是很常見的寫法。

二、Ruby 並沒有「屬性」(property/attribute)這樣的東西:

雖然 mary 看起來有一個 @age 實體變數,但不表示是可以直接存取的屬性。硬是要用 mary.age 問她年紀,或是要用 mary.age = 18 來設定她的年紀,她都會賞你一巴掌,給你錯誤訊息的。

mary.age 你以為是讀取 mary 上的 age 屬性,但事實上是在執行 mary.age() 方法,只是小括號被省略了。mary.age=18 你以為是設定 mary 的 age 屬性,但事實上是執行 mary.age=(18) 方法,只是小括號被省略了。

在 Ruby 裡,很多東西都跟你看到的不太一樣,例如,你以為 1 + 2 是簡單的數學運算嗎? 其實它是 1.+(2),它是對數字物件 1 送了一個 + 的訊息,並且把數字物件 2 當做參數傳給它。

好啦,即然知道它們都是方法,那要怎麼定義它們呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
class Girl
  def initialize(age)
    @age = age
  end

  def age
    return @age  # 這個 return 通常會省略
  end

  def age=(new_age)
    @age = new_age
  end
end

上面這段範例中,第 6 ~ 8 行的方法會回傳 @age,又常稱之 getter;第 10 ~ 12 行的方法它會設定 @age 的值,故又常稱之 setter。

不過…等一下! 為什麼方法後面有個等號?

請把等號當做一般的字母看待。在 Ruby 定義方法的時候,等號跟其它字元一樣都可以是方法名字的一部份,只是這個特殊字元必須要放在方法名稱的結尾(其實包括問號跟驚嘆號也都一樣)。

這個方法就叫做 age=,要使用它就是用 age=(18),沒錯,就是連等號一起呼叫它。所以,其實標準形態應該長這樣:

1
mary.age=(18)

又,因為前面提到,Ruby 可以省略小括號,所以可寫成:

1
mary.age=18

然後,Ruby 又有幫忙加了一些語法糖衣,讓你在中間加一些空白字元也是可以的:

1
mary.age = 18

最後就會讓你看起來像是在設定 age 屬性了。

這…會不會太麻煩了點?

照這樣說,如果每次想要用類似的屬性寫法,就必須要寫一對方法來回傳、設定,會不會有點太麻煩啊。

是的,就是要這麼麻煩。不過請放心,工程師都很懶的,所以有另外設計了幾個方法可以讓你快速的產生前面提到的 getter/setter。

如果你的 getter/setter 很單純,就只是有回傳、設定實體變數的話,那你可用 Ruby 內建的幾個方法:attr_readerattr_writer 以及 attr_accessor

1
2
3
4
5
6
7
8
9
10
11
class Girl
  attr_accessor :age
  def initialize(age)
    @age = age
  end
end

mary = Girl.new(20)
puts mary.age    #=> 20
mary.age = 18
puts mary.age    #=> 18

其中,attr_reader 只會幫你產生 getter,attr_writer 只會幫你產生 setter,而 attr_accessor 則會幫你產生 getter 及 setter。如果不相信,可以執行下面這行看一下:

1
p Girl.instance_methods(false)

應該會看到以下結果:

1
[:age, :age=]

的確是產生了兩個方法,分別是 age 以及 age=

用了 attr_accessor 還能自己寫 getter 或 setter 嗎?

當然是可以的,例如:

1
2
3
4
5
6
7
8
9
10
11
class Girl
  attr_accessor :age

  def age=(new_age)
    @age = (new_age > 18) ? 18 : new_age  # 如果大於 18,就只設定 18..
  end
end

mary = Girl.new
mary.age = 30     # 即使設定為 30 歲...
puts mary.age     # 還是會永保 18 歲 :)

因為你重新定義了 age= 方法,在執行的時候 Ruby 會跳出來跟你碎碎唸說 warning: method redefined; discarding old age=,但程式還是可執行。

所以,如果你只是想要客制化自己的 getter 或 setter 的話,可將 attr_accessor 改為 attr_readerattr_writer,就不會有這個警告訊息了。

以上,希望對大家有幫助 :)

新年新希望之誠徵 iOS app 開發學徒

image photo by Jocelyn Kinghorn

大概兩年前舉辦了一次「徵求 Ruby/Rails 有心人」活動,感謝大家不嫌棄,不過因為人數太多,最後變成了社群活動了 XD

最近再度因為手邊的 iOS 案件在持續增加中(同時也有 Rails 的專案同時在進行),與其要直接找現成的即戰力,不如趁這個機會自己從頭開始帶,雖然會比較辛苦、花時間,但一些開發的習慣可以從一開始就先養成好像也不錯,剛好順便練一下之前準備的 iOS/Swift 教材。

所以我又厚臉皮的想要來徵看看有沒有人願意來給我當學徒。

你可能會學到什麼

  • iOS app 開發
  • Objective-C / Swift
  • Git
  • 還有最重要的,可以一起組隊打 D3.. XD

權利義務

基本上不會有什麼特別的權利義務,也不會有什麼學費或薪水。 其實我個人的私心,是希望學成之後可以有能力幫我消化或維護我手邊的專案。這樣我這樣才有更多時間打 D3。或是最後就直接加入我們的團隊接手客戶的專案,當然,如果開始接手專案的話就是一份正式的工作了,自然就會開始有薪水了,至於待遇會不會超過 22K? 我想應該不會太難 :)

基本條件

  • 男女不拘,但基於我已經有兩個小朋友了,所以還是男性佳。女性佳我回家就要跪主機板了 (男性需役畢或免役)
  • 最好是從學校畢業畢業不久或是已工作一、二年。
  • 對寫程式有熱忱,不需要非常熟悉某種程式語言,但至少需要知道這個世界上不是只有 IE 這種瀏覽器。
  • 需要自備 Mac 電腦(iPhone 或 iPad 可能也會需要)。
  • 最好人是在北部。

人數限制

最多三位,再多我可能就沒辦法顧到品質了。

地點

五倍紅寶石 (台北車站附近)

時間

預計從三月份開始,每週找兩個晚上或週末的時間來練功(暫定)。

我不敢說自己是什麼大咖,但至少教大家如何寫 iOS app 來混口飯吃沒太大問題。 如果您有興趣,也覺得自己不會來個兩次就不來的,請點選以下的連結,並請簡單的介紹一下你自己。(預計會在農曆年前回覆錄取人選)

報名連結:http://5xruby.kktix.cc/events/ios-apprentice

感謝大家,先預祝大家新年都很快樂!

工商服務

順便偷渡幫忙宣傳一下我們自己最近開的課程資訊: