activeinteractor 1.0.0.beta.7 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +12 -0
  3. data/CHANGELOG.md +50 -158
  4. data/README.md +13 -856
  5. data/lib/active_interactor.rb +61 -4
  6. data/lib/active_interactor/base.rb +26 -20
  7. data/lib/active_interactor/config.rb +36 -18
  8. data/lib/active_interactor/configurable.rb +17 -9
  9. data/lib/active_interactor/context/attributes.rb +73 -26
  10. data/lib/active_interactor/context/base.rb +236 -65
  11. data/lib/active_interactor/context/loader.rb +20 -15
  12. data/lib/active_interactor/context/status.rb +38 -56
  13. data/lib/active_interactor/error.rb +15 -7
  14. data/lib/active_interactor/interactor/callbacks.rb +174 -160
  15. data/lib/active_interactor/interactor/context.rb +279 -87
  16. data/lib/active_interactor/interactor/perform.rb +256 -0
  17. data/lib/active_interactor/interactor/worker.rb +19 -14
  18. data/lib/active_interactor/models.rb +65 -0
  19. data/lib/active_interactor/organizer/base.rb +18 -0
  20. data/lib/active_interactor/organizer/callbacks.rb +153 -0
  21. data/lib/active_interactor/organizer/interactor_interface.rb +38 -26
  22. data/lib/active_interactor/organizer/interactor_interface_collection.rb +35 -32
  23. data/lib/active_interactor/organizer/organize.rb +67 -0
  24. data/lib/active_interactor/organizer/perform.rb +93 -0
  25. data/lib/active_interactor/rails.rb +0 -10
  26. data/lib/active_interactor/rails/orm/active_record.rb +1 -1
  27. data/lib/active_interactor/rails/railtie.rb +8 -11
  28. data/lib/active_interactor/version.rb +2 -1
  29. data/lib/rails/generators/active_interactor.rb +1 -38
  30. data/lib/rails/generators/active_interactor/application_context_generator.rb +21 -0
  31. data/lib/rails/generators/active_interactor/application_interactor_generator.rb +5 -15
  32. data/lib/rails/generators/active_interactor/application_organizer_generator.rb +21 -0
  33. data/lib/rails/generators/active_interactor/base.rb +29 -0
  34. data/lib/rails/generators/active_interactor/generator.rb +21 -0
  35. data/lib/rails/generators/active_interactor/install_generator.rb +2 -9
  36. data/lib/rails/generators/interactor/context/rspec_generator.rb +3 -10
  37. data/lib/rails/generators/interactor/context/test_unit_generator.rb +4 -11
  38. data/lib/rails/generators/interactor/context_generator.rb +7 -10
  39. data/lib/rails/generators/interactor/generates_context.rb +28 -0
  40. data/lib/rails/generators/interactor/interactor_generator.rb +8 -10
  41. data/lib/rails/generators/interactor/organizer_generator.rb +8 -12
  42. data/lib/rails/generators/interactor/rspec_generator.rb +2 -9
  43. data/lib/rails/generators/interactor/test_unit_generator.rb +3 -10
  44. data/lib/rails/generators/{active_interactor/templates/initializer.erb → templates/active_interactor.erb} +3 -11
  45. data/lib/rails/generators/{active_interactor/templates → templates}/application_context.rb +0 -0
  46. data/lib/rails/generators/{active_interactor/templates → templates}/application_interactor.rb +0 -0
  47. data/lib/rails/generators/templates/application_organizer.rb +4 -0
  48. data/lib/rails/generators/{interactor/templates → templates}/context.erb +0 -0
  49. data/lib/rails/generators/{interactor/context/templates/rspec.erb → templates/context_spec.erb} +0 -0
  50. data/lib/rails/generators/{interactor/context/templates/test_unit.erb → templates/context_test_unit.erb} +0 -0
  51. data/lib/rails/generators/{interactor/templates → templates}/interactor.erb +0 -0
  52. data/lib/rails/generators/{interactor/templates/rspec.erb → templates/interactor_spec.erb} +0 -0
  53. data/lib/rails/generators/{interactor/templates/test_unit.erb → templates/interactor_text_unit.erb} +0 -0
  54. data/lib/rails/generators/{interactor/templates → templates}/organizer.erb +0 -0
  55. data/spec/active_interactor/base_spec.rb +3 -3
  56. data/spec/active_interactor/interactor/{perform_options_spec.rb → perform/options_spec.rb} +1 -1
  57. data/spec/active_interactor/interactor/worker_spec.rb +14 -15
  58. data/spec/active_interactor/{organizer_spec.rb → organizer/base_spec.rb} +27 -17
  59. data/spec/integration/a_basic_interactor_spec.rb +106 -0
  60. data/spec/integration/a_basic_organizer_spec.rb +97 -0
  61. data/spec/integration/a_failing_interactor_spec.rb +42 -0
  62. data/spec/integration/active_record_integration_spec.rb +9 -9
  63. data/spec/integration/an_interactor_with_after_context_validation_callbacks_spec.rb +69 -0
  64. data/spec/integration/an_interactor_with_after_perform_callbacks_spec.rb +30 -0
  65. data/spec/integration/an_interactor_with_after_rollback_callbacks_spec.rb +33 -0
  66. data/spec/integration/an_interactor_with_an_existing_context_class_spec.rb +49 -0
  67. data/spec/integration/an_interactor_with_around_perform_callbacks_spec.rb +35 -0
  68. data/spec/integration/an_interactor_with_around_rollback_callbacks_spec.rb +39 -0
  69. data/spec/integration/an_interactor_with_before_perform_callbacks_spec.rb +30 -0
  70. data/spec/integration/an_interactor_with_before_rollback_callbacks_spec.rb +33 -0
  71. data/spec/integration/an_interactor_with_validations_on_called_spec.rb +40 -0
  72. data/spec/integration/an_interactor_with_validations_on_calling_spec.rb +36 -0
  73. data/spec/integration/an_interactor_with_validations_spec.rb +93 -0
  74. data/spec/integration/an_organizer_performing_in_parallel_spec.rb +48 -0
  75. data/spec/integration/an_organizer_with_after_each_callbacks_spec.rb +34 -0
  76. data/spec/integration/an_organizer_with_around_each_callbacks_spec.rb +39 -0
  77. data/spec/integration/an_organizer_with_before_each_callbacks_spec.rb +34 -0
  78. data/spec/integration/an_organizer_with_conditionally_organized_interactors_spec.rb +314 -0
  79. data/spec/spec_helper.rb +8 -12
  80. data/spec/support/coverage.rb +4 -0
  81. data/spec/support/coverage/reporters.rb +11 -0
  82. data/spec/support/coverage/reporters/codacy.rb +39 -0
  83. data/spec/support/coverage/reporters/simple_cov.rb +54 -0
  84. data/spec/support/coverage/runner.rb +66 -0
  85. data/spec/support/helpers/factories.rb +1 -1
  86. data/spec/support/shared_examples/a_class_with_interactor_callback_methods_example.rb +8 -8
  87. data/spec/support/shared_examples/a_class_with_interactor_context_methods_example.rb +5 -5
  88. data/spec/support/shared_examples/a_class_with_interactor_methods_example.rb +2 -2
  89. data/spec/support/shared_examples/a_class_with_organizer_callback_methods_example.rb +3 -3
  90. metadata +83 -40
  91. data/lib/active_interactor/interactor.rb +0 -84
  92. data/lib/active_interactor/interactor/perform_options.rb +0 -29
  93. data/lib/active_interactor/organizer.rb +0 -269
  94. data/lib/active_interactor/rails/config.rb +0 -45
  95. data/lib/active_interactor/rails/models.rb +0 -58
  96. data/lib/rails/generators/active_interactor/templates/application_organizer.rb +0 -4
  97. data/spec/active_interactor/rails/config_spec.rb +0 -29
  98. data/spec/active_interactor/rails_spec.rb +0 -24
  99. data/spec/integration/basic_callback_integration_spec.rb +0 -355
  100. data/spec/integration/basic_context_integration_spec.rb +0 -73
  101. data/spec/integration/basic_integration_spec.rb +0 -570
  102. data/spec/integration/basic_validations_integration_spec.rb +0 -204
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveInteractor
4
+ module Organizer
5
+ # Organizer organize methods. Because {Organize} is a module classes should include {Organize} rather than inherit
6
+ # from it.
7
+ #
8
+ # @author Aaron Allen <hello@aaronmallen.me>
9
+ # @since 1.0.0
10
+ module Organize
11
+ # Organizer organize class methods. Because {ClassMethods} is a module classes should extend {ClassMethods}
12
+ # rather than inherit from it.
13
+ #
14
+ # @author Aaron Allen <hello@aaronmallen.me>
15
+ # @since 1.0.0
16
+ module ClassMethods
17
+ # {#organized Organize} {ActiveInteractor::Base interactors} to be called by the {Base organizer}.
18
+ # A block will be evaluated on the {#organized InteractorInterfaceCollection} if a block is given.
19
+ #
20
+ # @since 0.1.0
21
+ #
22
+ # @example Basic organization of {ActiveInteractor::Base interactors}
23
+ # class MyInteractor1 < ActiveInteractor::Base; end
24
+ # class MyInteractor2 < ActiveInteractor::Base; end
25
+ # class MyOrganizer < ActiveInteractor::Organizer::Base
26
+ # organize :my_interactor_1, :my_interactor_2
27
+ # end
28
+ #
29
+ # @example Conditional organization of {ActiveInteractor::Base interactors}
30
+ # class MyInteractor1 < ActiveInteractor::Base; end
31
+ # class MyInteractor2 < ActiveInteractor::Base; end
32
+ # class MyOrganizer < ActiveInteractor::Organizer::Base
33
+ # organize do
34
+ # add :my_interactor_1
35
+ # add :my_interactor_2, if: -> { context.valid? }
36
+ # end
37
+ # end
38
+ #
39
+ # @example organization of {ActiveInteractor::Base interactors} with {Interactor::Perform::Options options}
40
+ # class MyInteractor1 < ActiveInteractor::Base; end
41
+ # class MyInteractor2 < ActiveInteractor::Base; end
42
+ # class MyOrganizer < ActiveInteractor::Organizer::Base
43
+ # organize do
44
+ # add :my_interactor_1, validate: false
45
+ # add :my_interactor_2, skip_perform_callbacks: true
46
+ # end
47
+ # end
48
+ #
49
+ # @param interactors [Array<Const, Symbol, String>, nil] the {ActiveInteractor::Base} interactor classes to be
50
+ # {#organized organized}
51
+ # @return [InteractorInterfaceCollection] the {#organized} {ActiveInteractor::Base interactors}
52
+ def organize(*interactors, &block)
53
+ organized.concat(interactors) if interactors
54
+ organized.instance_eval(&block) if block
55
+ organized
56
+ end
57
+
58
+ # An organized collection of {ActiveInteractor::Base interactors}
59
+ #
60
+ # @return [InteractorInterfaceCollection] an instance of {InteractorInterfaceCollection}
61
+ def organized
62
+ @organized ||= ActiveInteractor::Organizer::InteractorInterfaceCollection.new
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveInteractor
4
+ module Organizer
5
+ # Organizer perform methods. Because {Perform} is a module classes should include {Perform} rather than inherit
6
+ # from it.
7
+ #
8
+ # @author Aaron Allen <hello@aaronmallen.me>
9
+ # @since 1.0.0
10
+ module Perform
11
+ # Organizer perform class methods. Because {ClassMethods} is a module classes should extend {ClassMethods}
12
+ # rather than inherit from it.
13
+ #
14
+ # @author Aaron Allen <hello@aaronmallen.me>
15
+ # @since 1.0.0
16
+ #
17
+ # @!attribute [r] parallel
18
+ # If `true` the {Base organizer} will call {Interactor::Perform#perform #perform} on its
19
+ # {Organizer::Organize::ClassMethods#organize .organized} {ActiveInteractor::Base interactors} in parallel.
20
+ # An {Base organizer} will have {.parallel} `false` by default.
21
+ #
22
+ # @!scope class
23
+ # @since 1.0.0
24
+ #
25
+ # @return [Boolean] whether or not to call {Interactor::Perform#perform #perform} on its
26
+ # {Organizer::Organize::ClassMethods#organize .organized} {ActiveInteractor::Base interactors} in parallel.
27
+ module ClassMethods
28
+ # Set {.parallel} to `true`
29
+ #
30
+ # @example a basic {Base organizer} set to perform in parallel
31
+ # class MyOrganizer < ActiveInteractor::Organizer::Base
32
+ # perform_in_parallel
33
+ # end
34
+ def perform_in_parallel
35
+ self.parallel = true
36
+ end
37
+ end
38
+
39
+ def self.included(base)
40
+ base.class_eval do
41
+ class_attribute :parallel, instance_writer: false, default: false
42
+ end
43
+ end
44
+
45
+ # Call the {Organize::ClassMethods#organized .organized} {ActiveInteractor::Base interactors}
46
+ # {Interactor::Perform#perform #perform}. An {Base organizer} is expected not to define its own
47
+ # {Interactor::Perform#perform #perform} method in favor of this default implementation.
48
+ def perform
49
+ if self.class.parallel
50
+ perform_in_parallel
51
+ else
52
+ perform_in_order
53
+ end
54
+ end
55
+
56
+ private
57
+
58
+ def execute_interactor(interface, fail_on_error = false, perform_options = {})
59
+ interface.perform(self, context, fail_on_error, perform_options)
60
+ end
61
+
62
+ def execute_interactor_with_callbacks(interface, fail_on_error = false, perform_options = {})
63
+ args = [interface, fail_on_error, perform_options]
64
+ return execute_interactor(*args) if options.skip_each_perform_callbacks
65
+
66
+ run_callbacks :each_perform do
67
+ execute_interactor(*args)
68
+ end
69
+ end
70
+
71
+ def merge_contexts(contexts)
72
+ contexts.each { |context| @context.merge!(context) }
73
+ context_fail! if contexts.any?(&:failure?)
74
+ end
75
+
76
+ def perform_in_order
77
+ self.class.organized.each do |interface|
78
+ result = execute_interactor_with_callbacks(interface, true)
79
+ context.merge!(result) if result
80
+ end
81
+ rescue ActiveInteractor::Error::ContextFailure => e
82
+ context.merge!(e.context)
83
+ end
84
+
85
+ def perform_in_parallel
86
+ results = self.class.organized.map do |interface|
87
+ Thread.new { execute_interactor_with_callbacks(interface, false, skip_rollback: true) }
88
+ end
89
+ merge_contexts(results.map(&:value))
90
+ end
91
+ end
92
+ end
93
+ end
@@ -1,14 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'active_interactor'
4
- require 'active_interactor/rails/config'
5
- require 'active_interactor/rails/models'
6
4
  require 'active_interactor/rails/railtie'
