public
Description: All the extra stuff you could want for the Mack Framework.
Homepage: http://www.mackframework.com
Clone URL: git://github.com/markbates/mack-more.git
markbates (author)
Wed Jul 16 13:02:47 -0700 2008
commit  cf269199f654b3c95adc7c4c1d9799717dbb59da
tree    24be20650c8b069d7702323be924564ed74b2fc9
parent  009c91b5e8033ae22e9227545effe5411b60148b
mack-more / mack-facets / lib / utils / hookable.rb
100644 103 lines (95 sloc) 3.234 kb
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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
module Mack # :nodoc:
  module Utils # :nodoc:
    # Include this module into any class, or module, and it's methods can become hookable.
    # This allows for the ability to do really nice AOP programming.
    #
    # Example:
    # class User
    # def say_full_name
    # puts "mark"
    # end
    # end
    # User.before(:say_full_name) do
    # puts "hello"
    # end
    # User.new.say_full_name # => "hello" "mark"
    module Hookable
      
      def self.included(klass)
        eval %{
class ::#{klass}::Hooks
include Singleton
def initialize
@hooks = {:before => {}, :after => {}, :before_class_method => {}, :after_class_method => {}}
end
def hooks_for(state, meth)
(@hooks[state.to_sym][meth.to_sym] ||= [])
end
end
def hookable_class
::#{klass}::Hooks.instance
end
}
        klass.extend self
      end
 
      # Used to prefix an instance method with the assigned block
      def before(name, &block)
        hookable_class.hooks_for(:before, name.to_sym) << block
        build_hook_instance_method(name)
      end
      
      # Used to suffix an instance method with the assigned block
      def after(name, &block)
        hookable_class.hooks_for(:after, name.to_sym) << block
        build_hook_instance_method(name)
      end
      
      # Used to prefix a class method with the assigned block
      def before_class_method(name, &block)
        hookable_class.hooks_for(:before_class_method, name.to_sym) << block
        build_hook_class_method(name)
      end
      
      # Used to suffix a class method with the assigned block
      def after_class_method(name, &block)
        hookable_class.hooks_for(:after_class_method, name.to_sym) << block
        build_hook_class_method(name)
      end
      
      private
      def build_hook_instance_method(name)
        unless self.public_instance_methods.include?("hookable_#{name}")
          class_eval do
            alias_method "hookable_#{name}", name
            eval %{
def #{name}(*args)
hookable_class.hooks_for(:before, :#{name}).each do |p|
p.call
end
hookable_#{name}(*args)
hookable_class.hooks_for(:after, :#{name}).each do |p|
p.call
end
end
}
          end
        end
      end
      
      def build_hook_class_method(name)
        unless self.public_methods.include?("hookable_#{name}")
          class_eval do
            eval %{
class << self
alias_method :hookable_#{name}, :#{name}
def #{name}(*args)
hookable_class.hooks_for(:before_class_method, :#{name}).each do |p|
p.call
end
self.hookable_#{name}(*args)
hookable_class.hooks_for(:after_class_method, :#{name}).each do |p|
p.call
end
end
end
}
          end
        end
      end
      
    end # Hookable
  end # Utils
end # Mack