servactory 2.5.0.rc2 → 2.5.0.rc4

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 (29) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/lib/generators/servactory/install_generator.rb +21 -0
  4. data/lib/generators/servactory/rspec_generator.rb +88 -0
  5. data/lib/generators/servactory/service_generator.rb +49 -0
  6. data/lib/generators/servactory/templates/services/application_service/base.rb +60 -0
  7. data/lib/generators/servactory/templates/services/application_service/exceptions.rb +11 -0
  8. data/lib/generators/servactory/templates/services/application_service/result.rb +5 -0
  9. data/lib/servactory/context/workspace/internals.rb +1 -1
  10. data/lib/servactory/info/dsl.rb +56 -4
  11. data/lib/servactory/maintenance/attributes/options/registrar.rb +1 -1
  12. data/lib/servactory/result.rb +1 -1
  13. data/lib/servactory/test_kit/rspec/helpers.rb +95 -0
  14. data/lib/servactory/test_kit/rspec/matchers/have_service_attribute_matchers/consists_of_matcher.rb +121 -0
  15. data/lib/servactory/test_kit/rspec/matchers/have_service_attribute_matchers/inclusion_matcher.rb +70 -0
  16. data/lib/servactory/test_kit/rspec/matchers/have_service_attribute_matchers/must_matcher.rb +61 -0
  17. data/lib/servactory/test_kit/rspec/matchers/have_service_attribute_matchers/types_matcher.rb +72 -0
  18. data/lib/servactory/test_kit/rspec/matchers/have_service_input_matcher.rb +203 -0
  19. data/lib/servactory/test_kit/rspec/matchers/have_service_input_matchers/default_matcher.rb +67 -0
  20. data/lib/servactory/test_kit/rspec/matchers/have_service_input_matchers/optional_matcher.rb +63 -0
  21. data/lib/servactory/test_kit/rspec/matchers/have_service_input_matchers/required_matcher.rb +78 -0
  22. data/lib/servactory/test_kit/rspec/matchers/have_service_input_matchers/valid_with_matcher.rb +233 -0
  23. data/lib/servactory/test_kit/rspec/matchers/have_service_internal_matcher.rb +148 -0
  24. data/lib/servactory/test_kit/rspec/matchers.rb +295 -0
  25. data/lib/servactory/test_kit/utils/faker.rb +78 -0
  26. data/lib/servactory/tool_kit/dynamic_options/format.rb +16 -12
  27. data/lib/servactory/version.rb +1 -1
  28. data/lib/servactory.rb +1 -0
  29. metadata +26 -7
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ce8eb77c5347f231539b115081d7706f6546830ccda8269e414ba05d097d3d85
4
- data.tar.gz: 3393084486439b10177a5a13eb4826f4b2ab19c21aefbe92a23aaa7c5ce0096c
3
+ metadata.gz: '03950ff6fd385cfe8ba4506f2eb4dc7c2dc94dcf838d2a10b783e25e6b290af6'
4
+ data.tar.gz: 64182d91fa7a9042cefb99ec422d3710761f8ab412a8b00184c324bae23debfb
5
5
  SHA512:
6
- metadata.gz: 4ae8873bb459d8bedac4d64cee6ca8f4049e57fd87f62e3842b47e5f20415196cc6470704e3d9fd1d2c380e75ae2ffdcd5f93ad5dfc87e677b259ef215d8974b
7
- data.tar.gz: c0a37302c68b09fa238656f88bcdd536bb81d1a5f47db60ac8de52d7455d534b3622cbcd5568474e7d303b86bb7a6f5219f1f7e5fa0c7a5a0833d955194406d9
6
+ metadata.gz: f85e47114458349c416a1a64d69098d29a67b8d837025acff53acc91885225c9ecab6036422fcd269793f5cdd22a90fe6ba0b8899074dde10837a6e0989b0cc3
7
+ data.tar.gz: 96bfba7a9fdc2c763ed8ab4b355a966053fa2a1f0e7bd365bbaa7f51dda089edc30ed00a61566e25252fa12df07eac7e327fb814d5dcb6b77d474f5e3718e1e7
data/README.md CHANGED
@@ -14,7 +14,7 @@
14
14
 
