interactor-schema 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 1ecc8a0bdc552d23ab22db288cef8ba75f496eec
4
+ data.tar.gz: 332f9271bcde51e416b2e65fe9a1337c873d8a10
5
+ SHA512:
6
+ metadata.gz: 3390e8c315799c36e88f643cfaa2b5c11bf7bcc8b775e0b727ffff69580d0ad824f064a50f8221e3b01e07da34de9d3bcc58860dcd86506852913b6876432cad
7
+ data.tar.gz: 338f3d9000de50f1b1d97ea89947ed174911c6e6d51c6ca7946317378e4df71c760b68635f66c684810ae30912995967a26716db39918d69811a950133e8f5a8
@@ -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,3 @@
1
+ --color
2
+ --order random
3
+ --require spec_helper
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.3.1
5
+ before_install: gem install bundler -v 1.13.6
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in interactor-schema.gemspec
4
+ gemspec
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017 Bernardo Farah
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.
@@ -0,0 +1,175 @@
1
+ # Interactor::Schema
2
+
3
+ [![Build Status](https://img.shields.io/travis/berfarah/interactor-schema/master.svg?style=flat-square)](https://travis-ci.org/berfarah/interactor-schema)
4
+
5
+ Interactor::Schema provides an enforcable Interactor Context via a `schema`
6
+ class method.
7
+
8
+ This gem was born out of frustration. In using Interactor, I often wouldn't know
9
+ what methods would be defined on it. By forcing a schema upfront, and also
10
+ requiring methods in Interactor classes, we bring order to
11
+ `Interactor::Context`.
12
+
13
+ ## Installation
14
+
15
+ Put this in your application's Gemfile:
16
+
17
+ ```ruby
18
+ gem 'interactor'
19
+ gem 'interactor-schema'
20
+ ```
21
+
22
+ And then execute:
23
+
24
+ $ bundle
25
+
26
+ Or install it yourself as:
27
+
28
+ $ gem install interactor-schema
29
+
30
+ ## Usage
31
+
32
+ See [here](https://github.com/collectiveidea/interactor) for the official
33
+ Interactor documentation.
34
+
35
+
36
+ ### Setup
37
+
38
+ When you include `Interactor::Schema`, you are able to define a schema for valid
39
+ context methods using the `schema` DSL method.
40
+
41
+ ```rb
42
+ class SaveUser
43
+ include Interactor::Schema
44
+ schema :name
45
+ end
46
+ ```
47
+
48
+ ### Calling methods
49
+
50
+ When the attribute is part of the valid schema, you will have no problems
51
+ accessing it as usual:
52
+
53
+ ```rb
54
+ context = SaveUser.call(name: "Bob")
55
+ context.name
56
+ # => "Bob"
57
+ ```
58
+
59
+ For attributes outside the schema, you will get `NoMethodError`s. The same thing
60
+ applies when trying to set an attribute outside of the schema.
61
+
62
+ ```rb
63
+ context = SaveUser.call(age: 28)
64
+ context.age
65
+ # NoMethodError: undefined method 'age' for #<SaveUser:0x007f9521298b10>
66
+ ```
67
+
68
+ ### Use with Organizers
69
+
70
+ `Interactor::Schema` works great with `Interactor::Organizer`s!
71
+
72
+ ```rb
73
+ class SaveUser
74
+ include Interactor::Schema
75
+ include Interactor::Organizer
76
+
77
+ schema :name
78
+ organize ValidateInfo, SendMailer
79
+ end
80
+
81
+ class ValidateInfo
82
+ include Interactor
83
+
84
+ def call
85
+ # These will work, since name is in our schema
86
+ context.name
87
+ context.name = "Bob"
88
+
89
+ # These will fail with NoMethodErrors since age is not in our schema
90
+ context.age
91
+ context.age = 28
92
+ end
93
+ end
94
+ ```
95
+
96
+ The initial schema will be propagated through the entire chain, meaning no
97
+ attributes outside of `name` can be set. This is particularly helpful for
98
+ maintaining clarity of what information is being consumed by a chain of
99
+ `Interactor`s.
100
+
101
+ ### Multiple Schemas
102
+
103
+ In the case of multiple schemas being provided only the first one will be used
104
+ (others will be discarded). The idea is that you should know upfront what
105
+ attributes will be needed.
106
+
107
+ Modifying the schema in the middle of the chain could result in confusing
108
+ behavior.
109
+
110
+
111
+ ### Interactor Helper Methods
112
+
113
+ This gem comes with two helper methods - one that should help enforce context
114
+ cleanliness and one to delegate to the context.
115
+
116
+ #### require_in_context
117
+
118
+ `Interactor::require_in_context` ensures that the attributes provided as
119
+ arguments are present and not `nil?` when the service is called.
120
+
121
+ ```rb
122
+ class SaveUser
123
+ include Interactor
124
+ require_in_context :name
125
+ end
126
+ ```
127
+
128
+ This will fail:
129
+
130
+ ```rb
131
+ SaveUser.call
132
+ # => ArgumentError: Missing the following attributes in context: name
133
+ ```
134
+
135
+ Whereas this will work:
136
+
137
+ ```rb
138
+ SaveUser.call(name: "Bob")
139
+ # => #<Interactor::Context:0x007f9521298b10>
140
+ ```
141
+
142
+ #### delegate_to_context
143
+
144
+ `Interactor::delegate_to_context` creates a method that delegates to the
145
+ context.
146
+
147
+ ```rb
148
+ class SaveUser
149
+ include Interactor
150
+
151
+ delegate_to_context :name
152
+
153
+ # is equivalent to:
154
+
155
+ def name
156
+ context.name
157
+ end
158
+ end
159
+ ```
160
+
161
+ ## Development
162
+
163
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
164
+
165
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
166
+
167
+ ## Contributing
168
+
169
+ Bug reports and pull requests are welcome on GitHub at https://github.com/berfarah/interactor-schema.
170
+
171
+
172
+ ## License
173
+
174
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
175
+
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task default: :spec
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "interactor/schema"
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
+ require "pry"
10
+ Pry.start
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'interactor/schema/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "interactor-schema"
8
+ spec.version = Interactor::Schema::VERSION
9
+ spec.authors = ["Bernardo Farah"]
10
+ spec.email = ["ber@bernardo.me"]
11
+
12
+ spec.summary = "Enforce a schema with for Interactor Context"
13
+ spec.description = "Interactor::Schema provides an enforcable Interactor Context"
14
+ spec.homepage = "https://github.com/berfarah/interactor-schema"
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files`.split($/)
18
+ spec.test_files = spec.files.grep(/^spec/)
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "interactor", "~> 3.1"
22
+
23
+ spec.add_development_dependency "bundler", "~> 1.13"
24
+ spec.add_development_dependency "rake", "~> 10.0"
25
+ spec.add_development_dependency "rspec", "~> 3.1"
26
+ spec.add_development_dependency "pry", "~> 0.10"
27
+ end
@@ -0,0 +1,37 @@
1
+ require "interactor"
2
+ require "interactor/schema/context"
3
+ require "interactor/schema/context_extension"
4
+ require "interactor/schema/interactor_extension"
5
+ require "interactor/schema/version"
6
+
7
+ module Interactor
8
+ module Schema
9
+ # Internal: Install Interactor::Schema's behavior in the given class.
10
+ def self.included(base)
11
+ base.class_eval do
12
+ # Don't override methods when an Interactor has already been included
13
+ # For example, when Organizer has already been included
14
+ include Interactor unless base.ancestors.include? Interactor
15
+
16
+ extend ClassMethods
17
+ include InstanceMethods
18
+ end
19
+ end
20
+
21
+ module ClassMethods
22
+ def schema(*method_names)
23
+ @schema = method_names.flatten
24
+ end
25
+
26
+ def _schema
27
+ @schema ||= []
28
+ end
29
+ end
30
+
31
+ module InstanceMethods
32
+ def initialize(context = {})
33
+ @context = Interactor::Schema::Context.build(context, self.class._schema)
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,80 @@
1
+ module Interactor
2
+ module Schema
3
+ class Context
4
+ def self.build(context = {}, schema = [])
5
+ self === context ? context : new(schema).tap do |instance|
6
+ instance.assign(context)
7
+ end
8
+ end
9
+
10
+ def initialize(schema = [])
11
+ @schema = schema
12
+ @table = {}
13
+ define_schema_methods(schema)
14
+ end
15
+
16
+ def [](key)
17
+ @table[key]
18
+ end
19
+
20
+ def assign(context)
21
+ filtered_context = context.select { |k, _| _schema.include?(k) }
22
+ @table.merge!(filtered_context)
23
+ end
24
+
25
+ def success?
26
+ !failure?
27
+ end
28
+
29
+ def failure?
30
+ @failure || false
31
+ end
32
+
33
+ def fail!(context = {})
34
+ @table.merge!(context)
35
+ @failure = true
36
+ raise Interactor::Failure, self
37
+ end
38
+
39
+ def called!(interactor)
40
+ _called << interactor
41
+ end
42
+
43
+ def rollback!
44
+ return false if @rolled_back
45
+ _called.reverse_each(&:rollback)
46
+ @rolled_back = true
47
+ end
48
+
49
+ def _called
50
+ @called ||= []
51
+ end
52
+
53
+ def _schema
54
+ @schema
55
+ end
56
+
57
+ def to_h
58
+ @table
59
+ end
60
+
61
+ private
62
+
63
+ attr_reader :table
64
+
65
+ def define_schema_methods(schema)
66
+ singleton_class.class_exec(schema) do |names|
67
+ names.each do |name|
68
+ define_method(name) do
69
+ @table[name]
70
+ end
71
+
72
+ define_method("#{name}=") do |value|
73
+ @table[name] = value
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,10 @@
1
+ module Interactor
2
+ class Context
3
+ # Overriding build method to continue with a Schema Context
4
+ # when one is found
5
+ def self.build(context = {})
6
+ return context if context.is_a?(Interactor::Schema::Context)
7
+ self === context ? context : new(context)
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,34 @@
1
+ module Interactor
2
+ module ClassMethods
3
+ # Depends on Interactor::Hook
4
+ def require_in_context(*attributes)
5
+ before :require_in_context!
6
+ @required_in_context = attributes
7
+ end
8
+
9
+ def delegate_to_context(*attributes)
10
+ attributes.each do |attribute|
11
+ define_method(attribute) { context[attribute] }
12
+ end
13
+ end
14
+
15
+ attr_reader :required_in_context
16
+ end
17
+
18
+ private
19
+
20
+ def required_in_context
21
+ self.class.required_in_context
22
+ end
23
+
24
+ def require_in_context!
25
+ missing_attributes = required_in_context.select do |attribute|
26
+ context[attribute].nil?
27
+ end
28
+
29
+ return if missing_attributes.empty?
30
+ raise ArgumentError, <<-MESSAGE.strip
31
+ Missing the following attributes in context: #{missing_attributes.join(', ')}
32
+ MESSAGE
33
+ end
34
+ end
@@ -0,0 +1,5 @@
1
+ module Interactor
2
+ module Schema
3
+ VERSION = "0.2.0"
4
+ end
5
+ end
@@ -0,0 +1,210 @@
1
+ module Interactor::Schema
2
+ describe Context do
3
+ describe ".build" do
4
+ it "doesn't respond to methods when no schema is given" do
5
+ context = Context.build(foo: "bar")
6
+
7
+ expect(context).to be_a(Context)
8
+ expect { context.foo }.to raise_error(NoMethodError)
9
+ end
10
+
11
+ it "responds to methods when a schema is given" do
12
+ context = Context.build({ foo: "bar" }, [:foo])
13
+
14
+ expect(context).to be_a(Context)
15
+ expect(context.foo).to eq("bar")
16
+ end
17
+
18
+ it "only responds to methods defined in the schema" do
19
+ context = Context.build({ foo: "bar" }, [:baz])
20
+
21
+ expect(context).to be_a(Context)
22
+ expect { context.foo }.to raise_error(NoMethodError)
23
+ end
24
+ end
25
+
26
+ describe "#_schema" do
27
+ let(:context) { Context.build({}, schema) }
28
+ let(:schema) { [:foo] }
29
+
30
+ it "is empty by default" do
31
+ context = Context.build
32
+ expect(context._schema).to eq([])
33
+ end
34
+
35
+ it "holds onto the schema" do
36
+ expect(context._schema).to eq(schema)
37
+ end
38
+ end
39
+
40
+ describe "#to_h" do
41
+ let(:context) { Context.build({ foo: "one", bar: "two" }, [:foo]) }
42
+ it "only records allowed values" do
43
+ expect(context.to_h).to eq(foo: "one")
44
+ end
45
+ end
46
+
47
+ # NOTE:
48
+ # The tests below are copied from the original Interactor::Context
49
+ # The only thing modified was to add the context argument where needed.
50
+ #
51
+ # You can find them here:
52
+ # https://github.com/collectiveidea/interactor/blob/master/spec/interactor/context_spec.rb
53
+ describe ".build" do
54
+ it "builds an empty context if no hash is given" do
55
+ context = Context.build
56
+
57
+ expect(context).to be_a(Context)
58
+ expect(context.send(:table)).to eq({})
59
+ end
60
+
61
+ it "doesn't affect the original hash" do
62
+ hash = { foo: "bar" }
63
+ context = Context.build(hash, [:foo])
64
+
65
+ expect(context).to be_a(Context)
66
+ expect {
67
+ context.foo = "baz"
68
+ }.not_to change {
69
+ hash[:foo]
70
+ }
71
+ end
72
+
73
+ it "preserves an already built context" do
74
+ context1 = Context.build({ foo: "bar" }, [:foo])
75
+ context2 = Context.build(context1)
76
+
77
+ expect(context2).to be_a(Context)
78
+ expect {
79
+ context2.foo = "baz"
80
+ }.to change {
81
+ context1.foo
82
+ }.from("bar").to("baz")
83
+ end
84
+ end
85
+
86
+ describe "#success?" do
87
+ let(:context) { Context.build }
88
+
89
+ it "is true by default" do
90
+ expect(context.success?).to eq(true)
91
+ end
92
+ end
93
+
94
+ describe "#failure?" do
95
+ let(:context) { Context.build }
96
+
97
+ it "is false by default" do
98
+ expect(context.failure?).to eq(false)
99
+ end
100
+ end
101
+
102
+ describe "#fail!" do
103
+ let(:context) { Context.build({ foo: "bar" }, [:foo]) }
104
+
105
+ it "sets success to false" do
106
+ expect {
107
+ context.fail! rescue nil
108
+ }.to change {
109
+ context.success?
110
+ }.from(true).to(false)
111
+ end
112
+
113
+ it "sets failure to true" do
114
+ expect {
115
+ context.fail! rescue nil
116
+ }.to change {
117
+ context.failure?
118
+ }.from(false).to(true)
119
+ end
120
+
121
+ it "preserves failure" do
122
+ context.fail! rescue nil
123
+
124
+ expect {
125
+ context.fail! rescue nil
126
+ }.not_to change {
127
+ context.failure?
128
+ }
129
+ end
130
+
131
+ it "preserves the context" do
132
+ expect {
133
+ context.fail! rescue nil
134
+ }.not_to change {
135
+ context.foo
136
+ }
137
+ end
138
+
139
+ it "updates the context" do
140
+ expect {
141
+ context.fail!(foo: "baz") rescue nil
142
+ }.to change {
143
+ context.foo
144
+ }.from("bar").to("baz")
145
+ end
146
+
147
+ it "raises failure" do
148
+ expect {
149
+ context.fail!
150
+ }.to raise_error(Interactor::Failure)
151
+ end
152
+
153
+ it "makes the context available from the failure" do
154
+ begin
155
+ context.fail!
156
+ rescue Interactor::Failure => error
157
+ expect(error.context).to eq(context)
158
+ end
159
+ end
160
+ end
161
+
162
+ describe "#called!" do
163
+ let(:context) { Context.build }
164
+ let(:instance1) { double(:instance1) }
165
+ let(:instance2) { double(:instance2) }
166
+
167
+ it "appends to the internal list of called instances" do
168
+ expect {
169
+ context.called!(instance1)
170
+ context.called!(instance2)
171
+ }.to change {
172
+ context._called
173
+ }.from([]).to([instance1, instance2])
174
+ end
175
+ end
176
+
177
+ describe "#rollback!" do
178
+ let(:context) { Context.build }
179
+ let(:instance1) { double(:instance1) }
180
+ let(:instance2) { double(:instance2) }
181
+
182
+ before do
183
+ allow(context).to receive(:_called) { [instance1, instance2] }
184
+ end
185
+
186
+ it "rolls back each instance in reverse order" do
187
+ expect(instance2).to receive(:rollback).once.with(no_args).ordered
188
+ expect(instance1).to receive(:rollback).once.with(no_args).ordered
189
+
190
+ context.rollback!
191
+ end
192
+
193
+ it "ignores subsequent attempts" do
194
+ expect(instance2).to receive(:rollback).once
195
+ expect(instance1).to receive(:rollback).once
196
+
197
+ context.rollback!
198
+ context.rollback!
199
+ end
200
+ end
201
+
202
+ describe "#_called" do
203
+ let(:context) { Context.build }
204
+
205
+ it "is empty by default" do
206
+ expect(context._called).to eq([])
207
+ end
208
+ end
209
+ end
210
+ end
@@ -0,0 +1,41 @@
1
+ describe Interactor do
2
+ describe ".require_in_context" do
3
+ let(:interactor) do
4
+ Class.new do
5
+ include Interactor
6
+ require_in_context :foo
7
+ end
8
+ end
9
+
10
+ it "fails if the argument isn't passed in" do
11
+ expect { interactor.call }.to raise_error(ArgumentError)
12
+ end
13
+
14
+ it "fails if the argument is is nil" do
15
+ expect { interactor.call(foo: nil) }.to raise_error(ArgumentError)
16
+ end
17
+
18
+ it "succeeds if the argument is passed in" do
19
+ expect { interactor.call(foo: "bar") }.not_to raise_error
20
+ end
21
+ end
22
+
23
+ describe ".delegate_to_context" do
24
+ let(:interactor) do
25
+ Class.new do
26
+ include Interactor
27
+ delegate_to_context :foo
28
+
29
+ def call
30
+ foo
31
+ end
32
+ end
33
+ end
34
+ let(:context) { double("Context") }
35
+
36
+ it "delegates to the context" do
37
+ expect_any_instance_of(Interactor::Context).to receive(:[]).with(:foo).once
38
+ interactor.call
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,83 @@
1
+ module Interactor
2
+ describe Schema do
3
+ include_examples :lint
4
+
5
+ describe ".schema" do
6
+ context "with an Interactor" do
7
+ let(:interactor) do
8
+ Class.new do
9
+ include Interactor
10
+ include Interactor::Schema
11
+
12
+ schema :foo
13
+ end
14
+ end
15
+ let(:context) { double(:context) }
16
+
17
+ it "s schema is passed to its context" do
18
+ expect(Interactor::Schema::Context).to receive(:build).once.with({ foo: "bar" }, [:foo]) { context }
19
+
20
+ instance = interactor.new(foo: "bar")
21
+
22
+ expect(instance).to be_a(interactor)
23
+ expect(instance.context).to eq(context)
24
+ end
25
+ end
26
+
27
+ context "with an Interactor::Organizer" do
28
+ let(:interactor1) do
29
+ Class.new do
30
+ include Interactor
31
+
32
+ def call
33
+ context.baz = "yolo"
34
+ end
35
+ end
36
+ end
37
+
38
+ let(:interactor2) do
39
+ Class.new.send(:include, Interactor)
40
+ end
41
+
42
+ let(:organizer) do
43
+ # Need to cache these for some reason
44
+ klass_1 = interactor1
45
+ klass_2 = interactor2
46
+
47
+ Class.new do
48
+ include Interactor::Organizer
49
+ include Interactor::Schema
50
+
51
+ schema :foo, :baz
52
+ organize klass_1, klass_2
53
+ end
54
+ end
55
+
56
+ it "passes around its context" do
57
+ context = organizer.call(foo: "bar")
58
+
59
+ expect(context.foo).to eq("bar")
60
+ expect(context.baz).to eq("yolo")
61
+ end
62
+
63
+ context "when assigning an attribute not available in the schema" do
64
+ let(:interactor2) do
65
+ Class.new do
66
+ include Interactor
67
+
68
+ def call
69
+ context.undefined = "hi"
70
+ end
71
+ end
72
+ end
73
+
74
+ it "raises a NoMethodError" do
75
+ expect {
76
+ organizer.call(foo: "bar")
77
+ }.to raise_error(NoMethodError)
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,4 @@
1
+ # $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
2
+ require 'interactor/schema'
3
+
4
+ Dir[File.expand_path("../support/*.rb", __FILE__)].each { |f| require f }
@@ -0,0 +1,138 @@
1
+ # NOTE:
2
+ # Copied directly from the official interactor repo:
3
+ # https://github.com/collectiveidea/interactor/blob/master/spec/support/lint.rb
4
+ shared_examples :lint do
5
+ let(:interactor) { Class.new.send(:include, described_class) }
6
+
7
+ describe ".call" do
8
+ let(:context) { double(:context) }
9
+ let(:instance) { double(:instance, context: context) }
10
+
11
+ it "calls an instance with the given context" do
12
+ expect(interactor).to receive(:new).once.with(foo: "bar") { instance }
13
+ expect(instance).to receive(:run).once.with(no_args)
14
+
15
+ expect(interactor.call(foo: "bar")).to eq(context)
16
+ end
17
+
18
+ it "provides a blank context if none is given" do
19
+ expect(interactor).to receive(:new).once.with({}) { instance }
20
+ expect(instance).to receive(:run).once.with(no_args)
21
+
22
+ expect(interactor.call).to eq(context)
23
+ end
24
+ end
25
+
26
+ describe ".call!" do
27
+ let(:context) { double(:context) }
28
+ let(:instance) { double(:instance, context: context) }
29
+
30
+ it "calls an instance with the given context" do
31
+ expect(interactor).to receive(:new).once.with(foo: "bar") { instance }
32
+ expect(instance).to receive(:run!).once.with(no_args)
33
+
34
+ expect(interactor.call!(foo: "bar")).to eq(context)
35
+ end
36
+
37
+ it "provides a blank context if none is given" do
38
+ expect(interactor).to receive(:new).once.with({}) { instance }
39
+ expect(instance).to receive(:run!).once.with(no_args)
40
+
41
+ expect(interactor.call!).to eq(context)
42
+ end
43
+ end
44
+
45
+ describe ".new" do
46
+ let(:context) { double(:context) }
47
+
48
+ it "initializes a context" do
49
+ expect(Interactor::Schema::Context).to receive(:build).once.with({ foo: "bar" }, []) { context }
50
+
51
+ instance = interactor.new(foo: "bar")
52
+
53
+ expect(instance).to be_a(interactor)
54
+ expect(instance.context).to eq(context)
55
+ end
56
+
57
+ it "initializes a blank context if none is given" do
58
+ expect(Interactor::Schema::Context).to receive(:build).once.with({}, []) { context }
59
+
60
+ instance = interactor.new
61
+
62
+ expect(instance).to be_a(interactor)
63
+ expect(instance.context).to eq(context)
64
+ end
65
+ end
66
+
67
+ describe "#run" do
68
+ let(:instance) { interactor.new }
69
+
70
+ it "runs the interactor" do
71
+ expect(instance).to receive(:run!).once.with(no_args)
72
+
73
+ instance.run
74
+ end
75
+
76
+ it "rescues failure" do
77
+ expect(instance).to receive(:run!).and_raise(Interactor::Failure)
78
+
79
+ expect {
80
+ instance.run
81
+ }.not_to raise_error
82
+ end
83
+
84
+ it "raises other errors" do
85
+ expect(instance).to receive(:run!).and_raise("foo")
86
+
87
+ expect {
88
+ instance.run
89
+ }.to raise_error("foo")
90
+ end
91
+ end
92
+
93
+ describe "#run!" do
94
+ let(:instance) { interactor.new }
95
+
96
+ it "calls the interactor" do
97
+ expect(instance).to receive(:call).once.with(no_args)
98
+
99
+ instance.run!
100
+ end
101
+
102
+ it "raises failure" do
103
+ expect(instance).to receive(:run!).and_raise(Interactor::Failure)
104
+
105
+ expect {
106
+ instance.run!
107
+ }.to raise_error(Interactor::Failure)
108
+ end
109
+
110
+ it "raises other errors" do
111
+ expect(instance).to receive(:run!).and_raise("foo")
112
+
113
+ expect {
114
+ instance.run
115
+ }.to raise_error("foo")
116
+ end
117
+ end
118
+
119
+ describe "#call" do
120
+ let(:instance) { interactor.new }
121
+
122
+ it "exists" do
123
+ expect(instance).to respond_to(:call)
124
+ expect { instance.call }.not_to raise_error
125
+ expect { instance.method(:call) }.not_to raise_error
126
+ end
127
+ end
128
+
129
+ describe "#rollback" do
130
+ let(:instance) { interactor.new }
131
+
132
+ it "exists" do
133
+ expect(instance).to respond_to(:rollback)
134
+ expect { instance.rollback }.not_to raise_error
135
+ expect { instance.method(:rollback) }.not_to raise_error
136
+ end
137
+ end
138
+ end
metadata ADDED
@@ -0,0 +1,139 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: interactor-schema
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Bernardo Farah
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-01-31 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.13'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.13'
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
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.1'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.1'
69
+ - !ruby/object:Gem::Dependency
70
+ name: pry
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.10'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.10'
83
+ description: Interactor::Schema provides an enforcable Interactor Context
84
+ email:
85
+ - ber@bernardo.me
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - ".gitignore"
91
+ - ".rspec"
92
+ - ".travis.yml"
93
+ - Gemfile
94
+ - LICENSE.txt
95
+ - README.md
96
+ - Rakefile
97
+ - bin/console
98
+ - bin/setup
99
+ - interactor-schema.gemspec
100
+ - lib/interactor/schema.rb
101
+ - lib/interactor/schema/context.rb
102
+ - lib/interactor/schema/context_extension.rb
103
+ - lib/interactor/schema/interactor_extension.rb
104
+ - lib/interactor/schema/version.rb
105
+ - spec/interactor/schema/context_spec.rb
106
+ - spec/interactor/schema/interactor_extension_spec.rb
107
+ - spec/interactor/schema_spec.rb
108
+ - spec/spec_helper.rb
109
+ - spec/support/lint.rb
110
+ homepage: https://github.com/berfarah/interactor-schema
111
+ licenses:
112
+ - MIT
113
+ metadata: {}
114
+ post_install_message:
115
+ rdoc_options: []
116
+ require_paths:
117
+ - lib
118
+ required_ruby_version: !ruby/object:Gem::Requirement
119
+ requirements:
120
+ - - ">="
121
+ - !ruby/object:Gem::Version
122
+ version: '0'
123
+ required_rubygems_version: !ruby/object:Gem::Requirement
124
+ requirements:
125
+ - - ">="
126
+ - !ruby/object:Gem::Version
127
+ version: '0'
128
+ requirements: []
129
+ rubyforge_project:
130
+ rubygems_version: 2.5.1
131
+ signing_key:
132
+ specification_version: 4
133
+ summary: Enforce a schema with for Interactor Context
134
+ test_files:
135
+ - spec/interactor/schema/context_spec.rb
136
+ - spec/interactor/schema/interactor_extension_spec.rb
137
+ - spec/interactor/schema_spec.rb
138
+ - spec/spec_helper.rb
139
+ - spec/support/lint.rb