Module: Contracts::MethodDecorators
- Defined in:
- lib/contracts/decorators.rb
Defined Under Namespace
Modules: EigenclassWithOwner
Class Method Summary collapse
Instance Method Summary collapse
- #common_method_added(name, is_class_method) ⇒ Object
- #decorate(klass, *args) ⇒ Object
- #fetch_decorators ⇒ Object
-
#method_added(name) ⇒ Object
first, when you write a contract, the decorate method gets called which sets the @decorators variable.
- #pop_decorators ⇒ Object
- #singleton_method_added(name) ⇒ Object
Class Method Details
.extended(klass) ⇒ Object
3 4 5 6 7 8 9 |
# File 'lib/contracts/decorators.rb', line 3 def self.extended(klass) return if klass.respond_to?(:decorated_methods=) class << klass attr_accessor :decorated_methods end end |
Instance Method Details
#common_method_added(name, is_class_method) ⇒ Object
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 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 |
# File 'lib/contracts/decorators.rb', line 50 def common_method_added(name, is_class_method) decorators = fetch_decorators return if decorators.empty? @decorated_methods ||= {:class_methods => {}, :instance_methods => {}} if is_class_method method_reference = SingletonMethodReference.new(name, method(name)) method_type = :class_methods # private_methods is an array of strings on 1.8 and an array of symbols on 1.9 is_private = self.private_methods.include?(name) || self.private_methods.include?(name.to_s) else method_reference = MethodReference.new(name, instance_method(name)) method_type = :instance_methods # private_instance_methods is an array of strings on 1.8 and an array of symbols on 1.9 is_private = self.private_instance_methods.include?(name) || self.private_instance_methods.include?(name.to_s) end @decorated_methods[method_type][name] ||= [] pattern_matching = false 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 decorator = klass.new(self, method_reference, *args) @decorated_methods[method_type][name] << decorator pattern_matching ||= decorator.pattern_match? end if @decorated_methods[method_type][name].any? { |x| x.method != method_reference } @decorated_methods[method_type][name].each do |decorator| decorator.pattern_match! end pattern_matching = true end method_reference.make_alias(self) return if ENV["NO_CONTRACTS"] && !pattern_matching # 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. =begin Very important: THe line `current = #{self}` in the start is crucial. Not having it means that any method that used contracts could NOT use `super` (see this issue for example: https://github.com/egonSchiele/contracts.ruby/issues/27). Here's why: Suppose you have this code: class Foo Contract nil => String def to_s "Foo" end end class Bar < Foo Contract nil => String def to_s super + "Bar" end end b = Bar.new p b.to_s `to_s` in Bar calls `super`. So you expect this to call `Foo`'s to_s. However, we have overwritten the function (that's what this next defn is). So it gets a reference to the function to call by looking at `decorated_methods`. Now, this line used to read something like: current = self#{is_class_method ? "" : ".class"} In that case, `self` would always be `Bar`, regardless of whether you were calling Foo's to_s or Bar's to_s. So you would keep getting Bar's decorated_methods, which means you would always call Bar's to_s...infinite recursion! Instead, you want to call Foo's version of decorated_methods. So the line needs to be `current = #{self}`. =end current = self method_reference.make_definition(self) do |*args, &blk| 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[method_type][name] # 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 expected_error = methods[0].failure_exception while !success method = methods[i] i += 1 begin success = true result = method.call_with(self, *args, &blk) rescue expected_error => error success = false unless methods[i] begin ::Contract.failure_callback(error.data, false) rescue expected_error => final_error raise final_error.to_contract_error end end end end result end method_reference.make_private(self) if is_private end |
#decorate(klass, *args) ⇒ Object
177 178 179 180 181 182 183 184 |
# File 'lib/contracts/decorators.rb', line 177 def decorate(klass, *args) if self.singleton_class? return EigenclassWithOwner.lift(self).owner_class.decorate(klass, *args) end @decorators ||= [] @decorators << [klass, args] end |
#fetch_decorators ⇒ Object
46 47 48 |
# File 'lib/contracts/decorators.rb', line 46 def fetch_decorators pop_decorators + Eigenclass.lift(self).pop_decorators 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.
32 33 34 35 |
# File 'lib/contracts/decorators.rb', line 32 def method_added(name) common_method_added name, false super end |
#pop_decorators ⇒ Object
42 43 44 |
# File 'lib/contracts/decorators.rb', line 42 def pop_decorators Array(@decorators).tap { @decorators = nil } end |
#singleton_method_added(name) ⇒ Object
37 38 39 40 |
# File 'lib/contracts/decorators.rb', line 37 def singleton_method_added(name) common_method_added name, true super end |