15
15
  <p align="center">
16
16
  <a href="https://rubygems.org/gems/servactory"><img src="https://img.shields.io/gem/v/servactory?logo=rubygems&logoColor=fff" alt="Gem version"></a>
17
- <a href="https://github.com/afuno/servactory/releases"><img src="https://img.shields.io/github/release-date/afuno/servactory" alt="Release Date"></a>
17
+ <a href="https://github.com/servactory/servactory/releases"><img src="https://img.shields.io/github/release-date/servactory/servactory" alt="Release Date"></a>
18
18
  </p>
19
19
 
20
20
  ## Documentation
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators/base"
4
+
5
+ module Servactory
6
+ module Generators
7
+ class InstallGenerator < Rails::Generators::Base
8
+ source_root File.expand_path("templates", __dir__)
9
+
10
+ def copy_services
11
+ directory "services/application_service", "app/services/application_service"
12
+ end
13
+
14
+ def copy_locales
15
+ %i[en ru].each do |locale|
16
+ copy_file "../../../../config/locales/#{locale}.yml", "config/locales/servactory.#{locale}.yml"
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators/named_base"
4
+
5
+ module Servactory
6
+ module Generators
7
+ class RspecGenerator < Rails::Generators::NamedBase
8
+ source_root File.expand_path("templates", __dir__)
9
+
10
+ argument :attributes, type: :array, default: [], banner: "input_name"
11
+
12
+ def create_service
13
+ create_file "app/services/#{file_path}_spec.rb" do
14
+ <<~RUBY
15
+ # frozen_string_literal: true
16
+
17
+ RSpec.describe #{class_name}, type: :service do
18
+ pending "add some examples to (or delete) \#{__FILE__}"
19
+
20
+ # let(:attributes) do
21
+ # {
22
+ #{input_attribute_draw}
23
+ # }
24
+ # end
25
+ #
26
+ #{input_let_draw}
27
+ #
28
+ # describe "validation" do
29
+ # describe "inputs" do
30
+ #{input_validation_draw}
31
+ # end
32
+ #
33
+ # describe "internals" do
34
+ # it { expect { perform }.to have_internal(:some_data).type(String) }
35
+ # end
36
+ # end
37
+ #
38
+ # describe ".call!" do
39
+ # subject(:perform) { described_class.call!(**attributes) }
40
+ #
41
+ # describe "and the data required for work is also valid" do
42
+ # it { expect(perform).to be_success_service }
43
+ #
44
+ # # ...
45
+ # end
46
+ #
47
+ # describe "but the data required for work is invalid" do
48
+ # # Provide a reason why the data is invalid and then use this:
49
+ # it { expect(perform).to be_failure_service }
50
+ #
51
+ # # ...
52
+ # end
53
+ # end
54
+ end
55
+ RUBY
56
+ end
57
+ end
58
+
59
+ def input_attribute_draw
60
+ input_names.map do |input_name|
61
+ <<~RUBY.strip
62
+ # #{input_name}: #{input_name}
63
+ RUBY
64
+ end.join(",\n ")
65
+ end
66
+
67
+ def input_let_draw
68
+ input_names.map do |input_name|
69
+ <<~RUBY.strip
70
+ # let(:#{input_name}) { "Some value" }
71
+ RUBY
72
+ end.join("\n ")
73
+ end
74
+
75
+ def input_validation_draw
76
+ input_names.map do |input_name|
77
+ <<~RUBY.strip
78
+ # it { expect { perform }.to have_input(:#{input_name}).valid_with(attributes).type(String).required }
79
+ RUBY
80
+ end.join("\n ")
81
+ end
82
+
83
+ def input_names
84
+ @input_names ||= attributes_names
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators/named_base"
4
+
5
+ module Servactory
6
+ module Generators
7
+ class ServiceGenerator < Rails::Generators::NamedBase
8
+ source_root File.expand_path("templates", __dir__)
9
+
10
+ argument :attributes, type: :array, default: [], banner: "input_name"
11
+
12
+ def create_service # rubocop:disable Metrics/MethodLength
13
+ create_file "app/services/#{file_path}.rb" do
14
+ <<~RUBY
15
+ # frozen_string_literal: true
16
+
17
+ class #{class_name} < ApplicationService::Base
18
+ #{input_draw}
19
+
20
+ output :result, type: Symbol
21
+
22
+ make :something
23
+
24
+ private
25
+
26
+ def something
27
+ # Write your code here
28
+
29
+ outputs.result = :done
30
+ end
31
+ end
32
+ RUBY
33
+ end
34
+ end
35
+
36
+ def input_draw
37
+ input_names.map do |input_name|
38
+ <<~RUBY.squish
39
+ input :#{input_name}, type: String
40
+ RUBY
41
+ end.join("\n ")
42
+ end
43
+
44
+ def input_names
45
+ @input_names ||= attributes_names
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ApplicationService
4
+ class Base
5
+ include Servactory::DSL
6
+
7
+ # More information: https://servactory.com/guide/extensions
8
+ # include Servactory::DSL.with_extensions(
9
+ # ApplicationService::Extensions::YourExtension::DSL
10
+ # )
11
+
12
+ fail_on! ActiveRecord::RecordInvalid
13
+
14
+ # More information: https://servactory.com/guide/configuration
15
+ configuration do
16
+ input_exception_class ApplicationService::Exceptions::Input
17
+ internal_exception_class ApplicationService::Exceptions::Internal
18
+ output_exception_class ApplicationService::Exceptions::Output
19
+
20
+ failure_class ApplicationService::Exceptions::Failure
21
+
22
+ result_class ApplicationService::Result
23
+
24
+ # input_option_helpers(
25
+ # [
26
+ # Servactory::ToolKit::DynamicOptions::Format.use,
27
+ # Servactory::ToolKit::DynamicOptions::Min.use,
28
+ # Servactory::ToolKit::DynamicOptions::Max.use,
29
+ # ApplicationService::DynamicOptions::CustomEq.use
30
+ # ]
31
+ # )
32
+
33
+ # internal_option_helpers(
34
+ # [
35
+ # Servactory::ToolKit::DynamicOptions::Format.use,
36
+ # Servactory::ToolKit::DynamicOptions::Min.use,
37
+ # Servactory::ToolKit::DynamicOptions::Max.use,
38
+ # ApplicationService::DynamicOptions::CustomEq.use
39
+ # ]
40
+ # )
41
+
42
+ # output_option_helpers(
43
+ # [
44
+ # Servactory::ToolKit::DynamicOptions::Format.use,
45
+ # Servactory::ToolKit::DynamicOptions::Min.use,
46
+ # Servactory::ToolKit::DynamicOptions::Max.use,
47
+ # ApplicationService::DynamicOptions::CustomEq.use
48
+ # ]
49
+ # )
50
+
51
+ # collection_mode_class_names [ActiveRecord::Relation]
52
+
53
+ # hash_mode_class_names [CustomHash]
54
+
55
+ # action_shortcuts %i[assign build create save]
56
+
57
+ # action_aliases %i[do_it!]
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ApplicationService
4
+ module Exceptions
5
+ class Input < Servactory::Exceptions::Input; end
6
+ class Output < Servactory::Exceptions::Output; end
7
+ class Internal < Servactory::Exceptions::Internal; end
8
+
9
+ class Failure < Servactory::Exceptions::Failure; end
10
+ end
11
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ApplicationService
4
+ class Result < Servactory::Result; end
5
+ end
@@ -75,7 +75,7 @@ module Servactory
75
75
  internal_name: name