7
-
8
- module ActiveInteractor
9
- # Rails specific classes, helpers, and utlities
10
- # @author Aaron Allen <hello@aaronmallen.me>
11
- # @since 1.0.0
12
- module Rails
13
- end
14
- end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  ActiveSupport.on_load(:active_record) do
4
- extend ActiveInteractor::Rails::Models::ClassMethods
4
+ extend ActiveInteractor::Models
5
5
  end
@@ -2,23 +2,20 @@
2
2
 
3
3
  require 'rails'
4
4
 
5
- require 'active_interactor'
6
-
7
5
  module ActiveInteractor
6
+ # Rails classes and modules.
7
+ #
8
+ # @author Aaron Allen <hello@aaronmallen.me>
9
+ # @since 1.0.0
8
10
  module Rails
9
- # Configure ActiveInteractor for rails
11
+ # The ActiveInteractor Railtie
12
+ #
10
13
  # @author Aaron Allen <hello@aaronmallen.me>
11
14
  # @since 1.0.0
15
+ #
16
+ # @see https://api.rubyonrails.org/classes/Rails/Railtie.html
12
17
  class Railtie < ::Rails::Railtie
13
- config.active_interactor = ActiveInteractor::Rails.config
14
-
15
18
  config.eager_load_namespaces << ActiveInteractor
