Class: Stannum::Contracts::Base
- Inherits:
-
Stannum::Constraints::Base
- Object
- Stannum::Constraints::Base
- Stannum::Contracts::Base
- Defined in:
- lib/stannum/contracts/base.rb
Overview
A Contract aggregates constraints about the given object.
Direct Known Subclasses
Constant Summary
Constants inherited from Stannum::Constraints::Base
Stannum::Constraints::Base::NEGATED_TYPE, Stannum::Constraints::Base::TYPE
Instance Attribute Summary
Attributes inherited from Stannum::Constraints::Base
Instance Method Summary collapse
-
#==(other) ⇒ true, false
Performs an equality comparison.
-
#add_constraint(constraint, sanity: false, **options) ⇒ self
Adds a constraint to the contract.
-
#concat(other) ⇒ Stannum::Contract
Concatenate the constraints from the given other contract.
-
#does_not_match?(actual) ⇒ true, false
Checks that none of the added constraints match the object.
-
#each_constraint ⇒ Object
Iterates through the constraints defined for the contract.
-
#each_pair(actual) ⇒ Object
Iterates through the constraints and mapped values.
-
#errors_for(actual, errors: nil) ⇒ Stannum::Errors
Aggregates errors for each constraint that does not match the object.
-
#initialize(**options, &block) ⇒ Base
constructor
A new instance of Base.
-
#match(actual) ⇒ <Array(Boolean, Stannum::Errors)>
Matches and generates errors for each constraint.
-
#matches?(actual) ⇒ true, false
(also: #match?)
Checks that all of the added constraints match the object.
-
#negated_errors_for(actual, errors: nil) ⇒ Stannum::Errors
Aggregates errors for each constraint that matches the object.
-
#negated_match(actual) ⇒ <Array(Boolean, Stannum::Errors)>
Matches and generates errors for each constraint.
Methods inherited from Stannum::Constraints::Base
#clone, #dup, #message, #negated_message, #negated_type, #type, #with_options
Constructor Details
#initialize(**options, &block) ⇒ Base
Returns a new instance of Base.
107 108 109 110 111 112 113 114 |
# File 'lib/stannum/contracts/base.rb', line 107 def initialize(**, &block) @constraints = [] @concatenated = [] super(**) define_constraints(&block) end |
Instance Method Details
#==(other) ⇒ true, false
Performs an equality comparison.
122 123 124 |
# File 'lib/stannum/contracts/base.rb', line 122 def ==(other) super && equal_definitions?(other) end |
#add_constraint(constraint, sanity: false, **options) ⇒ self
Adds a constraint to the contract.
When the contract is matched with an object, the constraint will be evaluated with the object and the errors updated accordingly.
140 141 142 143 144 145 146 147 148 149 150 |
# File 'lib/stannum/contracts/base.rb', line 140 def add_constraint(constraint, sanity: false, **) validate_constraint(constraint) @constraints << Stannum::Contracts::Definition.new( constraint: constraint, contract: self, options: .merge(sanity: sanity) ) self end |
#concat(other) ⇒ Stannum::Contract
Concatenate the constraints from the given other contract.
Merges the constraints from the concatenated contract into the original. This is a dynamic process - if constraints are added to the concatenated contract at a later point, they will also be added to the original. This is also recursive - concatenating a contract will also merge the constraints from any contracts that were themselves concatenated in the concatenated contract.
There are two approaches for adding one contract to another. The first and simplest is to take advantage of the fact that each contract is, itself, a constraint. Adding the new contract to the original via #add_constraint works in most cases - the new contract will be called during #matches? and when generating errors. However, functionality that inspects the constraints directly (such as the :allow_extra_keys functionality in HashContract) will fail.
Concatenating a contract in another is a much closer relationship. Each time the constraints on the original contract are enumerated, it will also yield the constraints from the concatenated contract (and from any contracts that are concatenated in that contract, recursively).
To sum up, use #add_constraint when you want to constrain a property of the actual object with a contract. Use #concat when you want to add more constraints about the object itself.
196 197 198 199 200 201 202 |
# File 'lib/stannum/contracts/base.rb', line 196 def concat(other) validate_contract(other) @concatenated << other self end |
#does_not_match?(actual) ⇒ true, false
Checks that none of the added constraints match the object.
If the contract defines sanity constraints, the sanity constraints will be matched first. If any of the sanity constraints fail (#does_not_match? for the constraint returns true), then this method will immediately return true and all subsequent constraints will be skipped.
219 220 221 222 223 224 225 226 227 228 229 230 231 |
# File 'lib/stannum/contracts/base.rb', line 219 def does_not_match?(actual) each_pair(actual) do |definition, value| if definition.contract.match_negated_constraint(definition, value) next unless definition.sanity? return true end return false end true end |
#each_constraint ⇒ Enumerator #each_constraint {|definition| ... } ⇒ Object
Iterates through the constraints defined for the contract.
Any constraints defined on concatenated contracts are yielded, followed by any constraints defined on the contract itself.
Each constraint is represented as a Stannum::Contracts::Definition, which encapsulates the constraint, the original contract, and the options specified by #add_constraint.
If the contract defines sanity constraints, the sanity constraints will be returned or yielded first, followed by the remaining constraints.
254 255 256 257 258 259 260 261 262 263 264 |
# File 'lib/stannum/contracts/base.rb', line 254 def each_constraint return enum_for(:each_constraint) unless block_given? each_unscoped_constraint do |definition| yield definition if definition.sanity? end each_unscoped_constraint do |definition| # rubocop:disable Style/CombinableLoops yield definition unless definition.sanity? end end |
#each_pair(actual) ⇒ Enumerator #each_pair(actual) {|definition, value| ... } ⇒ Object
Iterates through the constraints and mapped values.
For each constraint defined for the contract, the contract defines a data mapping representing the object or property that the constraint will match. Calling #each_pair for an object yields the constraint and the mapped object or property for that constraint and object.
If the contract defines sanity constraints, the sanity constraints will be returned or yielded first, followed by the remaining constraints.
By default, this mapping returns the object itself; however, this can be overriden in subclasses based on the constraint options, such as matching constraints against the properties of an object rather than the object itself.
This enumerator is used internally to implement the Constraint interface for subclasses of Contract.
295 296 297 298 299 300 301 302 303 |
# File 'lib/stannum/contracts/base.rb', line 295 def each_pair(actual) return enum_for(:each_pair, actual) unless block_given? each_constraint do |definition| value = definition.contract.map_value(actual, **definition.) yield definition, value end end |
#errors_for(actual, errors: nil) ⇒ Stannum::Errors
Aggregates errors for each constraint that does not match the object.
For each defined constraint, the constraint is matched against the mapped value for that constraint and the object. If the constraint does not match the mapped value, the corresponding errors will be added to the errors object.
If the contract defines sanity constraints, the sanity constraints will be matched first. If any of the sanity constraints fail, #errors_for will immediately return the errors for the failed constraint.
325 326 327 328 329 330 331 332 333 334 335 336 337 |
# File 'lib/stannum/contracts/base.rb', line 325 def errors_for(actual, errors: nil) errors ||= Stannum::Errors.new each_pair(actual) do |definition, value| next if match_constraint(definition, value) definition.contract.add_errors_for(definition, value, errors) return errors if definition.sanity? end errors end |
#match(actual) ⇒ <Array(Boolean, Stannum::Errors)>
Matches and generates errors for each constraint.
For each defined constraint, the constraint is matched against the mapped value for that constraint and the object. If the constraint does not match the mapped value, the corresponding errors will be added to the errors object.
Finally, if all of the constraints match the mapped value, #match will return true and the errors object. Otherwise, #match will return false and the errors object.
If the contract defines sanity constraints, the sanity constraints will be matched first. If any of the sanity constraints fail (#matches? for the constraint returns false), then this method will immediately return false and the errors for the failed sanity constraint; and all subsequent constraints will be skipped.
365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 |
# File 'lib/stannum/contracts/base.rb', line 365 def match(actual) status = true errors = Stannum::Errors.new each_pair(actual) do |definition, value| next if definition.contract.match_constraint(definition, value) status = false definition.contract.send(:add_errors_for, definition, value, errors) return [status, errors] if definition.sanity? end [status, errors] end |
#matches?(actual) ⇒ true, false Also known as: match?
Checks that all of the added constraints match the object.
If the contract defines sanity constraints, the sanity constraints will be matched first. If any of the sanity constraints fail (#does_not_match? for the constraint returns true), then this method will immediately return false and all subsequent constraints will be skipped.
397 398 399 400 401 402 403 404 405 |
# File 'lib/stannum/contracts/base.rb', line 397 def matches?(actual) each_pair(actual) do |definition, value| unless definition.contract.match_constraint(definition, value) return false end end true end |
#negated_errors_for(actual, errors: nil) ⇒ Stannum::Errors
Aggregates errors for each constraint that matches the object.
For each defined constraint, the constraint is matched against the mapped value for that constraint and the object. If the constraint matches the mapped value, the corresponding errors will be added to the errors object.
If the contract defines sanity constraints, the sanity constraints will be matched first. If any of the sanity constraints fail, #errors_for will immediately return any errors already added to the errors object.
427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 |
# File 'lib/stannum/contracts/base.rb', line 427 def negated_errors_for(actual, errors: nil) errors ||= Stannum::Errors.new each_pair(actual) do |definition, value| if match_negated_constraint(definition, value) next unless definition.sanity? return errors end definition.contract.add_negated_errors_for(definition, value, errors) end errors end |
#negated_match(actual) ⇒ <Array(Boolean, Stannum::Errors)>
Matches and generates errors for each constraint.
For each defined constraint, the constraint is matched against the mapped value for that constraint and the object. If the constraint matches the mapped value, the corresponding errors will be added to the errors object.
Finally, if none of the constraints match the mapped value, #match will return true and the errors object. Otherwise, #match will return false and the errors object.
If the contract defines sanity constraints, the sanity constraints will be matched first. If any of the sanity constraints fail (#does_not_match? for the constraint returns true), then this method will immediately return true and any errors already set; and all subsequent constraints will be skipped.
469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 |
# File 'lib/stannum/contracts/base.rb', line 469 def negated_match(actual) # rubocop:disable Metrics/MethodLength status = true errors = Stannum::Errors.new each_pair(actual) do |definition, value| if definition.contract.match_negated_constraint(definition, value) next unless definition.sanity? return [true, errors] end status = false definition.contract.add_negated_errors_for(definition, value, errors) end [status, errors] end |