Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a Contracts::Attrs module containing attribute w/ contracts utilities. #255

Merged
merged 3 commits into from
Apr 20, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions TUTORIAL.md
Original file line number Diff line number Diff line change
Expand Up @@ -606,6 +606,32 @@ Possible validator overrides:

Default validators can be found here: [lib/contracts/validators.rb](https://github.com/egonSchiele/contracts.ruby/blob/master/lib/contracts/validators.rb).

## Contracts with attributes

You can include the `Contracts::Attrs` module in your class/module to get access to attribute utilities:

- `attr_reader_with_contract <symbol>..., <contract>`
- Wraps `attr_reader`, validates contract upon 'getting'
- `attr_writer_with_contract <symbol>..., <contract>`
- Wraps `attr_writer`, validates contract upon 'setting'
- `attr_accessor_with_contract <symbol>..., <contract>`
- Wraps `attr_accessor`, validates contract upon 'getting' or 'setting'

### Example

```ruby
class Person
include Contracts::Core
include Contracts::Attrs

attr_accessor_with_contract :name, String
end

person = Person.new
person.name = 'Jane'
person.name = 1.4 # This results in a contract error!
```

## Disabling contracts

If you want to disable contracts, set the `NO_CONTRACTS` environment variable. This will disable contracts and you won't have a performance hit. Pattern matching will still work if you disable contracts in this way! With NO_CONTRACTS only pattern-matching contracts are defined.
Expand Down
1 change: 1 addition & 0 deletions lib/contracts.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
require "contracts/attrs"
require "contracts/builtin_contracts"
require "contracts/decorators"
require "contracts/errors"
Expand Down
20 changes: 20 additions & 0 deletions lib/contracts/attrs.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
module Contracts
module Attrs
def attr_reader_with_contract(*names, contract)
Contract Contracts::None => contract
attr_reader(*names)
end

def attr_writer_with_contract(*names, contract)
Contract contract => contract
attr_writer(*names)
end

def attr_accessor_with_contract(*names, contract)
attr_reader_with_contract(*names, contract)
attr_writer_with_contract(*names, contract)
end
end

include Attrs
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this is included in place?
Allow people to just include ::Contract & use all methods?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i don't remember why i added that line; feel free to remove it

end
75 changes: 75 additions & 0 deletions spec/attrs_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
RSpec.describe "Contracts:" do
describe "Attrs:" do
class Person
include Contracts::Core
include Contracts::Attrs
include Contracts::Builtin

def initialize(name)
@name_r = name
@name_w = name
@name_rw = name
end

attr_reader_with_contract :name_r, String
attr_writer_with_contract :name_w, String
attr_accessor_with_contract :name_rw, String
end

context "attr_reader_with_contract" do
it "getting valid type" do
expect(Person.new("bob").name_r)
.to(eq("bob"))
end

it "getting invalid type" do
expect { Person.new(1.3).name_r }
.to(raise_error(ReturnContractError))
end

it "setting" do
expect { Person.new("bob").name_r = "alice" }
.to(raise_error(NoMethodError))
end
end

context "attr_writer_with_contract" do
it "getting" do
expect { Person.new("bob").name_w }
.to(raise_error(NoMethodError))
end

it "setting valid type" do
expect(Person.new("bob").name_w = "alice")
.to(eq("alice"))
end

it "setting invalid type" do
expect { Person.new("bob").name_w = 1.2 }
.to(raise_error(ParamContractError))
end
end

context "attr_accessor_with_contract" do
it "getting valid type" do
expect(Person.new("bob").name_rw)
.to(eq("bob"))
end

it "getting invalid type" do
expect { Person.new(1.2).name_rw }
.to(raise_error(ReturnContractError))
end

it "setting valid type" do
expect(Person.new("bob").name_rw = "alice")
.to(eq("alice"))
end

it "setting invalid type" do
expect { Person.new("bob").name_rw = 1.2 }
.to(raise_error(ParamContractError))
end
end
end
end