16
-
17
- config.to_prepare do
18
- ActiveInteractor.configure do |c|
19
- c.logger = ::Rails.logger
20
- end
21
- end
22
19
  end
23
20
  end
24
21
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveInteractor
4
+ # The ActiveInteractor version
4
5
  # @return [String] the ActiveInteractor version
5
- VERSION = '1.0.0.beta.7'
6
+ VERSION = '1.0.0'
6
7
  end
@@ -1,42 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rails/generators/named_base'
4
- require 'active_interactor'
5
-
6
- module ActiveInteractor
7
- module Generators
8
- module Generator
9
- private
10
-
11
- def interactor_directory
12
- @interactor_directory ||= ActiveInteractor::Rails.config.directory
13
- end
14
- end
15
-
16
- module GeneratesContext
17
- def self.included(base)
18
- base.class_eval do
19
- argument :context_attributes, type: :array, default: [], banner: 'attribute attribute'
20
- class_option :skip_context, type: :boolean, desc: 'Whether or not to generate a context class'
21
- end
22
- end
23
-
24
- private
25
-
26
- def skip_context?
27
- options[:skip_context] == true || ActiveInteractor::Rails.config.generate_context_classes == false
28
- end
29
- end
30
-
31
- class Base < ::Rails::Generators::Base
32
- include Generator
33
- end
34
-
35
- class NamedBase < ::Rails::Generators::NamedBase
36
- include Generator
37
- end
38
- end
39
- end
40
-
41
3
  Dir[File.expand_path('active_interactor/*.rb', __dir__)].sort.each { |file| require file }
