clean-architecture 2.0.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (118) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/rspec.yml +21 -0
  3. data/.gitignore +1 -0
  4. data/.ruby-version +1 -1
  5. data/CHANGELOG.md +42 -0
  6. data/Gemfile +1 -0
  7. data/README.md +423 -4
  8. data/clean-architecture.gemspec +9 -5
  9. data/generate_require_files.rb +1 -0
  10. data/lib/clean-architecture.rb +1 -0
  11. data/lib/clean_architecture/adapters/all.rb +1 -0
  12. data/lib/clean_architecture/adapters/attribute_hash_base.rb +1 -0
  13. data/lib/clean_architecture/all.rb +3 -0
  14. data/lib/clean_architecture/builders/abstract_active_record_entity_builder.rb +124 -0
  15. data/lib/clean_architecture/builders/all.rb +6 -0
  16. data/lib/clean_architecture/checks/all.rb +1 -0
  17. data/lib/clean_architecture/checks/authorization.rb +1 -0
  18. data/lib/clean_architecture/entities/all.rb +1 -0
  19. data/lib/clean_architecture/entities/failure_details.rb +2 -1
  20. data/lib/clean_architecture/entities/targeted_parameters.rb +1 -0
  21. data/lib/clean_architecture/entities/untargeted_parameters.rb +1 -0
  22. data/lib/clean_architecture/interfaces/all.rb +1 -0
  23. data/lib/clean_architecture/interfaces/authorization_parameters.rb +1 -0
  24. data/lib/clean_architecture/interfaces/base_parameters.rb +1 -0
  25. data/lib/clean_architecture/interfaces/jsonable.rb +1 -0
  26. data/lib/clean_architecture/interfaces/success_payload.rb +1 -0
  27. data/lib/clean_architecture/interfaces/targeted_parameters.rb +1 -0
  28. data/lib/clean_architecture/interfaces/use_case.rb +1 -0
  29. data/lib/clean_architecture/interfaces/use_case_actor.rb +1 -0
  30. data/lib/clean_architecture/interfaces/use_case_target.rb +1 -0
  31. data/lib/clean_architecture/matchers/all.rb +1 -0
  32. data/lib/clean_architecture/matchers/use_case_result.rb +1 -0
  33. data/lib/clean_architecture/queries/all.rb +1 -0
  34. data/lib/clean_architecture/queries/http_failure_code.rb +1 -0
  35. data/lib/clean_architecture/queries/http_success_code.rb +1 -0
  36. data/lib/clean_architecture/serializers/all.rb +1 -0
  37. data/lib/clean_architecture/serializers/html_response_from_result.rb +1 -0
  38. data/lib/clean_architecture/serializers/json_response_from_result.rb +1 -0
  39. data/lib/clean_architecture/serializers/success_collection_payload.rb +1 -0
  40. data/lib/clean_architecture/serializers/success_payload.rb +1 -0
  41. data/lib/clean_architecture/types.rb +2 -1
  42. data/lib/clean_architecture/use_cases/abstract_use_case.rb +62 -0
  43. data/lib/clean_architecture/use_cases/all.rb +10 -0
  44. data/lib/clean_architecture/use_cases/contract.rb +9 -0
  45. data/lib/clean_architecture/use_cases/errors.rb +57 -0
  46. data/lib/clean_architecture/use_cases/form.rb +116 -0
  47. data/lib/clean_architecture/use_cases/parameters.rb +42 -0
  48. data/lib/clean_architecture/version.rb +2 -1
  49. data/sorbet/config +2 -0
  50. data/sorbet/rbi/gems/activemodel.rbi +74 -0
  51. data/sorbet/rbi/gems/activesupport.rbi +440 -0
  52. data/sorbet/rbi/gems/ast.rbi +47 -0
  53. data/sorbet/rbi/gems/axiom-types.rbi +159 -0
  54. data/sorbet/rbi/gems/byebug.rbi +1039 -0
  55. data/sorbet/rbi/gems/codeclimate-engine-rb.rbi +123 -0
  56. data/sorbet/rbi/gems/coderay.rbi +91 -0
  57. data/sorbet/rbi/gems/coercible.rbi +156 -0
  58. data/sorbet/rbi/gems/concurrent-ruby.rbi +1587 -0
  59. data/sorbet/rbi/gems/descendants_tracker.rbi +17 -0
  60. data/sorbet/rbi/gems/docile.rbi +31 -0
  61. data/sorbet/rbi/gems/dry-configurable.rbi +89 -0
  62. data/sorbet/rbi/gems/dry-container.rbi +88 -0
  63. data/sorbet/rbi/gems/dry-core.rbi +79 -0
  64. data/sorbet/rbi/gems/dry-equalizer.rbi +25 -0
  65. data/sorbet/rbi/gems/dry-inflector.rbi +72 -0
  66. data/sorbet/rbi/gems/dry-initializer.rbi +209 -0
  67. data/sorbet/rbi/gems/dry-logic.rbi +304 -0
  68. data/sorbet/rbi/gems/dry-matcher.rbi +33 -0
  69. data/sorbet/rbi/gems/dry-monads.rbi +508 -0
  70. data/sorbet/rbi/gems/dry-schema.rbi +790 -0
  71. data/sorbet/rbi/gems/dry-struct.rbi +165 -0
  72. data/sorbet/rbi/gems/dry-types.rbi +688 -0
  73. data/sorbet/rbi/gems/dry-validation.rbi +284 -0
  74. data/sorbet/rbi/gems/duckface-interfaces.rbi +93 -0
  75. data/sorbet/rbi/gems/equalizer.rbi +22 -0
  76. data/sorbet/rbi/gems/i18n.rbi +132 -0
  77. data/sorbet/rbi/gems/ice_nine.rbi +66 -0
  78. data/sorbet/rbi/gems/jaro_winkler.rbi +14 -0
  79. data/sorbet/rbi/gems/kwalify.rbi +339 -0
  80. data/sorbet/rbi/gems/method_source.rbi +63 -0
  81. data/sorbet/rbi/gems/parallel.rbi +81 -0
  82. data/sorbet/rbi/gems/parser.rbi +1293 -0
  83. data/sorbet/rbi/gems/pry-byebug.rbi +149 -0
  84. data/sorbet/rbi/gems/pry.rbi +1964 -0
  85. data/sorbet/rbi/gems/psych.rbi +462 -0
  86. data/sorbet/rbi/gems/rainbow.rbi +117 -0
  87. data/sorbet/rbi/gems/rake.rbi +634 -0
  88. data/sorbet/rbi/gems/rb-readline.rbi +766 -0
  89. data/sorbet/rbi/gems/reek.rbi +1066 -0
  90. data/sorbet/rbi/gems/rspec-core.rbi +1658 -0
  91. data/sorbet/rbi/gems/rspec-expectations.rbi +430 -0
  92. data/sorbet/rbi/gems/rspec-mocks.rbi +815 -0
  93. data/sorbet/rbi/gems/rspec-support.rbi +268 -0
  94. data/sorbet/rbi/gems/rspec.rbi +14 -0
  95. data/sorbet/rbi/gems/rubocop-rspec.rbi +875 -0
  96. data/sorbet/rbi/gems/rubocop.rbi +7014 -0
  97. data/sorbet/rbi/gems/ruby-progressbar.rbi +304 -0
  98. data/sorbet/rbi/gems/simplecov-html.rbi +30 -0
  99. data/sorbet/rbi/gems/simplecov.rbi +225 -0
  100. data/sorbet/rbi/gems/stackprof.rbi +51 -0
  101. data/sorbet/rbi/gems/thread_safe.rbi +81 -0
  102. data/sorbet/rbi/gems/timecop.rbi +97 -0
  103. data/sorbet/rbi/gems/unicode-display_width.rbi +16 -0
  104. data/sorbet/rbi/gems/virtus.rbi +421 -0
  105. data/sorbet/rbi/hidden-definitions/errors.txt +7332 -0
  106. data/sorbet/rbi/hidden-definitions/hidden.rbi +17521 -0
  107. data/sorbet/rbi/sorbet-typed/lib/activemodel/all/activemodel.rbi +422 -0
  108. data/sorbet/rbi/sorbet-typed/lib/activesupport/>=6.0.0.rc1/activesupport.rbi +23 -0
  109. data/sorbet/rbi/sorbet-typed/lib/activesupport/all/activesupport.rbi +625 -0
  110. data/sorbet/rbi/sorbet-typed/lib/bundler/all/bundler.rbi +8684 -0
  111. data/sorbet/rbi/sorbet-typed/lib/minitest/all/minitest.rbi +99 -0
  112. data/sorbet/rbi/sorbet-typed/lib/rainbow/all/rainbow.rbi +254 -0
  113. data/sorbet/rbi/sorbet-typed/lib/ruby/all/gem.rbi +4222 -0
  114. data/sorbet/rbi/sorbet-typed/lib/ruby/all/open3.rbi +111 -0
  115. data/sorbet/rbi/sorbet-typed/lib/ruby/all/resolv.rbi +543 -0
  116. data/sorbet/rbi/todo.rbi +12 -0
  117. metadata +156 -24
  118. data/Gemfile.lock +0 -187
