Module: MethodDecorators

Defined in:
lib/decorators.rb

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.extended(klass) ⇒ Object



2
3
4
5
6
# File 'lib/decorators.rb', line 2

def self.extended(klass)
  class << klass
    attr_accessor :decorated_methods
  end
end

Instance Method Details

#common_method_added(name, is_class_method) ⇒ Object



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
# File 'lib/decorators.rb', line 23

def common_method_added name, is_class_method
  return unless @decorators

  decorators = @decorators.dup
  @decorators = nil
  @decorated_methods ||= {:class_methods => {}, :instance_methods => {}}

  # attr_accessor on the class variable decorated_methods
  class << self; attr_accessor :decorated_methods; end

  is_private = nil
  decorators.each do |klass, args|
    # a reference to the method gets passed into the contract here. This is good because
    # we are going to redefine this method with a new name below...so this reference is
    # now the *only* reference to the old method that exists.
    # We assume here that the decorator (klass) responds to .new
    if is_class_method
      decorator = klass.new(self, method(name), *args)
      @decorated_methods[:class_methods][name] ||= []
      @decorated_methods[:class_methods][name] << decorator
      is_private = self.private_methods.include?(name.to_s)
    else
      decorator = klass.new(self, instance_method(name), *args)
      @decorated_methods[:instance_methods][name] ||= []
      @decorated_methods[:instance_methods][name] << decorator
      is_private = self.private_instance_methods.include?(name.to_s)
    end
  end

  # in place of this method, we are going to define our own method. This method
  # just calls the decorator passing in all args that were to be passed into the method.
  # The decorator in turn has a reference to the actual method, so it can call it
  # on its own, after doing it's decorating of course.
  class_eval <<-ruby_eval, __FILE__, __LINE__ + 1
    def #{is_class_method ? "self." : ""}#{name}(*args, &blk)
      current = self#{is_class_method ? "" : ".class"}
      ancestors = current.ancestors
      ancestors.shift # first one is just the class itself
      while current && !current.respond_to?(:decorated_methods) || current.decorated_methods.nil?
        current = ancestors.shift
      end
      if !current.respond_to?(:decorated_methods) || current.decorated_methods.nil?
        raise "Couldn't find decorator for method " + self.class.name + ":#{name}.\nDoes this method look correct to you? If you are using contracts from rspec, rspec wraps classes in it's own class.\nLook at the specs for contracts.ruby as an example of how to write contracts in this case."
      end
      methods = current.decorated_methods[#{is_class_method ? ":class_methods" : ":instance_methods"}][#{name.inspect}]

      # this adds support for overloading methods. Here we go through each method and call it with the arguments.
      # If we get a ContractError, we move to the next function. Otherwise we return the result.
      # If we run out of functions, we raise the last ContractError.
      success = false
      i = 0
      result = nil
      while !success
        method = methods[i]
        i += 1
        begin
          success = true
          result = method.call_with(self, *args, &blk)
        rescue ContractError => e
          success = false
          raise e unless methods[i]
        end
      end
      result
    end
    #{is_private ? "private #{name.inspect}" : ""}
  ruby_eval
end

#decorate(klass, *args) ⇒ Object



92
93
94
95
# File 'lib/decorators.rb', line 92

def decorate(klass, *args)
  @decorators ||= []
  @decorators << [klass, args]
end

#method_added(name) ⇒ Object

first, when you write a contract, the decorate method gets called which sets the @decorators variable. Then when the next method after the contract is defined, method_added is called and we look at the @decorators variable to find the decorator for that method. This is how we associate decorators with methods.



13
14
15
16
# File 'lib/decorators.rb', line 13

def method_added(name)
  common_method_added name, false
  super
end

#singleton_method_added(name) ⇒ Object



18
19
20
21
# File 'lib/decorators.rb', line 18

def singleton_method_added name
  common_method_added name, true
  super
end