4
+ Dir[File.expand_path('interactor/context/*.rb', __dir__)].sort.each { |file| require file }
42
5
  Dir[File.expand_path('interactor/*.rb', __dir__)].sort.each { |file| require file }
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/generators/active_interactor/base'
4
+
5
+ module ActiveInteractor
6
+ module Generators
7
+ class ApplicationContextGenerator < Base
8
+ def create_application_organizer
9
+ return if File.exist?(file_path)
10
+
11
+ template 'application_context.rb', file_path
12
+ end
13
+
14
+ private
15
+
16
+ def file_path
17
+ "app/#{active_interactor_directory}/application_context.rb"
18
+ end
19
+ end
20
+ end
21
+ end
@@ -1,30 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative '../active_interactor'
3
+ require 'rails/generators/active_interactor/base'
4
4
 
5
5
  module ActiveInteractor
6
6
  module Generators
7
7
  class ApplicationInteractorGenerator < Base
8
- source_root File.expand_path('templates', __dir__)
9
-
10
8
  def create_application_interactor
11
- template 'application_interactor.rb', File.join(target_path, 'application_interactor.rb')
12
- end
13
-
14
- def create_application_organizer
15
- template 'application_organizer.rb', File.join(target_path, 'application_organizer.rb')
16
- end
17
-
18
- def create_application_context
19
- return if ActiveInteractor::Rails.config.generate_context_classes == false
9
+ return if File.exist?(file_path)
20
10
 
