contracts 0.3 → 0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -218,22 +218,23 @@ module Contracts
218
218
  end
219
219
  end
220
220
 
221
- # Takes a class +A+. If argument.is_a? +A+, the contract passes.
222
- # Example: <tt>IsA[Numeric]</tt>
223
- class IsA < CallableClass
221
+ # Takes a class +A+. If argument is an object of type +A+, the contract passes.
222
+ # If it is a subclass of A (or not related to A in any way), it fails.
223
+ # Example: <tt>Exactly[Numeric]</tt>
224
+ class Exactly < CallableClass
224
225
  def initialize(cls)
225
226
  @cls = cls
226
227
  end
227
228
 
228
229
  def valid?(val)
229
- val.is_a? @cls
230
+ val.class == @cls
230
231
  end
231
232
 
232
233
  def to_s
233
- "a #{@cls.inspect}"
234
+ "exactly #{@cls.inspect}"
234
235
  end
235
236
  end
236
-
237
+
237
238
  # Takes a variable number of contracts. The contract
238
239
  # passes if all of those contracts fail for the given argument.
239
240
  # Example: <tt>Not[nil]</tt>
@@ -313,6 +314,27 @@ module Contracts
313
314
  end
314
315
  end
315
316
 
317
+ # Use this to specify the Hash characteristics. Takes two contracts,
318
+ # one for hash keys and one for hash values.
319
+ # Example: <tt>HashOf[Symbol, String]</tt>
320
+ class HashOf < CallableClass
321
+ def initialize(key, value)
322
+ @key = key
323
+ @value = value
324
+ end
325
+
326
+ def valid?(hash)
327
+ keys_match = hash.keys.map {|k| Contract.valid?(k, @key) }.all?
328
+ vals_match = hash.values.map {|v| Contract.valid?(v, @value) }.all?
329
+
330
+ [keys_match, vals_match].all?
331
+ end
332
+
333
+ def to_s
334
+ "Hash<#{@key.to_s}, #{@value.to_s}>"
335
+ end
336
+ end
337
+
316
338
  # Takes a Contract.
317
339
  # The contract passes if the contract passes or the given value is nil.
318
340
  # Maybe(foo) is equivalent to Or[foo, nil].
@@ -21,7 +21,7 @@ module Contracts
21
21
  if contracts.nil?
22
22
  "No contract for #{self}.#{funcname}"
23
23
  else
24
- "#{funcname} :: #{contracts}"
24
+ "#{funcname} :: #{contracts[0]}"
25
25
  end
26
26
  end
27
27
  end
@@ -35,7 +35,7 @@ module Contracts
35
35
  if contracts.nil?
36
36
  "No contract for #{self.class}.#{funcname}"
37
37
  else
38
- "#{funcname} :: #{contracts}"
38
+ "#{funcname} :: #{contracts[0]}"
39
39
  end
40
40
  end
41
41
  end
@@ -100,7 +100,14 @@ class Contract < Decorator
100
100
  position = file + ":" + line.to_s
101
101
  end
102
102
  method_name = data[:method].is_a?(Proc) ? "Proc" : data[:method].name