76
76
  )
77
77
 
78
- raise @context.class.config.input_exception_class.new(message: message_text)
78
+ raise @context.class.config.internal_exception_class.new(message: message_text)
79
79
  end
80
80
  end
81
81
  end
@@ -8,11 +8,63 @@ module Servactory
8
8
  end
9
9
 
10
10
  module ClassMethods
11
- def info
11
+ def info # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
12
12
  Servactory::Info::Result.new(
13
- inputs: collection_of_inputs.names,
14
- internals: collection_of_internals.names,
15
- outputs: collection_of_outputs.names
13
+ inputs: collection_of_inputs.to_h do |input|
14
+ work = input.class::Work.new(input)
15
+ consists_of = input.collection_of_options.find_by(name: :consists_of)
16
+ inclusion = input.collection_of_options.find_by(name: :inclusion)
17
+ must = input.collection_of_options.find_by(name: :must)
18
+
19
+ [
20
+ input.name,
21
+ {
22
+ work: work,
23
+ types: input.types,
24
+ required: input.required,
25
+ default: input.default,
26
+ consists_of: consists_of.body,
27
+ inclusion: inclusion.body,
28
+ must: must.body
29
+ }
30
+ ]
31
+ end,
32
+
33
+ internals: collection_of_internals.to_h do |internal|
34
+ work = internal.class::Work.new(internal)
35
+ consists_of = internal.collection_of_options.find_by(name: :consists_of)
36
+ inclusion = internal.collection_of_options.find_by(name: :inclusion)
37
+ must = internal.collection_of_options.find_by(name: :must)
38
+
39
+ [
40
+ internal.name,
41
+ {
42
+ work: work,
43
+ types: internal.types,
44
+ consists_of: consists_of.body,
45
+ inclusion: inclusion.body,
46
+ must: must.body
47
+ }
48
+ ]
49
+ end,
50
+
51
+ outputs: collection_of_outputs.to_h do |output|
52
+ work = output.class::Work.new(output)
53
+ consists_of = output.collection_of_options.find_by(name: :consists_of)
54
+ inclusion = output.collection_of_options.find_by(name: :inclusion)
55
+ must = output.collection_of_options.find_by(name: :must)
56
+
57
+ [
58
+ output.name,
59
+ {
60
+ work: work,
61
+ types: output.types,
62
+ consists_of: consists_of.body,
63
+ inclusion: inclusion.body,
64
+ must: must.body
65
+ }
66
+ ]
67
+ end
16
68
  )