21
- template 'application_context.rb', File.join(target_path, 'application_context.rb')
11
+ template 'application_interactor.rb', file_path
22
12
  end
23
13
 
24
14
  private
25
15
 
26
- def target_path
27
- "app/#{interactor_directory}"
16
+ def file_path
17
+ "app/#{active_interactor_directory}/application_interactor.rb"
28
18
  end
29
19
  end
30
20
  end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/generators/active_interactor/base'
4
+
5
+ module ActiveInteractor
6
+ module Generators
7
+ class ApplicationOrganizerGenerator < Base
8
+ def create_application_organizer
9
+ return if File.exist?(file_path)
10
+
11
+ template 'application_organizer.rb', file_path
12
+ end
13
+
14
+ private
15
+
16
+ def file_path
17
+ "app/#{active_interactor_directory}/application_organizer.rb"
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/generators'
4
+
5
+ require 'rails/generators/active_interactor/generator'
6
+
7
+ module ActiveInteractor
8
+ module Generators
9
+ class Base < ::Rails::Generators::Base
10
+ extend Generator
11
+ include Generator
12
+ end
13
+
14
+ class NamedBase < ::Rails::Generators::NamedBase
15
+ extend Generator
16
+ include Generator
17
+
18
+ private
19
+
20
+ def active_interactor_file(parent_dir: 'app', suffix: nil)
21
+ File.join(parent_dir, active_interactor_directory, class_path, "#{file_name_with_suffix(suffix)}.rb")
22
+ end
23
+
24
+ def file_name_with_suffix(suffix)
25
+ suffix ? "#{file_name}_#{suffix}" : file_name
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveInteractor
4
+ module Generators
5
+ module Generator
6
+ def source_root
7
+ File.expand_path('../templates', __dir__)
8
+ end
9
+
10
+ private
11
+
12
+ def active_interactor_directory
13
+ active_interactor_options.fetch(:dir, 'interactors')
14
+ end
15
+
16
+ def active_interactor_options
17
+ ::Rails.application.config.generators.options.fetch(:active_interactor, {})
18
+ end
19
+ end
20
+ end
21
+ end
@@ -1,22 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative '../active_interactor'
3
+ require 'rails/generators/active_interactor/base'
4
4
 
