File: method_builder.rb

package info (click to toggle)
ruby-memoizable 0.4.2-3
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, forky, sid, trixie
  • size: 192 kB
  • sloc: ruby: 605; makefile: 2
file content (145 lines) | stat: -rw-r--r-- 3,566 bytes parent folder | download | duplicates (3)
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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
# encoding: utf-8

module Memoizable

  # Build the memoized method
  class MethodBuilder

    # Raised when the method arity is invalid
    class InvalidArityError < ArgumentError

      # Initialize an invalid arity exception
      #
      # @param [Module] descendant
      # @param [Symbol] method
      # @param [Integer] arity
      #
      # @api private
      def initialize(descendant, method, arity)
        super("Cannot memoize #{descendant}##{method}, its arity is #{arity}")
      end

    end # InvalidArityError

    # Raised when a block is passed to a memoized method
    class BlockNotAllowedError < ArgumentError

      # Initialize a block not allowed exception
      #
      # @param [Module] descendant
      # @param [Symbol] method
      #
      # @api private
      def initialize(descendant, method)
        super("Cannot pass a block to #{descendant}##{method}, it is memoized")
      end

    end # BlockNotAllowedError

    # The original method before memoization
    #
    # @return [UnboundMethod]
    #
    # @api public
    attr_reader :original_method

    # Initialize an object to build a memoized method
    #
    # @param [Module] descendant
    # @param [Symbol] method_name
    # @param [#call] freezer
    #
    # @return [undefined]
    #
    # @api private
    def initialize(descendant, method_name, freezer)
      @descendant          = descendant
      @method_name         = method_name
      @freezer             = freezer
      @original_visibility = visibility
      @original_method     = @descendant.instance_method(@method_name)
      assert_arity(@original_method.arity)
    end

    # Build a new memoized method
    #
    # @example
    #   method_builder.call  # => creates new method
    #
    # @return [MethodBuilder]
    #
    # @api public
    def call
      remove_original_method
      create_memoized_method
      set_method_visibility
      self
    end

  private

    # Assert the method arity is zero
    #
    # @param [Integer] arity
    #
    # @return [undefined]
    #
    # @raise [InvalidArityError]
    #
    # @api private
    def assert_arity(arity)
      if arity.nonzero?
        fail InvalidArityError.new(@descendant, @method_name, arity)
      end
    end

    # Remove the original method
    #
    # @return [undefined]
    #
    # @api private
    def remove_original_method
      name = @method_name
      @descendant.module_eval { undef_method(name) }
    end

    # Create a new memoized method
    #
    # @return [undefined]
    #
    # @api private
    def create_memoized_method
      name, method, freezer = @method_name, @original_method, @freezer
      @descendant.module_eval do
        define_method(name) do |&block|
          fail BlockNotAllowedError.new(self.class, name) if block
          memoized_method_cache.fetch(name) do
            freezer.call(method.bind(self).call)
          end
        end
      end
    end

    # Set the memoized method visibility to match the original method
    #
    # @return [undefined]
    #
    # @api private
    def set_method_visibility
      @descendant.send(@original_visibility, @method_name)
    end

    # Get the visibility of the original method
    #
    # @return [Symbol]
    #
    # @api private
    def visibility
      if    @descendant.private_method_defined?(@method_name)   then :private
      elsif @descendant.protected_method_defined?(@method_name) then :protected
      else                                                           :public
      end
    end

  end # MethodBuilder
end # Memoizable