activeinteractor 2.0.0.alpha.2.1.0 → 2.0.0.alpha.2.3.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8353806528fdb9ad9625f469d16d400929d9ef3cae024490c85982354f4d9bad
4
- data.tar.gz: af9cfad32cffe177d1b30fa7fb482f3305a6757152090d82482aef39f448a628
3
+ metadata.gz: c201330d4b6c13c9b409888776d669e0b1ea73fcae310f4869de55fc0bbcc500
4
+ data.tar.gz: 62be913b6bee629928ba114f82a7c2a526610cab1d9978459fc8e9f4f70869b3
5
5
  SHA512:
6
- metadata.gz: 45a07cd05ee5ad9e27fb23f2ebe1a99c5bc01cdeeda5987e35a501b22230ecb56ea49ccc36c233348a838a6c25cd2fa8b6945326bef13839d1f4541a0d3ac043
7
- data.tar.gz: c11cbf40fa73910ce12a43a60db15f13983ec6174006627eae29eecd068002be2fcbd50b7062d4cd0d955736594a246611f66c570db59b152e889053c9b5128d
6
+ metadata.gz: ef8f56b5ca0f79d60b9f45b3c8a1e9e394b15c15c89c98e7f747eaecf6621671020b624a0d5ba141d7948468eb83938ecd8bf62849038ad39e2f974a03bfce11
7
+ data.tar.gz: ed637132df8ef4ae80843e042d780b0423480e74f5d4cd9d12128118c10e3197467cb2e31f49d2d6a62cbadfd9c83ef4abad0beb123959ddb908d2f5f39fadc2
@@ -2,9 +2,23 @@
2
2
 
3
3
  module ActiveInteractor
4
4
  class Base
5
+ include Type::HasTypes
6
+
5
7
  class << self
6
- def argument(name, type, description = nil, **options)
7
- attributes[:arguments][name.to_sym] = { name: name, type: type, description: description }.merge(options)
8
+ delegate :argument, :argument_names, :arguments, to: :input_context_class
9
+ delegate :returns, :field_names, :fields, to: :output_context_class
10
+
11
+ def input_context_class
12
+ @input_context_class ||= const_set(:InputContext, Class.new(Context::Input))
13
+ end
14
+
15
+ def accepts_arguments_matching(set_input_context_class)
16
+ @input_context_class = set_input_context_class
17
+ end
18
+ alias input_context accepts_arguments_matching
19
+
20
+ def output_context_class
21
+ @output_context_class ||= const_set(:OutputContext, Class.new(Context::Output))
8
22
  end
9
23
 
10
24
  def perform!(input_context = {})
@@ -19,26 +33,31 @@ module ActiveInteractor
19
33
  Result.failure(errors: e.message)
20
34
  end
21
35
 
22
- def returns(name, type, description = nil, **options)
23
- attributes[:fields][name.to_sym] = { name: name, type: type, description: description }.merge(options)
36
+ def returns_data_matching(set_output_context_class)
37
+ @output_context_class = set_output_context_class
24
38
  end
25
-
26
- private
27
-
28
- def attributes
29
- @attributes ||= { arguments: {}, fields: {} }
39
+ alias output_context returns_data_matching
40
+
41
+ def runtime_context_class
42
+ @runtime_context_class ||= begin
43
+ context_class = const_set(:RuntimeContext, Class.new(Context::Runtime))
44
+ context_class.send(:attribute_set).merge(input_context_class.send(:attribute_set).attributes)
45
+ context_class.send(:attribute_set).merge(output_context_class.send(:attribute_set).attributes)
46
+ context_class
47
+ end
30
48
  end
31
49
  end
32
50
 
33
51
  def initialize(input = {})
34
- @input = input.freeze
35
- @context = parse_input!
52
+ @raw_input = input.dup
53
+ validate_input_and_generate_runtime_context!
36
54
  end
37
55
 
38
56
  def perform!
39
57
  with_notification(:perform) do |payload|
40
58
  interact
41
- payload[:result] = Result.success(data: parse_output!)
59
+ generate_and_validate_output_context!
60
+ payload[:result] = Result.success(data: @output)
42
61
  end
43
62
  end
44
63
 
@@ -68,36 +87,20 @@ module ActiveInteractor
68
87
  raise Error, result
69
88
  end
70
89
 
