Skip to content

Commit

Permalink
Add a Contracts::Attrs module containing attribute w/ contracts uti…
Browse files Browse the repository at this point in the history
…lities. (#255)

* Add `Attrs` module containing attribute w/ contracts utilities.

* Add documentation for new `Attrs` module.

* Add tests for new `Attrs` module.
  • Loading branch information
frewsxcv authored and egonSchiele committed Apr 20, 2017
1 parent c408bcc commit 56c2075
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 0 deletions.
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
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

0 comments on commit 56c2075

Please sign in to comment.