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

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.
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