@@ -19,13 +19,17 @@ Gem::Specification.new do |spec|
19
19
  end
20
20
  spec.require_paths = ['lib']
21
21
 
22
- spec.add_dependency 'dry-matcher', '~> 0.0'
23
- spec.add_dependency 'dry-monads', '~> 1.0'
24
- spec.add_dependency 'dry-struct', '~> 0.0'
25
- spec.add_dependency 'dry-types', '~> 0.0'
22
+ spec.add_dependency 'activemodel', '>= 5'
23
+ spec.add_dependency 'activesupport', '>= 5'
24
+ spec.add_dependency 'dry-matcher'
25
+ spec.add_dependency 'dry-monads'
26
+ spec.add_dependency 'dry-struct'
27
+ spec.add_dependency 'dry-types'
28
+ spec.add_dependency 'dry-validation', '>= 1.0.0'
26
29
  spec.add_dependency 'duckface-interfaces', '~> 0.0'
30
+ spec.add_dependency 'sorbet-runtime'
27
31
 
28
- spec.add_development_dependency 'bundler', '~> 1.13'
32
+ spec.add_development_dependency 'bundler'
29
33
  spec.add_development_dependency 'rake', '~> 12.0'
30
34
  spec.add_development_dependency 'rspec', '~> 3.0'
