不得不再說一次,這些主題都超級重要,不先學Ruby直接學Rails感覺很卡。果然要從基礎開始打才是王道。在看這篇之前請先看完method / block / yield / Proc / lambda ,很多觀念都是從這邊的延伸。
conventions block有兩種表達方式
single block braces if the block is a single line
1 words.each { |word| puts word }
multiblock do/end if it’s multiple lines
1 2 3 4 words.each do |word| backward_word = word.reverse puts backward_wordend
yield 這邊有一個yield的範例
1 2 3 4 def call_this_block_twice yield yield end
1 2 call_this_block_twice{ puts "twitter" }
一開始看一定看不懂他是怎麼做的,因為他省略了很多東西,其實call block可以拆解成三個部分,第一個部分是定義呼叫block的function,在這個例子中就是
1 2 3 4 def call_this_block_twice yield yield end
使用yield的時候可以省略傳入的&block與呼叫的.call方法,上面這段程式碼等同於
1 2 3 4 def call_this_block_twice (&block) block.call block.callend
第二個部分是宣告block 在這個例子中省略了這一部分,其實”twitter”就是一個傳入的block。
第三部分是傳入block到function之中輸出結果 把”twitter”這個block傳進能夠處理block的function之中。
1 2 call_this_block_twice{ puts "twitter" }
yield - arguments 再來看另一個範例
1 2 3 def call_this_block yield "tweet" end
yield中傳入了參數”tweet”,也就等於block.call(“tweet”)
1 2 call_this_block { |myarg| puts myarg }
1 2 call_this_block { |myarg| puts myarg.upcase }
這時候如果要在call block的時候呼叫參數,要用|
和|
把輸入的參數包起來。接著後面的陳述式就可以對輸入的進行一些處理。
Your own “each” 現在我的class中有兩個method,一個要列出朋友的tweet,一個要儲存朋友的tweet。可以發現@user.friends.each do |friend|
這段iteration重複了。
1 2 3 4 5 6 7 8 9 10 11 12 13 class Timeline def list_tweets @user .friends.each do |friend| friend.tweets.each { |tweet| puts tweet } end end def store_tweets @user .friends.each do |friend| friend.tweets.each { |tweet| tweet.cache } end end end
於是我們來著撰寫自己的each
1 2 3 4 5 6 7 8 9 10 class Timeline def each @user .friends.each do |friend| friend.tweets.each { |tweet| yield tweet } end end end timeline = Timeline.new(user) timeline.each { |tweet| puts tweet } timeline.each { |tweet| tweet.cache }
加入Enumerable擴充更多的method
1 2 3 4 5 6 class Timeline def each ...end include Enumerableend
在include Enumerable這個module之後,瞬間多了很多method(如下),要解了更多就去看看ruby-doc Enumerable 吧。
1 2 3 timeline.sort_by { |tweet| tweet.created_at } timeline.map { |tweet| tweet.status } timeline.find_all { |tweet| tweet.status =~ /\@codeschool/ }
重構練習 1 2 3 4 5 6 7 8 9 10 def update_status (user, tweet) begin sign_in(user) post(tweet) rescue ConnectionError => e logger.error(e)ensure sign_out(user) end end
1 2 3 4 5 6 7 8 9 10 def get_list (user, list_name) begin sign_in(user) retrieve_list(list_name) rescue ConnectionError => e logger.error(e)ensure sign_out(user) end end
有裡個method裡面除了核心邏輯以外全部都一樣。這樣的話我們需要進行重構來實現DRY原則。因為Proc本身就是匿名函數也就是一段未執行程式碼,所以特別適用這個情況。
把重複的地方獨立出來,核心邏輯用yield代替。
1 2 3 4 5 6 7 8 9 def while_signed_in_as (user) begin sign_in(user) yield rescue ConnectionError => e logger.error(e) ensure sign_out(user) end end
使用do的時候會省略傳入的&block,所以這段程式碼其實是對block做些處理,並顯示在block.call也就是yield的部份。
1 2 3 while_signed_in_as(user) do post(tweet)end
1 2 3 tweets = while_signed_in_as(user) do retrieve_list(list_name)end
最後可以改寫的精簡一點 去掉不必要的begin和end
1 2 3 4 5 6 7 8 def while_signed_in_as (user) sign_in(user) yield rescue ConnectionError => e logger.error(e) ensure sign_out(user)end
作業實作 6.1 使用each代替for迴圈。原來使用for迴圈的程式:
1 2 3 4 5 6 def list for i in 0 ...(games.length) game = games[i] puts game.name end end
改寫成each
1 2 3 4 5 def list games.each do |game| puts game.name end end
6.2 現在我們有一個class叫做Game裡面裝著每場比賽的資訊。 有一個陣列叫做Games,裡面蒐集了很多場的比賽。 現在我們要在Library中寫一個方法叫做each_on_system(system),讓他可以讀出Games陣列裡面符合輸入的system的比賽。
1 2 3 4 5 6 7 8 9 10 11 class Library attr_accessor :games def initialize (games = []) self .games = games end def each_on_system (system) end end
使用範例example.rb
1 2 library = Library.new(GAMES) library.each_on_system("SNES" ) { puts "Found a Super Nintendo game" }
將library修正後,即為所得。
1 2 3 4 5 6 7 8 9 10 11 12 13 class Library attr_accessor :games def initialize (games = []) self .games = games end def each_on_system (system) games.each do |game| yield if game.system == system end end end
6.3 Passing Argument to Blocks 讓產生的block能夠使用iterator的參數。如下圖可以使用|game|。
1 2 library = Library.new(GAMES) library.each_on_system("SNES" ) { |game| puts game.name }
6.4 Return Value From block Modify the list method to yield to a block and print whatever the block returns. 除了能夠在block中使用以外還要可以直接印出來。
1 2 library = Library.new(GAMES) library.list { |game| "#{game.name} (#{game.system} ) - #{game.year} " }
1 2 3 4 5 6 7 8 9 10 11 12 13 class Library attr_accessor :games def initialize (games = []) self .games = games end def list games.each do |game| puts yield game end end end
6.5include Enumerable module 6.6重構以避免重複 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 class Game attr_accessor :name , :year , :system attr_reader :created_at def initialize (name, options={}) self .name = name self .year = options[:year ] self .system = options[:system ] @created_at = Time.now end def play begin emulator = Emulator.new(system) emulator.play(self ) rescue Exception => e puts "Emulator failed: #{e} " end end def screenshot begin emulator = Emulator.new(system) emulator.start(self ) emulator.screenshot rescue Exception => e puts "Emulator failed: #{e} " end end end
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 class Game attr_accessor :name , :year , :system attr_reader :created_at def initialize (name, options={}) self .name = name self .year = options[:year ] self .system = options[:system ] @created_at = Time.now end def play emulate do |emulator| emulator.play(self ) end end def screenshot emulate do |emulator| emulator.start(self ) emulator.screenshot end end private def emulate begin emulator = Emulator.new(system) yield emulator rescue Exception => e puts "Emulator failed: #{e} " end end end
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Emulator def initialize (system) end def play (game) end def start (game) end def screenshot end end