troupe 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,3 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.2
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.com'
2
+
3
+ # Specify your gem's dependencies in troupe.gemspec
4
+ gemspec
5
+
6
+ group :test do
7
+ gem "rspec", "~> 3.1"
8
+ end
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
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
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
@@ -0,0 +1,3 @@
1
+ module Troupe
2
+ VERSION = "0.1.0"
3
+ 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: []