5
5
  module ActiveInteractor
6
6
  module Generators
7
7
  class InstallGenerator < Base
8
- source_root File.expand_path('templates', __dir__)
9
-
10
8
  desc 'Install ActiveInteractor'
11
9
  class_option :orm
12
- argument :directory, type: :string, default: 'interactors', banner: 'directory'
13
10
 
14
11
  def create_initializer
15
- template 'initializer.erb', File.join('config', 'initializers', 'active_interactor.rb')
16
- end
17
-
18
- def create_application_interactor_and_context
19
- generate :'active_interactor:application_interactor'
12
+ template 'active_interactor.erb', File.join('config', 'initializers', 'active_interactor.rb')
20
13
  end
21
14
  end
22
15
  end
@@ -1,22 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative '../../active_interactor'
3
+ require 'rails/generators/active_interactor/base'
4
4
 
5
5
  module Interactor
6
6
  module Context
7
7
  module Generators
8
8
  class RspecGenerator < ActiveInteractor::Generators::NamedBase
9
- source_root File.expand_path('templates', __dir__)
10
- desc 'Generate an interactor spec'
9
+ desc 'Generate an interactor context spec'
11
10
 
12
11
  def create_spec
13
- template 'rspec.erb', file_path
14
- end
15
-
16
- private
17
-
18
- def file_path
19
- File.join('spec', interactor_directory, File.join(class_path), "#{file_name}_context_spec.rb")
12
+ template 'context_spec.erb', active_interactor_file(parent_dir: 'spec', suffix: 'context_spec')
20
13
  end
21
14
  end
22
15
  end