File: aspect.rb

package info (click to toggle)
libinnate-ruby 2010.07-1
  • links: PTS, VCS
  • area: main
  • in suites: squeeze
  • size: 812 kB
  • ctags: 621
  • sloc: ruby: 4,242; makefile: 2
file content (125 lines) | stat: -rw-r--r-- 3,739 bytes parent folder | download | duplicates (2)
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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
module Innate
  module Helper

    # Provides before/after wrappers for actions
    #
    # This helper is essential for proper working of {Action#render}.
    module Aspect
      AOP = Hash.new{|h,k| h[k] = Hash.new{|hh,kk| hh[kk] = {} }}

      def self.included(into)
        into.extend(SingletonMethods)
        into.add_action_wrapper(5.0, :aspect_wrap)
      end

      # Consider objects that have Aspect included
      def self.ancestral_aop(from)
        aop = {}
        from.ancestors.reverse.map{|anc| aop.merge!(AOP[anc]) if anc < Aspect }
        aop
      end

      def aspect_call(position, name)
        return unless aop = Aspect.ancestral_aop(self.class)
        return unless block = at_position = aop[position]

        block = at_position[name.to_sym] unless at_position.is_a?(Proc)

        instance_eval(&block) if block
      end

      def aspect_wrap(action)
        return yield unless method = action.name

        aspect_call(:before_all, method)
        aspect_call(:before, method)
        result = yield
        aspect_call(:after, method)
        aspect_call(:after_all, method)

        result
      end

      # This awesome piece of hackery implements action AOP.
      #
      # The so-called aspects are simply methods that may yield the next aspect
      # in the chain, this is similar to racks concept of middleware, but instead
      # of initializing with an app we simply pass a block that may be yielded
      # with the action being processed.
      #
      # This gives us things like logging, caching, aspects, authentication, etc.
      #
      # Add the name of your method to the trait[:wrap] to add your own method to
      # the wrap_action_call chain.
      #
      # @example adding your method
      #
      #   class MyNode
      #     Innate.node '/'
      #
      #     private
      #
      #     def wrap_logging(action)
      #       Innate::Log.info("Executing #{action.name}")
      #       yield
      #     end
      #
      #     trait[:wrap]
      #   end
      #
      #
      # methods may register
      # themself in the trait[:wrap] and will be called in left-to-right order,
      # each being passed the action instance and a block that they have to yield
      # to continue the chain.
      #
      # @param [Action] action instance that is being passed to every registered method
      # @param [Proc] block contains the instructions to call the action method if any
      #
      # @see Action#render
      # @author manveru
      def wrap_action_call(action, &block)
        return yield if action.options[:is_layout]
        wrap = SortedSet.new
        action.node.ancestral_trait_values(:wrap).each{|sset| wrap.merge(sset) }
        head, *tail = wrap.map{|k,v| v }
        tail.reverse!
        combined = tail.inject(block){|s,v| lambda{ __send__(v, action, &s) } }
        __send__(head, action, &combined)
      end

      module SingletonMethods
        include Traited

        def before_all(&block)
          AOP[self][:before_all] = block
        end

        def before(*names, &block)
          names.each{|name| AOP[self][:before][name] = block }
        end

        def after_all(&block)
          AOP[self][:after_all] = block
        end

        def after(*names, &block)
          names.each{|name| AOP[self][:after][name] = block }
        end

        def wrap(*names, &block)
          before(*names, &block)
          after(*names, &block)
        end

        def add_action_wrapper(order, method_name)
          if wrap = trait[:wrap]
            wrap.merge(SortedSet[[order, method_name.to_s]])
          else
            trait :wrap => SortedSet[[order, method_name.to_s]]
          end
        end
      end
    end
  end
end