31
35
  end
@@ -1,3 +1,4 @@
1
+ # typed: true
1
2
  # frozen_string_literal: true
2
3
 
3
4
  def directories_in(root)
@@ -1,3 +1,4 @@
1
+ # typed: strong
1
2
  # frozen_string_literal: true
2
3
 
3
4
  require 'clean_architecture/all'
@@ -1,3 +1,4 @@
1
+ # typed: strong
1
2
  # frozen_string_literal: true
2
3
 
3
4
  # THIS FILE IS AUTOGENERATED AND SHOULD NOT BE MANUALLY MODIFIED
@@ -1,3 +1,4 @@
1
+ # typed: true
1
2
  # frozen_string_literal: true
2
3
 
3
4
  require 'time'
@@ -1,14 +1,17 @@
1
+ # typed: strong
1
2
  # frozen_string_literal: true
2
3
 
3
4
  # THIS FILE IS AUTOGENERATED AND SHOULD NOT BE MANUALLY MODIFIED
4
5
 
5
6
  require 'clean_architecture/adapters/all'
7
+ require 'clean_architecture/builders/all'
6
8
  require 'clean_architecture/checks/all'
7
9
  require 'clean_architecture/entities/all'
8
10
  require 'clean_architecture/interfaces/all'
9
11
  require 'clean_architecture/matchers/all'
10
12
  require 'clean_architecture/queries/all'
11
13
  require 'clean_architecture/serializers/all'
14
+ require 'clean_architecture/use_cases/all'
12
15
 
13
16
  require 'clean_architecture/types'
14
17
  require 'clean_architecture/version'