17
69
  end
18
70
  end
@@ -213,7 +213,7 @@ module Servactory
213
213
  )
214
214
  end
215
215
 
216
- def register_prepare_option # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
216
+ def register_prepare_option # rubocop:disable Metrics/MethodLength
217
217
  collection << Servactory::Maintenance::Attributes::Option.new(
218
218
  name: :prepare,
219
219
  attribute: @attribute,
@@ -110,7 +110,7 @@ module Servactory
110
110
  ########################################################################
111
111
 
112
112
  def rescue_no_method_error_with(exception:) # rubocop:disable Metrics/MethodLength
113
- raise exception if @context.blank?
113
+ raise exception if @context.blank? || @context.instance_of?(Servactory::TestKit::Result)
114
114
 
115
115
  raise @context.class.config.failure_class.new(
116
116
  type: :base,
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Servactory
4
+ module TestKit
5
+ module Rspec
6
+ module Helpers
7
+ def allow_service_as_success!(service_class_name, &block)
8
+ allow_service!(service_class_name, :as_success, &block)
9
+ end
10
+
11
+ def allow_service_as_success(service_class_name, &block)
12
+ allow_service(service_class_name, :as_success, &block)
13
+ end
14
+
15
+ def allow_service_as_failure!(service_class_name, &block)
16
+ allow_service!(service_class_name, :as_failure, &block)
17
+ end
18
+
19
+ def allow_service_as_failure(service_class_name, &block)
20
+ allow_service(service_class_name, :as_failure, &block)
21
+ end
22
+
23
+ ########################################################################
24
+
25
+ def allow_service!(service_class_name, result_type, &block)
26
+ allow_servactory(service_class_name, :call!, result_type, &block)
27
+ end
28
+
29
+ def allow_service(service_class_name, result_type, &block)
30
+ allow_servactory(service_class_name, :call, result_type, &block)
31
+ end
32
+
33
+ ########################################################################
34
+
35
+ # rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
36
+ def allow_servactory(service_class_name, method_call, result_type)
37
+ method_call = method_call.to_sym
38
+ result_type = result_type.to_sym
39
+
40
+ unless %i[call! call].include?(method_call)
41
+ raise ArgumentError, "Invalid value for `method_call`. Must be `:call!` or `:call`."
42
+ end
43
+
44
+ unless %i[as_success as_failure].include?(result_type)
45
+ raise ArgumentError, "Invalid value for `result_type`. Must be `:as_success` or `:as_failure`."
46
+ end
47
+
48
+ as_success = result_type == :as_success
49
+ with_bang = method_call == :call!
50
+
51
+ if block_given? && !yield.is_a?(Hash) && as_success
52
+ raise ArgumentError, "Invalid value for block. Must be a Hash with attributes."
53
+ end
54
+
55
+ and_return_or_raise = with_bang && !as_success ? :and_raise : :and_return
56
+
57
+ result = if block_given?
58
+ if yield.is_a?(Hash)
59
+ yield
60
+ else
61
+ { with_bang ? :exception : :error => yield }
62
+ end
63
+ else
64
+ {}
65
+ end
66
+
67
+ # puts
68
+ # puts <<~RUBY
69
+ # allow(#{service_class_name}).to(
70
+ # receive(#{method_call.inspect})
71
+ # .public_send(
72
+ # #{and_return_or_raise.inspect},
73
+ # Servactory::TestKit::Result.public_send(#{result_type.inspect}, #{result})
74
+ # )
75
+ # )
76
+ # RUBY
77
+ # puts
78
+
79
+ allow(service_class_name).to(
80
+ receive(method_call)
81
+ .public_send(
82
+ and_return_or_raise,
83
+ if as_success
84
+ Servactory::TestKit::Result.public_send(result_type, **result)
85
+ else
86
+ Servactory::TestKit::Result.public_send(result_type, result)
87
+ end
88
+ )
89
+ )
90
+ end
91
+ # rubocop:enable Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Servactory
4
+ module TestKit
5
+ module Rspec
6
+ module Matchers
7
+ module HaveServiceAttributeMatchers
8
+ class ConsistsOfMatcher
9
+ attr_reader :missing_option
10
+
11
+ def initialize(described_class, attribute_type, attribute_name, option_types, consists_of_types,
12
+ custom_message)
13
+ @described_class = described_class
14
+ @attribute_type = attribute_type
15
+ @attribute_type_plural = attribute_type.to_s.pluralize.to_sym
16
+ @attribute_name = attribute_name
17
+ @option_types = option_types
18
+ @consists_of_types = consists_of_types
19
+ @custom_message = custom_message
20
+
21
+ @attribute_data = described_class.info.public_send(attribute_type_plural).fetch(attribute_name)
22
+
23
+ @missing_option = ""
24
+ end
25
+
26
+ def description
27
+ result = "consists_of: "
28
+ result + consists_of_types.join(", ")
29
+ end
30
+
31
+ def matches?(subject)
32
+ if submatcher_passes?(subject)
33
+ true
34
+ else
35
+ @missing_option = build_missing_option
36
+
37
+ false
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ attr_reader :described_class,
44
+ :attribute_type,
45
+ :attribute_type_plural,
46
+ :attribute_name,
47
+ :option_types,
48
+ :consists_of_types,
49
+ :custom_message,
50
+ :attribute_data
51
+
52
+ def submatcher_passes?(_subject)
53
+ attribute_consists_of = Array(attribute_data.fetch(:consists_of).fetch(:type) || [])
54
+
55
+ matched = attribute_consists_of.difference(consists_of_types).empty?
56
+
57
+ matched &&= attribute_consists_of_message.casecmp(custom_message).zero? if custom_message.present?
58
+
59
+ matched
60
+ end
61
+
62
+ def attribute_consists_of_message # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
63
+ attribute_consists_of_message = attribute_data.fetch(:consists_of).fetch(:message)
64
+
65
+ if attribute_consists_of_message.nil?
66
+ I18n.t(
67
+ "servactory.#{attribute_type_plural}.validations.required.default_error.for_collection",
68
+ service_class_name: described_class.name,
69
+ "#{attribute_type}_name": attribute_name
70
+ )
71
+ elsif attribute_consists_of_message.is_a?(Proc)
72
+ input_work = attribute_data.fetch(:work)
73
+
74
+ attribute_consists_of_message.call(
75
+ input: input_work,
76
+ expected_type: String,
77
+ given_type: Servactory::TestKit::FakeType.new.class.name
78
+ )
79
+ else
80
+ attribute_consists_of_message
81
+ end
82
+ end
83
+
84
+ def build_missing_option # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
85
+ attribute_consists_of = Array(attribute_data.fetch(:consists_of).fetch(:type) || [])
86
+
87
+ unless attribute_consists_of.difference(consists_of_types).empty?
88
+ text_about_types = option_types.size > 1 ? "the following types" : "type"
89
+
90
+ return <<~MESSAGE
91
+ should be a collection consisting of #{text_about_types}
92
+
93
+ expected #{consists_of_types.inspect}
94
+ got #{attribute_consists_of.inspect}
95
+ MESSAGE
96
+ end
97
+
98
+ if custom_message.present? && !attribute_consists_of_message.casecmp(custom_message).zero?
99
+ return <<~MESSAGE
100
+ should be a collection with a message
101
+
102
+ expected #{custom_message.inspect}
103
+ got #{attribute_consists_of_message.inspect}
104
+ MESSAGE
105
+ end
106
+
107
+ <<~MESSAGE
108
+ got an unexpected case when using `consists_of`
109
+
110
+ Please try to build an example based on the documentation.
111
+ Or report your problem to us:
112
+
113
+ https://github.com/servactory/servactory/issues
114
+ MESSAGE
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Servactory
4
+ module TestKit
5
+ module Rspec
6
+ module Matchers
7
+ module HaveServiceAttributeMatchers
8
+ class InclusionMatcher
9
+ attr_reader :missing_option
10
+
11
+ def initialize(described_class, attribute_type, attribute_name, values)
12
+ @described_class = described_class
13
+ @attribute_type = attribute_type
14
+ @attribute_type_plural = attribute_type.to_s.pluralize.to_sym
15
+ @attribute_name = attribute_name
16
+ @values = values
17
+
18
+ @attribute_data = described_class.info.public_send(attribute_type_plural).fetch(attribute_name)
19
+
20
+ @missing_option = ""
21
+ end
22
+
23
+ def description
24
+ "inclusion: #{values.join(', ')}"
25
+ end
26
+
27
+ def matches?(subject)
28
+ if submatcher_passes?(subject)
29
+ true
30
+ else
31
+ @missing_option = build_missing_option
32
+
33
+ false
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ attr_reader :described_class,
40
+ :attribute_type,
41
+ :attribute_type_plural,
42
+ :attribute_name,
43
+ :values,
44
+ :attribute_data
45
+
46
+ def submatcher_passes?(_subject)
47
+ attribute_inclusion = attribute_data.fetch(:inclusion)
48
+ attribute_inclusion_in = attribute_inclusion.fetch(:in)
49
+
50
+ attribute_inclusion_in.difference(values).empty? &&
51
+ values.difference(attribute_inclusion_in).empty?
52
+ end
53
+
54
+ def build_missing_option
55
+ attribute_inclusion = attribute_data.fetch(:inclusion)
56
+ attribute_inclusion_in = attribute_inclusion.fetch(:in)
57
+
58
+ <<~MESSAGE
59
+ should include the expected values
60
+
61
+ expected #{values.inspect}
62
+ got #{attribute_inclusion_in.inspect}
63
+ MESSAGE
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end