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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cff6e5d81156369ad42cbb55c06b1931b4949da5c1c3c1a80a2425d3307b459f
4
- data.tar.gz: 6fa2e337acd93963c6fa915f70a4109b81ec2540721b086416a275b981593eac
3
+ metadata.gz: 79fccd9c446b769e45c8e7c4a6f378789cd33ae10a299e7966186c6b8f8161e1
4
+ data.tar.gz: 566434a7e22887b2228811569bb95c4dd6e252453bec836860f5437584c8850e
5
5
  SHA512:
6
- metadata.gz: ec3751be4de839c3887ec1f4ca3a7ed3f7c6abe3068cdccfa12976f93cf8882416731e74be99507e555607889f1521145b17d46e435cfa9702370bca3492f0fc
7
- data.tar.gz: 46d4eb2641a76e284265d54c870ecc695381befd52c3f786eee87703aa5081cb8fdb64fee34aedb38de0dcf9dd0e4c87920bd7b6b6c6bf36010c2903b1ed2e4d
6
+ metadata.gz: 121377ec84ca3bc904248256b38022d206edb19e149c025be35d66296df48ac9fd4f54ee1c1924d52140a07533bd5384f9d0a2825b897a5b603f9c51414bca6e
7
+ data.tar.gz: 4e5cb96519180360d657833b2c853e41023bf363d249e3eaf2468cf72f1fcccc541706602dfe251838cae46cf39a1addea9a99c6e21812c06e3d81255f3b0003
@@ -0,0 +1,21 @@
1
+ name: RSpec Test Suite
2
+
3
+ on: [push]
4
+
5
+ jobs:
6
+ build:
7
+ runs-on: ubuntu-latest
8
+ steps:
9
+ - name: Checkout branch
10
+ uses: actions/checkout@v1
11
+ - name: Set up Ruby
12
+ uses: actions/setup-ruby@v1
13
+ with:
14
+ ruby-version: 2.5.5
15
+ - name: Install system packages
16
+ run: |
17
+ command -v bundler || gem install bundler
18
+ - name: Install gems
19
+ run: bundle install --jobs $(nproc) --retry 3
20
+ - name: Run RSpec test suite
21
+ run: bundle exec rspec spec
data/.gitignore CHANGED
@@ -1,2 +1,3 @@
1
1
  coverage
2
2
  tags
3
+ /Gemfile.lock
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 2.4.2
1
+ 2.5.1
data/CHANGELOG.md CHANGED
@@ -1,3 +1,45 @@
1
+ 3.0.0
2
+
3
+ * Add sorbet and support for Sorbet T::Struct
4
+
5
+ 2.6.1
6
+
7
+ * Update dependency on dry-validation
8
+
9
+ 2.6.0
10
+
11
+ * Bring `UseCases::AbstractUseCase` into line with dry-validation 1.0 & co.
12
+ * Use cases now have a 'contract', params act as per normal
13
+ * Predicates have been replaced with macros, they can still be shared
14
+ * Context variables passed into the use case parameters are accessible within the use case as `context(:my_context)`
15
+ * There is no need to redefine `initialize` in use cases, you can access context with `#context` and params via `#result_of_validating_params`
16
+
17
+ 2.5.0
18
+
19
+ * Remove restrictions on dry-rb gem
20
+
21
+ 2.4.0
22
+
23
+ * Introduce `UseCases::AbstractUseCase` to make it easier to create use cases with built in
24
+ validation.
25
+ * Add `UseCases::Form` to help map HTTP parameters to a use case's parameter object.
26
+
27
+ 2.3.1
28
+
29
+ * Fixed an issue with optional belongs_to relations in `AbstractActiveRecordEntityBuilder`
30
+
31
+ 2.3.0
32
+
33
+ * `AbstractActiveRecordEntityBuilder` now has a DSL for streamlining the use of builders for `has_many` and `belongs_to` relations.
34
+
35
+ 2.2.0
36
+
37
+ * Made `AbstractActiveRecordEntityBuilder` work with dry-struct >= 0.0.6.
38
+
39
+ 2.1.0
40
+
41
+ * Added `AbstractActiveRecordEntityBuilder` to help map ActiveRecord instances to `Dry::Struct` based entities.
42
+
1
43
  2.0.0