71
- def parse_input!
72
- errors = {}
73
- context = self.class.send(:attributes)[:arguments].each_with_object({}) do |(name, options), hash|
74
- errors = validate_attribute_value(@input[name], name, options, errors)
75
- hash[name] = @input[name] || options[:default]
76
- end
77
-
78
- raise Error, Result.failure(errors: errors, status: Result::STATUS[:failed_on_input]) unless errors.empty?
79
-
80
- context
81
- end
82
-
83
- def parse_output!
84
- errors = {}
85
- context = self.class.send(:attributes)[:fields].each_with_object({}) do |(name, options), hash|
86
- errors = validate_attribute_value(self.context[name], name, options, errors)
87
- hash[name] = self.context[name] || options[:default]
88
- end
89
-
90
- raise Error, Result.failure(errors: errors, status: Result::STATUS[:failed_on_output]) unless errors.empty?
90
+ def generate_and_validate_output_context!
91
+ @output = self.class.output_context_class.new(context.attributes)
92
+ @output.validate!
93
+ return @output if @output.errors.empty?
91
94
 
92
- context
95
+ raise Error, Result.failure(errors: @output.errors, status: Result::STATUS[:failed_at_output])
93
96
  end
94
97
 
95
- def validate_attribute_value(value, attribute_name, attribute_options, errors)
96
- return errors unless value.blank? && attribute_options[:required] && !attribute_options[:default]
98
+ def validate_input_and_generate_runtime_context!
99
+ @input = self.class.input_context_class.new(@raw_input)
100
+ @input.validate!
101
+ return (@context = self.class.runtime_context_class.new(@raw_input)) if @input.errors.empty?
97
102
 
98
- errors[attribute_name] ||= []
99
- errors[attribute_name] << :blank
100
- errors
103
+ raise Error, Result.failure(errors: @input.errors, status: Result::STATUS[:failed_at_input])
101
104
  end
102
105
 
