troupe 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/.travis.yml +3 -0
- data/Gemfile +8 -0
- data/LICENSE +22 -0
- data/LICENSE.txt +21 -0
- data/README.md +160 -0
- data/Rakefile +1 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/lib/troupe.rb +36 -0
- data/lib/troupe/contract.rb +145 -0
- data/lib/troupe/contract/property.rb +36 -0
- data/lib/troupe/contract/property_table.rb +66 -0
- data/lib/troupe/contract_violation.rb +16 -0
- data/lib/troupe/version.rb +3 -0
- data/troupe.gemspec +26 -0
- metadata +103 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 36d3441bf24fea4d1712b7694245912ac0ad6af2
|
4
|
+
data.tar.gz: ff3ba18a37d8bb618855bbbfaf23d51c1a4780f0
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 21449aca99de6d0123a18c9a7697052ba359f28231adf4137126591a804afd83ccc0fb29d3194b475579cadbf4a71cbce471fc3396da15cfdbf38ef51e53addd
|
7
|
+
data.tar.gz: 87cfeb509d80f21b6445b59c4e0c0d7c78da1579e485b5b7428ca5491c04abca7f09085f8333b5acdd282d19ac73dd2a4eb205636abf2efad1ba5bcf35869283
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015 Jon Stokes
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
22
|
+
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015 Jon Stokes
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,160 @@
|
|
1
|
+
# troupe
|
2
|
+
Troupe is a contract DSL for the [interactor gem](https://github.com/collectiveidea/interactor). It's backwards-compatible with
|
3
|
+
interactor 3.1.0 and higher, so you can introduce it into your codebase gradually.
|
4
|
+
|
5
|
+
# Getting Started
|
6
|
+
|
7
|
+
Add troupe to your Gemfile and bundle install.
|
8
|
+
```ruby
|
9
|
+
gem "troupe"
|
10
|
+
```
|
11
|
+
Use it just like you would Interactor, by doing `include Troupe` in your class instead of `include Interactor`. Including the former will also include the latter by default, and monkey patch it so that it works with contracts as described below.
|
12
|
+
|
13
|
+
# Requirements
|
14
|
+
Ruby 2.0 or higher
|
15
|
+
|
16
|
+
# DSL
|
17
|
+
|
18
|
+
Here's the normal way to write an interactor:
|
19
|
+
```ruby
|
20
|
+
class PlaceOrder
|
21
|
+
include Interactor
|
22
|
+
|
23
|
+
before do
|
24
|
+
context.user ||= User.find(context.user_id)
|
25
|
+
end
|
26
|
+
|
27
|
+
def call
|
28
|
+
context.order = context.user.orders.create(context.attributes)
|
29
|
+
|
30
|
+
context.fail! unless context.order.persisted?
|
31
|
+
end
|
32
|
+
end
|
33
|
+
```
|
34
|
+
|
35
|
+
Here's the same interactor using the DSL
|
36
|
+
```ruby
|
37
|
+
class PlaceOrder
|
38
|
+
include Troupe
|
39
|
+
|
40
|
+
expects :attributes
|
41
|
+
permits :user_id
|
42
|
+
|
43
|
+
permits(:user) do
|
44
|
+
User.find(user_id)
|
45
|
+
end
|
46
|
+
|
47
|
+
provides(:order) do
|
48
|
+
user.orders.create(attributes)
|
49
|
+
end
|
50
|
+
|
51
|
+
before { context.fail! unless order.persisted? }
|
52
|
+
end
|
53
|
+
```
|
54
|
+
Here's a quick description of all the main verbs in the DSL:
|
55
|
+
|
56
|
+
### expects
|
57
|
+
E.g. `expects :property1, :property2`
|
58
|
+
|
59
|
+
Any properties listed here must be part of the context, or else the interactor will raise a `ContractViolation` error.
|
60
|
+
|
61
|
+
### permits
|
62
|
+
E.g. `permits :property1, :property2, default: :get_property_default`
|
63
|
+
|
64
|
+
Any attributes listed here can be part of the context and can be accessed inside the interactor as if they were local variables declared with `attr_accessor`, e.g. `puts "#{attr}"` and `self.foo = :bar`. If an attribute is part of the context and isn't listed under `expects` or `permits`, then it has no such getter or setter.
|
65
|
+
|
66
|
+
Defaults can be set either as above, with a symbolized method name, or with a block as below:
|
67
|
+
```ruby
|
68
|
+
permits(:property1, :property2) do
|
69
|
+
'default value'
|
70
|
+
end
|
71
|
+
```
|
72
|
+
If either `property` or `property` are when the interactor is called, then the above code will set the nil property (or properties) to 'default value'. The default applies to every listed property. If you want to set individual defaults, use separate `permits` clauses for each property.
|
73
|
+
|
74
|
+
### provides
|
75
|
+
E.g. `provides :property1, :property2, default: :get_property_default`
|
76
|
+
|
77
|
+
The `provides` verb is basically an alias for `permits`, and is offered for the sake of documentation and ease of reading.
|
78
|
+
|
79
|
+
The one nice thing about `provides` is that all post-`call` evaluations* happen in the following order: expected properties, permitted properties, provided properties. So anything declared with `provides` can expect to have anything that's expected or permitted already defined.
|
80
|
+
|
81
|
+
(* See below for what I mean by the phrase "post-`call` evaluations".)
|
82
|
+
|
83
|
+
### A word about order
|
84
|
+
|
85
|
+
The TL;DR version of this section can be expressed in two simple rules:
|
86
|
+
1. All property defaults are lazy evaluated at the time that they're first called in the hooks or in the `call` method.
|
87
|
+
2. Any property defaults that have not been so evaluated once the interactor is completely done will be evaluated in the order that they were declared, subject to the constraint that expected defaults go first, then permitted ones, then provided ones.
|
88
|
+
|
89
|
+
No for the longer explanation:
|
90
|
+
|
91
|
+
The default values given for the verbs above are lazy evaluated within your interactor's hooks and `call` method. So in the example above, the code `User.find(user_id)` would not be evaluated until you actually reference `user` from within `call` or one of the hooks, and then it would be evaluated only if the interactor had been called without the `:user` key in the context object.
|
92
|
+
|
93
|
+
If the `call` method ends and all the hooks are run and `user` the getter for `user` has still not been called and there still is no `user` key in the context, then an `ensure_contract_defaults` method will run and will call its getter just to ensure that it gets referenced at least once and therefore added to the context with any default that may have been set.
|
94
|
+
|
95
|
+
In other words, after the `call` and all of the hooks are run, the interactor essentially does the following:
|
96
|
+
|
97
|
+
```ruby
|
98
|
+
(expected_properties + permitted_properties + provided_properties).each do |property|
|
99
|
+
send(property)
|
100
|
+
end
|
101
|
+
```
|
102
|
+
That call to `send(property)` just returns `context[property]` if that propert is a key in the context, otherwise it checks for a default block and tries to set the key with that.
|
103
|
+
|
104
|
+
What all of this means is that the following code is just not a problem and behaves predictably every time, provided that you call `MyInteractor` with either `property1` or `property2` set:
|
105
|
+
```ruby
|
106
|
+
class MyInteractor
|
107
|
+
permits :property1 do
|
108
|
+
property2
|
109
|
+
end
|
110
|
+
|
111
|
+
permits :property2 do
|
112
|
+
property1
|
113
|
+
end
|
114
|
+
|
115
|
+
def call
|
116
|
+
property1
|
117
|
+
end
|
118
|
+
end
|
119
|
+
```
|
120
|
+
Of course, if you do `MyInteractor.call` without either property key, then you'll have a stack overflow.
|
121
|
+
|
122
|
+
And again, just to be clear, you can use `expects` and `permits` and `provides` in whatever order -- it generally doesn't matter, and everything will work as you expect.
|
123
|
+
|
124
|
+
## Hooks
|
125
|
+
|
126
|
+
A contract violation will raise a `Troupe::ContractViolation` error, but it doesn't have to. You can handle violations yourself with the following hooks.
|
127
|
+
|
128
|
+
### on_violation
|
129
|
+
Example:
|
130
|
+
```ruby
|
131
|
+
class MyInteractor
|
132
|
+
include Troupe
|
133
|
+
|
134
|
+
expects :property1, :property2
|
135
|
+
|
136
|
+
on_violation do |violation|
|
137
|
+
if violation.property == :property1
|
138
|
+
puts "Property1 violated the contract!"
|
139
|
+
else
|
140
|
+
context.fail!(error: violation.message)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
```
|
145
|
+
The above should be self-explanatory. One thing to note: a `ContractViolation` object has a `property` method that returns the name of the property that raised the violation, and a `message` method that returns the error message that would otherwise be raised.
|
146
|
+
|
147
|
+
### on_violation_for
|
148
|
+
Example:
|
149
|
+
```ruby
|
150
|
+
class MyInteractor
|
151
|
+
include Troupe
|
152
|
+
|
153
|
+
expects :property1
|
154
|
+
|
155
|
+
on_violation_for(:property1) do |violation|
|
156
|
+
context.fail!(error: violation.message)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
```
|
160
|
+
Yep, it's technically redundant to `on_violation`, but can make the hooks a little cleaner if you're only handling one or two properties.
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "troupe"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
data/lib/troupe.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
require "interactor"
|
2
|
+
require "troupe/version"
|
3
|
+
require "troupe/contract_violation"
|
4
|
+
require "troupe/contract"
|
5
|
+
require "troupe/contract/property"
|
6
|
+
require "troupe/contract/property_table"
|
7
|
+
|
8
|
+
module Troupe
|
9
|
+
def self.included(base)
|
10
|
+
|
11
|
+
Interactor::Context.class_eval do
|
12
|
+
def members
|
13
|
+
@table.keys
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
Interactor.class_eval do
|
18
|
+
def run!
|
19
|
+
validate_contract_expectations
|
20
|
+
with_hooks do
|
21
|
+
call
|
22
|
+
context.called!(self)
|
23
|
+
end
|
24
|
+
ensure_contract_defaults
|
25
|
+
rescue
|
26
|
+
context.rollback!
|
27
|
+
raise
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
base.class_eval do
|
32
|
+
include Interactor
|
33
|
+
include Contract
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,145 @@
|
|
1
|
+
module Troupe
|
2
|
+
module Contract
|
3
|
+
VALID_TYPES = %i(open closed)
|
4
|
+
|
5
|
+
def self.included(base)
|
6
|
+
base.class_eval do
|
7
|
+
extend ClassMethods
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def violation_table
|
13
|
+
@violation_table ||= {}
|
14
|
+
end
|
15
|
+
|
16
|
+
def validate_contract_expectations
|
17
|
+
populate_violation_table
|
18
|
+
check_each_violation
|
19
|
+
end
|
20
|
+
|
21
|
+
def missing_properties
|
22
|
+
@missing_properties ||= self.class.missing_properties(context)
|
23
|
+
end
|
24
|
+
|
25
|
+
def ensure_contract_defaults
|
26
|
+
self.class.all_properties.each do |attr|
|
27
|
+
send(attr)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def populate_violation_table
|
32
|
+
missing_properties.each do |property_name|
|
33
|
+
violation_table[property_name] = ContractViolation.new(
|
34
|
+
self,
|
35
|
+
property: property_name,
|
36
|
+
message: "Expected context to include property '#{property_name}'."
|
37
|
+
)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def check_each_violation
|
42
|
+
return if violation_table.empty?
|
43
|
+
violation_table.each do |property_name, violation|
|
44
|
+
if block = violation_block_for(property_name)
|
45
|
+
instance_exec(violation, &block)
|
46
|
+
else
|
47
|
+
raise violation
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def violation_block_for(property_name)
|
53
|
+
self.class.violation_block_for(property_name) ||
|
54
|
+
self.class.on_violation_block
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
module ClassMethods
|
59
|
+
# Core DSL
|
60
|
+
#
|
61
|
+
def property(attr, opts={}, &block)
|
62
|
+
opts.merge!(default: block) if block
|
63
|
+
property_table.set(attr, opts)
|
64
|
+
|
65
|
+
delegate_properties
|
66
|
+
end
|
67
|
+
|
68
|
+
def on_violation_for(*args, &block)
|
69
|
+
args.each do |arg|
|
70
|
+
next unless property = property_table.get(arg)
|
71
|
+
property.on_violation = block
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def on_violation(&block)
|
76
|
+
@on_violation_block = block
|
77
|
+
end
|
78
|
+
|
79
|
+
# Sugar for core DSL
|
80
|
+
#
|
81
|
+
def expects(*args, &block)
|
82
|
+
presence_is(:expected, args, block)
|
83
|
+
end
|
84
|
+
|
85
|
+
def permits(*args, &block)
|
86
|
+
presence_is(:permitted, args, block)
|
87
|
+
end
|
88
|
+
|
89
|
+
def provides(*args, &block)
|
90
|
+
presence_is(:provided, args, block)
|
91
|
+
end
|
92
|
+
|
93
|
+
def on_violation_block
|
94
|
+
@on_violation_block
|
95
|
+
end
|
96
|
+
|
97
|
+
def expected_properties; property_table.expected_properties; end
|
98
|
+
def permitted_properties; property_table.permitted_properties; end
|
99
|
+
def provided_properties; property_table.provided_properties; end
|
100
|
+
def all_properties; property_table.all_properties; end
|
101
|
+
def default_for(attr); property_table.default_for(attr); end
|
102
|
+
def violation_block_for(attr); property_table.get(attr).on_violation; end
|
103
|
+
def missing_properties(context); property_table.missing_properties(context); end
|
104
|
+
|
105
|
+
def expected_and_permitted_properties
|
106
|
+
property_table.expected_and_permitted_properties
|
107
|
+
end
|
108
|
+
|
109
|
+
private
|
110
|
+
|
111
|
+
def property_table
|
112
|
+
@property_table ||= PropertyTable.new
|
113
|
+
end
|
114
|
+
|
115
|
+
|
116
|
+
def delegate_properties
|
117
|
+
all_properties.each do |attr|
|
118
|
+
define_method attr do
|
119
|
+
next context[attr] if context.members.include?(attr)
|
120
|
+
default = self.class.default_for(attr)
|
121
|
+
context[attr] = if default.is_a?(Proc)
|
122
|
+
instance_exec(&default)
|
123
|
+
elsif default.is_a?(Symbol)
|
124
|
+
send(default)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
define_method "#{attr}=" do |value|
|
129
|
+
context[attr] = value
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def presence_is(presence, args, block)
|
135
|
+
opts = args.detect { |arg| arg.is_a?(Hash) } || {}
|
136
|
+
opts.merge!(presence: presence)
|
137
|
+
args.reject! { |arg| arg.is_a?(Hash) }
|
138
|
+
|
139
|
+
args.each do |arg|
|
140
|
+
property(arg, opts, &block)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Troupe
|
2
|
+
module Contract
|
3
|
+
class Property
|
4
|
+
DEFAULTS = { presence: :provided }
|
5
|
+
|
6
|
+
VALID_OPTIONS = {
|
7
|
+
presence: [:expected, :permitted, :provided]
|
8
|
+
}
|
9
|
+
|
10
|
+
attr_accessor :default, :on_violation, :presence
|
11
|
+
|
12
|
+
def initialize(opts={})
|
13
|
+
validate_options(opts)
|
14
|
+
opts = DEFAULTS.merge(opts)
|
15
|
+
@default = opts[:default]
|
16
|
+
@on_violation = opts[:on_violation]
|
17
|
+
@presence = opts[:presence]
|
18
|
+
end
|
19
|
+
|
20
|
+
def merge!(hash)
|
21
|
+
hash.each do |k, v|
|
22
|
+
send("#{k}=", v)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def validate_options(opts)
|
29
|
+
opts.each do |k, v|
|
30
|
+
next unless values = VALID_OPTIONS[k]
|
31
|
+
raise "Invalid value '#{v}' for option #{k}" unless values.include?(v)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module Troupe
|
2
|
+
module Contract
|
3
|
+
class PropertyTable
|
4
|
+
def initialize
|
5
|
+
@table ||= {}
|
6
|
+
end
|
7
|
+
|
8
|
+
def get(property_name)
|
9
|
+
@table[property_name]
|
10
|
+
end
|
11
|
+
|
12
|
+
def set(property_name, opts={})
|
13
|
+
@table[property_name] ||= Property.new(opts)
|
14
|
+
@table[property_name].merge!(opts)
|
15
|
+
end
|
16
|
+
|
17
|
+
def each_property
|
18
|
+
@table.each do |property_name, property|
|
19
|
+
yield property_name, property
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def select(args)
|
24
|
+
@table.select do |_, property|
|
25
|
+
args == args.select do |k, v|
|
26
|
+
property.send(k) == v
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def expected; select(presence: :expected); end
|
32
|
+
def permitted; select(presence: :permitted); end
|
33
|
+
def provided; select(presence: :provided); end
|
34
|
+
|
35
|
+
def all_properties
|
36
|
+
expected_properties +
|
37
|
+
permitted_properties +
|
38
|
+
provided_properties
|
39
|
+
end
|
40
|
+
|
41
|
+
def expected_properties; expected.keys; end
|
42
|
+
def permitted_properties; permitted.keys; end
|
43
|
+
def provided_properties; provided.keys; end
|
44
|
+
|
45
|
+
def expected_and_permitted_properties
|
46
|
+
expected_properties + permitted_properties
|
47
|
+
end
|
48
|
+
|
49
|
+
def undeclared_properties(context)
|
50
|
+
context.members.select do |attr|
|
51
|
+
!expected_and_permitted_properties.include?(attr)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def missing_properties(context)
|
56
|
+
expected_properties.select do |attr|
|
57
|
+
!context.members.include?(attr)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def default_for(property_name)
|
62
|
+
@table[property_name].default
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Troupe
|
2
|
+
class ContractViolation < StandardError
|
3
|
+
attr_reader :context, :property
|
4
|
+
|
5
|
+
def initialize(context=nil, opts={})
|
6
|
+
@context = context
|
7
|
+
@property = opts[:property]
|
8
|
+
@message = opts[:message]
|
9
|
+
super()
|
10
|
+
end
|
11
|
+
|
12
|
+
def message
|
13
|
+
@message || "Property '#{property}' violated the interactor's contract."
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/troupe.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'troupe/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "troupe"
|
8
|
+
spec.version = Troupe::VERSION
|
9
|
+
spec.authors = ["Jon Stokes"]
|
10
|
+
spec.email = ["jon@jonstokes.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{These (inter)actors have contracts.}
|
13
|
+
spec.description = %q{This gem layers a contract DSL onto the interactor gem.}
|
14
|
+
spec.homepage = "http://github.com/jonstokes/troupe"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
18
|
+
spec.bindir = "exe"
|
19
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
|
22
|
+
spec.add_dependency 'interactor', '~> 3.1'
|
23
|
+
|
24
|
+
spec.add_development_dependency "bundler", "~> 1.9"
|
25
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
26
|
+
end
|
metadata
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: troupe
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jon Stokes
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-04-23 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: interactor
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '3.1'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '3.1'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.9'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.9'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '10.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '10.0'
|
55
|
+
description: This gem layers a contract DSL onto the interactor gem.
|
56
|
+
email:
|
57
|
+
- jon@jonstokes.com
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- ".gitignore"
|
63
|
+
- ".rspec"
|
64
|
+
- ".travis.yml"
|
65
|
+
- Gemfile
|
66
|
+
- LICENSE
|
67
|
+
- LICENSE.txt
|
68
|
+
- README.md
|
69
|
+
- Rakefile
|
70
|
+
- bin/console
|
71
|
+
- bin/setup
|
72
|
+
- lib/troupe.rb
|
73
|
+
- lib/troupe/contract.rb
|
74
|
+
- lib/troupe/contract/property.rb
|
75
|
+
- lib/troupe/contract/property_table.rb
|
76
|
+
- lib/troupe/contract_violation.rb
|
77
|
+
- lib/troupe/version.rb
|
78
|
+
- troupe.gemspec
|
79
|
+
homepage: http://github.com/jonstokes/troupe
|
80
|
+
licenses:
|
81
|
+
- MIT
|
82
|
+
metadata: {}
|
83
|
+
post_install_message:
|
84
|
+
rdoc_options: []
|
85
|
+
require_paths:
|
86
|
+
- lib
|
87
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
88
|
+
requirements:
|
89
|
+
- - ">="
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
version: '0'
|
92
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
requirements: []
|
98
|
+
rubyforge_project:
|
99
|
+
rubygems_version: 2.4.6
|
100
|
+
signing_key:
|
101
|
+
specification_version: 4
|
102
|
+
summary: These (inter)actors have contracts.
|
103
|
+
test_files: []
|