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
|
# frozen_string_literal: true
module Contracts
module Invariants
def self.included(base)
common base
end
def self.extended(base)
common base
end
def self.common(base)
return if base.respond_to?(:Invariant)
base.extend(InvariantExtension)
end
def verify_invariants!(method)
return unless self.class.respond_to?(:invariants)
self.class.invariants.each do |invariant|
invariant.check_on(self, method)
end
end
module InvariantExtension
def invariant(name, &condition)
return if ENV["NO_CONTRACTS"]
invariants << Invariant.new(self, name, &condition)
end
def invariants
@invariants ||= []
end
end
class Invariant
def initialize(klass, name, &condition)
@klass, @name, @condition = klass, name, condition
end
def expected
"#{@name} condition to be true"
end
def check_on(target, method)
return if target.instance_eval(&@condition)
self.class.failure_callback({
expected: expected,
actual: false,
target: target,
method: method,
})
end
def self.failure_callback(data)
fail InvariantError, failure_msg(data)
end
def self.failure_msg(data)
%{Invariant violation:
Expected: #{data[:expected]}
Actual: #{data[:actual]}
Value guarded in: #{data[:target].class}::#{Support.method_name(data[:method])}
At: #{Support.method_position(data[:method])}}
end
end
end
end
|