interactor-schema 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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