2
44
 
3
45
  * Support collections for use case targets and success payloads
data/Gemfile CHANGED
@@ -15,6 +15,7 @@ group :development, :test do
15
15
  gem 'rspec'
16
16
  gem 'rubocop'
17
17
  gem 'rubocop-rspec'
18
+ gem 'sorbet'
18
19
  gem 'stackprof'
19
20
  gem 'timecop'
20
21
  end
data/README.md CHANGED
@@ -3,6 +3,33 @@
3
3
  This gem provides helper interfaces and classes to assist in the construction of application with
4
4
  Clean Architecture, as described in [Robert Martin's seminal book](https://www.amazon.com/gp/product/0134494164).
5
5
 
6
+ Table of Contents
7
+ =================
8
+
9
+ Generated by https://github.com/ekalinin/github-markdown-toc/blob/master/gh-md-toc
10
+
11
+ * [Installation](#installation)
12
+ * [Philosophy](#philosophy)
13
+ * [Screaming architecture - use cases as an organisational principle](#screaming-architecture---use-cases-as-an-organisational-principle)
14
+ * [Design principles](#design-principles)
15
+ * [SRP - The Single Responsibility principle](#srp---the-single-responsibility-principle)
16
+ * [OCP - The Open/Closed Principle, LSP - The Liskov Substitution Principle and DIP - The Dependency Inversion Principle](#ocp---the-openclosed-principle-lsp---the-liskov-substitution-principle-and-dip---the-dependency-inversion-principle)
17
+ * [ISP - The Interface Segregation Principle](#isp---the-interface-segregation-principle)
18
+ * [Component cohesion](#component-cohesion)
19
+ * [REP - The Reuse/Release Equivalence Principle, CCP - The Common Closure Principle & CRP - The Common Reuse Principle](#rep---the-reuserelease-equivalence-principle-ccp---the-common-closure-principle--crp---the-common-reuse-principle)
20
+ * [Component coupling](#component-coupling)
21
+ * [ADP - The Acyclic Dependencies Principle](#adp---the-acyclic-dependencies-principle)
22
+ * [SDP - The Stable Dependencies Principle](#sdp---the-stable-dependencies-principle)
23
+ * [SAP - The Stable Abstractions Principle](#sap---the-stable-abstractions-principle)
24
+ * [Structure](#structure)
25
+ * [Practical suggestions for implementation](#practical-suggestions-for-implementation)
26
+ * [Conventions](#conventions)
27
+ * [Result objects](#result-objects)
28
+ * [Idiomatic FP](#idiomatic-fp)
29
+ * [Multiple bind operations](#multiple-bind-operations)
30
+ * [Transactions](#transactions)
31
+ * [Helper classes](#helper-classes)
32
+
6
33
  ## Installation
7
34
 
8
35
  Add this line to your application's Gemfile:
@@ -16,15 +43,13 @@ And then execute:
16
43
  $ bundle install
17
44
  $ bundle binstubs clean-architecture
18
45
 
19
- ## Usage
46
+ ## Philosophy
20
47
 
21
48
  The intention of this gem is to help you build applications that are built from the use case down,
22
49
  and decisions about I/O can be deferred until the last possible moment. It relies heavily on the
23
50
  [duckface-interfaces](https://github.com/samuelgiles/duckface) gem to enforce interface
24
51
  implementation.
25
52
 
26
- ## Clean architecture principles
27
-
28
53
  ### Screaming architecture - use cases as an organisational principle
29
54
 
30
55
  Uncle Bob suggests that your source code organisation should allow developers to easily find a listing of all use cases your application provides. Here's an example of how this might look in a
@@ -128,7 +153,9 @@ We satisfy the SAP by:
128
153
 
129
154
  - Thinking hard about the methods and parameters we specify in our interfaces. Are they solving for a general problem? Are we likely to have to change them when requirements change, and how we can avoid that?
130
155
 
131
- ## Practical suggestions for implementation
156
+ ## Structure
157
+
158
+ ### Practical suggestions for implementation
132
159
 
133
160
  * The code that manages your inputs (e.g. a Rails controller) instantiates a persistence layer
134
161
  object
@@ -166,3 +193,395 @@ We satisfy the SAP by:
166
193
  ```
167
194
  use_case = MyBankingApplication::UseCases::RetailCustomerMakesADeposit.new(input_port)
168
195
  ```
196
+
197
+ ## Conventions
198
+
199
+ ### Result objects
200
+
201
+ We make use of the [Dry-Rb](https://dry-rb.org/) collection of Gems to
202
+ provide better control flow instead of relying on `raise` and `rescue`.
203
+ Specifically, we use:
204
+
205
+ - https://dry-rb.org/gems/dry-matcher/result-matcher/
206
+ - https://dry-rb.org/gems/dry-monads/1.0/result/
207
+
208
+ ### Idiomatic FP
209
+
210
+ #### Multiple `bind` operations
211
+
212
+ When you want to bind or chain multiple method calls using the
213
+ previous return value, consider using [Do
214
+ Notation](https://dry-rb.org/gems/dry-monads/1.0/do-notation/)
215
+
216
+ This is inspired by the Haskell do-notation which lets you go from
217
+ writing this:
218
+
219
+ ```haskell
220
+ action1
221
+ >>=
222
+ (\ x1 -> action2
223
+ >>=
224
+ (\ x2 -> mk_action3 x1 x2 ))
225
+ ```
226
+
227
+ to this:
228
+
229
+ ```haskell
230
+ do
231
+ x1 <- action1
232
+ x2 <- action2
233
+ mk_action3 x1 x2
234
+ ```
235
+
236
+ #### Transactions
237
+
238
+ If you don't want to manually handle the wiring between multiple
239
+ Success/Failure objects, you can use the
240
+ [dry-transaction](https://dry-rb.org/gems/dry-transaction/) gem which
241
+ abstracts this away so that you just need to define steps, and deal with
242
+ the input from the output of the previous result.
243
+
244
+ ```ruby
245
+ require "dry/transaction"
246
+
247
+ class CreateUser
248
+ include Dry::Transaction
249
+
250
+ step :validate
251
+ step :create
252
+
253
+ private
254
+
255
+ def validate(input)
256
+ # returns Success(valid_data) or Failure(validation)
257
+ end
258
+
259
+ def create(input)
260
+ # returns Success(user)
261
+ end
262
+ end
263
+ ```
264
+
265
+ # Helper classes
266
+
267
+ The gem comes with some useful classes that can help you achieve a cleaner architecture with less work.
268
+
269
+ ## Active Record Entity Builder
270
+
271
+ Maintain a separation between your business entities and your database requires the use of gateways that build your entities from records in the database.
272
+
273
+ For Rails applications using ActiveRecord this can involve a bunch of boilerplate code where your simply creating a hash from the attributes of the database record & using those to create a new instance of your struct based entity.
274
+
275
+ The `CleanArchitecture::Builders::AbstractActiveRecordEntityBuilder` can help remove this boilerplate by handling 99% of the mapping for you.
276
+
277
+ ### Usage:
278
+
279
+ Create a builder class and have it inherit from `CleanArchitecture::Builders::AbstractActiveRecordEntityBuilder`, from here you need to point the builder at the entity you wish for it to create instances of with `.acts_as_builder_for_entity`, from there its just a case of instantiating the builder with an instance of your AR model and calling `#build`.
280
+
281
+ Relations are handled easily, just define a builder for said entity and then declare the relation with `has_many :relation_name, use: MyBuilderClass` and `belongs_to :relation_name, use: MyBuilderClass`.
282
+
283
+ If you wish to override the attributes used to construct the entity you can define a `#attributes_for_entity` method with said attributes in a hash, this can be useful for complex relations, files and other attributes that don't map perfectly from the database to your struct based entity.
284
+
285
+ ```ruby
286
+ class Person < ApplicationRecord
287
+ has_many :interests, autosave: true, dependent: :destroy
288
+ belongs_to :father
289
+ end
290
+
291
+ class Entities::Person < Dry::Struct
292
+ attribute :forename, Types::Strict::String
293
+ attribute :surname, Types::Strict::String
294
+ attribute :father, Types.Instance(Person)
295
+ attribute :interests, Types.Array(Types.Instance(Interest))
296
+ attribute :birth_month, Types::Strict::String
297
+ end
298
+
299
+ class PersonBuilder < CleanArchitecture::Builders::AbstractActiveRecordEntityBuilder
300
+ acts_as_builder_for_entity Entities::Person
301
+
302
+ has_many :interests, use: InterestBuilder
303
+ belongs_to :father, use: PersonBuilder
304
+
305
+ def attributes_for_entity
306
+ { birth_month: @ar_model_instance.birth_date.month }
307
+ end
308
+ end
309
+ ```
310
+
311
+ ## Use cases with contracts, errors & form objects
312
+
313
+ Finding a way to map HTTP parameters to parameters within your use case, pass back & display validation errors and coerce types are difficult to replace when moving away from the typical `MyMode.update(params.permit(:some_param))` that a standard Rails app might use.
314
+
315
+ The `CleanArchitecture::UseCases` component contains some useful classes for helping to make replacing these functions a little easier whilst still maintaining good boundaries.
316
+
317
+ The 'contracts' use `dry-validation` and support all options included the sharing of contracts between use cases, more information can be found here: https://dry-rb.org/gems/dry-validation/. Don't be afraid of the seemingly magical `.contract` method that use cases have, all its doing is creating an anonymous `Class` and storing it in a class variable, the methods existence is justified by how it enables form objects & helps to standardise the process a little.
318
+
319
+ `dry-validation` itself is actually built on top of `dry-schema`, as such most of the useful information on predicates can be found here: https://dry-rb.org/gems/dry-schema/basics/built-in-predicates/
320
+
321
+ ### Usage:
322
+
323
+ Usage is fairly simple, use cases define a contract, parameters handed to a use case are validated, at which point if the parameters aren't valid you'll get an `Errors` object back within a `Failure`, if they are you'll get a success with a `Parameters`.
324
+
325
+ Here is an example use case for a user updating their username that does a pre-flight check to ensure the username is available:
326
+
327
+ ```ruby
328
+ module MyBusinessDomain
329
+ module UseCases
330
+ class UserUpdatesNickname < CleanArchitecture::UseCases::AbstractUseCase
331
+ contract do
332
+ option :my_persistence_object
333
+
334
+ params do
335
+ required(:user_id).filled(:id)
336
+ required(:nickname).filled(:str)
337
+ end
338
+
339
+ rule(:nickname).validate(:not_already_taken)
340
+
341
+ register_macro(:not_already_taken) do
342
+ unless my_persistence_object.username_is_available?(values[key_name])
343
+ key.failure('is already taken')
344
+ end
345
+ end
346
+ end
347
+
348
+ extend Forwardable
349
+ include Dry::Monads::Do.for(:result)
350
+
351
+ def result
352
+ valid_params = yield result_of_validating_params
353
+ context(:my_persistence_object).result_of_updating_nickname(
354
+ valid_params[:id],
355
+ valid_params[:nickname]
356
+ )
357
+ end
358
+ end
359
+ end
360
+ end
361
+ ```
362
+
363
+ You could imagine a page with a simple form asking the user to enter their new username and you may want this form to display that message if the username isn't available. The `Form` class can be used to assist with the mapping of http parameters to the use case parameters. Best of all since the forms aren't tied to the use cases they can live within your web app far away from your business logic.
364
+
365
+ ```ruby
366
+ module MyWebApp
367
+ class NicknameUpdateForm < CleanArchitecture::UseCases::Form
368
+ acts_as_form_for MyBusinessDomain::UseCases::UserUpdatesNickname
369
+ end
370
+ end
371
+ ```
372
+
373
+ The standard Rails form builder works with instances of `Form`.
374
+
375
+ Putting these both together a controller action would look like the below example.
376
+
377
+ - A new instance of the use case is passed a parameter object built from `params`.
378
+ - If the use case is successful we'll show a flash message.
379
+ - If unsuccessful we'll take the returned `Errors` (`Entities::FailureDetails` and plain strings are also handled by `#with_errors`) and add them to the form with `#with_errors` and re-render the `edit` action.
380
+
381
+ ```ruby
382
+ module MyWebApp
383
+ class NicknamesController < ApplicationController
384
+ def update
385
+ Dry::Matcher::ResultMatcher.call(user_updates_nickname.result) do |matcher|
386
+ matcher.success do |_|
387
+ flash[:success] = 'Nickname successfully updated'
388
+ redirect_to action: :edit
389
+ end
390
+
391
+ matcher.failure do |errors|
392
+ @form = nickname_update_form.with_errors(errors)
393
+ render :edit
394
+ end
395
+ end
396
+ end
397
+
398
+ private
399
+
400
+ def user_updates_nickname
401
+ MyBusinessDomain::UseCases::UserUpdatesNickname.new(nickname_update_form.to_parameter_object)
402
+ end
403
+
404
+ def nickname_update_form
405
+ @nickname_update_form ||= NicknameUpdateForm.new(
406
+ params: params.permit(:user_id, :nickname),
407
+ context: { my_persistence_object: MyPersistence.new }
408
+ )
409
+ end
410
+ end
411
+ end
412
+ ```
413
+
414
+ There won't always be a complex form in front of a use case, sometimes its just one parameter, using the above example example you could easily execute the use case with a manually constructed parameter object if it was say an API only endpoint:
415
+
416
+ ```ruby
417
+ module MyWebApp
418
+ class NicknamesController < ApplicationController
419
+ def update
420
+ Dry::Matcher::ResultMatcher.call(user_updates_nickname.result) do |matcher|
421
+ matcher.success do |_|
422
+ render json: { success: true }
423
+ end
424
+
425
+ matcher.failure do |errors|
426
+ render json: { errors: errors.full_messages }
427
+ end
428
+ end
429
+ end
430
+
431
+ private
432
+
433
+ def user_updates_nickname
434
+ MyBusinessDomain::UseCases::UserUpdatesNickname.new(user_updates_nickname_parameters)
435
+ end
436
+
437
+ def user_updates_nickname_parameters
438
+ MyBusinessDomain::UseCases::UserUpdatesNickname.parameters(
439
+ context: { my_persistence_object: MyPersistence.new },
440
+ user_id: params[:user_id],
441
+ nickname: params[:nickname]
442
+ )
443
+ end
444
+ end
445
+ end
446
+ ```
447
+
448
+ Elements of contracts can be shared amongst use cases, this can be very helpful for `options` (context) that you know every use case in a domain may require or validation rules that you know will be used in multiple use cases. Shared contracts can help tidy up your specs too by allowing you to test all your validation logic separately to what the use case itself does.
449
+
450
+ ```ruby
451
+ module MyBusinessDomain
452
+ module UseCases
453
+ class SharedContract < CleanArchitecture::UseCases::Contract
454
+ option :my_persistence_object
455
+
456
+ register_macro(:not_already_taken?) do
457
+ unless not_already_taken?(values[key_name])
458
+ key.failure('is already taken')
459
+ end
460
+ end
461
+
462
+ private
463
+
464
+ def not_already_taken?(username)
465
+ my_persistence_object.username_is_available?(values[key_name])
466
+ end
467
+ end
468
+ end
469
+ end
470
+ ```
471
+
472
+ Using a shared contract is simple; when you define the contract for a use case just specify the shared contract as an argument to `.contract`:
473
+
474
+ ```ruby
475
+ module MyBusinessDomain
476
+ module UseCases
477
+ class UserUpdatesNickname < CleanArchitecture::UseCases::AbstractUseCase
478
+ contract(SharedContract) do
479
+ option :my_persistence_object
480
+
481
+ params do
482
+ required(:user_id).filled(:id)
483
+ required(:nickname).filled(:str)
484
+ end
485
+
486
+ rule(:nickname).validate(:not_already_taken)
487
+ ```
488
+
489
+ Use cases themselves are outside of their params just plain old ruby objects. There are only a few methods you'll use composing use cases:
490
+
491
+ ### `#result_of_validating_params`
492
+
493
+ This methods gives you a Result monad with either `Success` containing a hash of the valid params or `Failure` with an `Errors` instance containing the validation errors. The `Do` syntax from `dry-monads` helps to tidy the usage of this method up:
494
+
495
+ ```ruby
496
+ module MyBusinessDomain
497
+ module UseCases
498
+ class UserUpdatesAge < CleanArchitecture::UseCases::AbstractUseCase
499
+ contract do
500
+ params do
501
+ required(:user_id).filled(:int)
502
+ required(:age).filled(:int)
503
+ end
504
+ end
505
+
506
+ include Dry::Monads::Do.for(:result)
507
+
508
+ def result
509
+ valid_params = yield result_of_validating_params
510
+
511
+ Dry::Monads::Success(valid_params[:age] * 365)
512
+ end
513
+ end
514
+ end
515
+ end
516
+ ```
517
+
518
+ ### `#context`
519
+
520
+ Any context variables defined as `option`'s in your use case contract have to be specified whenever creating an instance of the parameter objects for your use case. In practice this means you can't accidentally forget to pass in say a persistence object / repository / factory / etc.
521
+
522
+ These context variables can be used within the use case using the `context` method:
523
+
524
+ ```ruby
525
+ module MyBusinessDomain
526
+ module UseCases
527
+ class UserUpdatesAge < CleanArchitecture::UseCases::AbstractUseCase
528
+ contract do
529
+ option :required_persistence_object
530
+
531
+ params do
532
+ required(:user_id).filled(:int)
533
+ required(:age).filled(:int)
534
+ end
535
+ end
536
+
537
+ include Dry::Monads::Do.for(:result)
538
+
539
+ def result
540
+ valid_params = yield result_of_validating_params
541
+
542
+ context(:required_persistence_object).update_user_age_result(
543
+ valid_params[:user_id],
544
+ valid_params[:age]
545
+ )
546
+ end
547
+ end
548
+ end
549
+ end
550
+ ```
551
+
552
+ You may wish to tidy access to context variables away into private methods to mask the implementation details.
553
+
554
+ ### `#fail_with_error_message`
555
+
556
+ This method can be used for returning a simple message wrapped in an instance of `Errors`. Optionally you can specify the type of error should you wish for your controller to react different for say a record not being found vs an API connection error.
557
+
558
+ ```ruby
559
+ module MyBusinessDomain
560
+ module UseCases
561
+ class UserUpdatesChristmasWishlist < CleanArchitecture::UseCases::AbstractUseCase
562
+ contract do
563
+ option :required_persistence_object
564
+
565
+ params do
566
+ required(:user_id).filled(:int)
567
+ required(:most_wanted_gift).filled(:str)
568
+ end
569
+ end
570
+
571
+ include Dry::Monads::Do.for(:result)
572
+
573
+ CHRISTMAS_DAY = Date.new('2019', '12', '25')
574
+
575
+ def result
576
+ valid_params = yield result_of_validating_params
577
+
578
+ if Date.today == CHRISTMAS_DAY
579
+ return fail_with_error_message('Uh oh, Santa has already left the North Pole!')
580
+ end
581
+
582
+ context(:required_persistence_object).change_most_wanted_gift(user_id, most_wanted_gift)
583
+ end
584
+ end
585
+ end
586
+ end
587
+ ```