103
106
  def with_notification(action)
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveInteractor
4
+ module Context
5
+ class Attribute
6
+ NO_DEFAULT_VALUE = :__no_default_value__
7
+ attr_reader :description, :error_messages, :name
8
+
9
+ def initialize(owner, name, type, description = nil, **options)
10
+ @owner = owner
11
+ @name = name.to_sym
12
+ @type_expression = type
13
+ @description = description || options[:description]
14
+ @options = { required: false, default: NO_DEFAULT_VALUE }.merge(options)
15
+ @error_messages = []
16
+ end
17
+
18
+ def assign_value(value)
19
+ @user_provided_value = value
20
+ end
21
+
22
+ def default_value
23
+ return if @options[:default] == NO_DEFAULT_VALUE
24
+
25
+ @options[:default]
26
+ end
27
+
28
+ def required?
29
+ @options[:required]
30
+ end
31
+
32
+ def type
33
+ @type_expression
34
+ end
35
+
36
+ def validate!
37
+ validate_presence! && validate_type!
38
+ end
39
+
40
+ def value
41
+ @user_provided_value || default_value
42
+ end
43
+
44
+ private
45
+
46
+ def type_is_a_active_interactor_type?
47
+ type.is_a?(ActiveInteractor::Type::Base) || type.superclass == ActiveInteractor::Type::Base
48
+ end
49
+
50
+ def validate_presence!
51
+ return true unless required? && value.nil?
52
+
53
+ error_messages << :blank
54
+ end
55
+
56
+ def validate_type!
57
+ return true if value.nil?
58
+ return true if %i[any untyped].include?(type)
59
+
60
+ if type_is_a_active_interactor_type?
61
+ return true if type.valid?(value)
62
+
63
+ error_messages << :invalid
64
+ elsif value.is_a?(type)
65
+ return true
66
+ end
67
+
68
+ error_messages << :invalid
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveInteractor
4
+ module Context
5
+ module AttributeAssignment
6
+ extend ActiveSupport::Concern
7
+
8
+ def [](attribute_name)
9
+ read_attribute_value(attribute_name)
10
+ end
11
+
12
+ def []=(attribute_name, value)
13
+ assign_attribute_value(attribute_name, value)
14
+ end
15
+
16
+ protected
17
+
18
+ def assign_attribute_value(attribute_name, value)
19
+ attribute = attribute_set.find(attribute_name)
20
+ raise NoMethodError, "unknown attribute '#{attribute_name}' for #{self.class.name}" unless attribute
21
+
22
+ attribute.assign_value(value)
23
+ end
24
+
25
+ def assignment_method_missing(method_name, *arguments)
26
+ if arguments.length != 1
27
+ raise ArgumentError,
28
+ "wrong number of arguments (given #{arguments.length}, expected 1)"
29
+ end
30
+
31
+ assign_attribute_value(method_name, arguments.first)
32
+ end
33
+
34
+ def method_missing(method_id, *arguments)
35
+ return super unless respond_to_missing?(method_id)
36
+
37
+ method_name = method_id[/.*(?==\z)/m]
38
+ return assignment_method_missing(method_name, *arguments) if method_name
39
+
40
+ read_attribute_value(method_id)
41
+ end
42
+
43
+ def read_attribute_value(attribute_name)
44
+ attribute = attribute_set.find(attribute_name)
45
+ raise NoMethodError, "unknown attribute '#{attribute_name}' for #{self.class.name}" unless attribute
46
+
47
+ attribute.value
48
+ end
49
+
50
+ def respond_to_missing?(method_name, _include_private = false)
51
+ return true if attribute_set.attribute_names.include?(method_name.to_sym)
52
+ return true if attribute_set.attribute_names.include?(method_name[/.*(?==\z)/m]&.to_sym)
53
+
54
+ super
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveInteractor
4
+ module Context
5
+ module AttributeRegistration
6
+ extend ActiveSupport::Concern
7
+
8
+ module ClassMethods
9
+ protected
10
+
11
+ def attribute_set
12
+ @attribute_set ||= AttributeSet.new(self)
13
+ end
14
+ end
15
+
16
+ included do
17
+ extend ClassMethods
18
+ end
19
+
20
+ protected
21
+
22
+ def attribute_set
23
+ @attribute_set ||= AttributeSet.new(self, *self.class.send(:attribute_set).attributes.map(&:dup))
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveInteractor
4
+ module Context
5
+ class AttributeSet
6
+ def initialize(owner, *attributes)
7
+ @owner = owner
8
+ @set = {}
9
+ attributes.each { |attribute| @set[attribute.name] = attribute }
10
+ end
11
+
12
+ def add(*attribute_args)
13
+ attribute_options = attribute_args.extract_options!
14
+ attribute = Attribute.new(@owner, *attribute_args, **attribute_options)
15
+ @set[attribute.name] = attribute
16
+ end
17
+
18
+ def attribute_names
19
+ @set.keys
20
+ end
21
+
22
+ def attributes
23
+ @set.values
24
+ end
25
+
26
+ def find(attribute_name)
27
+ @set[attribute_name.to_sym]
28
+ end
29
+
30
+ def merge(attributes)
31
+ attributes.each { |attribute| @set[attribute.name] = attribute }
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveInteractor
4
+ module Context
5
+ class Base
6
+ include ActiveModel::Validations
7
+ include HasActiveModelErrors
8
+ include AttributeRegistration
9
+ include AttributeAssignment
10
+ include Type::HasTypes
11
+
12
+ attr_reader :errors
13
+
14
+ def initialize(attributes = {})
15
+ @errors = ActiveModel::Errors.new(self)
16
+ attribute_set.attributes.each do |attribute|
17
+ next unless attributes.with_indifferent_access.key?(attribute.name)
18
+
19
+ assign_attribute_value(attribute.name, attributes.with_indifferent_access[attribute.name])
20
+ end
21
+ end
22
+
23
+ def validate!
24
+ attribute_set.attributes.each do |attribute|
25
+ attribute.validate!
26
+ attribute.error_messages.each { |message| errors.add(attribute.name, message) }
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveInteractor
4
+ module Context
5
+ class Input < Base
6
+ def self.argument(*attribute_args)
7
+ attribute_set.add(*attribute_args)
8
+ end
9
+
10
+ def self.argument_names
11
+ attribute_set.attribute_names
12
+ end
13
+
14
+ def self.arguments
15
+ attribute_set.attributes
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveInteractor
4
+ module Context
5
+ class Output < Base
6
+ delegate :to_h, :to_hash, :to_json, to: :fields
7
+
8
+ def self.returns(*attribute_args)
9
+ attribute_set.add(*attribute_args)
10
+ end
11
+
12
+ def self.field_names
13
+ attribute_set.attribute_names
14
+ end
15
+
16
+ def self.fields
17
+ attribute_set.attributes
18
+ end
19
+
20
+ def fields
21
+ attribute_set.attributes.each_with_object({}) do |attribute, result|
22
+ result[attribute.name] = attribute.value
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveInteractor
4
+ module Context
5
+ class Runtime < Base
6
+ def initialize(attributes = {})
7
+ super
8
+ @table = {}
9
+ end
10
+
11
+ def attributes
12
+ clean = attribute_set.attributes.each_with_object({}) do |attribute, result|
13
+ result[attribute.name] = attribute.value
14
+ end
15
+ @table.merge(clean)
16
+ end
17
+
18
+ private
19
+
20
+ def assign_attribute_value(attribute_name, value)
21
+ attribute = attribute_set.find(attribute_name)
22
+ return super if attribute
23
+
24
+ assign_dirty_attribute_value(attribute_name, value)
25
+ end
26
+
27
+ def assign_dirty_attribute_value(attribute_name, value)
28
+ @table[attribute_name.to_sym] = value
29
+ end
30
+
31
+ def method_missing(method_id, *arguments)
32
+ method_name = method_id[/.*(?==\z)/m]
33
+ return assignment_method_missing(method_name, *arguments) if method_name
34
+
35
+ read_attribute_value(method_id)
36
+ end
37
+
38
+ def read_attribute_value(attribute_name)
39
+ attribute = attribute_set.find(attribute_name)
40
+ return super if attribute
41
+
42
+ read_dirty_attribute_value(attribute_name)
43
+ end
44
+
45
+ def read_dirty_attribute_value(attribute_name)
46
+ @table[attribute_name.to_sym]
47
+ end
48
+
49
+ def respond_to_missing?(_method_name, _include_private = false)
50
+ true
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveInteractor
4
+ module Context
5
+ extend ActiveSupport::Autoload
6
+
7
+ autoload :Attribute
8
+ autoload :AttributeRegistration
9
+ autoload :AttributeSet
10
+ autoload :AttributeAssignment
11
+ autoload :Base
12
+ autoload :Input
13
+ autoload :Output
14
+ autoload :Runtime
15
+ end
16
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveInteractor
4
+ module HasActiveModelErrors
5
+ extend ActiveSupport::Concern
6
+
7
+ module ClassMethods
8
+ def self.human_attribute_name(attribute, _options = {})
9
+ attribute.respond_to?(:to_s) ? attribute.to_s.humanize : attribute
10
+ end
11
+
12
+ def self.lookup_ancestors
13
+ [self]
14
+ end
15
+ end
16
+
17
+ included do
18
+ extend ActiveModel::Naming
19
+ extend ClassMethods
20
+
21
+ attr_reader :errors
22
+ end
23
+
24
+ def read_attribute_for_validation(attribute_name)
25
+ send(attribute_name.to_sym)
26
+ end
27
+ end
28
+ end
@@ -2,7 +2,9 @@
2
2
 
