對于alias, alias_method, alias_method_chain的深入了解是有益的,因為rails3的源碼裡很多地方使用了alias_method_chain的魔法。 有人評論說alias_method_chain使用的過多不好,具體怎麼不好,是後話了,這篇文章集中在了解這3個方法上面。
如果想轉載本文,請注明出處,謝謝!請尊重别人的勞動成果,為建構豐富web原創内容做貢獻!
1. alias
Ruby裡的關鍵字,用于定義方法或者全局變量的别名。 例子:
class A
def m1
puts "m1"
end
alias m2 m1
end
=> nil
a = A.new
=> #<A:0xb7ef5234>
a.m1
m1
a.m2
在使用的時候,注意原有的方法名在最後位置,用空格分開。
2. alias_method
作用和alias差不多,是Module的一個私有執行個體方法,隻能用于給方法起别名,并且參數隻能是字元串或者符号(alias後面跟的直接是方法名,不是字元串也不是符号)。例子:
class B
def b
p "b"
end
alias_method :c, :b
end
=> B
b = B.new
=> #<B:0xb7ee75bc>
b.c
"b"
b.b
注意,alias_method的參數必須是字元串或者是符号,并且用逗号分隔。
3. alias_method_chain
是ActiveSupport的一個公有執行個體方法。同樣接受兩個參數,可以是符号,也可以是字元串,但要注意一下第1個參數才是原始方法(alias_method的第2個參數是原始方法)。例子:
class A
def m1
puts 'm1'
def m1_with_m2
puts "do something befor m1"
m1_without_m2
puts "do something after m2"
alias_method_chain :m1, :m2
=> A
=> #<A:0xb7bd9820>
a.m1
do something befor m1
do something after m2
上面的代碼用alias或者alias_method也能完成:
class A
def m1
puts 'm1'
alias m1_without_m2 m1
def m1_with_m2
puts 'do something else'
m1_without_m2
end
alias m1 m1_with_m2
那麼其原理也一目了然了:
當調用m1的時候, m1_with_m2會執行, 在puts "do something befor m1"之後,執行m1_without_m2,這個時候是執行了真正的m1方法。 這樣就形成了一個類似于AOP的行為。
也可以說,對外把m1方法隐藏起來了,對類外部,實際上把m1_with_m2改頭換面已經成為了另一個方法,隻是我們不知道而已,因為它還叫m1.
再來看看alias_method_chain的源碼:
def alias_method_chain(target, feature)
# Strip out punctuation on predicates or bang methods since
# e.g. target?_without_feature is not a valid method name.
aliased_target, punctuation = target.to_s.sub(/([?!=])$/, ''), $1
yield(aliased_target, punctuation) if block_given?
with_method, without_method = "#{aliased_target}_with_#{feature}#{punctuation}", "#{aliased_target}_without_#{feature}#{punctuation}"
alias_method without_method, target
alias_method target, with_method
case
when public_method_defined?(without_method)
public target
when protected_method_defined?(without_method)
protected target
when private_method_defined?(without_method)
private target
end
一個道理。
更實際的例子:
在一些rails比較老的系統裡,搜尋功能的日期選擇可能會用到date_select,這個方法會生成類似于這樣的頁面元素:
search_form[start_from(1i)]年
search_form[start_from(2i)]月
search_form[start_from(3i)]日
把這樣的參數傳回去,就無法查詢到對應的日期。這個時候我們需要在背景得到查詢條件之後來處理日期,比如:
get_conditions 這個方法假如是得到頁面查詢條件的,它傳回一個數組,這個時候我們可以定義:
def get_conditions_with_handle_date
puts "你可以在get_conditions方法執行前幹點别的,如果你願意"
get_conditions_without_handle_date
puts "get_conditions執行完了,我們可以在其後幹點别的,比如說處理日期"
conditions.reject!{|condition|condition[0] =~ /\([1-3]i\)/} # 把條件數組裡的1i,2i,3i之類的去掉。
conditions << ["? <= #{@model.table_name}.created_at", @search.start_from] if @search.start_from #給搜尋對象裡添加正确的查詢日期條件
conditions << ["#{@model.table_name}.created_at < ?", @search.end_to + 1.day] if @search.end_to #給搜尋對象裡添加正确的查詢日期條件
#然後實施魔法
alias_method_chain :get_conditions, :handle_date
這樣我們就搞定了。