chrisjpowers (owner)

Revisions

gist: 147210 Download_button fork
public
Description:
Allows you to declare alias_method_chain before either method has been defined.
Public Clone URL: git://gist.github.com/147210.git
Embed All Files: show embed
delayed_method_chain.rb #
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# Allows you to declare an alias_method_chain before the original
# method has been defined.
#
# class A
# delayed_method_chain :name, :hello
#
# def name_with_hello
# "Hello #{name_without_hello}"
# end
#
# # This method is immediately chained when defined
# def name
# 'Bill'
# end
# end
#
# A.new.name #=> "Hello Bill"
#
module DelayedMethodChain
  def delayed_method_chain(name, suffix)
    # if both methods already exist, just chain and forget the method_added stuff
    if method_defined?(name.to_sym) && method_defined?(DelayedMethodChain.suffixed_name(name, 'with', suffix))
      alias_method DelayedMethodChain.suffixed_name(name, 'without', suffix), name.to_sym
      alias_method name.to_sym, DelayedMethodChain.suffixed_name(name, 'with', suffix)
      return
    end
    
    (class << self; self; end).class_eval do
      @@delayed_methods ||= []
      @@delayed_methods << [name, suffix]
      return if method_defined?(:method_added_with_delay_chain)
      
      def method_added_with_delay_chain(added_name)
        # prevent infinite looping of method_added when aliasing method
        @currently_being_delayed_methods ||= {}
        return if @currently_being_delayed_methods[added_name]
        
        # listen for both methods, only chain if both are defined
        @@delayed_methods.select {|name, suffix| [name.to_sym, DelayedMethodChain.suffixed_name(name, 'with', suffix)].include?(added_name.to_sym)}.each do |name, suffix|
          next unless method_defined?(name.to_sym) && method_defined?(DelayedMethodChain.suffixed_name(name, 'with', suffix))
          
          @currently_being_delayed_methods[name] = :aliasing
          alias_method DelayedMethodChain.suffixed_name(name, 'without', suffix), name.to_sym
          alias_method name.to_sym, DelayedMethodChain.suffixed_name(name, 'with', suffix)
          @currently_being_delayed_methods[name] = nil
        end
        
        if respond_to?(:method_added_without_delay_chain)
          method_added_without_delay_chain(added_name)
        end
      end
      
      # chain method_added if this class already had one
      if method_defined?(:method_added)
        alias_method :method_added_without_delay_chain, :method_added
      end
      alias_method :method_added, :method_added_with_delay_chain
    end
  end
 
  # takes query, bang and setter method names into account
  def self.suffixed_name(name, with, suffix)
    if m = name.to_s.match(/(.*)([\?!=])$/)
      "#{m[1]}_#{with}_#{suffix}#{m[2]}".to_sym
    else
      "#{name}_#{with}_#{suffix}".to_sym
    end
  end
 
end
 
Object.send :extend, DelayedMethodChain