103
- %{Contract violation:
103
+
104
+ header = if data[:return_value]
105
+ "Contract violation for return value:"
106
+ else
107
+ "Contract violation for argument #{data[:arg_pos]} of #{data[:total_args]}:"
108
+ end
109
+
110
+ %{#{header}
104
111
  Expected: #{expected},
105
112
  Actual: #{data[:arg].inspect}
106
113
  Value guarded in: #{data[:class]}::#{method_name}
@@ -177,7 +184,7 @@ class Contract < Decorator
177
184
  if contract.respond_to? :valid?
178
185
  lambda { |arg| contract.valid?(arg) }
179
186
  elsif klass == Class
180
- lambda { |arg| contract == arg.class }
187
+ lambda { |arg| arg.is_a?(contract) }
181
188
  else
182
189
  lambda { |arg| contract == arg }
183
190
  end
@@ -204,14 +211,14 @@ class Contract < Decorator
204
211
  j = i < last_index ? i : last_index
205
212
  #unless true #@args_contracts[i].valid?(args[i])
206
213
  unless @args_validators[j][_args[i]]
207
- call_function = Contract.failure_callback({:arg => _args[i], :contract => @args_contracts[j], :class => @klass, :method => @method, :contracts => self})
214
+ call_function = Contract.failure_callback({:arg => _args[i], :contract => @args_contracts[j], :class => @klass, :method => @method, :contracts => self, :arg_pos => i+1, :total_args => _args.size})
208
215
  return unless call_function
209
216
  end
210
217
  end
211
218
 
212
219
  if @has_func_contracts
213
220
  # contracts on methods
214
- contracts.each_with_index do |contract, i|
221
+ @args_contracts.each_with_index do |contract, i|
215
222
  if contract.is_a? Contracts::Func
216
223
  args[i] = Contract.new(@klass, args[i], *contract.contracts)
217
224
  end
@@ -226,7 +233,7 @@ class Contract < Decorator
226
233
  @method.call(*args, &blk)
227
234
  end
228
235
  unless @ret_validator[result]
229
- Contract.failure_callback({:arg => result, :contract => @ret_contract, :class => @klass, :method => @method, :contracts => self})
236
+ Contract.failure_callback({:arg => result, :contract => @ret_contract, :class => @klass, :method => @method, :contracts => self, :return_value => true})
230
237
  end
231
238
  result
232
239
  end
@@ -40,12 +40,14 @@ module MethodDecorators
40
40
  decorator = klass.new(self, method(name), *args)
41
41
  @decorated_methods[:class_methods][name] ||= []
42
42
  @decorated_methods[:class_methods][name] << decorator
43
- is_private = self.private_methods.include?(name.to_s)
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)
44
45
  else
45
46
  decorator = klass.new(self, instance_method(name), *args)
46
47
  @decorated_methods[:instance_methods][name] ||= []
47
48
  @decorated_methods[:instance_methods][name] << decorator
48
- is_private = self.private_instance_methods.include?(name.to_s)
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)
49
51
  end
50
52
  end
51
53
 
@@ -53,9 +55,46 @@ module MethodDecorators
53
55
  # just calls the decorator passing in all args that were to be passed into the method.
54
56
  # The decorator in turn has a reference to the actual method, so it can call it
55
57
  # on its own, after doing it's decorating of course.
56
- class_eval <<-ruby_eval, __FILE__, __LINE__ + 1
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 = %{
57
96
  def #{is_class_method ? "self." : ""}#{name}(*args, &blk)
58
- current = self#{is_class_method ? "" : ".class"}
97
+ current = #{self}
59
98
  ancestors = current.ancestors
60
99
  ancestors.shift # first one is just the class itself
61
100
  while current && !current.respond_to?(:decorated_methods) || current.decorated_methods.nil?
@@ -86,7 +125,9 @@ module MethodDecorators
86
125
  result
87
126
  end
88
127
  #{is_private ? "private #{name.inspect}" : ""}
89
- ruby_eval
128
+ }
129
+
130
+ class_eval method_def, __FILE__, __LINE__ + 1
90
131
  end
91
132
 
92
133
  def decorate(klass, *args)
metadata CHANGED
@@ -1,12 +1,12 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: contracts
3
3
  version: !ruby/object:Gem::Version
4
- hash: 13
4
+ hash: 3
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
- - 3
9
- version: "0.3"
8
+ - 4
9
+ version: "0.4"
10
10
  platform: ruby
11
11
  authors:
12
12
  - Aditya Bhargava
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2012-06-07 00:00:00 Z
17
+ date: 2014-05-08 00:00:00 Z
18
18
  dependencies: []
19
19
 
20
20
  description: This library provides contracts for Ruby. Contracts let you clearly express how your code behaves, and free you from writing tons of boilerplate, defensive code.