Skip to content

Conditional Specs

Jamie English edited this page Jul 23, 2017 · 1 revision

Speculation allows you to combine specs with S.or and S.and.

S.and

S.and allows you to construct a spec using multiple specs. For a given value to conform to the spec, it must conform to every constituent spec.

S.valid? S.and(Integer, ->(i) { i.positive? }), "foo" # => false
S.valid? S.and(Integer, ->(i) { i.positive? }), -1    # => false
S.valid? S.and(Integer, ->(i) { i.positive? }), 1     # => true

Combining specs with S.and allows you reuse specs in different contexts. It also makes it easy to avoid calling predicate methods on objects that might not implement them. For example:

is_positive = ->(i) { i.positive? }
S.valid? is_positive, "foo" # NoMethodError: undefined method `positive?' for "foo":String

S.or

Sometimes there are multiple ways a given data structure can be considered valid. S.or will consider a value valid if it conforms to any of its constituent specs:

S.valid? S.or(s: String, i: Integer), "foo" # => true
S.valid? S.or(s: String, i: Integer), 1     # => true
S.valid? S.or(s: String, i: Integer), 1.0   # => false

S.explain S.or(s: String, i: Integer), 1.0
# => val: 1.0 fails at: [:s] predicate: [String, [1.0]]
# => val: 1.0 fails at: [:i] predicate: [Integer, [1.0]]

It can be useful to know how a data structure conformed to an or spec. Because we've labelled our specs, S.conform will tell us which spec the value conformed to:

S.conform S.or(s: String, i: Integer), "foo" # => [:s, "foo"]
S.conform S.or(s: String, i: Integer), 1     # => [:i, 1]
Clone this wiki locally