3
3
  module ActiveInteractor
4
4
  class Result
5
- extend ActiveModel::Naming
5
+ include HasActiveModelErrors
6
+
7
+ delegate :to_json, to: :to_hash
6
8
 
7
9
  STATUS = {
8
10
  success: 0,
@@ -26,14 +28,6 @@ module ActiveInteractor
26
28
  result
27
29
  end
28
30
 
29
- def human_attribute_name(attribute, _options = {})
30
- attribute.respond_to?(:to_s) ? attribute.to_s.humanize : attribute
31
- end
32
-
33
- def lookup_ancestors
34
- [self]
35
- end
36
-
37
31
  private
38
32
 
39
33
  def parse_errors(errors)
@@ -62,12 +56,21 @@ module ActiveInteractor
62
56
  alias failed? failure?
63
57
 
64
58
  def read_attribute_for_validation(attribute_name)
65
- data[attribute_name]
59
+ data.send(attribute_name.to_sym)
66
60
  end
67
61
 
68
62
  def success?
69
63
  @status == STATUS[:success]
70
64
  end
71
65
  alias successful? success?
66
+
67
+ def to_hash
68
+ {
69
+ success: success?,
70
+ errors: errors.to_hash,
71
+ data: data.to_hash
72
+ }
73
+ end
74
+ alias to_h to_hash
72
75
  end
73
76
  end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveInteractor
4
+ module Type
5
+ class Base
6
+ def self.valid?(value); end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveInteractor
4
+ module Type
5
+ class Boolean < Base
6
+ TRUE_VALUES = [true, 1, '1', 't', 'T', 'true', 'TRUE', 'on', 'ON', 'yes', 'YES'].freeze
7
+ FALSE_VALUES = [false, 0, '0', 'f', 'F', 'false', 'FALSE', 'off', 'OFF', 'no', 'NO'].freeze
8
+
9
+ def self.valid?(value)
10
+ (TRUE_VALUES + FALSE_VALUES).include?(value)
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveInteractor
4
+ module Type
5
+ module HasTypes
6
+ extend ActiveSupport::Concern
7
+
8
+ Boolean = ActiveInteractor::Type::Boolean
9
+
10
+ module ClassMethods
11
+ def any
12
+ :any
13
+ end
14
+
15
+ def list(type)
16
+ List.new(type)
17
+ end
18
+ alias array list
19
+
20
+ def union(*types)
21
+ Union.new(*types)
22
+ end
23
+
24
+ def untyped
25
+ :untyped
26
+ end
27
+ end
28
+
29
+ included do
30
+ extend ClassMethods
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveInteractor
4
+ module Type
5
+ class List < Base
6
+ def initialize(type) # rubocop:disable Lint/MissingSuper
7
+ @type = type
8
+ end
9
+
10
+ def valid?(value)
11
+ return false unless value.is_a?(Array)
12
+
13
+ value.all? do |element|
14
+ element.nil? || element.is_a?(@type) || (@type.is_a?(ActiveInteractor::Type::Base) && @type.valid?(element))
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ def value_is_a_valid_active_interactor_type?(_type, value)
21
+ unless @type.is_a?(ActiveInteractor::Type::Base) || @type.superclass == ActiveInteractor::Type::Base
22
+ return false
23
+ end
24
+
25
+ @type.valid?(value)
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveInteractor
4
+ module Type
5
+ class Union < Base
6
+ def initialize(*types) # rubocop:disable Lint/MissingSuper
7
+ @valid_types = types
8
+ end
9
+
10
+ def valid?(value)
11
+ return true if value.nil?
12
+
13
+ @valid_types.any? do |type|
14
+ value_is_a_valid_active_interactor_type?(type, value) || value.is_a?(type)
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ def value_is_a_valid_active_interactor_type?(type, value)
21
+ return false unless type.is_a?(ActiveInteractor::Type::Base) || type.superclass == ActiveInteractor::Type::Base
22
+
23
+ type.valid?(value)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveInteractor
4
+ module Type
5
+ extend ActiveSupport::Autoload
6
+
7
+ autoload :Base
8
+ autoload :Boolean
9
+ autoload :HasTypes
10
+ autoload :List
11
+ autoload :Union
12
+ end
13
+ end
@@ -10,5 +10,8 @@ module ActiveInteractor
10
10
  extend ActiveSupport::Autoload
11
11
 
12
12
  autoload :Base
13
+ autoload :Context
14
+ autoload :HasActiveModelErrors
13
15
  autoload :Result
16
+ autoload :Type
14
17
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activeinteractor
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0.alpha.2.1.0
4
+ version: 2.0.0.alpha.2.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aaron Allen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-10-11 00:00:00.000000000 Z
11
+ date: 2023-10-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activemodel
@@ -64,17 +64,33 @@ files:
64
64
  - README.md
65
65
  - lib/active_interactor.rb
66
66
  - lib/active_interactor/base.rb
67
+ - lib/active_interactor/context.rb
68
+ - lib/active_interactor/context/attribute.rb
69
+ - lib/active_interactor/context/attribute_assignment.rb
70
+ - lib/active_interactor/context/attribute_registration.rb
71
+ - lib/active_interactor/context/attribute_set.rb
72
+ - lib/active_interactor/context/base.rb
73
+ - lib/active_interactor/context/input.rb
74
+ - lib/active_interactor/context/output.rb
75
+ - lib/active_interactor/context/runtime.rb
67
76
  - lib/active_interactor/errors.rb
77
+ - lib/active_interactor/has_active_model_errors.rb
68
78
  - lib/active_interactor/result.rb
79
+ - lib/active_interactor/type.rb
80
+ - lib/active_interactor/type/base.rb
81
+ - lib/active_interactor/type/boolean.rb
82
+ - lib/active_interactor/type/has_types.rb
83
+ - lib/active_interactor/type/list.rb
84
+ - lib/active_interactor/type/union.rb
69
85
  - sig/active_interactor.rbs
70
86
  homepage: https://github.com/activeinteractor/activeinteractor
71
87
  licenses:
72
88
  - MIT
73
89
  metadata:
74
90
  bug_tracker_uri: https://github.com/activeinteractor/activeinteractor/issues
75
- changelog_uri: https://github.com/activeinteractor/activeinteractor/blob/v2.0.0-alpha.2.1.0/CHANGELOG.md
91
+ changelog_uri: https://github.com/activeinteractor/activeinteractor/blob/v2.0.0-alpha.2.3.0/CHANGELOG.md
76
92
  homepage_uri: https://github.com/activeinteractor/activeinteractor
77
- source_code_uri: https://github.com/activeinteractor/activeinteractor/tree/v2.0.0-alpha.2.1.0
93
+ source_code_uri: https://github.com/activeinteractor/activeinteractor/tree/v2.0.0-alpha.2.3.0
78
94
  wiki_uri: https://github.com/activeinteractor/activeinteractor/wiki
79
95
  rubygems_mfa_required: 'true'
80
96
  post_install_message: