Module: Contracts::ClassMethods

Defined in:
lib/contracts.rb

Instance Method Summary collapse

Instance Method Details

#contract(name = nil, **options, &block) ⇒ Object



825
826
827
828
829
830
# File 'lib/contracts.rb', line 825

def contract(name = nil, **options, &block)
  if name.nil? then @__contracts_pending = [options, block, caller_locations(1, 1).first]
                    return
  end
  declare_contract(name, :instance, options, &block)
end

#declare_contract(name, type, options, source_location: caller_locations(2, 1).first, &block) ⇒ Object



864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
# File 'lib/contracts.rb', line 864

def declare_contract(name, type, options, source_location: caller_locations(2, 1).first, &block)
  contract = Contract.new(owner: self, method_name: name, method_type: type, source_location: source_location,
                          options: options)
  ContractBuilder.new(contract).instance_eval(&block) if block
  merge_parent_contract!(contract) unless Contracts.configuration.inheritance_mode == :independent
  if contract.snapshot_block.nil? && @__contracts_snapshot
    contract.instance_variable_set(:@snapshot_block,
                                   @__contracts_snapshot)
  end
  Contracts.registry.register(contract)

  wrap_contract(name, contract) if method_defined?(name,
                                                   false) || private_method_defined?(name,
                                                                                     false) || protected_method_defined?(
                                                                                       name, false
                                                                                     )
  contract
end

#invariant(description = "invariant", &block) ⇒ Object



832
833
834
835
836
837
838
839
840
841
842
843
844
# File 'lib/contracts.rb', line 832

def invariant(description = "invariant", &block)
  location = caller_locations(1, 1).first
  contract = Contracts.registry.find(self,
                                     :__invariant__) || Contract.new(owner: self, method_name: :__invariant__,
                                                                     source_location: location)
  contract.invariants << Invariant.new(id: "#{name || object_id}:#{caller_locations(1, 1).first.lineno}",
                                       owner: self, description: description, predicate: block, source_location: caller_locations(1, 1).first, options: {})

  Contracts.registry.register(contract)
  @__contracts_has_invariants = true
  wrap_initialize_for_invariants if method_defined?(:initialize,
                                                    false) || private_method_defined?(:initialize, false)
end

#merge_parent_contract!(contract) ⇒ Object



883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
# File 'lib/contracts.rb', line 883

def merge_parent_contract!(contract)
  parent = ancestors.drop(1).lazy.map do |ancestor|
    Contracts.registry.for_class(ancestor).find do |candidate|
      candidate.method_name == contract.method_name && candidate.method_type == contract.method_type
    end
  end.find(&:itself)
  return unless parent

  if Contracts.configuration.inheritance_mode == :strict
    parent.parameters.each do |name, constraint|
      child = contract.parameters[name]
      if child && child.description != constraint.description
        raise InheritanceViolation.new(owner: self, method_name: contract.method_name, contract_type: :inheritance,
                                       description: "parameter #{name} changes parent constraint #{constraint.description}")
      end
    end
  end
  contract.parameters = parent.parameters.merge(contract.parameters) unless parent.parameters.empty?
  contract.positionals = parent.positionals if contract.positionals.empty?
  contract.preconditions.unshift(*parent.preconditions)
  contract.postconditions.unshift(*parent.postconditions)
  contract.return_constraint = parent.return_constraint unless contract.return_constraint
  contract.allowed_exceptions.unshift(*parent.allowed_exceptions)
  contract.observed.unshift(*parent.observed.reject do |observation|
    contract.observed.any? do |own|
      own.name == observation.name
    end
  end)
end

#method_added(name) ⇒ Object



848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
# File 'lib/contracts.rb', line 848

def method_added(name)
  return if @__contracts_hook

  if (pending = @__contracts_pending)
    @__contracts_pending = nil

    declare_contract(name, :instance, pending[0], source_location: pending[2], &pending[1])
  elsif (contract = Contracts.registry.find(self, name, method_type: :instance)) && contract.owner == self
    wrap_contract(name, contract)
  elsif (contract = Contracts.registry.find(self, name, method_type: :singleton)) && contract.owner == self
    wrap_contract(name, contract)
  end
  wrap_initialize_for_invariants if name == :initialize && @__contracts_has_invariants
  super
end

#snapshot(&block) ⇒ Object



846
# File 'lib/contracts.rb', line 846

def snapshot(&block) = (@__contracts_snapshot = block)

#wrap_contract(name, contract) ⇒ Object



913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
# File 'lib/contracts.rb', line 913

def wrap_contract(name, contract)
  return if contract.instance_variable_defined?(:@wrapped)

  original = instance_method(name)

  contract.method_source_location = original.source_location
  visibility = if private_method_defined?(name)
                 :private
               else
                 protected_method_defined?(name) ? :protected : :public
               end
  @__contracts_hook = true
  define_method(name) do |*args, **kwargs, &block|
    Contracts.invoke(self, contract, args, kwargs, block) do
      original.bind_call(self, *args, **kwargs, &block)
    end
  end
  send(visibility, name)
  contract.instance_variable_set(:@wrapped, true)
ensure
  @__contracts_hook = false
end

#wrap_initialize_for_invariantsObject



936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
# File 'lib/contracts.rb', line 936

def wrap_initialize_for_invariants
  return if @__contracts_initialize_wrapped

  original = instance_method(:initialize)
  @__contracts_hook = true
  define_method(:initialize) do |*args, **kwargs, &block|
    original.bind_call(self, *args, **kwargs, &block).tap do
      Contracts.check_invariants!(self) if Contracts.configuration.check_invariants_after_initialize && Contracts.configuration.invariant_checking != :disabled
    end
  end
  private :initialize
  @__contracts_initialize_wrapped = true
ensure
  @__contracts_hook = false
end