@@ -0,0 +1,124 @@
1
+ # typed: false
2
+ # frozen_string_literal: true
3
+
4
+ module CleanArchitecture
5
+ module Builders
6
+ # Helps to take an instance of an AR model and wrap it up in the given Entity
7
+ # Any columns from the AR model that do not directly map to an attribute on the Entity
8
+ # can be specified by overriding #attributes_for_entity.
9
+ class AbstractActiveRecordEntityBuilder
10
+ # @param [Class] A Dry::Struct based entity that this builder will construct instances of
11
+ def self.acts_as_builder_for_entity(entity_class)
12
+ @has_many_builders = []
13
+ @belongs_to_builders = []
14
+
15
+ define_singleton_method :has_many_builders do
16
+ @has_many_builders
17
+ end
18
+
19
+ define_singleton_method :belongs_to_builders do
20
+ @belongs_to_builders
21
+ end
22
+
23
+ define_method :entity_class do
24
+ entity_class
25
+ end
26
+
27
+ private :entity_class
28
+ end
29
+
30
+ def self.has_many(relation_name, use:)
31
+ @has_many_builders << [relation_name, use]
32
+ end
33
+
34
+ def self.belongs_to(relation_name, use:)
35
+ @belongs_to_builders << [relation_name, use]
36
+ end
37
+
38
+ # @param [ActiveRecord::Base] An ActiveRecord model to map to the entity
39
+ def initialize(ar_model_instance)
40
+ @ar_model_instance = ar_model_instance
41
+ end
42
+
43
+ def build
44
+ entity_class.new(all_attributes_for_entity)
45
+ end
46
+
47
+ private
48
+
49
+ attr_reader :ar_model_instance
50
+
51
+ def entity_attribute_names
52
+ @entity_attributes ||= begin
53
+ if entity_class.respond_to?(:schema) # Dry::Struct
54
+ schema_keys = entity_class.schema.keys
55
+ elsif entity_class.respond_to?(:decorator) # T::Struct
56
+ schema_keys = entity_class.decorator.props.keys
57
+ else
58
+ raise 'Cannot determine schema format'
59
+ end
60
+ first_key = schema_keys.first
61
+ if first_key.is_a?(Symbol)
62
+ schema_keys
63
+ elsif first_key.respond_to?(:name)
64
+ schema_keys.map(&:name)
65
+ else
66
+ raise 'Cannot determine schema format'
67
+ end
68
+ end
69
+ end
70
+
71
+ def ar_model_instance_attributes
72
+ @ar_model_instance_attributes ||= @ar_model_instance.attributes
73
+ end
74
+
75
+ def symbolized_ar_model_instance_attributes
76
+ @symbolized_ar_model_instance_attributes ||= Hash[
77
+ ar_model_instance_attributes.map{|(key, value)| [key.to_sym, value]}
78
+ ]
79
+ end
80
+
81
+ def ar_attributes_for_entity
82
+ symbolized_ar_model_instance_attributes.slice(*entity_attribute_names)
83
+ end
84
+
85
+ def attributes_for_belongs_to_relations
86
+ self.class.belongs_to_builders.map do |belongs_to_builder_config|
87
+ relation_name, builder_class = belongs_to_builder_config
88
+ relation = @ar_model_instance.public_send(relation_name)
89
+
90
+ [
91
+ relation_name,
92
+ relation ? builder_class.new(relation).build : nil
93
+ ]
94
+ end.to_h
95
+ end
96
+
97
+ def attributes_for_has_many_relations
98
+ self.class.has_many_builders.map do |has_many_builder_config|
99
+ relation_name, builder_class = has_many_builder_config
100
+ relations = @ar_model_instance.public_send(relation_name)
101
+ built_relations = relations.map do |relation|
102
+ builder_class.new(relation).build
103
+ end
104
+
105
+ [
106
+ relation_name,
107
+ built_relations
108
+ ]
109
+ end.to_h
110
+ end
111
+
112
+ def attributes_for_entity
113
+ {}
114
+ end
115
+
116
+ def all_attributes_for_entity
117
+ ar_attributes_for_entity
118
+ .merge(attributes_for_belongs_to_relations)
119
+ .merge(attributes_for_has_many_relations)
120
+ .merge(attributes_for_entity)
121
+ end
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,6 @@
1
+ # typed: strong
2
+ # frozen_string_literal: true
3
+
4
+ # THIS FILE IS AUTOGENERATED AND SHOULD NOT BE MANUALLY MODIFIED
5
+
6
+ require 'clean_architecture/builders/abstract_active_record_entity_builder'
@@ -1,3 +1,4 @@
1
+ # typed: strong
1
2
  # frozen_string_literal: true
