contracts 0.4 → 0.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,164 +0,0 @@
1
- module MethodDecorators
2
- def self.extended(klass)
3
- class << klass
4
- attr_accessor :decorated_methods
5
- end
6
- end
7
-
8
- # first, when you write a contract, the decorate method gets called which
9
- # sets the @decorators variable. Then when the next method after the contract
10
- # is defined, method_added is called and we look at the @decorators variable
11
- # to find the decorator for that method. This is how we associate decorators
12
- # with methods.
13
- def method_added(name)
14
- common_method_added name, false
15
- super
16
- end
17
-
18
- def singleton_method_added name
19
- common_method_added name, true
20
- super
21
- end
22
-
23
- def common_method_added name, is_class_method
24
- return unless @decorators
25
-
26
- decorators = @decorators.dup
27
- @decorators = nil
28
- @decorated_methods ||= {:class_methods => {}, :instance_methods => {}}
29
-
30
- # attr_accessor on the class variable decorated_methods
31
- class << self; attr_accessor :decorated_methods; end
32
-
33
- is_private = nil
34
- decorators.each do |klass, args|
35
- # a reference to the method gets passed into the contract here. This is good because
36
- # we are going to redefine this method with a new name below...so this reference is
37
- # now the *only* reference to the old method that exists.
38
- # We assume here that the decorator (klass) responds to .new
39
- if is_class_method
40
- decorator = klass.new(self, method(name), *args)
41
- @decorated_methods[:class_methods][name] ||= []
42
- @decorated_methods[:class_methods][name] << decorator
43
- # private_instance_methods is an array of strings on 1.8 and an array of symbols on 1.9
44
- is_private = self.private_methods.include?(name) || self.private_methods.include?(name.to_s)
45
- else
46
- decorator = klass.new(self, instance_method(name), *args)
47
- @decorated_methods[:instance_methods][name] ||= []
48
- @decorated_methods[:instance_methods][name] << decorator
49
- # private_instance_methods is an array of strings on 1.8 and an array of symbols on 1.9
50
- is_private = self.private_instance_methods.include?(name) || self.private_instance_methods.include?(name.to_s)
51
- end
52
- end
53
-
54
- # in place of this method, we are going to define our own method. This method
55
- # just calls the decorator passing in all args that were to be passed into the method.
56
- # The decorator in turn has a reference to the actual method, so it can call it
57
- # on its own, after doing it's decorating of course.
58
-
59
- =begin
60
- Very important: THe line `current = #{self}` in the start is crucial.
61
- Not having it means that any method that used contracts could NOT use `super`
62
- (see this issue for example: https://github.com/egonSchiele/contracts.ruby/issues/27).
63
- Here's why: Suppose you have this code:
64
-
65
- class Foo
66
- Contract nil => String
67
- def to_s
68
- "Foo"
69
- end
70
- end
71
-
72
- class Bar < Foo
73
- Contract nil => String
74
- def to_s
75
- super + "Bar"
76
- end
77
- end
78
-
79
- b = Bar.new
80
- p b.to_s
81
-
82
- `to_s` in Bar calls `super`. So you expect this to call `Foo`'s to_s. However,
83
- we have overwritten the function (that's what this next defn is). So it gets a
84
- reference to the function to call by looking at `decorated_methods`.
85
-
86
- Now, this line used to read something like:
87
-
88
- current = self#{is_class_method ? "" : ".class"}
89
-
90
- In that case, `self` would always be `Bar`, regardless of whether you were calling
91
- Foo's to_s or Bar's to_s. So you would keep getting Bar's decorated_methods, which
92
- means you would always call Bar's to_s...infinite recursion! Instead, you want to
93
- call Foo's version of decorated_methods. So the line needs to be `current = #{self}`.
94
- =end
95
- method_def = %{
96
- def #{is_class_method ? "self." : ""}#{name}(*args, &blk)
97
- current = #{self}
98
- ancestors = current.ancestors
99
- ancestors.shift # first one is just the class itself
100
- while current && !current.respond_to?(:decorated_methods) || current.decorated_methods.nil?
101
- current = ancestors.shift
102
- end
103
- if !current.respond_to?(:decorated_methods) || current.decorated_methods.nil?
104
- 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."
105
- end
106
- methods = current.decorated_methods[#{is_class_method ? ":class_methods" : ":instance_methods"}][#{name.inspect}]
107
-
108
- # this adds support for overloading methods. Here we go through each method and call it with the arguments.
109
- # If we get a ContractError, we move to the next function. Otherwise we return the result.
110
- # If we run out of functions, we raise the last ContractError.
111
- success = false
112
- i = 0
113
- result = nil
114
- while !success
115
- method = methods[i]
116
- i += 1
117
- begin
118
- success = true
119
- result = method.call_with(self, *args, &blk)
120
- rescue ContractError => e
121
- success = false
122
- raise e unless methods[i]
123
- end
124
- end
125
- result
126
- end
127
- #{is_private ? "private #{name.inspect}" : ""}
128
- }
129
-
130
- class_eval method_def, __FILE__, __LINE__ + 1
131
- end
132
-
133
- def decorate(klass, *args)
134
- @decorators ||= []
135
- @decorators << [klass, args]
136
- end
137
- end
138
-
139
- class Decorator
140
- # an attr_accessor for a class variable:
141
- class << self; attr_accessor :decorators; end
142
-
143
- def self.inherited(klass)
144
- name = klass.name.gsub(/^./) {|m| m.downcase}
145
-
146
- return if name =~ /^[^A-Za-z_]/ || name =~ /[^0-9A-Za-z_]/
147
-
148
- # the file and line parameters set the text for error messages
149
- # make a new method that is the name of your decorator.
150
- # that method accepts random args and a block.
151
- # inside, `decorate` is called with those params.
152
- MethodDecorators.module_eval <<-ruby_eval, __FILE__, __LINE__ + 1
153
- def #{klass}(*args, &blk)
154
- decorate(#{klass}, *args, &blk)
155
- end
156
- ruby_eval
157
- end
158
-
159
- def initialize(klass, method)
160
- @method = method
161
- end
162
- end
163
-
164
-