Ruby bits ( 5 ):Modules

Ruby bits的課程真的很不錯,講到的主題都是很重要的東西。有點相見恨晚的感覺,趕快把它做完吧!

本節學習目標

  1. module
  2. activesupport ::Concern
  3. 了解self的意義

part1: module

class method與instance method

use extend to expose methods as class method

1
2
3
class Tweet
extend Searchable
end

使用的時候,直接呼叫class(開頭大寫)。

1
Tweet.find_all_from('@GreggPollack')

use include to expose methods as instance methods

instance method用include引進module

1
2
3
class Image
include ImageUtils
end
1
2
image = user.image
image.preview

part2 :how to include class method and instance method in the same time

第一種方法:依照直覺該怎麼寫

第一種方法是在class中includ module並且extend classmothods。
下例中Image想要使用ImageUtils這個module的class method與instance method,所以用兩種方式各自引用。

1
2
3
4
class Image
include ImageUtils
extend ImageUtils::ClassMethods
end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
module ImageUtils

def preview
end

def transfer(destination)
end

module ClassMethods
def fetch_from_twitter(user)
end
end

end

使用上可以按照之前所學的來呼叫class method與instance method

1
2
3
image = user.image
image.preview
Image.fetch_from_twitter('gregg')

第二種方法:method hooks

這樣每次使用module的時候都需要explore兩種module實在太麻煩了,引入class module這個動作在module完成,這樣我們就不需要每次都引入兩種method,只要引入module就可以了。於是第二種方法method hook產生了:

1
2
3
4
class Image
include ImageUtils
#extend ImageUtils::ClassMethods 刪除掉引入class method的這一行
end
1
2
3
4
5
6
7
8
9
10
11
12
13
module ImageUtils
def self.included(base) #加入self.include (self就是ImageUtils這個module)
base.extend(ClassMethods)
end
def preview
end
def transfer(destination)
end
module ClassMethods
def fetch_from_twitter(user)
end
end
end

part3: 使用Activesupport :: Concern解決相依性問題

Activesupport :: Concern代表什麼意思
:: is basically a namespace resolution operator. It allows you to access items in modules, or class-level items in classes. For example, say you had this setup:
關鍵字double colon ruby
What is Ruby’s double-colon (::) all about?

使用方式

在terminal下gem install activesupport安裝activesupport
在module檔中(xxx.rb)中require ‘active_support/concern’

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
require 'active_support/concern' module ImageUtils

extend ActiveSupport::Concern

included do
clean_up
end

module ClassMethods
def fetch_from_twitter(user)
end

def clean_up
end
end
end

這樣就可以直接include ImageUtil的ClassMethods

1
2
3
class Image
include ImageUtils
end

這樣看起來沒什麼了不起的對吧?只是換個寫法。實際上cocern的出現是為了解決更重要的問題,讓我們看下去。

Acitvesupport::concern要解決的問題

1
2
3
4
5
6
7
8
module ImageUtils
def self.included(base) #base is ImageProcessing module
base.extend(ClassMethods)
end
module ClassMethods
def clean_up; end
end
end
1
2
3
4
5
6
7
8
module ImageProcessing

include ImageUtils

def self.included(base)
base.clean_up #undefined method error
end
end
1
2
3
class Image
include ImageProcessing
end

這樣乍看之下好像沒問題,但是卻有個嚴重的問題導致無法執行,因為ImageUtils變成是由ImageProcessing所 include,所以對 ImageUtils 的 self.included 來說,他的參數 base 變成了 ImageProcessing 了,所以他就沒辦法存取到宿主 Host 的任何函式及變數,do_host_something 時就會失敗。

Okay,ActiveSupport::Concern 就是來幫助解決這個難題,我們希望宿主可以不需要知道 modules 之間的 dependencies 關係。dependencies 關係寫在 module 裡面就好了。

1
2
3
4
5
6
7
8
module ImageUtils

extend ActiveSupport::Concern

module ClassMethods
def clean_up; end
end
end
1
2
3
4
5
6
7
8
9
module ImageProcessing

extend ActiveSupport::Concern
include ImageUtils

included do
clean_up
end
end
1
2
3
class Image
include ImageProcessing
end

Dependencies are properly resolved!!

part4 作答中遇到的問題

5.1 宣告成class method

原本的

1
2
3
4
module GameUtils
def lend_to_friend(game, friend_email)
end
end

改成

1
2
3
4
module GameUtils
def self.lend_to_friend(game, friend_email)
end
end

這樣呼叫時就會從原本的

1
2
game = Game.new("Contra")
game.lend_to_friend(game, "[email protected]")

變成

1
2
game = Game.new("Contra")
GameUtils.lend_to_friend(game, "[email protected]")

5.2 reopen game and include the gameutil module

5.3 reopen Game and expose the method from module as class method of Game class

原本

1
2
class Game
end

加上GameUtils的class method

1
2
3
class Game
extend GameUtils
end

5.4 extend the single game object with Playable module

原本

1
2
game = Game.new("Contra")
game.play

加入module Playable的method到game這個instance中。

1
2
3
game = Game.new("Contra")
game.extend(Playable)
game.play

module長這樣

1
2
3
4
module Playable
def play
end
end

這個用法讓我有點困惑,特地查了一下ruby-doc
extend的解釋如下
Adds to obj the instance methods from each module given as a parameter.
雖然是用extend這個字,但並非是繼承的意思,而是加入instance method。

5.5

使用self.include初始化class method

5.6

使用ActiveSupport::Concern代替self.included

5.7 AcitveSupport::Concern part II

使用included class method

5.8 AcitveSupport::Concern part III

延伸閱讀:

深入Rails3: ActiveSupport::Concern
Self - The current/default object
ActiveSupport::Concern

評論