2
3
 
3
4
  # THIS FILE IS AUTOGENERATED AND SHOULD NOT BE MANUALLY MODIFIED
@@ -1,3 +1,4 @@
1
+ # typed: true
1
2
  # frozen_string_literal: true
2
3
 
3
4
  require 'dry/monads/all'
@@ -1,3 +1,4 @@
1
+ # typed: strong
1
2
  # frozen_string_literal: true
2
3
 
3
4
  # THIS FILE IS AUTOGENERATED AND SHOULD NOT BE MANUALLY MODIFIED
@@ -1,3 +1,4 @@
1
+ # typed: false
1
2
  # frozen_string_literal: true
2
3
 
3
4
  require 'clean_architecture/types'
@@ -15,7 +16,7 @@ module CleanArchitecture
15
16
 
16
17
  attribute :type, FailureTypes
17
18
  attribute :message, Types::Strict::String
18
- attribute :other_properties, Types::Strict::Hash.default({})
19
+ attribute :other_properties, Types::Strict::Hash.default({}.freeze)
19
20
 
20
21
  def self.from_array(array)
21
22
  new(message: array.map(&:to_s).join(', '), other_properties: {}, type: 'error')
@@ -1,3 +1,4 @@
1
+ # typed: true
1
2
  # frozen_string_literal: true
2
3
 
3
4
  require 'clean_architecture/interfaces/targeted_parameters'
@@ -1,3 +1,4 @@
1
+ # typed: true
1
2
  # frozen_string_literal: true
2
3
 
3
4
  require 'clean_architecture/interfaces/base_parameters'
@@ -1,3 +1,4 @@
1
+ # typed: strong
1
2
  # frozen_string_literal: true
2
3
 
3
4
  # THIS FILE IS AUTOGENERATED AND SHOULD NOT BE MANUALLY MODIFIED
@@ -1,3 +1,4 @@
1
+ # typed: false
1
2
  # frozen_string_literal: true
2
3
 
3
4
  require 'clean_architecture/interfaces/base_parameters'
@@ -1,3 +1,4 @@
1
+ # typed: false
1
2
  # frozen_string_literal: true
2
3
 
3
4
  require 'duckface'
@@ -1,3 +1,4 @@
1
+ # typed: false
1
2
  # frozen_string_literal: true
2
3
 
3
4
  require 'duckface'
@@ -1,3 +1,4 @@
1
+ # typed: false
1
2
  # frozen_string_literal: true
2
3
 
3
4
  require 'duckface'
@@ -1,3 +1,4 @@
1
+ # typed: false
1
2
  # frozen_string_literal: true
2
3
 
3
4
  require 'clean_architecture/interfaces/base_parameters'
@@ -1,3 +1,4 @@
1
+ # typed: false
1
2
  # frozen_string_literal: true
2
3
 
3
4
  require 'duckface'
@@ -1,3 +1,4 @@
1
+ # typed: false
1
2
  # frozen_string_literal: true
2
3
 
3
4
  require 'duckface'
@@ -1,3 +1,4 @@
1
+ # typed: false
1
2
  # frozen_string_literal: true
2
3
 
3
4
  require 'duckface'
@@ -1,3 +1,4 @@
1
+ # typed: strong
1
2
  # frozen_string_literal: true
2
3
 
3
4
  # THIS FILE IS AUTOGENERATED AND SHOULD NOT BE MANUALLY MODIFIED
