Skip to content

Commit

Permalink
Merge pull request #34 from davidrunger/add-method-shape
Browse files Browse the repository at this point in the history
Add a `Method` shape
  • Loading branch information
davidrunger committed Jun 24, 2020
2 parents 5e1926f + 6a83345 commit 0ca6344
Show file tree
Hide file tree
Showing 7 changed files with 94 additions and 6 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
## Unreleased
### BREAKING CHANGES
- Rename the `Or` shape to `Any`
- Add a `Method` shape (where the shape description is the name of a method which, when called on a
test object, must return a truthy value). This is a breaking change because the `Shaped::Shape`
constructor will now return an instance of `Shaped::Shapes::Method` rather than
`Shaped::Shapes::Equality` when called with a Symbol argument.

### Added
- Add an `All` shape (w/ multiple sub-shapes, all of which must be matched)
Expand Down
18 changes: 17 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Validate the "shape" of Ruby objects!
* [Shaped::Shapes::Array](#shapedshapesarray)
* [Shaped::Shapes::Class](#shapedshapesclass)
* [ActiveModel validations](#activemodel-validations)
* [Shaped::Shapes::Method](#shapedshapesmethod)
* [Shaped::Shapes::Callable](#shapedshapescallable)
* [Shaped::Shapes::Equality](#shapedshapesequality)
* [Shaped::Shapes::Any](#shapedshapesany)
Expand All @@ -30,7 +31,7 @@ Validate the "shape" of Ruby objects!
* [For maintainers](#for-maintainers)
* [License](#license)

<!-- Added by: david, at: Wed Jun 24 13:23:10 PDT 2020 -->
<!-- Added by: david, at: Wed Jun 24 13:56:49 PDT 2020 -->

<!--te-->

Expand Down Expand Up @@ -205,6 +206,21 @@ shape.matched_by?('[email protected]') # too short
# => false
```

## Shaped::Shapes::Method

This shape allows specifying a method name that, when called upon a test object, must return a
truthy value in order for `matched_by?` to be true.

```rb
shape = Shaped::Shape(:odd?)

shape.matched_by?(55)
# => true

shape.matched_by?(60)
# => false
```

## Shaped::Shapes::Callable

This shape is very powerful if you need a very customized shape definition; you can define any
Expand Down
1 change: 1 addition & 0 deletions lib/shaped.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ def self.Shape(*shape_descriptions)
when Shaped::Shape then shape_description
when Hash then Shaped::Shapes::Hash.new(shape_description)
when Array then Shaped::Shapes::Array.new(shape_description)
when Symbol then Shaped::Shapes::Method.new(shape_description)
when Class then Shaped::Shapes::Class.new(shape_description, validation_options)
else
if shape_description.respond_to?(:call)
Expand Down
15 changes: 15 additions & 0 deletions lib/shaped/shapes/method.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# frozen_string_literal: true

class Shaped::Shapes::Method < Shaped::Shape
def initialize(method_name)
@method_name = method_name
end

def matched_by?(object)
!!object.public_send(@method_name)
end

def to_s
"object returning truthy for ##{@method_name}"
end
end
8 changes: 8 additions & 0 deletions spec/shaped_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@
end
end

context 'when called with a Symbol' do
let(:shape_description) { :valid? }

it 'returns an instance of Shaped::Shapes::Method' do
expect(shape).to be_a(Shaped::Shapes::Method)
end
end

context 'when called with a Class' do
let(:shape_description) { Numeric }

Expand Down
10 changes: 5 additions & 5 deletions spec/shapes/all_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
RSpec.describe Shaped::Shapes::All do
subject(:all_shape) { Shaped::Shapes::All.new(*all_shape_descriptions) }

let(:all_shape_descriptions) { [Numeric, ->(number) { number.even? }] }
let(:all_shape_descriptions) { [Numeric, :even?] }
let(:test_object) { 88 }

describe '#initialize' do
Expand Down Expand Up @@ -32,7 +32,7 @@
context 'when the test object satisfies all of the sub-shape descriptions' do
before do
expect(test_object).to be_a(all_shape_descriptions.first)
expect(all_shape_descriptions.second.call(test_object)).to eq(true)
expect(test_object.public_send(all_shape_descriptions.second)).to eq(true)
end

it 'returns true' do
Expand All @@ -45,7 +45,7 @@

before do
expect(test_object).to be_a(all_shape_descriptions.first) # matched
expect(all_shape_descriptions.second.call(test_object)).to eq(false) # not matched
expect(test_object.public_send(all_shape_descriptions.second)).to eq(false) # not matched
end

it 'returns false' do
Expand All @@ -57,8 +57,8 @@
describe '#to_s' do
subject(:to_s) { all_shape.to_s }

it 'returns a readably formatted description of the list of allowed shapes' do
expect(to_s).to match(/Numeric AND Proc test defined at .*all_spec\.rb:\d+/)
it 'returns a readably formatted description of the list of required shapes' do
expect(to_s).to eq('Numeric AND object returning truthy for #even?')
end
end
end
44 changes: 44 additions & 0 deletions spec/shapes/method_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# frozen_string_literal: true

RSpec.describe Shaped::Shapes::Method do
subject(:method_shape) { Shaped::Shapes::Method.new(method_shape_description) }

let(:method_shape_description) { :even? }
let(:test_object) { 64 }

describe '#initialize' do
it 'does not raise an error' do
expect { method_shape }.not_to raise_error
end
end

describe '#matched_by?' do
subject(:matched_by?) { method_shape.matched_by?(test_object) }

context 'when the test object returns a truthy value when called with the specified method' do
before { expect(test_object).to be_even }

it 'returns true' do
expect(matched_by?).to eq(true)
end
end

context 'when the test object returns a falsy value when called with the specified method' do
before { expect(test_object).not_to be_even }

let(:test_object) { 71 }

it 'returns false' do
expect(matched_by?).to eq(false)
end
end
end

describe '#to_s' do
subject(:to_s) { method_shape.to_s }

it 'returns a string naming the method that must be matched' do
expect(to_s).to eq('object returning truthy for #even?')
end
end
end

0 comments on commit 0ca6344

Please sign in to comment.