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.
- checksums.yaml +4 -4
- data/.github/workflows/rspec.yml +21 -0
- data/.gitignore +1 -0
- data/.ruby-version +1 -1
- data/CHANGELOG.md +42 -0
- data/Gemfile +1 -0
- data/README.md +423 -4
- data/clean-architecture.gemspec +9 -5
- data/generate_require_files.rb +1 -0
- data/lib/clean-architecture.rb +1 -0
- data/lib/clean_architecture/adapters/all.rb +1 -0
- data/lib/clean_architecture/adapters/attribute_hash_base.rb +1 -0
- data/lib/clean_architecture/all.rb +3 -0
- data/lib/clean_architecture/builders/abstract_active_record_entity_builder.rb +124 -0
- data/lib/clean_architecture/builders/all.rb +6 -0
- data/lib/clean_architecture/checks/all.rb +1 -0
- data/lib/clean_architecture/checks/authorization.rb +1 -0
- data/lib/clean_architecture/entities/all.rb +1 -0
- data/lib/clean_architecture/entities/failure_details.rb +2 -1
- data/lib/clean_architecture/entities/targeted_parameters.rb +1 -0
- data/lib/clean_architecture/entities/untargeted_parameters.rb +1 -0
- data/lib/clean_architecture/interfaces/all.rb +1 -0
- data/lib/clean_architecture/interfaces/authorization_parameters.rb +1 -0
- data/lib/clean_architecture/interfaces/base_parameters.rb +1 -0
- data/lib/clean_architecture/interfaces/jsonable.rb +1 -0
- data/lib/clean_architecture/interfaces/success_payload.rb +1 -0
- data/lib/clean_architecture/interfaces/targeted_parameters.rb +1 -0
- data/lib/clean_architecture/interfaces/use_case.rb +1 -0
- data/lib/clean_architecture/interfaces/use_case_actor.rb +1 -0
- data/lib/clean_architecture/interfaces/use_case_target.rb +1 -0
- data/lib/clean_architecture/matchers/all.rb +1 -0
- data/lib/clean_architecture/matchers/use_case_result.rb +1 -0
- data/lib/clean_architecture/queries/all.rb +1 -0
- data/lib/clean_architecture/queries/http_failure_code.rb +1 -0
- data/lib/clean_architecture/queries/http_success_code.rb +1 -0
- data/lib/clean_architecture/serializers/all.rb +1 -0
- data/lib/clean_architecture/serializers/html_response_from_result.rb +1 -0
- data/lib/clean_architecture/serializers/json_response_from_result.rb +1 -0
- data/lib/clean_architecture/serializers/success_collection_payload.rb +1 -0
- data/lib/clean_architecture/serializers/success_payload.rb +1 -0
- data/lib/clean_architecture/types.rb +2 -1
- data/lib/clean_architecture/use_cases/abstract_use_case.rb +62 -0
- data/lib/clean_architecture/use_cases/all.rb +10 -0
- data/lib/clean_architecture/use_cases/contract.rb +9 -0
- data/lib/clean_architecture/use_cases/errors.rb +57 -0
- data/lib/clean_architecture/use_cases/form.rb +116 -0
- data/lib/clean_architecture/use_cases/parameters.rb +42 -0
- data/lib/clean_architecture/version.rb +2 -1
- data/sorbet/config +2 -0
- data/sorbet/rbi/gems/activemodel.rbi +74 -0
- data/sorbet/rbi/gems/activesupport.rbi +440 -0
- data/sorbet/rbi/gems/ast.rbi +47 -0
- data/sorbet/rbi/gems/axiom-types.rbi +159 -0
- data/sorbet/rbi/gems/byebug.rbi +1039 -0
- data/sorbet/rbi/gems/codeclimate-engine-rb.rbi +123 -0
- data/sorbet/rbi/gems/coderay.rbi +91 -0
- data/sorbet/rbi/gems/coercible.rbi +156 -0
- data/sorbet/rbi/gems/concurrent-ruby.rbi +1587 -0
- data/sorbet/rbi/gems/descendants_tracker.rbi +17 -0
- data/sorbet/rbi/gems/docile.rbi +31 -0
- data/sorbet/rbi/gems/dry-configurable.rbi +89 -0
- data/sorbet/rbi/gems/dry-container.rbi +88 -0
- data/sorbet/rbi/gems/dry-core.rbi +79 -0
- data/sorbet/rbi/gems/dry-equalizer.rbi +25 -0
- data/sorbet/rbi/gems/dry-inflector.rbi +72 -0
- data/sorbet/rbi/gems/dry-initializer.rbi +209 -0
- data/sorbet/rbi/gems/dry-logic.rbi +304 -0
- data/sorbet/rbi/gems/dry-matcher.rbi +33 -0
- data/sorbet/rbi/gems/dry-monads.rbi +508 -0
- data/sorbet/rbi/gems/dry-schema.rbi +790 -0
- data/sorbet/rbi/gems/dry-struct.rbi +165 -0
- data/sorbet/rbi/gems/dry-types.rbi +688 -0
- data/sorbet/rbi/gems/dry-validation.rbi +284 -0
- data/sorbet/rbi/gems/duckface-interfaces.rbi +93 -0
- data/sorbet/rbi/gems/equalizer.rbi +22 -0
- data/sorbet/rbi/gems/i18n.rbi +132 -0
- data/sorbet/rbi/gems/ice_nine.rbi +66 -0
- data/sorbet/rbi/gems/jaro_winkler.rbi +14 -0
- data/sorbet/rbi/gems/kwalify.rbi +339 -0
- data/sorbet/rbi/gems/method_source.rbi +63 -0
- data/sorbet/rbi/gems/parallel.rbi +81 -0
- data/sorbet/rbi/gems/parser.rbi +1293 -0
- data/sorbet/rbi/gems/pry-byebug.rbi +149 -0
- data/sorbet/rbi/gems/pry.rbi +1964 -0
- data/sorbet/rbi/gems/psych.rbi +462 -0
- data/sorbet/rbi/gems/rainbow.rbi +117 -0
- data/sorbet/rbi/gems/rake.rbi +634 -0
- data/sorbet/rbi/gems/rb-readline.rbi +766 -0
- data/sorbet/rbi/gems/reek.rbi +1066 -0
- data/sorbet/rbi/gems/rspec-core.rbi +1658 -0
- data/sorbet/rbi/gems/rspec-expectations.rbi +430 -0
- data/sorbet/rbi/gems/rspec-mocks.rbi +815 -0
- data/sorbet/rbi/gems/rspec-support.rbi +268 -0
- data/sorbet/rbi/gems/rspec.rbi +14 -0
- data/sorbet/rbi/gems/rubocop-rspec.rbi +875 -0
- data/sorbet/rbi/gems/rubocop.rbi +7014 -0
- data/sorbet/rbi/gems/ruby-progressbar.rbi +304 -0
- data/sorbet/rbi/gems/simplecov-html.rbi +30 -0
- data/sorbet/rbi/gems/simplecov.rbi +225 -0
- data/sorbet/rbi/gems/stackprof.rbi +51 -0
- data/sorbet/rbi/gems/thread_safe.rbi +81 -0
- data/sorbet/rbi/gems/timecop.rbi +97 -0
- data/sorbet/rbi/gems/unicode-display_width.rbi +16 -0
- data/sorbet/rbi/gems/virtus.rbi +421 -0
- data/sorbet/rbi/hidden-definitions/errors.txt +7332 -0
- data/sorbet/rbi/hidden-definitions/hidden.rbi +17521 -0
- data/sorbet/rbi/sorbet-typed/lib/activemodel/all/activemodel.rbi +422 -0
- data/sorbet/rbi/sorbet-typed/lib/activesupport/>=6.0.0.rc1/activesupport.rbi +23 -0
- data/sorbet/rbi/sorbet-typed/lib/activesupport/all/activesupport.rbi +625 -0
- data/sorbet/rbi/sorbet-typed/lib/bundler/all/bundler.rbi +8684 -0
- data/sorbet/rbi/sorbet-typed/lib/minitest/all/minitest.rbi +99 -0
- data/sorbet/rbi/sorbet-typed/lib/rainbow/all/rainbow.rbi +254 -0
- data/sorbet/rbi/sorbet-typed/lib/ruby/all/gem.rbi +4222 -0
- data/sorbet/rbi/sorbet-typed/lib/ruby/all/open3.rbi +111 -0
- data/sorbet/rbi/sorbet-typed/lib/ruby/all/resolv.rbi +543 -0
- data/sorbet/rbi/todo.rbi +12 -0
- metadata +156 -24
- data/Gemfile.lock +0 -187
data/clean-architecture.gemspec
CHANGED
@@ -19,13 +19,17 @@ Gem::Specification.new do |spec|
|
|
19
19
|
end
|
20
20
|
spec.require_paths = ['lib']
|
21
21
|
|
22
|
-
spec.add_dependency '
|
23
|
-
spec.add_dependency '
|
24
|
-
spec.add_dependency 'dry-
|
25
|
-
spec.add_dependency 'dry-
|
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'
|
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
|
data/generate_require_files.rb
CHANGED
data/lib/clean-architecture.rb
CHANGED
@@ -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
|
@@ -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')
|
@@ -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'
|