@@ -1,3 +1,4 @@
1
+ # typed: true
1
2
  # frozen_string_literal: true
2
3
 
3
4
  require 'clean_architecture/entities/failure_details'
@@ -1,3 +1,4 @@
1
+ # typed: strong
1
2
  # frozen_string_literal: true
2
3
 
3
4
  # THIS FILE IS AUTOGENERATED AND SHOULD NOT BE MANUALLY MODIFIED
@@ -1,3 +1,4 @@
1
+ # typed: true
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module CleanArchitecture
@@ -1,3 +1,4 @@
1
+ # typed: true
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module CleanArchitecture
@@ -1,3 +1,4 @@
1
+ # typed: strong
1
2
  # frozen_string_literal: true
2
3
 
3
4
  # THIS FILE IS AUTOGENERATED AND SHOULD NOT BE MANUALLY MODIFIED
@@ -1,3 +1,4 @@
1
+ # typed: true
1
2
  # frozen_string_literal: true
2
3
 
3
4
  require 'clean_architecture/entities/failure_details'
@@ -1,3 +1,4 @@
1
+ # typed: true
1
2
  # frozen_string_literal: true
2
3
 
3
4
  require 'clean_architecture/entities/failure_details'
@@ -1,3 +1,4 @@
1
+ # typed: true
1
2
  # frozen_string_literal: true
2
3
 
3
4
  require 'duckface'
@@ -1,3 +1,4 @@
1
+ # typed: true
1
2
  # frozen_string_literal: true
2
3
 
3
4
  require 'duckface'
@@ -1,7 +1,8 @@
1
+ # typed: ignore
1
2
  # frozen_string_literal: true
2
3
 
3
4
  require 'dry-types'
4
5
 
5
6
  module Types
6
- include Dry::Types.module
7
+ include Dry.Types()
7
8
  end
@@ -0,0 +1,62 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ require 'dry/monads/result'
5
+ require 'dry/validation'
6
+ require 'dry/matcher/result_matcher'
7
+ require 'clean_architecture/use_cases/errors'
8
+ require 'clean_architecture/use_cases/parameters'
9
+ require 'clean_architecture/use_cases/contract'
10
+ require 'clean_architecture/entities/failure_details'
11
+
12
+ module CleanArchitecture
13
+ module UseCases
14
+ class AbstractUseCase
15
+ extend Forwardable
16
+
17
+ @contract = nil
18
+
19
+ def self.contract(base_contract = Contract)
20
+ @contract ||= begin
21
+ Class.new(base_contract, &Proc.new)
22
+ end
23
+ end
24
+
25
+ def self.parameters(params)
26
+ raise 'You must define a contract first' if @contract.nil?
27
+
28
+ context = params.fetch(:context, {})
29
+ Parameters.new(
30
+ context,
31
+ contract.new(context).call(params)
32
+ )
33
+ end
34
+
35
+ def initialize(params)
36
+ @params = params
37
+ end
38
+
39
+ def result
40
+ raise NotImplementedError
41
+ end
42
+
43
+ private
44
+
45
+ DEFAULT_FAILURE_TYPE = 'error'
46
+
47
+ def fail_with_error_message(message, failure_type = DEFAULT_FAILURE_TYPE)
48
+ new_errors = Errors.new(nil, failure_type)
49
+ new_errors.add(:base, message)
50
+ Dry::Monads::Failure(new_errors)
51
+ end
52
+
53
+ def result_of_validating_params
54
+ @params.to_monad
55
+ end
56
+
57
+ def context(key)
58
+ @params.context(key)
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,10 @@
1
+ # typed: strong
2
+ # frozen_string_literal: true
3
+
4
+ # THIS FILE IS AUTOGENERATED AND SHOULD NOT BE MANUALLY MODIFIED
5
+
6
+ require 'clean_architecture/use_cases/abstract_use_case'
7
+ require 'clean_architecture/use_cases/contract'
8
+ require 'clean_architecture/use_cases/errors'
9
+ require 'clean_architecture/use_cases/form'
10
+ require 'clean_architecture/use_cases/parameters'