activeinteractor 1.0.0.beta.5 → 1.0.0.beta.6

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4d3d5616823da9559ae98eab086893b6daf7b9030b6baa2cf1cf29c0bc5b4ea3
4
- data.tar.gz: e97629bee106e82dc438c227354f9ef598c8c1945f8f28a0a03e99e012e86ece
3
+ metadata.gz: 2607fe1a40ac6e082fa6d3bb2ffb59c0987226c8e88a01cfd9f66329bf2439d3
4
+ data.tar.gz: 2fc83930cd1bc7f011e63b78028d0ba658f29dcbfc9001469b5168b15a6c25de
5
5
  SHA512:
6
- metadata.gz: 6f0b4ebbf1e49d32536d07b52e4d92608df89a4413a215edb1c4f330da2ad5f4e63b6c1e7eff908bd93f74ecb42f5a7e5a829df3ddab86d4aa739c1dfbf0f89c
7
- data.tar.gz: 2666da64595c132aa988c3b7818c4bee589935d826a4e9d480ab5467a31e9c45106f6ca1aaee4b4a0069f95c411c420472535a723409d9f369b6c5c239bacea2
6
+ metadata.gz: 1a6d8e00458fce9b81929008eadd2a8d0c83a1dde70c0ca4cd65130fb8d3ab6b51a1c123db707baa777af618fb8c86924a067b5eb8c3baba6463e095b3abd21c
7
+ data.tar.gz: 1e9e136432675755607b891c54d9a2d50cfbd67b79ab17bd48e63981331da099a223009b3e40d8067b05049a00258cac406190adf221cf178df2d4be47d63cc5
data/CHANGELOG.md CHANGED
@@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning].
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [v1.0.0-beta.6] - 2020-01-21
11
+
12
+ ### Added
13
+
14
+ - [#124] `ActiveInteractor::Context::Status`
15
+ - [#124] `ActiveInteractor::Rails::ActiveRecord`
16
+
17
+ ### Changed
18
+
19
+ - [#124] Abstracted status methods from `ActiveInteractor::Context::Base`
20
+ into `ActiveInteractor::Context::Status`
21
+
10
22
  ## [v1.0.0-beta.5] - 2020-01-21
11
23
 
12
24
  ### Added
@@ -182,7 +194,8 @@ and this project adheres to [Semantic Versioning].
182
194
 
183
195
  <!-- versions -->
184
196
 
185
- [Unreleased]: https://github.com/aaronmallen/activeinteractor/compare/v1.0.0-beta.5...HEAD
197
+ [Unreleased]: https://github.com/aaronmallen/activeinteractor/compare/v1.0.0-beta.6...HEAD
198
+ [v1.0.0-beta.6]: https://github.com/aaronmallen/activeinteractor/compare/v1.0.0-beta.5...v1.0.0-beta.6
186
199
  [v1.0.0-beta.5]: https://github.com/aaronmallen/activeinteractor/compare/v1.0.0-beta.4...v1.0.0-beta.5
187
200
  [v1.0.0-beta.4]: https://github.com/aaronmallen/activeinteractor/compare/v1.0.0-beta.3...v1.0.0-beta.4
188
201
  [v1.0.0-beta.3]: https://github.com/aaronmallen/activeinteractor/compare/v1.0.0-beta.2...v1.0.0-beta.3
@@ -218,6 +231,7 @@ and this project adheres to [Semantic Versioning].
218
231
  [#105]: https://github.com/aaronmallen/activeinteractor/pull/105
219
232
  [#109]: https://github.com/aaronmallen/activeinteractor/pull/109
220
233
  [#110]: https://github.com/aaronmallen/activeinteractor/pull/110
221
- [#114]:https://github.com/aaronmallen/activeinteractor/pull/114
234
+ [#114]: https://github.com/aaronmallen/activeinteractor/pull/114
222
235
  [#115]: https://github.com/aaronmallen/activeinteractor/pull/115
223
236
  [#122]: https://github.com/aaronmallen/activeinteractor/pull/122
237
+ [#124]: https://github.com/aaronmallen/activeinteractor/pull/124
data/README.md CHANGED
@@ -37,6 +37,8 @@ see [v0.1.7](https://github.com/aaronmallen/activeinteractor/tree/0-1-stable)**
37
37
  * [Rollback Callbacks](#rollback-callbacks)
38
38
  * [Organizer Callbacks](#organizer-callbacks)
39
39
  * [Working With Rails](#working-with-rails)
40
+ * [Generators](#generators)
41
+ * [ActiveRecord Helper Methods](#activerecord-helper-methods)
40
42
  * [Development](#development)
41
43
  * [Contributing](#contributing)
42
44
  * [Acknowledgements](#acknowledgements)
@@ -49,7 +51,7 @@ see [v0.1.7](https://github.com/aaronmallen/activeinteractor/tree/0-1-stable)**
49
51
  Add this line to your application's Gemfile:
50
52
 
51
53
  ```ruby
52
- gem 'activeinteractor', '~> 1.0.0.beta.5'
54
+ gem 'activeinteractor', '~> 1.0.0.beta.6'
53
55
  ```
54
56
 
55
57
  And then execute:
@@ -835,7 +837,10 @@ application (defaults to 'interactors').
835
837
  This will create an initializer a some new classes `ApplicationInteractor`, `ApplicationOrganizer` and
836
838
  `ApplicationContext` in the `app/<directory>` directory.
837
839
 
838
- You can then automatically generate interactors, organizers, and contexts with:
840
+ ### Generators
841
+
842
+ ActiveInteractor comes bundled with some rails generators to automatically generate interactors,
843
+ organizers, and contexts with:
839
844
 
840
845
  ```bash
841
846
  rails generate interactor MyInteractor
@@ -851,6 +856,32 @@ rails generate interactor:context MyContext
851
856
 
852
857
  These generators will automatically create the approriate classes and matching spec or test files.
853
858
 
859
+ ### ActiveRecord Helper Methods
860
+
861
+ In some instances you may want to use an `ActiveRecord` model as a context for an interactor. You can
862
+ do this by calling the `acts_as_context` method on any `ActiveRecord` model, and then simply call the
863
+ `contextualize_with` method on your interactor or organizer to point it to the approriate class.
864
+
865
+ ```ruby
866
+ # app/models/user
867
+ class User < ApplicationRecord
868
+ acts_as_context
869
+ end
870
+
871
+ # app/interactors/create_user
872
+ class CreateUser < ApplicationInteractor
873
+ contextualize_with :user
874
+
875
+ def perform
876
+ context.email&.downcase!
877
+ context.save
878
+ end
879
+ end
880
+
881
+ CreateUser.perform(email: 'HELLO@AARONMALLEN.ME')
882
+ #=> <#User id=1 email='hello@aaronmallen.me'>
883
+ ```
884
+
854
885
  ## Development
855
886
 
856
887
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests.
@@ -14,6 +14,7 @@ module ActiveInteractor
14
14
  class Base < OpenStruct
15
15
  include ActiveModel::Validations
16
16
  include Attributes
17
+ include Status
17
18
 
18
19
  # @param context [Hash|Context::Base] attributes to assign to the context
19
20
  # @return [Context::Base] a new instance of {Context::Base}
@@ -29,58 +30,6 @@ module ActiveInteractor
29
30
  # https://github.com/rails/rails/blob/master/activemodel/lib/active_model/validations.rb#L305
30
31
  # ActiveModel::Validations#valid?
31
32
 
32
- # @api private
33
- # Track that an Interactor has been called. The {#called!} method
34
- # is used by the interactor being invoked with this context. After an
35
- # interactor is successfully called, the interactor instance is tracked in
36
- # the context for the purpose of potential future rollback
37
- # @param interactor [ActiveInteractor::Base] the called interactor
38
- # @return [Array<ActiveInteractor::Base>] all called interactors
39
- def called!(interactor)
40
- _called << interactor
41
- end
42
-
43
- # Fail the context instance. Failing a context raises an error
44
- # that may be rescued by the calling interactor. The context is also flagged
45
- # as having failed
46
- #
47
- # @example Fail an interactor context
48
- # class MyInteractor < ActiveInteractor::Base
49
- # def perform
50
- # context.fail!
51
- # end
52
- # end
53
- #
54
- # MyInteractor.perform!
55
- # #=> ActiveInteractor::Error::ContextFailure: <#MyInteractor::Context>
56
- # @param errors [ActiveModel::Errors|nil] errors to add to the context on failure
57
- # @see https://api.rubyonrails.org/classes/ActiveModel/Errors.html ActiveModel::Errors
58
- # @raise [Error::ContextFailure]
59
- def fail!(errors = nil)
60
- merge_errors!(errors) if errors
61
- @_failed = true
62
- raise Error::ContextFailure, self
63
- end
64
-
65
- # Whether the context instance has failed. By default, a new
66
- # context is successful and only changes when explicitly failed
67
- # @note The {#failure?} method is the inverse of the {#success?} method
68
- # @example Check if a context has failed
69
- # class MyInteractor < ActiveInteractor::Base
70
- # def perform; end
71
- # end
72
- #
73
- # result = MyInteractor.perform
74
- # #=> <#MyInteractor::Context>
75
- #
76
- # result.failure?
77
- # #=> false
78
- # @return [Boolean] `false` by default or `true` if failed
79
- def failure?
80
- @_failed || false
81
- end
82
- alias fail? failure?
83
-
84
33
  # Merge an instance of context or a hash into an existing context
85
34
  # @since 1.0.0
86
35
  # @example
@@ -112,75 +61,8 @@ module ActiveInteractor
112
61
  self
113
62
  end
114
63
 
115
- # Roll back an interactor context. Any interactors to which this
116
- # context has been passed and which have been successfully called are asked
117
- # to roll themselves back by invoking their
118
- # {ActiveInteractor::Base#rollback} instance methods.
119
- # @example Rollback an interactor's context
120
- # class MyInteractor < ActiveInteractor::Base
121
- # def perform
122
- # context.fail!
123
- # end
124
- #
125
- # def rollback
126
- # context.user&.destroy
127
- # end
128
- # end
129
- #
130
- # user = User.create
131
- # #=> <#User>
132
- #
133
- # result = MyInteractor.perform(user: user)
134
- # #=> <#MyInteractor::Context user=<#User>>
135
- #
136
- # result.user.destroyed?
137
- # #=> true
138
- # @return [Boolean] `true` if rolled back successfully or `false` if already
139
- # rolled back
140
- def rollback!
141
- return false if @_rolled_back
142
-
143
- _called.reverse_each(&:rollback)
144
- @_rolled_back = true
145
- end
146
-
147
- # Whether the context instance is successful. By default, a new
148
- # context is successful and only changes when explicitly failed
149
- # @note the {#success?} method is the inverse of the {#failure?} method
150
- # @example Check if a context is successful
151
- # class MyInteractor < ActiveInteractor::Base
152
- # def perform; end
153
- # end
154
- #
155
- # result = MyInteractor.perform
156
- # #=> <#MyInteractor::Context>
157
- #
158
- # result.success?
159
- # #=> true
160
- # @return [Boolean] `true` by default or `false` if failed
161
- def success?
162
- !failure?
163
- end
164
- alias successful? success?
165
-
166
64
  private
167
65
 
168
- def _called
169
- @_called ||= []
170
- end
171
-
172
- def copy_called!(context)
173
- value = context.instance_variable_get('@_called') || []
174
- instance_variable_set('@_called', value)
175
- end
176
-
177
- def copy_flags!(context)
178
- %w[_failed _rolled_back].each do |flag|
179
- value = context.instance_variable_get("@#{flag}")
180
- instance_variable_set("@#{flag}", value)
181
- end
182
- end
183
-
184
66
  def merge_errors!(errors)
185
67
  if errors.is_a? String
186
68
  self.errors.add(:context, errors)
@@ -0,0 +1,131 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveInteractor
4
+ module Context
5
+ # Context status methods included by all {Context::Base}
6
+ # @author Aaron Allen <hello@aaronmallen.me>
7
+ # @since 1.0.0
8
+ module Status
9
+ # @api private
10
+ # Track that an Interactor has been called. The {#called!} method
11
+ # is used by the interactor being invoked with this context. After an
12
+ # interactor is successfully called, the interactor instance is tracked in
13
+ # the context for the purpose of potential future rollback
14
+ # @param interactor [ActiveInteractor::Base] the called interactor
15
+ # @return [Array<ActiveInteractor::Base>] all called interactors
16
+ def called!(interactor)
17
+ _called << interactor
18
+ end
19
+
20
+ # Fail the context instance. Failing a context raises an error
21
+ # that may be rescued by the calling interactor. The context is also flagged
22
+ # as having failed
23
+ #
24
+ # @example Fail an interactor context
25
+ # class MyInteractor < ActiveInteractor::Base
26
+ # def perform
27
+ # context.fail!
28
+ # end
29
+ # end
30
+ #
31
+ # MyInteractor.perform!
32
+ # #=> ActiveInteractor::Error::ContextFailure: <#MyInteractor::Context>
33
+ # @param errors [ActiveModel::Errors|nil] errors to add to the context on failure
34
+ # @see https://api.rubyonrails.org/classes/ActiveModel/Errors.html ActiveModel::Errors
35
+ # @raise [Error::ContextFailure]
36
+ def fail!(errors = nil)
37
+ merge_errors!(errors) if errors
38
+ @_failed = true
39
+ raise Error::ContextFailure, self
40
+ end
41
+
42
+ # Whether the context instance has failed. By default, a new
43
+ # context is successful and only changes when explicitly failed
44
+ # @note The {#failure?} method is the inverse of the {#success?} method
45
+ # @example Check if a context has failed
46
+ # class MyInteractor < ActiveInteractor::Base
47
+ # def perform; end
48
+ # end
49
+ #
50
+ # result = MyInteractor.perform
51
+ # #=> <#MyInteractor::Context>
52
+ #
53
+ # result.failure?
54
+ # #=> false
55
+ # @return [Boolean] `false` by default or `true` if failed
56
+ def failure?
57
+ @_failed || false
58
+ end
59
+ alias fail? failure?
60
+
61
+ # Roll back an interactor context. Any interactors to which this
62
+ # context has been passed and which have been successfully called are asked
63
+ # to roll themselves back by invoking their
64
+ # {ActiveInteractor::Base#rollback} instance methods.
65
+ # @example Rollback an interactor's context
66
+ # class MyInteractor < ActiveInteractor::Base
67
+ # def perform
68
+ # context.fail!
69
+ # end
70
+ #
71
+ # def rollback
72
+ # context.user&.destroy
73
+ # end
74
+ # end
75
+ #
76
+ # user = User.create
77
+ # #=> <#User>
78
+ #
79
+ # result = MyInteractor.perform(user: user)
80
+ # #=> <#MyInteractor::Context user=<#User>>
81
+ #
82
+ # result.user.destroyed?
83
+ # #=> true
84
+ # @return [Boolean] `true` if rolled back successfully or `false` if already
85
+ # rolled back
86
+ def rollback!
87
+ return false if @_rolled_back
88
+
89
+ _called.reverse_each(&:rollback)
90
+ @_rolled_back = true
91
+ end
92
+
93
+ # Whether the context instance is successful. By default, a new
94
+ # context is successful and only changes when explicitly failed
95
+ # @note the {#success?} method is the inverse of the {#failure?} method
96
+ # @example Check if a context is successful
97
+ # class MyInteractor < ActiveInteractor::Base
98
+ # def perform; end
99
+ # end
100
+ #
101
+ # result = MyInteractor.perform
102
+ # #=> <#MyInteractor::Context>
103
+ #
104
+ # result.success?
105
+ # #=> true
106
+ # @return [Boolean] `true` by default or `false` if failed
107
+ def success?
108
+ !failure?
109
+ end
110
+ alias successful? success?
111
+
112
+ private
113
+
114
+ def _called
115
+ @_called ||= []
116
+ end
117
+
118
+ def copy_called!(context)
119
+ value = context.instance_variable_get('@_called') || []
120
+ instance_variable_set('@_called', value)
121
+ end
122
+
123
+ def copy_flags!(context)
124
+ %w[_failed _rolled_back].each do |flag|
125
+ value = context.instance_variable_get("@#{flag}")
126
+ instance_variable_set("@#{flag}", value)
127
+ end
128
+ end
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveInteractor
4
+ module Rails
5
+ # ActiveRecord helper methods
6
+ # @author Aaron Allen <hello@aaronmallen.me>
7
+ # @since 1.0.0
8
+ module ActiveRecord
9
+ # Include ActiveRecord helper methods on load
10
+ def self.include_helpers
11
+ ActiveSupport.on_load(:active_record_base) do
12
+ extend ClassMethods
13
+ end
14
+ end
15
+
16
+ # ActiveRecord class helper methods
17
+ # @author Aaron Allen <hello@aaronmallen.me>
18
+ # @since 1.0.0
19
+ module ClassMethods
20
+ # Include {Context::Status} methods
21
+ def acts_as_context
22
+ class_eval do
23
+ include InstanceMethods
24
+ include ActiveInteractor::Context::Status
25
+ delegate :each_pair, to: :attributes
26
+ end
27
+ end
28
+ end
29
+
30
+ module InstanceMethods
31
+ # Override ActiveRecord's initialize method to ensure
32
+ # context flags are copied to the new instance
33
+ # @param context [Hash|nil] attributes to assign to the class
34
+ # @param options [Hash|nil] options for the class
35
+ def initialize(context = nil, options = {})
36
+ copy_flags!(context) if context
37
+ copy_called!(context) if context
38
+ attributes = context.to_h if context
39
+ super(attributes, options)
40
+ end
41
+
42
+ # Merge an ActiveRecord::Base instance and ensure
43
+ # context flags are copied to the new instance
44
+ # @param context [*] the instance to be merged
45
+ # @return [*] the merged instance
46
+ def merge!(context)
47
+ copy_flags!(context)
48
+ context.each_pair do |key, value|
49
+ self[key] = value
50
+ end
51
+ self
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -14,6 +14,10 @@ module ActiveInteractor
14
14
 
15
15
  config.eager_load_namespaces << ActiveInteractor
16
16
 
17
+ initializer 'active_interactor.active_record_helpers' do
18
+ ActiveInteractor::Rails::ActiveRecord.include_helpers
19
+ end
20
+
17
21
  config.to_prepare do
18
22
  ActiveInteractor.configure do |c|
19
23
  c.logger = ::Rails.logger
@@ -1,8 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rails'
4
-
5
3
  require 'active_interactor'
4
+ require 'active_interactor/rails/active_record'
6
5
  require 'active_interactor/rails/config'
7
6
  require 'active_interactor/rails/railtie'
8
7
 
@@ -2,5 +2,5 @@
2
2
 
3
3
  module ActiveInteractor
4
4
  # @return [String] the ActiveInteractor version
5
- VERSION = '1.0.0.beta.5'
5
+ VERSION = '1.0.0.beta.6'
6
6
  end
@@ -28,7 +28,18 @@ module ActiveInteractor
28
28
  extend ActiveSupport::Autoload
29
29
 
30
30
  autoload :Base
31
- autoload :Context
31
+
32
+ # ActiveInteractor::Context classes
33
+ # @author Aaron Allen <hello@aaronmallen.me>
34
+ # @since 0.0.1
35
+ module Context
36
+ extend ActiveSupport::Autoload
37
+
38
+ autoload :Base
39
+ autoload :Loader
40
+ autoload :Status
41
+ end
42
+
32
43
  autoload :Organizer
33
44
 
34
45
  eager_autoload do
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+ begin
5
+ require 'active_interactor/rails'
6
+
7
+ RSpec.describe ActiveInteractor::Rails::ActiveRecord do
8
+ describe '.include_helpers' do
9
+ subject { described_class.include_helpers }
10
+ let!(:active_record_mock) { build_class('ActiveRecordBaseMock') }
11
+
12
+ it 'is expected to extend ClassMethods' do
13
+ subject
14
+ ActiveSupport.run_load_hooks(:active_record_base, active_record_mock)
15
+ expect(active_record_mock).to respond_to :acts_as_context
16
+ end
17
+ end
18
+ end
19
+ rescue LoadError
20
+ RSpec.describe 'ActiveInteractor::Rails::ActiveRecord' do
21
+ pending 'Rails not found skipping specs...'
22
+ end
23
+ end
@@ -1,23 +1,29 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'spec_helper'
4
- require 'active_interactor/rails'
4
+ begin
5
+ require 'active_interactor/rails'
5
6
 
6
- RSpec.describe ActiveInteractor::Rails::Config do
7
- subject { described_class.new }
7
+ RSpec.describe ActiveInteractor::Rails::Config do
8
+ subject { described_class.new }
8
9
 
9
- it { is_expected.to respond_to :directory }
10
- it { is_expected.to respond_to :generate_context_classes }
10
+ it { is_expected.to respond_to :directory }
11
+ it { is_expected.to respond_to :generate_context_classes }
11
12
 
12
- describe '.defaults' do
13
- subject { described_class.defaults }
13
+ describe '.defaults' do
14
+ subject { described_class.defaults }
14
15
 
15
- it 'is expected to have attributes :directory => "interactors"' do
16
- expect(subject[:directory]).to eq 'interactors'
17
- end
16
+ it 'is expected to have attributes :directory => "interactors"' do
17
+ expect(subject[:directory]).to eq 'interactors'
18
+ end
18
19
 
19
- it 'is expected to have attributes :generate_context_classes => true' do
20
- expect(subject[:generate_context_classes]).to eq true
20
+ it 'is expected to have attributes :generate_context_classes => true' do
21
+ expect(subject[:generate_context_classes]).to eq true
22
+ end
21
23
  end
22
24
  end
25
+ rescue LoadError
26
+ RSpec.describe 'ActiveInteractor::Rails::Config' do
27
+ pending 'Rails not found skipping specs...'
28
+ end
23
29
  end
@@ -1,18 +1,24 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'spec_helper'
4
- require 'active_interactor/rails'
4
+ begin
5
+ require 'active_interactor/rails'
5
6
 
6
- RSpec.describe ActiveInteractor::Rails do
7
- describe '.config' do
8
- subject { described_class.config }
7
+ RSpec.describe ActiveInteractor::Rails do
8
+ describe '.config' do
9
+ subject { described_class.config }
9
10
 
10
- it { is_expected.to be_a ActiveInteractor::Rails::Config }
11
- end
11
+ it { is_expected.to be_a ActiveInteractor::Rails::Config }
12
+ end
12
13
 
13
- describe '.configure' do
14
- it 'is expected to yield config' do
15
- expect { |b| described_class.configure(&b) }.to yield_control
14
+ describe '.configure' do
15
+ it 'is expected to yield config' do
16
+ expect { |b| described_class.configure(&b) }.to yield_control
17
+ end
16
18
  end
17
19
  end
20
+ rescue LoadError
21
+ RSpec.describe 'ActiveInteractor::Rails' do
22
+ pending 'Rails not found skipping specs...'
23
+ end
18
24
  end
@@ -0,0 +1,369 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+ begin
5
+ require 'active_interactor/rails'
6
+
7
+ RSpec.describe 'ActiveRecord Integration', type: :integration do
8
+ let!(:active_record_base_mock) do
9
+ build_class('ActiveRecordBaseMock') do
10
+ def self.attr_accessor(*attributes)
11
+ attribute_keys.concat(attributes.map(&:to_sym))
12
+ super
13
+ end
14
+
15
+ def self.attribute_keys
16
+ @attribute_keys ||= []
17
+ end
18
+
19
+ def initialize(attributes = nil, _options = {})
20
+ (attributes || {}).each do |key, value|
21
+ instance_variable_set("@#{key}", value)
22
+ end
23
+ end
24
+
25
+ def [](name)
26
+ instance_variable_get("@#{name}")
27
+ end
28
+
29
+ def []=(name, value)
30
+ instance_variable_set("@#{name}", value)
31
+ end
32
+
33
+ def attributes
34
+ self.class.attribute_keys.each_with_object({}) do |key, hash|
35
+ hash[key] = instance_variable_get("@#{key}")
36
+ end
37
+ end
38
+
39
+ def to_h
40
+ attributes.to_h
41
+ end
42
+ end
43
+ end
44
+
45
+ context 'after ActiveInteractor::Rails::ActiveRecord.include_helpers has been invoked' do
46
+ before { ActiveInteractor::Rails::ActiveRecord.include_helpers }
47
+
48
+ context 'after ActiveSupport.run_load_hooks has been invoked with :active_record_base' do
49
+ before { ActiveSupport.run_load_hooks(:active_record_base, active_record_base_mock) }
50
+
51
+ describe 'an ActiveRecord model class with .acts_as_context' do
52
+ let(:model_mock) do
53
+ build_class('ModelMock', active_record_base_mock) do
54
+ attr_accessor :foo
55
+ acts_as_context
56
+ end
57
+ end
58
+
59
+ describe 'as an instance' do
60
+ subject { model_mock.new }
61
+
62
+ it { is_expected.to respond_to :called! }
63
+ it { is_expected.to respond_to :fail! }
64
+ it { is_expected.to respond_to :fail? }
65
+ it { is_expected.to respond_to :failure? }
66
+ it { is_expected.to respond_to :merge! }
67
+ it { is_expected.to respond_to :rollback! }
68
+ it { is_expected.to respond_to :success? }
69
+ it { is_expected.to respond_to :successful? }
70
+ end
71
+
72
+ describe '.new' do
73
+ subject { model_mock.new(attributes) }
74
+ let(:attributes) { nil }
75
+
76
+ it 'is expected not to receive #copy_flags!' do
77
+ expect_any_instance_of(model_mock).not_to receive(:copy_flags!)
78
+ subject
79
+ end
80
+
81
+ it 'is expected not to receive #copy_called!' do
82
+ expect_any_instance_of(model_mock).not_to receive(:copy_called!)
83
+ subject
84
+ end
85
+
86
+ it 'is expected to invoke super on parent class with nil attributes' do
87
+ expect(active_record_base_mock).to receive(:new).with(nil)
88
+ subject
89
+ end
90
+
91
+ context 'with attributes { :foo => "foo" }' do
92
+ let(:attributes) { { foo: 'foo' } }
93
+
94
+ it { expect { subject }.not_to raise_error }
95
+
96
+ it 'is expected to receive #copy_flags!' do
97
+ expect_any_instance_of(model_mock).to receive(:copy_flags!).with(foo: 'foo')
98
+ subject
99
+ end
100
+
101
+ it 'is expected to receive #copy_called!' do
102
+ expect_any_instance_of(model_mock).to receive(:copy_called!).with(foo: 'foo')
103
+ subject
104
+ end
105
+
106
+ it 'is expected to invoke super on parent class with { :foo => "foo" }' do
107
+ expect(active_record_base_mock).to receive(:new).with(foo: 'foo')
108
+ subject
109
+ end
110
+ end
111
+
112
+ context 'with attributes being an instance of ModelMock having attributes { :foo => "foo" }' do
113
+ let(:previous_instance) { model_mock.new(foo: 'foo') }
114
+ let(:attributes) { previous_instance }
115
+
116
+ it { is_expected.to have_attributes(foo: 'foo') }
117
+
118
+ context 'with _failed equal to true on previous instance' do
119
+ before { previous_instance.instance_variable_set('@_failed', true) }
120
+
121
+ it { is_expected.to be_failure }
122
+ end
123
+
124
+ context 'with _rolled_back equal to true on previous instance' do
125
+ before { previous_instance.instance_variable_set('@_rolled_back', true) }
126
+
127
+ it 'is expected to have instance_variable @_rolled_back eq to true' do
128
+ expect(subject.instance_variable_get('@_rolled_back')).to eq true
129
+ end
130
+ end
131
+
132
+ context 'with _called eq to ["foo"] on previous instance' do
133
+ before { previous_instance.instance_variable_set('@_called', %w[foo]) }
134
+
135
+ it 'is expected to have instance_variable @_called eq to ["foo"]' do
136
+ expect(subject.instance_variable_get('@_called')).to eq %w[foo]
137
+ end
138
+ end
139
+ end
140
+ end
141
+
142
+ describe '#merge!' do
143
+ subject { model_mock.new(attributes).merge!(merge_instance) }
144
+ context 'having attributes { :foo => nil }' do
145
+ let(:attributes) { { foo: nil } }
146
+
147
+ context 'with merging instance having attributes { :foo => "foo" }' do
148
+ let(:merge_instance) { model_mock.new(foo: 'foo') }
149
+
150
+ it { is_expected.to have_attributes(foo: 'foo') }
151
+
152
+ context 'with _failed equal to true on merging instance' do
153
+ before { merge_instance.instance_variable_set('@_failed', true) }
154
+
155
+ it { is_expected.to be_failure }
156
+ end
157
+
158
+ context 'with _rolled_back equal to true on merging instance' do
159
+ before { merge_instance.instance_variable_set('@_rolled_back', true) }
160
+
161
+ it 'is expected to have instance_variable @_rolled_back eq to true' do
162
+ expect(subject.instance_variable_get('@_rolled_back')).to eq true
163
+ end
164
+ end
165
+ end
166
+ end
167
+
168
+ context 'having attributes { :foo => "foo"}' do
169
+ let(:attributes) { { foo: 'foo' } }
170
+
171
+ context 'with merging instance having attributes { :foo => "bar" }' do
172
+ let(:merge_instance) { model_mock.new(foo: 'bar') }
173
+
174
+ it { is_expected.to have_attributes(foo: 'bar') }
175
+
176
+ context 'with _failed equal to true on merging instance' do
177
+ before { merge_instance.instance_variable_set('@_failed', true) }
178
+
179
+ it { is_expected.to be_failure }
180
+ end
181
+
182
+ context 'with _rolled_back equal to true on merging instance' do
183
+ before { merge_instance.instance_variable_set('@_rolled_back', true) }
184
+
185
+ it 'is expected to have instance_variable @_rolled_back eq to true' do
186
+ expect(subject.instance_variable_get('@_rolled_back')).to eq true
187
+ end
188
+ end
189
+ end
190
+ end
191
+ end
192
+ end
193
+
194
+ describe 'a basic interactor using an ActiveRecord model class as context' do
195
+ let!(:model_mock) do
196
+ require 'active_model'
197
+ build_class('ModelMock', active_record_base_mock) do
198
+ include ActiveModel::Validations
199
+ attr_accessor :foo
200
+ acts_as_context
201
+ end
202
+ end
203
+
204
+ let(:interactor_class) do
205
+ build_interactor do
206
+ contextualize_with :model_mock
207
+
208
+ def perform
209
+ context.foo = 'foo'
210
+ end
211
+ end
212
+ end
213
+
214
+ include_examples 'a class with interactor methods'
215
+ include_examples 'a class with interactor callback methods'
216
+ include_examples 'a class with interactor context methods'
217
+
218
+ describe '.context_class' do
219
+ subject { interactor_class.context_class }
220
+
221
+ it { is_expected.to eq model_mock }
222
+ end
223
+
224
+ describe '.perform' do
225
+ subject { interactor_class.perform }
226
+
227
+ it { is_expected.to be_a model_mock }
228
+ it { is_expected.to be_successful }
229
+ it { is_expected.to have_attributes(foo: 'foo') }
230
+ end
231
+
232
+ describe '.perform!' do
233
+ subject { interactor_class.perform! }
234
+
235
+ it { expect { subject }.not_to raise_error }
236
+ it { is_expected.to be_a model_mock }
237
+ it { is_expected.to be_successful }
238
+ it { is_expected.to have_attributes(foo: 'foo') }
239
+ end
240
+
241
+ context 'failing the context on #perform' do
242
+ let(:interactor_class) do
243
+ build_interactor do
244
+ contextualize_with :model_mock
245
+
246
+ def perform
247
+ context.fail!
248
+ end
249
+ end
250
+ end
251
+
252
+ describe '.perform' do
253
+ subject { interactor_class.perform }
254
+
255
+ it { expect { subject }.not_to raise_error }
256
+ it { is_expected.to be_a model_mock }
257
+ it { is_expected.to be_failure }
258
+ it 'is expected to #rollback' do
259
+ expect_any_instance_of(interactor_class).to receive(:rollback)
260
+ subject
261
+ end
262
+ end
263
+
264
+ describe '.perform!' do
265
+ subject { interactor_class.perform! }
266
+
267
+ it { expect { subject }.to raise_error(ActiveInteractor::Error::ContextFailure) }
268
+ end
269
+ end
270
+ end
271
+
272
+ describe 'a basic organizer using an ActiveRecord model class as context' do
273
+ let!(:model_mock) do
274
+ require 'active_model'
275
+ build_class('ModelMock', active_record_base_mock) do
276
+ include ActiveModel::Validations
277
+ attr_accessor :first_name, :last_name
278
+ acts_as_context
279
+ end
280
+ end
281
+
282
+ context 'with each organized interactor using the model' do
283
+ let!(:test_interactor_1) do
284
+ build_interactor('TestInteractor1') do
285
+ contextualize_with :model_mock
286
+
287
+ def perform
288
+ context.first_name = 'Test'
289
+ end
290
+ end
291
+ end
292
+
293
+ let!(:test_interactor_2) do
294
+ build_interactor('TestInteractor2') do
295
+ contextualize_with :model_mock
296
+
297
+ def perform
298
+ context.last_name = 'User'
299
+ end
300
+ end
301
+ end
302
+
303
+ let(:interactor_class) do
304
+ build_organizer do
305
+ contextualize_with :model_mock
306
+ organize :test_interactor_1, :test_interactor_2
307
+ end
308
+ end
309
+
310
+ include_examples 'a class with interactor methods'
311
+ include_examples 'a class with interactor callback methods'
312
+ include_examples 'a class with interactor context methods'
313
+ include_examples 'a class with organizer callback methods'
314
+
315
+ describe '.perform' do
316
+ subject { interactor_class.perform }
317
+
318
+ it { is_expected.to be_a model_mock }
319
+ it { is_expected.to be_successful }
320
+ it { is_expected.to have_attributes(first_name: 'Test', last_name: 'User') }
321
+ end
322
+ end
323
+
324
+ context 'with each organized interactor using their default context' do
325
+ let!(:test_interactor_1) do
326
+ build_interactor('TestInteractor1') do
327
+ def perform
328
+ context.first_name = 'Test'
329
+ end
330
+ end
331
+ end
332
+
333
+ let!(:test_interactor_2) do
334
+ build_interactor('TestInteractor2') do
335
+ def perform
336
+ context.last_name = 'User'
337
+ end
338
+ end
339
+ end
340
+
341
+ let(:interactor_class) do
342
+ build_organizer do
343
+ contextualize_with :model_mock
344
+ organize :test_interactor_1, :test_interactor_2
345
+ end
346
+ end
347
+
348
+ include_examples 'a class with interactor methods'
349
+ include_examples 'a class with interactor callback methods'
350
+ include_examples 'a class with interactor context methods'
351
+ include_examples 'a class with organizer callback methods'
352
+
353
+ describe '.perform' do
354
+ subject { interactor_class.perform }
355
+
356
+ it { is_expected.to be_a model_mock }
357
+ it { is_expected.to be_successful }
358
+ it { is_expected.to have_attributes(first_name: 'Test', last_name: 'User') }
359
+ end
360
+ end
361
+ end
362
+ end
363
+ end
364
+ end
365
+ rescue LoadError
366
+ RSpec.describe 'ActiveRecord Integration', type: :integration do
367
+ pending 'Rails not found skipping specs...'
368
+ end
369
+ end
@@ -13,7 +13,7 @@ module Spec
13
13
  module Factories
14
14
  def build_class(class_name, parent_class = nil, &block)
15
15
  Object.send(:remove_const, class_name.to_sym) if Object.const_defined?(class_name)
16
- klass = Object.const_set(class_name, Class.new(parent_class))
16
+ klass = create_class(class_name, parent_class)
17
17
  klass.class_eval(&block) if block
18
18
  FactoryCollection.factories << klass.name.to_sym
19
19
  klass
@@ -36,6 +36,14 @@ module Spec
36
36
  Object.send(:remove_const, factory) if Object.const_defined?(factory)
37
37
  end
38
38
  end
39
+
40
+ private
41
+
42
+ def create_class(class_name, parent_class = nil)
43
+ return Object.const_set(class_name, Class.new) unless parent_class
44
+
45
+ Object.const_set(class_name, Class.new(parent_class))
46
+ end
39
47
  end
40
48
  end
41
49
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activeinteractor
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.beta.5
4
+ version: 1.0.0.beta.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aaron Allen
@@ -108,10 +108,10 @@ files:
108
108
  - lib/active_interactor/base.rb
109
109
  - lib/active_interactor/config.rb
110
110
  - lib/active_interactor/configurable.rb
111
- - lib/active_interactor/context.rb
112
111
  - lib/active_interactor/context/attributes.rb
113
112
  - lib/active_interactor/context/base.rb
114
113
  - lib/active_interactor/context/loader.rb
114
+ - lib/active_interactor/context/status.rb
115
115
  - lib/active_interactor/error.rb
116
116
  - lib/active_interactor/interactor.rb
117
117
  - lib/active_interactor/interactor/callbacks.rb
@@ -122,6 +122,7 @@ files:
122
122
  - lib/active_interactor/organizer/interactor_interface.rb
123
123
  - lib/active_interactor/organizer/interactor_interface_collection.rb
124
124
  - lib/active_interactor/rails.rb
125
+ - lib/active_interactor/rails/active_record.rb
125
126
  - lib/active_interactor/rails/config.rb
126
127
  - lib/active_interactor/rails/railtie.rb
127
128
  - lib/active_interactor/version.rb
@@ -155,9 +156,11 @@ files:
155
156
  - spec/active_interactor/organizer/interactor_interface_collection_spec.rb
156
157
  - spec/active_interactor/organizer/interactor_interface_spec.rb
157
158
  - spec/active_interactor/organizer_spec.rb
159
+ - spec/active_interactor/rails/active_record_spec.rb
158
160
  - spec/active_interactor/rails/config_spec.rb
159
161
  - spec/active_interactor/rails_spec.rb
160
162
  - spec/active_interactor_spec.rb
163
+ - spec/integration/active_record_integration_spec.rb
161
164
  - spec/integration/basic_callback_integration_spec.rb
162
165
  - spec/integration/basic_context_integration_spec.rb
163
166
  - spec/integration/basic_integration_spec.rb
@@ -174,10 +177,10 @@ licenses:
174
177
  - MIT
175
178
  metadata:
176
179
  bug_tracker_uri: https://github.com/aaronmallen/activeinteractor/issues
177
- changelog_uri: https://github.com/aaronmallen/activeinteractor/blob/v1.0.0.beta.5/CHANGELOG.md
178
- documentation_uri: https://www.rubydoc.info/gems/activeinteractor/1.0.0.beta.5
180
+ changelog_uri: https://github.com/aaronmallen/activeinteractor/blob/v1.0.0.beta.6/CHANGELOG.md
181
+ documentation_uri: https://www.rubydoc.info/gems/activeinteractor/1.0.0.beta.6
179
182
  hompage_uri: https://github.com/aaronmallen/activeinteractor
180
- source_code_uri: https://github.com/aaronmallen/activeinteractor/tree/v1.0.0.beta.5
183
+ source_code_uri: https://github.com/aaronmallen/activeinteractor/tree/v1.0.0.beta.6
181
184
  wiki_uri: https://github.com/aaronmallen/activeinteractor/wiki
182
185
  post_install_message:
183
186
  rdoc_options: []
@@ -200,6 +203,7 @@ specification_version: 4
200
203
  summary: Ruby interactors with ActiveModel::Validations
201
204
  test_files:
202
205
  - spec/active_interactor_spec.rb
206
+ - spec/integration/active_record_integration_spec.rb
203
207
  - spec/integration/basic_validations_integration_spec.rb
204
208
  - spec/integration/basic_integration_spec.rb
205
209
  - spec/integration/basic_callback_integration_spec.rb
@@ -217,6 +221,7 @@ test_files:
217
221
  - spec/active_interactor/organizer_spec.rb
218
222
  - spec/active_interactor/organizer/interactor_interface_spec.rb
219
223
  - spec/active_interactor/organizer/interactor_interface_collection_spec.rb
224
+ - spec/active_interactor/rails/active_record_spec.rb
220
225
  - spec/active_interactor/rails/config_spec.rb
221
226
  - spec/active_interactor/rails_spec.rb
222
227
  - spec/active_interactor/interactor/worker_spec.rb
@@ -1,13 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ActiveInteractor
4
- # ActiveInteractor::Context classes
5
- # @author Aaron Allen <hello@aaronmallen.me>
6
- # @since 0.0.1
7
- module Context
8
- extend ActiveSupport::Autoload
9
-
10
- autoload :Base
11
- autoload :Loader
12
- end
13
- end