├── .gitignore ├── README.md └── docs ├── 00_INSTALLATION.md ├── 01_REST_API_DOC.md ├── 02_WEB_APP_SCREENSHOTS.md ├── screenshots ├── 010_sign_in.jpg ├── 011_sign_in_error.jpg ├── 020_forgot_password.jpg ├── 030_sign_up.jpg ├── 031_sign_up_errors.jpg ├── 032_sign_up_success.jpg ├── 040_new_task.jpg ├── 041_task_created.jpg ├── 042_task_completed.jpg ├── 043_tasks_completed.jpg ├── 044_tasks_incomplete.jpg ├── 045_edit_task.jpg ├── 050_task_lists.jpg ├── 051_new_task_list.jpg ├── 052_select_task_list.jpg ├── 060_settings_profile.jpg ├── 061_settings_account_deletion.jpg ├── 062_settings_api_token.jpg └── 070_sign_out.jpg └── versions ├── controller ├── api │ ├── README.md │ ├── solid-process-0.rb │ ├── solid-process-1.rb │ ├── solid-process-2.00--2.80.rb │ ├── solid-process-2.95.rb │ ├── solid-process-2.99--4.rb │ └── vanilla-rails.rb └── web │ ├── README.md │ ├── solid-process-0.rb │ ├── solid-process-1--2.95.rb │ ├── solid-process-2.99--4.rb │ └── vanilla-rails.rb └── model ├── README.md ├── solid-process-0.rb ├── solid-process-1.rb ├── solid-process-2.00.rb ├── solid-process-2.20--2.40.rb ├── solid-process-2.60.rb ├── solid-process-2.80.rb ├── solid-process-2.95--2.99.rb ├── solid-process-3.rb └── solid-process-4.rb /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files for more about ignoring files. 2 | # 3 | # If you find yourself ignoring temporary files generated by your text editor 4 | # or operating system, you probably want to add a global ignore instead: 5 | # git config --global core.excludesfile '~/.gitignore_global' 6 | 7 | # Ignore bundler config. 8 | /.bundle 9 | 10 | # Ignore all environment files (except templates). 11 | /.env* 12 | !/.env*.erb 13 | 14 | # Ignore all logfiles and tempfiles. 15 | /log/* 16 | /tmp/* 17 | !/log/.keep 18 | !/tmp/.keep 19 | 20 | # Ignore pidfiles, but keep the directory. 21 | /tmp/pids/* 22 | !/tmp/pids/ 23 | !/tmp/pids/.keep 24 | 25 | # Ignore storage (uploaded files in development and any SQLite databases). 26 | /storage/* 27 | !/storage/.keep 28 | /tmp/storage/* 29 | !/tmp/storage/ 30 | !/tmp/storage/.keep 31 | 32 | /public/assets 33 | 34 | # Ignore master key for decrypting credentials and more. 35 | /config/master.key 36 | 37 | /coverage/ 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | > `MENU` **README** | [How to run locally](./docs/00_INSTALLATION.md) | [REST API doc](./docs/01_REST_API_DOC.md) | [Web app screenshots](./docs/02_WEB_APP_SCREENSHOTS.md) 4 | 5 | 6 | 7 | # ✨ Solid Rails App 8 | 9 | Web and REST API application made with [Ruby on Rails](https://guides.rubyonrails.org/) + [solid-process](https://github.com/solid-process/solid-process). 10 | 11 | ## 📚 Table of contents 12 | 13 | - [📢 Disclaimer](#-disclaimer) 14 | - [🙌 Repository branches](#-repository-branches) 15 | - [🌟 Highlights of what solid-process can bring to you/your team](#-highlights-of-what-solid-process-can-bring-to-youyour-team) 16 | - [🤔 How can we be aware of critical system processes?](#-how-can-we-be-aware-of-critical-system-processes) 17 | - [👋 About](#-about) 18 | 19 | ## 📢 Disclaimer 20 | 21 | The goal of this project is to demonstrate how the `solid-process` can be gradually integrated into a Rails application. 22 | 23 | It's important to note that the **Rails Way** and the **Solid Process** are not mutually exclusive. You can implement the `solid-process` where it best fits your needs, allowing both approaches to coexist harmoniously and beneficially. 24 | 25 | That said, this is not an invitation, guide, or recommendation to implement all applications in the more complex manner demonstrated in this project. 26 | 27 | I (Rodrigo Serradura) believe in a pragmatic approach using the best tool for the job. The _**vanilla-rails**_ version is excellent and capable of handling all the complexity within this system's scope. 28 | 29 | Therefore, view this project as a showcase of what the `solid-process` gem can achieve. You may discover valuable tools to add to your toolbox. Enjoy! ✌️😊 30 | 31 | ## 🙌 Repository branches 32 | 33 | This repository has twelve branches that represent the application's evolution. 34 | 35 | The `2.##` branches are percentages that indicate gradual progress toward the next version. 36 | 37 | Architectural patterns are applied according to the following criteria: 38 | - `0` to `2.99` , stays on the **Layered Architecture**. 39 | - `3` onwards, applies the **Ports and Adapters (_Hexagonal_) Architecture**. 40 | 41 | __*Click in the arrows*__ to see the summary of each branch: 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 76 | 77 | 78 | 79 | 80 | 81 | 97 | 98 | 99 | 100 | 101 | 102 | 118 | 119 | 120 | 121 | 122 | 123 | 143 | 144 | 145 | 146 | 147 | 148 | 164 | 165 | 166 | 167 | 168 | 169 | 187 | 188 | 189 | 190 | 191 | 192 | 208 | 209 | 210 | 211 | 212 | 213 | 231 | 232 | 233 | 234 | 235 | 236 | 252 | 253 | 254 | 255 | 256 | 257 | 271 | 272 | 273 | 274 | 275 | 276 | 292 | 293 | 294 | 295 | 296 | 297 | 313 | 314 | 315 | 316 |
BranchLOC / GRADE
54 |
55 |     vanilla-rails 56 | 57 | --- 58 | It is the base version that implements a web application and REST API using the Rails Way approach. The business rules are mainly implemented around the ActiveRecord lifecycle/features (normalization, validation, callbacks, and macros), which the application controllers orchestrate. 59 | 60 | However, with each new version, these models and controllers will have fewer responsibilities as we implement processes to wrap and orchestrate the core business logic. 61 | 62 | Note: Three concepts differ from a traditional CRUD. Are they: 63 | 64 | 1. [Account Member](https://github.com/solid-process/solid-rails-app/blob/vanilla-rails/app/models/account/member.rb): This PORO performs access/scope control in the accounts. 65 | 66 | 2. [User Registration](https://github.com/solid-process/solid-rails-app/blob/vanilla-rails/app/models/user.rb#L18-L49): This operation consists of creating the user and its account, defining the user as the account's owner, creating the user API token, and sending an email to confirm the user email. 67 | 68 | 3. [User API token](https://github.com/solid-process/solid-rails-app/blob/vanilla-rails/app/models/user_token.rb): This implementation is based on the [prefixed API token concept](https://github.com/seamapi/prefixed-api-key), which consists of a short (public) and a long (encrypted) token. 69 | 70 | **Version samples:** 71 | - [API Controller](./docs/versions/controller/api/vanilla-rails.rb) 72 | - [Web Controller](./docs/versions/controller/web/vanilla-rails.rb) 73 | 74 |
75 |
1434 / 94.06
82 |
83 |     solid-process-0 84 | 85 | --- 86 | Introduces the solid-process gem and implements the application's first process ([User::Registration](https://github.com/solid-process/solid-rails-app/blob/solid-process-0/app/models/user/registration.rb)). 87 | 88 | It shows the low learning curve required to use gem features. Although basic, this implementation removes callbacks from the [User](https://github.com/solid-process/solid-rails-app/blob/solid-process-0/app/models/user.rb) model, as the process will orchestrate all account creation within a transaction and send a confirmation email after its commit. 89 | 90 | **Version samples:** 91 | - [API Controller](./docs/versions/controller/api/solid-process-0.rb) ([Diff](./docs/versions/controller/api/README.md#vanilla-railssolid-process-0)) 92 | - [Web Controller](./docs/versions/controller/web/solid-process-0.rb) ([Diff](./docs/versions/controller/web/README.md#vanilla-railsrbsolid-process-0)) 93 | - [Process](./docs/versions/model/solid-process-0.rb) 94 | 95 |
96 |
1459 / 94.08
103 |
104 |     solid-process-1 105 | 106 | --- 107 | Defines types for input attributes and uses steps to perform all operations within a transaction block that will perform a rollback if any step returns a failure. After the commit, the confirmation email is sent, and the created user is exposed in the [User::Registration](https://github.com/solid-process/solid-rails-app/blob/solid-process-1/app/models/user/registration.rb) result. 108 | 109 | Note: In this version, the process input ([User::Registration::Input](https://github.com/solid-process/solid-rails-app/blob/solid-process-1/app/controllers/web/guest/registrations_controller.rb#L6)) is used in the view forms, causing the view to be coupled to it and no longer to the User model (ActiveRecord). 110 | 111 | **Version samples:** 112 | - [API Controller](./docs/versions/controller/api/solid-process-1.rb) ([Diff](./docs/versions/controller/api/README.md#solid-process-0solid-process-1)) 113 | - [Web Controller](./docs/versions/controller/web/solid-process-1--2.95.rb) ([Diff](./docs/versions/controller/web/README.md#solid-process-0solid-process-1--295)) 114 | - [Process](./docs/versions/model/solid-process-1.rb) ([Diff](./docs/versions/model/README.md#solid-process-0solid-process-1)) 115 | 116 |
117 |
1481 / 93.98
124 |
125 |     solid-process-2.00 126 | 127 | --- 128 | From version 2 onwards, all contexts (Account and User) implement their operations through processes. Because of this, it becomes possible to create a composition of processes where they are defined as dependencies that will be orchestrated through their steps. 129 | 130 | Notes: 131 | 132 | 1. [UserToken](https://github.com/solid-process/solid-rails-app/blob/solid-process-2.00/app/models/user_token.rb) becomes hugely lean because the model's behavior has been diluted into different components within the [User::Token namespace](https://github.com/solid-process/solid-rails-app/tree/solid-process-2.00/app/models/user/token). I am highlighting [User::Token::Entity](https://github.com/solid-process/solid-rails-app/blob/solid-process-2.00/app/models/user/token/entity.rb), a PORO capable of representing a token without the need to interact with the database (ActiveRecord model). 133 | 134 | 2. New features are added to input blocks, such as data normalization and validation. 135 | 136 | **Version samples:** 137 | - [API Controller](./docs/versions/controller/api/solid-process-2.00--2.80.rb) ([Diff](./docs/versions/controller/api/README.md#solid-process-1solid-process-200--280)) 138 | - [Web Controller](./docs/versions/controller/web/solid-process-1--2.95.rb) ([Diff](./docs/versions/controller/web/README.md#solid-process-0solid-process-1--295)) 139 | - [Process](./docs/versions/model/solid-process-2.00.rb) ([Diff](./docs/versions/model/README.md#solid-process-1solid-process-200)) 140 | 141 |
142 |
1866 / 93.78
149 |
150 |     solid-process-2.20 151 | 152 | --- 153 | If we analyze the previous version, we will see that the ActiveRecord models are not defined within the context of the user or account. 154 | 155 | What this version does is rename the ActiveRecord models as [Record](https://github.com/solid-process/solid-rails-app/blob/solid-process-2.20/app/models/user/record.rb) (for this, it is necessary to define the table_name and class_name in the classes) within the [Account](https://github.com/solid-process/solid-rails-app/tree/solid-process-2.20/app/models/account) and [User](https://github.com/solid-process/solid-rails-app/tree/solid-process-2.20/app/models/user) namespaces (they become mere modules since before both were also an ActiveRecord model) 156 | 157 | **Version samples:** 158 | - [API Controller](./docs/versions/controller/api/solid-process-2.00--2.80.rb) ([Diff](./docs/versions/controller/api/README.md#solid-process-1solid-process-200--280)) 159 | - [Web Controller](./docs/versions/controller/web/solid-process-1--2.95.rb) ([Diff](./docs/versions/controller/web/README.md#solid-process-0solid-process-1--295)) 160 | - [Process](./docs/versions/model/solid-process-2.20--2.40.rb) ([Diff](./docs/versions/model/README.md#solid-process-200solid-process-220--240)) 161 | 162 |
163 |
1894 / 93.74
170 |
171 |     solid-process-2.40 172 | 173 | --- 174 | The **vanilla-rails summary** presents the Account::Member as one of the application's most important components. It is responsible for controlling access to the accounts. 175 | 176 | It turns out that although it is a PORO (Solid::Model), its implementation also contains queries (ActiveRecord stuff). 177 | 178 | This version introduces [Account::Member::Repository](https://github.com/solid-process/solid-rails-app/blob/solid-process-2.40/app/models/account/member/repository.rb) to enhance the separation of concerns. This new component/pattern will serve as an abstraction layer for the data source, allowing queries to be moved to it and making the [Account::Member implementation more straightforward and concise](https://github.com/solid-process/solid-rails-app/blob/solid-process-2.40/app/models/account/member.rb). 179 | 180 | **Version samples:** 181 | - [API Controller](./docs/versions/controller/api/solid-process-2.00--2.80.rb) ([Diff](./docs/versions/controller/api/README.md#solid-process-1solid-process-200--280)) 182 | - [Web Controller](./docs/versions/controller/web/solid-process-1--2.95.rb) ([Diff](./docs/versions/controller/web/README.md#solid-process-0solid-process-1--295)) 183 | - [Process](./docs/versions/model/solid-process-2.20--2.40.rb) ([Diff](./docs/versions/model/README.md#solid-process-200solid-process-220--240)) 184 | 185 |
186 |
1904 / 93.80
193 |
194 |     solid-process-2.60 195 | 196 | --- 197 | Since version 2.40 introduces the Repository pattern, what would happen if all processes/contexts started using this pattern? 198 | 199 | This version answers this question in practice by introducing a repository for each context and forcing the application to start using them instead of directly using the ActiveRecord models. 200 | 201 | **Version samples:** 202 | - [API Controller](./docs/versions/controller/api/solid-process-2.00--2.80.rb) ([Diff](./docs/versions/controller/api/README.md#solid-process-1solid-process-200--280)) 203 | - [Web Controller](./docs/versions/controller/web/solid-process-1--2.95.rb) ([Diff](./docs/versions/controller/web/README.md#solid-process-0solid-process-1--295)) 204 | - [Process](./docs/versions/model/solid-process-2.60.rb) ([Diff](./docs/versions/model/README.md#solid-process-220--240solid-process-260)) 205 | 206 |
207 |
2144 / 91.14
214 |
215 |     solid-process-2.80 216 | 217 | --- 218 | Due to the addition of repositories in the previous version, it is evident that User::Registration and User::AccountDeletion have methods in their repositories that use an Account::Record. In other words, it is clear that User::Record is tightly coupled to Account::Record and should not be. 219 | 220 | To solve this, a new [account_members table](https://github.com/solid-process/solid-rails-app/blob/solid-process-2.80/db/schema.rb#L14-L19) (and model Account::Member::Record) is created with just one column (uuid), and a column [uuid is also added to the user's table](https://github.com/solid-process/solid-rails-app/blob/solid-process-2.80/db/schema.rb#L73). 221 | 222 | While we won't be using a foreign key, the plan is to start referencing the users' UUID in the account members table. This approach will allow us to transfer several associations from the [User::Record](https://github.com/solid-process/solid-rails-app/blob/solid-process-2.80/app/models/user/record.rb) model to [Account::Member::Record](https://github.com/solid-process/solid-rails-app/blob/solid-process-2.80/app/models/account/member/record.rb), effectively decoupling the User and Account contexts. This will significantly enhance the system's orthogonality, minimizing the risk of changes in one context affecting the other. 223 | 224 | **Version samples:** 225 | - [API Controller](./docs/versions/controller/api/solid-process-2.00--2.80.rb) ([Diff](./docs/versions/controller/api/README.md#solid-process-1solid-process-200--280)) 226 | - [Web Controller](./docs/versions/controller/web/solid-process-1--2.95.rb) ([Diff](./docs/versions/controller/web/README.md#solid-process-0solid-process-1--295)) 227 | - [Process](./docs/versions/model/solid-process-2.80.rb) ([Diff](./docs/versions/model/README.md#solid-process-260solid-process-280)) 228 | 229 |
230 |
2234 / 91.34
237 |
238 |     solid-process-2.95 239 | 240 | --- 241 | Once the contexts are more decoupled, the next step is to make the processes start to expose and receive only [entities (POROS)](https://github.com/solid-process/solid-rails-app/blob/solid-process-2.95/app/models/user/entity.rb) and no longer ActiveRecord objects. 242 | 243 | With this approach, each context gains enhanced control over side effects, be it writing or reading. All interactions with the database/ActiveRecord are now wrapped within the repositories, ensuring a more controlled and predictable system behavior. 244 | 245 | **Version samples:** 246 | - [API Controller](./docs/versions/controller/api/solid-process-2.95.rb) ([Diff](./docs/versions/controller/api/README.md#solid-process-200--280solid-process-295)) 247 | - [Web Controller](./docs/versions/controller/web/solid-process-1--2.95.rb) ([Diff](./docs/versions/controller/web/README.md#solid-process-0solid-process-1--295)) 248 | - [Process](./docs/versions/model/solid-process-2.95--2.99.rb) ([Diff](./docs/versions/model/README.md#solid-process-280solid-process-295--299)) 249 | 250 |
251 |
2365 / 91.50
258 |
259 |     solid-process-2.99 260 | 261 | --- 262 | This version transforms the User and Account modules into facades. Processes and repositories are now exposed through simple methods. Another benefit is that shared and repeated behavior (such as instantiating entities) can be done through the facade, eliminating duplication and abstracting this complexity from the entry points. 263 | 264 | **Version samples:** 265 | - [API Controller](./docs/versions/controller/api/solid-process-2.99--4.rb) ([Diff](./docs/versions/controller/api/README.md#solid-process-295solid-process-299--4)) 266 | - [Web Controller](./docs/versions/controller/web/solid-process-2.99--4.rb) ([Diff](./docs/versions/controller/web/README.md#solid-process-1--295solid-process-299--4)) 267 | - [Process](./docs/versions/model/solid-process-2.95--2.99.rb) ([Diff](./docs/versions/model/README.md#solid-process-280solid-process-295--299)) 268 | 269 |
270 |
2474 / 92.40
277 |
278 |     solid-process-3 279 | 280 | --- 281 | This version implements the Ports and Adapters architectural pattern (Hexagonal Architecture). 282 | 283 | In version [2.99](https://github.com/solid-process/solid-rails-app/blob/solid-process-2.99), the account and user namespaces encapsulated the core business logic. They began to receive and expose objects that belonged to them, in other words, objects that did not directly relate to the framework (Rails). Because of this, it becomes feasible to isolate this core outside the app folder; for this reason, these components were moved to [lib/core](https://github.com/solid-process/solid-rails-app/tree/solid-process-3/lib/core). So, through a new lib, [Solid::Adapters](https://github.com/solid-process/solid-adapters) was added, it is possible to use [initializers](https://github.com/solid-process/solid-rails-app/tree/solid-process-3/config/initializers/solid_adapters) to plug the adapters defined in the app folder (framework side) into the core. This way, it becomes protected as the app uses/implements its ports (interfaces). 284 | 285 | **Version samples:** 286 | - [API Controller](./docs/versions/controller/api/solid-process-2.99--4.rb) ([Diff](./docs/versions/controller/api/README.md#solid-process-295solid-process-299--4)) 287 | - [Web Controller](./docs/versions/controller/web/solid-process-2.99--4.rb) ([Diff](./docs/versions/controller/web/README.md#solid-process-1--295solid-process-299--4)) 288 | - [Process](./docs/versions/model/solid-process-3.rb) ([Diff](./docs/versions/model/README.md#solid-process-295--299solid-process-3)) 289 | 290 |
291 |
2527 / 93.42
298 |
299 |     solid-process-4 300 | 301 | --- 302 | This version makes usage of another `Solid::Adapters` feature, the [interface mechanism](https://github.com/solid-process/solid-adapters?tab=readme-ov-file#solidadaptersinterface) to strengthen the contracts between the core and the application. Because of this, the core can [establish](https://github.com/solid-process/solid-rails-app/blob/solid-process-4/lib/core/user/adapters/repository_interface.rb) and [demand](https://github.com/solid-process/solid-rails-app/blob/solid-process-4/lib/core/user/registration.rb#L13) its contracts (ports). 303 | 304 | Note: In case of a breach of contract, the system will raise an exception indicating what was compromised and needs to be fixed. 305 | 306 | **Version samples:** 307 | - [API Controller](./docs/versions/controller/api/solid-process-2.99--4.rb) ([Diff](./docs/versions/controller/api/README.md#solid-process-295solid-process-299--4)) 308 | - [Web Controller](./docs/versions/controller/web/solid-process-2.99--4.rb) ([Diff](./docs/versions/controller/web/README.md#solid-process-1--295solid-process-299--4)) 309 | - [Process](./docs/versions/model/solid-process-4.rb) ([Diff](./docs/versions/model/README.md#solid-process-3solid-process-4)) 310 | 311 |
312 |
2947 / 93.42
317 | 318 | The following commands were used to generate the LOC and GRADE reports: 319 | - **LOC** (lines of code): `bin/rails stats` 320 | - **GRADE** (code quality): `bin/rails rubycritic` 321 | 322 |

⬆ back to top

323 | 324 | ## 🌟 Highlights of what solid-process can bring to you/your team 325 | 326 | 1. The [`solid-process`](https://github.com/solid-process/solid-process) uses Rails's known components, such as ActiveModel attributes, validations, callbacks, and more. This way, you can use the same tools you are already familiar with. 327 | 328 | 2. A way for representing/writing critical system operations. It feels like having code that documents itself. You can see the operation's steps, inputs, outputs, side effects, and more in one place. 329 | 330 | 3. A less coupled codebase, given that this structure encourages the creation of cohesive operations (with a specific purpose), thus reducing the concentration of logic in ActiveRecord models and/or controllers. 331 | 332 | 4. Standardization of instrumentation and observability of what occurs within each process (Implement a listener to do this automatically and transparently for the developer [[1]](https://github.com/solid-process/solid-rails-app/blob/solid-process-2.00/config/initializers/solid_process.rb)). This will help you better understand what is happening within the system. 333 | 334 |
335 | User::Registration event logs sample (output from solid-process-2.00 branch) 336 | 337 | ``` 338 | #0 User::Registration 339 | * Given(email:, password:, password_confirmation:) 340 | * Continue() from method: check_if_email_is_taken 341 | * Continue(user:) from method: create_user 342 | * Continue(account:) from method: create_user_account 343 | #1 Account::Task::List::Creation 344 | * Given(name:, inbox:, account:) 345 | * Continue(task_lists:) from method: fetch_task_lists_relation 346 | * Continue() from method: validate_uniqueness_if_inbox 347 | * Continue(task_list:) from method: create_task_list 348 | * Success(:task_list_created, task_list:) 349 | * Continue() from method: create_user_inbox 350 | #2 User::Token::Creation 351 | * Given(user:, token:) 352 | * Continue() from method: validate_token 353 | * Continue() from method: check_token_existance 354 | * Continue(token:) from method: create_token_if_not_exists 355 | * Success(:token_created, token:) 356 | * Continue() from method: create_user_token 357 | * Continue() from method: send_email_confirmation 358 | * Success(:user_registered, user:) 359 | ``` 360 | 361 |
362 | 363 | 5. The file structure reveals the system's critical processes, making it easier to understand its behavior and find where to make changes. Check out the [app/models](https://github.com/solid-process/solid-rails-app/blob/solid-process-2.00/app/models) directory. 364 | 365 |
366 | app/models file structure (output from solid-process-2.00 branch) 367 | 368 | ```sh 369 | app/models 370 | ├── account 371 | │ ├── member.rb 372 | │ └── task 373 | │ ├── item 374 | │ │ ├── completion.rb 375 | │ │ ├── creation.rb 376 | │ │ ├── deletion.rb 377 | │ │ ├── finding.rb 378 | │ │ ├── incomplete.rb 379 | │ │ ├── listing.rb 380 | │ │ └── updating.rb 381 | │ └── list 382 | │ ├── creation.rb 383 | │ ├── deletion.rb 384 | │ ├── finding.rb 385 | │ ├── listing.rb 386 | │ └── updating.rb 387 | ├── user 388 | │ ├── account_deletion.rb 389 | │ ├── authentication.rb 390 | │ ├── email.rb 391 | │ ├── password 392 | │ │ ├── resetting.rb 393 | │ │ ├── sending_reset_instructions.rb 394 | │ │ └── updating.rb 395 | │ ├── password.rb 396 | │ ├── registration.rb 397 | │ └── token 398 | │ ├── creation.rb 399 | │ ├── entity.rb 400 | │ ├── long_value.rb 401 | │ ├── refreshing.rb 402 | │ └── short_value.rb 403 | └── ... 404 | ``` 405 | 406 |
407 | 408 |

⬆ back to top

409 | 410 | ## 🤔 How can we be aware of critical system processes? 411 | 412 | Adding a new folder under `app` is more common to focus on different patterns, such as `services`, `operations`, `queries`, etc. However, because of this idea of ​​putting processes inside the `app/models` directory, it can be argued that knowing what and where they are will be challenging. 413 | 414 | To address this need, you can use the below command to list all existing processes in the application ([this task](https://github.com/solid-process/solid-rails-app/blob/solid-process-2.00/lib/tasks/solid_process.rake) is available on all solid-process-* branches). 415 | 416 |
417 | bin/rails solid:processes (output from solid-process-2.00 branch) 418 | 419 | ```sh 420 | Lines: 421 | 27 ./app/models/user/token/refreshing.rb 422 | 37 ./app/models/user/token/creation.rb 423 | 31 ./app/models/user/password/updating.rb 424 | 30 ./app/models/user/password/sending_reset_instructions.rb 425 | 37 ./app/models/user/password/resetting.rb 426 | 23 ./app/models/user/authentication.rb 427 | 20 ./app/models/user/account_deletion.rb 428 | 84 ./app/models/user/registration.rb 429 | 54 ./app/models/account/task/item/updating.rb 430 | 15 ./app/models/account/task/item/deletion.rb 431 | 27 ./app/models/account/task/item/listing.rb 432 | 15 ./app/models/account/task/item/incomplete.rb 433 | 25 ./app/models/account/task/item/creation.rb 434 | 15 ./app/models/account/task/item/completion.rb 435 | 23 ./app/models/account/task/item/finding.rb 436 | 29 ./app/models/account/task/list/updating.rb 437 | 15 ./app/models/account/task/list/deletion.rb 438 | 17 ./app/models/account/task/list/listing.rb 439 | 40 ./app/models/account/task/list/creation.rb 440 | 23 ./app/models/account/task/list/finding.rb 441 | 587 total 442 | 443 | Files: 20 444 | ``` 445 | 446 |

⬆ back to top

447 | 448 | ## 👋 About 449 | 450 | [Rodrigo Serradura](https://rodrigoserradura.com/) created this project. He is the creator of Solid Process, among other projects similar to this, such as ["from fat controllers to use cases"](https://github.com/serradura/from-fat-controllers-to-use-cases) and ["todo-bcdd"](https://github.com/serradura/todo-bcdd). These other two were made with an ecosystem before solid-process ([u-case](https://github.com/serradura/u-case), [kind](https://github.com/serradura/kind) - which will be archived). 451 | 452 | The primary goal of this project is to create an application that reflects the new ecosystem's capabilities, a product of collective learning over the years. This learning journey was enriched by technical knowledge and the invaluable contributions of the Ruby/Rails community (with special mention to the [Ada.rb](https://adarb.org/) community). 453 | 454 | Within the Rails community, we have people at different career stages and companies in different phases (validating idea, refining, scaling product); the objective of Solid Process is to be able to follow each of these stages (whether of people or companies that are made up of people). Rails is notoriously known for maximizing development speed. With this in mind, the idea is to have a set of gems that adhere to [its principles and values](https://rubyonrails.org/doctrine) ​​and add value when implementing business/domain logic. 455 | 456 | In other words, the gem was created to implement simple services/form objects and even something more complex and decoupled from the framework, such as ports and adapters. All this without fighting and harming the use of Rails. On the contrary, the objective is the difficult task of adding to what is already extremely good (Rails rocks!!! 🤘😎) 457 | 458 |

⬆ back to top

459 | -------------------------------------------------------------------------------- /docs/00_INSTALLATION.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | > `MENU` [README](../README.md) | **How to run locally** | [REST API doc](./01_REST_API_DOC.md) | [Web app screenshots](./02_WEB_APP_SCREENSHOTS.md) 4 | 5 | 6 | 7 | # ✨ Solid Rails App 8 | 9 | Instructions to setup and run the application locally. 10 | 11 | ## 📚 Table of contents 12 | 13 | - [System dependencies](#system-dependencies) 14 | - [How to setup the application](#how-to-setup-the-application) 15 | - [How to run the application locally](#how-to-run-the-application-locally) 16 | - [How to run the test suite (and generate coverage report)](#how-to-run-the-test-suite-and-generate-coverage-report) 17 | - [How to generate the code quality](#how-to-generate-the-code-quality) 18 | - [How to generate the App statistics](#how-to-generate-the-app-statistics) 19 | 20 | ## System dependencies 21 | * SQLite3 22 | * Ruby `3.3.1` 23 | * bundler `>= 2.5.6` 24 | 25 | ## How to setup the application 26 | 27 | 1. Install system dependencies 28 | 2. Access one of the [branches](../README.md#-repository-branches) 29 | 3. Create a `config/master.key` file with the following content: 30 | ```sh 31 | echo 'a061933f96843c82342fb8ab9e9db503' > config/master.key 32 | 33 | chmod 600 config/master.key 34 | ``` 35 | 3. Run `bin/setup` 36 | 37 |

⬆ back to top

38 | 39 | ## How to run the application locally 40 | 41 | 1. `bin/rails s` 42 | 2. Open in your browser: `http://localhost:3000` 43 | 44 |

⬆ back to top

45 | 46 | ## How to run the test suite (and generate coverage report) 47 | 48 | * `bin/rails test` 49 | 50 | ## How to generate the code quality 51 | 52 | * `bin/rails rubycritic` 53 | 54 | ## How to generate the App statistics 55 | 56 | * `bin/rails stats` 57 | 58 |

⬆ back to top

59 | -------------------------------------------------------------------------------- /docs/01_REST_API_DOC.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | > `MENU` [README](../README.md) | [How to run locally](./00_INSTALLATION.md) | **REST API doc** | [Web app screenshots](./02_WEB_APP_SCREENSHOTS.md) 4 | 5 | 6 | 7 | # ✨ Solid Rails App 8 | 9 | REST API documentation (cURL examples). 10 | 11 | ## 📚 Table of contents 12 | 13 | - [User](#user) 14 | - [Registration](#registration) 15 | - [Authentication](#authentication) 16 | - [Account deletion](#account-deletion) 17 | - [API token updating](#api-token-updating) 18 | - [Password updating](#password-updating) 19 | - [Password resetting - Link to change the password](#password-resetting---link-to-change-the-password) 20 | - [Password resetting - Change the password](#password-resetting---change-the-password) 21 | - [Task List](#task-list) 22 | - [Listing](#listing) 23 | - [Creation](#creation) 24 | - [Updating](#updating) 25 | - [Deletion](#deletion) 26 | - [Task](#task) 27 | - [Listing](#listing-1) 28 | - [Creation](#creation-1) 29 | - [Updating](#updating-1) 30 | - [Deletion](#deletion-1) 31 | 32 | Set the following environment variables to use the examples below: 33 | 34 | ```bash 35 | export API_HOST="http://localhost:3000" 36 | export API_TOKEN="MY_USER_TOKEN" 37 | ``` 38 | 39 | You can get the `API_TOKEN` by: 40 | 1. Using the below `User / Registration` request. 41 | 2. or performing the below `User / Authentication` request. 42 | 3. or copying the `user token` from `Sign In >> Settings >> API` page. 43 | 44 |

⬆ back to top

45 | 46 | ### User 47 | 48 | #### Registration 49 | 50 | ```bash 51 | curl -X POST "$API_HOST/api/v1/user/registrations" \ 52 | -H "Content-Type: application/json" \ 53 | -d '{ 54 | "user": { 55 | "email": "email@example.com", 56 | "password": "123123123", 57 | "password_confirmation": "123123123" 58 | } 59 | }' 60 | ``` 61 | 62 |

⬆ back to top

63 | 64 | #### Authentication 65 | 66 | ```bash 67 | curl -X POST "$API_HOST/api/v1/user/sessions" \ 68 | -H "Content-Type: application/json" \ 69 | -d '{ 70 | "user": { 71 | "email": "email@example.com", 72 | "password": "123123123" 73 | } 74 | }' 75 | ``` 76 | 77 |

⬆ back to top

78 | 79 | #### Account deletion 80 | 81 | ```bash 82 | curl -X DELETE "$API_HOST/api/v1/user/registrations" \ 83 | -H "Content-Type: application/json" \ 84 | -H "Authorization: Bearer $API_TOKEN" 85 | ``` 86 | 87 |

⬆ back to top

88 | 89 | #### API token updating 90 | 91 | ```bash 92 | curl -X PUT "$API_HOST/api/v1/user/tokens" \ 93 | -H "Content-Type: application/json" \ 94 | -H "Authorization: Bearer $API_TOKEN" 95 | ``` 96 | 97 |

⬆ back to top

98 | 99 | #### Password updating 100 | 101 | ```bash 102 | curl -X PUT "$API_HOST/api/v1/user/passwords" \ 103 | -H "Content-Type: application/json" \ 104 | -H "Authorization: Bearer $API_TOKEN" \ 105 | -d '{ 106 | "user": { 107 | "current_password": "123123123", 108 | "password": "321321321", 109 | "password_confirmation": "321321321" 110 | } 111 | }' 112 | ``` 113 | 114 |

⬆ back to top

115 | 116 | #### Password resetting - Link to change the password 117 | 118 | ```bash 119 | curl -X POST "$API_HOST/api/v1/user/passwords/reset" \ 120 | -H "Content-Type: application/json" \ 121 | -d '{"user": {"email": "email@example.com"}}' 122 | ``` 123 | 124 |

⬆ back to top

125 | 126 | #### Password resetting - Change the password 127 | 128 | ```bash 129 | curl -X PUT "$API_HOST/api/v1/user/passwords/reset" \ 130 | -H "Content-Type: application/json" \ 131 | -d '{ 132 | "user": { 133 | "token": "TOKEN_RETRIEVED_BY_EMAIL", 134 | "password": "123123123", 135 | "password_confirmation": "123123123" 136 | } 137 | }' 138 | ``` 139 | 140 |

⬆ back to top

141 | 142 | ### Task List 143 | 144 | #### Listing 145 | 146 | ```bash 147 | curl -X GET "$API_HOST/api/v1/task/lists" \ 148 | -H "Content-Type: application/json" \ 149 | -H "Authorization: Bearer $API_TOKEN" 150 | ``` 151 | 152 |

⬆ back to top

153 | 154 | #### Creation 155 | 156 | ```bash 157 | curl -X POST "$API_HOST/api/v1/task/lists" \ 158 | -H "Content-Type: application/json" \ 159 | -H "Authorization: Bearer $API_TOKEN" \ 160 | -d '{"task_list": {"name": "My Task List"}}' 161 | ``` 162 | 163 |

⬆ back to top

164 | 165 | #### Updating 166 | 167 | ```bash 168 | curl -X PUT "$API_HOST/api/v1/task/lists/2" \ 169 | -H "Content-Type: application/json" \ 170 | -H "Authorization: Bearer $API_TOKEN" \ 171 | -d '{"task_list": {"name": "My List"}}' 172 | ``` 173 | 174 |

⬆ back to top

175 | 176 | #### Deletion 177 | 178 | ```bash 179 | curl -X DELETE "$API_HOST/api/v1/task/lists/2" \ 180 | -H "Content-Type: application/json" \ 181 | -H "Authorization: Bearer $API_TOKEN" 182 | ``` 183 | 184 |

⬆ back to top

185 | 186 | ### Task 187 | 188 | #### Listing 189 | 190 | ```bash 191 | # ?filter=completed | incomplete 192 | 193 | curl -X GET "$API_HOST/api/v1/task/lists/1/items" \ 194 | -H "Content-Type: application/json" \ 195 | -H "Authorization: Bearer $API_TOKEN" 196 | ``` 197 | 198 |

⬆ back to top

199 | 200 | #### Creation 201 | 202 | ```bash 203 | curl -X POST "$API_HOST/api/v1/task/lists/1/items" \ 204 | -H "Content-Type: application/json" \ 205 | -H "Authorization: Bearer $API_TOKEN" \ 206 | -d '{"task": {"name": "My Task"}}' 207 | ``` 208 | 209 |

⬆ back to top

210 | 211 | #### Updating 212 | 213 | ```bash 214 | # "completed": true | 1 | false | 0 215 | 216 | curl -X PUT "$API_HOST/api/v1/task/lists/1/items/1" \ 217 | -H "Content-Type: application/json" \ 218 | -H "Authorization: Bearer $API_TOKEN" \ 219 | -d '{"task": {"name": "My Task", "completed": true}}' 220 | ``` 221 | 222 |

⬆ back to top

223 | 224 | #### Deletion 225 | 226 | ```bash 227 | curl -X DELETE "$API_HOST/api/v1/task/lists/1/items/1" \ 228 | -H "Content-Type: application/json" \ 229 | -H "Authorization: Bearer $API_TOKEN" 230 | ``` 231 | 232 |

⬆ back to top

233 | -------------------------------------------------------------------------------- /docs/02_WEB_APP_SCREENSHOTS.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | > `MENU` [README](../README.md) | [How to run locally](./00_INSTALLATION.md) | [REST API doc](./01_REST_API_DOC.md) | **Web app screenshots** 4 | 5 | 6 | 7 | # ✨ Solid Rails App 8 | 9 | This document contains screenshots of web application pages. 10 | 11 | ## 📚 Table of contents 12 | 13 | - [Sign in](#sign-in) 14 | - [Forgot password](#forgot-password) 15 | - [Sign up](#sign-up) 16 | - [Tasks](#tasks) 17 | - [Task Lists](#task-lists) 18 | - [Settings](#settings) 19 | - [Sign out](#sign-out) 20 | 21 | 22 | ### Sign in 23 | 24 | 25 | 26 | 27 |

⬆ back to top

28 | 29 | ### Forgot password 30 | 31 | 32 | 33 |

⬆ back to top

34 | 35 | ### Sign up 36 | 37 | 38 | 39 | 40 | 41 |

⬆ back to top

42 | 43 | ### Tasks 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 |

⬆ back to top

53 | 54 | ### Task Lists 55 | 56 | 57 | 58 | 59 | 60 |

⬆ back to top

61 | 62 | ### Settings 63 | 64 | 65 | 66 | 67 | 68 |

⬆ back to top

69 | 70 | ### Sign out 71 | 72 | 73 | 74 |

⬆ back to top

75 | -------------------------------------------------------------------------------- /docs/screenshots/010_sign_in.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solid-process/solid-rails-app/1b8445913656854f4912726c2e3030d3fec7f5f7/docs/screenshots/010_sign_in.jpg -------------------------------------------------------------------------------- /docs/screenshots/011_sign_in_error.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solid-process/solid-rails-app/1b8445913656854f4912726c2e3030d3fec7f5f7/docs/screenshots/011_sign_in_error.jpg -------------------------------------------------------------------------------- /docs/screenshots/020_forgot_password.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solid-process/solid-rails-app/1b8445913656854f4912726c2e3030d3fec7f5f7/docs/screenshots/020_forgot_password.jpg -------------------------------------------------------------------------------- /docs/screenshots/030_sign_up.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solid-process/solid-rails-app/1b8445913656854f4912726c2e3030d3fec7f5f7/docs/screenshots/030_sign_up.jpg -------------------------------------------------------------------------------- /docs/screenshots/031_sign_up_errors.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solid-process/solid-rails-app/1b8445913656854f4912726c2e3030d3fec7f5f7/docs/screenshots/031_sign_up_errors.jpg -------------------------------------------------------------------------------- /docs/screenshots/032_sign_up_success.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solid-process/solid-rails-app/1b8445913656854f4912726c2e3030d3fec7f5f7/docs/screenshots/032_sign_up_success.jpg -------------------------------------------------------------------------------- /docs/screenshots/040_new_task.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solid-process/solid-rails-app/1b8445913656854f4912726c2e3030d3fec7f5f7/docs/screenshots/040_new_task.jpg -------------------------------------------------------------------------------- /docs/screenshots/041_task_created.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solid-process/solid-rails-app/1b8445913656854f4912726c2e3030d3fec7f5f7/docs/screenshots/041_task_created.jpg -------------------------------------------------------------------------------- /docs/screenshots/042_task_completed.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solid-process/solid-rails-app/1b8445913656854f4912726c2e3030d3fec7f5f7/docs/screenshots/042_task_completed.jpg -------------------------------------------------------------------------------- /docs/screenshots/043_tasks_completed.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solid-process/solid-rails-app/1b8445913656854f4912726c2e3030d3fec7f5f7/docs/screenshots/043_tasks_completed.jpg -------------------------------------------------------------------------------- /docs/screenshots/044_tasks_incomplete.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solid-process/solid-rails-app/1b8445913656854f4912726c2e3030d3fec7f5f7/docs/screenshots/044_tasks_incomplete.jpg -------------------------------------------------------------------------------- /docs/screenshots/045_edit_task.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solid-process/solid-rails-app/1b8445913656854f4912726c2e3030d3fec7f5f7/docs/screenshots/045_edit_task.jpg -------------------------------------------------------------------------------- /docs/screenshots/050_task_lists.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solid-process/solid-rails-app/1b8445913656854f4912726c2e3030d3fec7f5f7/docs/screenshots/050_task_lists.jpg -------------------------------------------------------------------------------- /docs/screenshots/051_new_task_list.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solid-process/solid-rails-app/1b8445913656854f4912726c2e3030d3fec7f5f7/docs/screenshots/051_new_task_list.jpg -------------------------------------------------------------------------------- /docs/screenshots/052_select_task_list.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solid-process/solid-rails-app/1b8445913656854f4912726c2e3030d3fec7f5f7/docs/screenshots/052_select_task_list.jpg -------------------------------------------------------------------------------- /docs/screenshots/060_settings_profile.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solid-process/solid-rails-app/1b8445913656854f4912726c2e3030d3fec7f5f7/docs/screenshots/060_settings_profile.jpg -------------------------------------------------------------------------------- /docs/screenshots/061_settings_account_deletion.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solid-process/solid-rails-app/1b8445913656854f4912726c2e3030d3fec7f5f7/docs/screenshots/061_settings_account_deletion.jpg -------------------------------------------------------------------------------- /docs/screenshots/062_settings_api_token.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solid-process/solid-rails-app/1b8445913656854f4912726c2e3030d3fec7f5f7/docs/screenshots/062_settings_api_token.jpg -------------------------------------------------------------------------------- /docs/screenshots/070_sign_out.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solid-process/solid-rails-app/1b8445913656854f4912726c2e3030d3fec7f5f7/docs/screenshots/070_sign_out.jpg -------------------------------------------------------------------------------- /docs/versions/controller/api/README.md: -------------------------------------------------------------------------------- 1 | # Diff 2 | 3 | Listed from latest to earliest versions. 4 | 5 | - [solid-process-2.95..solid-process-2.99--4](#solid-process-295solid-process-299--4) 6 | - [solid-process-2.00--2.80..solid-process-2.95](#solid-process-200--280solid-process-295) 7 | - [solid-process-1..solid-process-2.00--2.80](#solid-process-1solid-process-200--280) 8 | - [solid-process-0..solid-process-1](#solid-process-0solid-process-1) 9 | - [vanilla-rails..solid-process-0](#vanilla-railssolid-process-0) 10 | 11 | ## solid-process-2.95..solid-process-2.99--4 12 | 13 | ```diff 14 | skip_before_action :authenticate_user!, only: [:create] 15 | 16 | def create 17 | - case ::User::Registration.call(user_params) 18 | + case ::User.register(user_params) 19 | in Solid::Success(user_token:) 20 | render_json_with_success(status: :created, data: {user_token:}) 21 | in Solid::Failure(input:) 22 | @@ -12,7 +12,7 @@ 23 | end 24 | 25 | def destroy 26 | - result = ::User::AccountDeletion.call(user: current_user) 27 | + result = ::User.delete_account(user: current_user) 28 | 29 | result.account_deleted? and return render_json_with_success(status: :ok) 30 | end 31 | ``` 32 | 33 |

⬆ back to top

34 | 35 | 36 | ## solid-process-2.00--2.80..solid-process-2.95 37 | 38 | ```diff 39 | def create 40 | case ::User::Registration.call(user_params) 41 | - in Solid::Success(user:) 42 | - render_json_with_success(status: :created, data: {user_token: user.token.value}) 43 | + in Solid::Success(user_token:) 44 | + render_json_with_success(status: :created, data: {user_token:}) 45 | in Solid::Failure(input:) 46 | render_json_with_model_errors(input) 47 | end 48 | ``` 49 | 50 |

⬆ back to top

51 | 52 | ## solid-process-1..solid-process-2.00--2.80 53 | 54 | ```diff 55 | end 56 | 57 | def destroy 58 | - current_user.destroy! 59 | + result = ::User::AccountDeletion.call(user: current_user) 60 | 61 | - render_json_with_success(status: :ok) 62 | + result.account_deleted? and return render_json_with_success(status: :ok) 63 | end 64 | 65 | private 66 | ``` 67 | 68 |

⬆ back to top

69 | 70 | ## solid-process-0..solid-process-1 71 | 72 | ```diff 73 | case ::User::Registration.call(user_params) 74 | in Solid::Success(user:) 75 | render_json_with_success(status: :created, data: {user_token: user.token.value}) 76 | - in Solid::Failure(user:) 77 | - render_json_with_model_errors(user) 78 | + in Solid::Failure(input:) 79 | + render_json_with_model_errors(input) 80 | end 81 | end 82 | ``` 83 | 84 |

⬆ back to top

85 | 86 | ## vanilla-rails..solid-process-0 87 | 88 | ```diff 89 | skip_before_action :authenticate_user!, only: [:create] 90 | 91 | def create 92 | - user = ::User.new(user_params) 93 | - 94 | - if user.save 95 | + case ::User::Registration.call(user_params) 96 | + in Solid::Success(user:) 97 | render_json_with_success(status: :created, data: {user_token: user.token.value}) 98 | - else 99 | + in Solid::Failure(user:) 100 | render_json_with_model_errors(user) 101 | end 102 | end 103 | ``` 104 | 105 |

⬆ back to top

106 | -------------------------------------------------------------------------------- /docs/versions/controller/api/solid-process-0.rb: -------------------------------------------------------------------------------- 1 | module API::V1 2 | class User::RegistrationsController < BaseController 3 | skip_before_action :authenticate_user!, only: [:create] 4 | 5 | def create 6 | case ::User::Registration.call(user_params) 7 | in Solid::Success(user:) 8 | render_json_with_success(status: :created, data: {user_token: user.token.value}) 9 | in Solid::Failure(user:) 10 | render_json_with_model_errors(user) 11 | end 12 | end 13 | 14 | def destroy 15 | current_user.destroy! 16 | 17 | render_json_with_success(status: :ok) 18 | end 19 | 20 | private 21 | 22 | def user_params 23 | params.require(:user).permit(:email, :password, :password_confirmation) 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /docs/versions/controller/api/solid-process-1.rb: -------------------------------------------------------------------------------- 1 | module API::V1 2 | class User::RegistrationsController < BaseController 3 | skip_before_action :authenticate_user!, only: [:create] 4 | 5 | def create 6 | case ::User::Registration.call(user_params) 7 | in Solid::Success(user:) 8 | render_json_with_success(status: :created, data: {user_token: user.token.value}) 9 | in Solid::Failure(input:) 10 | render_json_with_model_errors(input) 11 | end 12 | end 13 | 14 | def destroy 15 | current_user.destroy! 16 | 17 | render_json_with_success(status: :ok) 18 | end 19 | 20 | private 21 | 22 | def user_params 23 | params.require(:user).permit(:email, :password, :password_confirmation) 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /docs/versions/controller/api/solid-process-2.00--2.80.rb: -------------------------------------------------------------------------------- 1 | module API::V1 2 | class User::RegistrationsController < BaseController 3 | skip_before_action :authenticate_user!, only: [:create] 4 | 5 | def create 6 | case ::User::Registration.call(user_params) 7 | in Solid::Success(user:) 8 | render_json_with_success(status: :created, data: {user_token: user.token.value}) 9 | in Solid::Failure(input:) 10 | render_json_with_model_errors(input) 11 | end 12 | end 13 | 14 | def destroy 15 | result = ::User::AccountDeletion.call(user: current_user) 16 | 17 | result.account_deleted? and return render_json_with_success(status: :ok) 18 | end 19 | 20 | private 21 | 22 | def user_params 23 | params.require(:user).permit(:email, :password, :password_confirmation) 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /docs/versions/controller/api/solid-process-2.95.rb: -------------------------------------------------------------------------------- 1 | module API::V1 2 | class User::RegistrationsController < BaseController 3 | skip_before_action :authenticate_user!, only: [:create] 4 | 5 | def create 6 | case ::User::Registration.call(user_params) 7 | in Solid::Success(user_token:) 8 | render_json_with_success(status: :created, data: {user_token:}) 9 | in Solid::Failure(input:) 10 | render_json_with_model_errors(input) 11 | end 12 | end 13 | 14 | def destroy 15 | result = ::User::AccountDeletion.call(user: current_user) 16 | 17 | result.account_deleted? and return render_json_with_success(status: :ok) 18 | end 19 | 20 | private 21 | 22 | def user_params 23 | params.require(:user).permit(:email, :password, :password_confirmation) 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /docs/versions/controller/api/solid-process-2.99--4.rb: -------------------------------------------------------------------------------- 1 | module API::V1 2 | class User::RegistrationsController < BaseController 3 | skip_before_action :authenticate_user!, only: [:create] 4 | 5 | def create 6 | case ::User.register(user_params) 7 | in Solid::Success(user_token:) 8 | render_json_with_success(status: :created, data: {user_token:}) 9 | in Solid::Failure(input:) 10 | render_json_with_model_errors(input) 11 | end 12 | end 13 | 14 | def destroy 15 | result = ::User.delete_account(user: current_user) 16 | 17 | result.account_deleted? and return render_json_with_success(status: :ok) 18 | end 19 | 20 | private 21 | 22 | def user_params 23 | params.require(:user).permit(:email, :password, :password_confirmation) 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /docs/versions/controller/api/vanilla-rails.rb: -------------------------------------------------------------------------------- 1 | module API::V1 2 | class User::RegistrationsController < BaseController 3 | skip_before_action :authenticate_user!, only: [:create] 4 | 5 | def create 6 | user = ::User.new(user_params) 7 | 8 | if user.save 9 | render_json_with_success(status: :created, data: {user_token: user.token.value}) 10 | else 11 | render_json_with_model_errors(user) 12 | end 13 | end 14 | 15 | def destroy 16 | current_user.destroy! 17 | 18 | render_json_with_success(status: :ok) 19 | end 20 | 21 | private 22 | 23 | def user_params 24 | params.require(:user).permit(:email, :password, :password_confirmation) 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /docs/versions/controller/web/README.md: -------------------------------------------------------------------------------- 1 | # Diff 2 | 3 | Listed from latest to earliest versions. 4 | 5 | - [solid-process-1--2.95..solid-process-2.99--4](#solid-process-1--295solid-process-299--4) 6 | - [solid-process-0..solid-process-1--2.95](#solid-process-0solid-process-1--295) 7 | - [vanilla-rails.rb..solid-process-0](#vanilla-railsrbsolid-process-0) 8 | 9 | # solid-process-1--2.95..solid-process-2.99--4 10 | 11 | ```diff 12 | module Web::Guest 13 | class RegistrationsController < BaseController 14 | def new 15 | - render("web/guest/registrations/new", locals: {user: ::User::Registration::Input.new}) 16 | + render("web/guest/registrations/new", locals: {user: ::User.register.input.new}) 17 | end 18 | 19 | def create 20 | - case ::User::Registration.call(registrations_params) 21 | + case ::User.register(registrations_params) 22 | in Solid::Success(user:) 23 | sign_in(user) 24 | ``` 25 | 26 |

⬆ back to top

27 | 28 | # solid-process-0..solid-process-1--2.95 29 | 30 | ```diff 31 | module Web::Guest 32 | class RegistrationsController < BaseController 33 | def new 34 | - render("web/guest/registrations/new", locals: {user: ::User.new}) 35 | + render("web/guest/registrations/new", locals: {user: ::User::Registration::Input.new}) 36 | end 37 | 38 | def create 39 | @@ -10,8 +10,8 @@ 40 | sign_in(user) 41 | 42 | redirect_to web_task_items_path, notice: "You have successfully registered!" 43 | - in Solid::Failure(user:) 44 | - render("web/guest/registrations/new", locals: {user:}, status: :unprocessable_entity) 45 | + in Solid::Failure(input:) 46 | + render("web/guest/registrations/new", locals: {user: input}, status: :unprocessable_entity) 47 | end 48 | end 49 | ``` 50 | 51 |

⬆ back to top

52 | 53 | # vanilla-rails.rb..solid-process-0 54 | 55 | ```diff 56 | def create 57 | - user = ::User.new(registrations_params) 58 | - 59 | - if user.save 60 | + case ::User::Registration.call(registrations_params) 61 | + in Solid::Success(user:) 62 | sign_in(user) 63 | 64 | redirect_to web_task_items_path, notice: "You have successfully registered!" 65 | - else 66 | + in Solid::Failure(user:) 67 | render("web/guest/registrations/new", locals: {user:}, status: :unprocessable_entity) 68 | end 69 | end 70 | ``` 71 | 72 |

⬆ back to top

73 | -------------------------------------------------------------------------------- /docs/versions/controller/web/solid-process-0.rb: -------------------------------------------------------------------------------- 1 | module Web::Guest 2 | class RegistrationsController < BaseController 3 | def new 4 | render("web/guest/registrations/new", locals: {user: ::User.new}) 5 | end 6 | 7 | def create 8 | case ::User::Registration.call(registrations_params) 9 | in Solid::Success(user:) 10 | sign_in(user) 11 | 12 | redirect_to web_task_items_path, notice: "You have successfully registered!" 13 | in Solid::Failure(user:) 14 | render("web/guest/registrations/new", locals: {user:}, status: :unprocessable_entity) 15 | end 16 | end 17 | 18 | private 19 | 20 | def registrations_params 21 | params.require(:guest).permit(:email, :password, :password_confirmation) 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /docs/versions/controller/web/solid-process-1--2.95.rb: -------------------------------------------------------------------------------- 1 | module Web::Guest 2 | class RegistrationsController < BaseController 3 | def new 4 | render("web/guest/registrations/new", locals: {user: ::User::Registration::Input.new}) 5 | end 6 | 7 | def create 8 | case ::User::Registration.call(registrations_params) 9 | in Solid::Success(user:) 10 | sign_in(user) 11 | 12 | redirect_to web_task_items_path, notice: "You have successfully registered!" 13 | in Solid::Failure(input:) 14 | render("web/guest/registrations/new", locals: {user: input}, status: :unprocessable_entity) 15 | end 16 | end 17 | 18 | private 19 | 20 | def registrations_params 21 | params.require(:guest).permit(:email, :password, :password_confirmation) 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /docs/versions/controller/web/solid-process-2.99--4.rb: -------------------------------------------------------------------------------- 1 | module Web::Guest 2 | class RegistrationsController < BaseController 3 | def new 4 | render("web/guest/registrations/new", locals: {user: ::User.register.input.new}) 5 | end 6 | 7 | def create 8 | case ::User.register(registrations_params) 9 | in Solid::Success(user:) 10 | sign_in(user) 11 | 12 | redirect_to web_task_items_path, notice: "You have successfully registered!" 13 | in Solid::Failure(input:) 14 | render("web/guest/registrations/new", locals: {user: input}, status: :unprocessable_entity) 15 | end 16 | end 17 | 18 | private 19 | 20 | def registrations_params 21 | params.require(:guest).permit(:email, :password, :password_confirmation) 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /docs/versions/controller/web/vanilla-rails.rb: -------------------------------------------------------------------------------- 1 | module Web::Guest 2 | class RegistrationsController < BaseController 3 | def new 4 | render("web/guest/registrations/new", locals: {user: ::User.new}) 5 | end 6 | 7 | def create 8 | user = ::User.new(registrations_params) 9 | 10 | if user.save 11 | sign_in(user) 12 | 13 | redirect_to web_task_items_path, notice: "You have successfully registered!" 14 | else 15 | render("web/guest/registrations/new", locals: {user:}, status: :unprocessable_entity) 16 | end 17 | end 18 | 19 | private 20 | 21 | def registrations_params 22 | params.require(:guest).permit(:email, :password, :password_confirmation) 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /docs/versions/model/README.md: -------------------------------------------------------------------------------- 1 | # Diff 2 | 3 | Listed from latest to earliest versions. 4 | 5 | - [solid-process-3..solid-process-4](#solid-process-3solid-process-4) 6 | - [solid-process-2.95--2.99..solid-process-3](#solid-process-295--299solid-process-3) 7 | - [solid-process-2.80..solid-process-2.95--2.99](#solid-process-280solid-process-295--299) 8 | - [solid-process-2.60..solid-process-2.80](#solid-process-260solid-process-280) 9 | - [solid-process-2.20--2.40..solid-process-2.60](#solid-process-220--240solid-process-260) 10 | - [solid-process-2.00..solid-process-2.20--2.40](#solid-process-200solid-process-220--240) 11 | - [solid-process-1..solid-process-2.00](#solid-process-1solid-process-200) 12 | - [solid-process-0..solid-process-1](#solid-process-0solid-process-1) 13 | 14 | ## solid-process-3..solid-process-4 15 | 16 | ```diff 17 | attribute :repository, default: -> { User::Adapters.repository } 18 | attribute :temporary_token, default: -> { User::Adapters.temporary_token } 19 | 20 | - validates :mailer, respond_to: [:send_email_confirmation] 21 | - validates :repository, respond_to: [:create!, :exists?] 22 | - validates :temporary_token, respond_to: [:to] 23 | + validates :mailer, kind_of: User::Adapters::MailerInterface 24 | + validates :repository, kind_of: User::Adapters::RepositoryInterface 25 | + validates :temporary_token, kind_of: User::Adapters::TemporaryTokenInterface 26 | end 27 | 28 | input do 29 | ``` 30 | 31 |

⬆ back to top

32 | 33 | ## solid-process-2.95--2.99..solid-process-3 34 | 35 | ```diff 36 | attribute :token_creation, default: User::Token::Creation 37 | attribute :account_creation, default: Account::Member::OwnerCreation 38 | 39 | - attribute :mailer, default: UserMailer 40 | - attribute :repository, default: User::Repository 41 | - attribute :temporary_token, default: User::TemporaryToken 42 | + attribute :mailer, default: -> { User::Adapters.mailer } 43 | + attribute :repository, default: -> { User::Adapters.repository } 44 | + attribute :temporary_token, default: -> { User::Adapters.temporary_token } 45 | 46 | - validates :repository, respond_to: [:exists?, :create!] 47 | + validates :mailer, respond_to: [:send_email_confirmation] 48 | + validates :repository, respond_to: [:create!, :exists?] 49 | validates :temporary_token, respond_to: [:to] 50 | end 51 | 52 | @@ -73,7 +74,7 @@ 53 | def send_email_confirmation(user:, **) 54 | token = deps.temporary_token.to(:email_confirmation, user) 55 | 56 | - deps.mailer.with(token:, email: user.email).email_confirmation.deliver_later 57 | + deps.mailer.send_email_confirmation(token:, email: user.email) 58 | 59 | Continue() 60 | end 61 | ``` 62 | 63 |

⬆ back to top

64 | 65 | ## solid-process-2.80..solid-process-2.95--2.99 66 | 67 | ```diff 68 | class User::Registration < Solid::Process 69 | deps do 70 | - attribute :mailer, default: UserMailer 71 | - attribute :repository, default: User::Repository 72 | attribute :token_creation, default: User::Token::Creation 73 | attribute :account_creation, default: Account::Member::OwnerCreation 74 | 75 | + attribute :mailer, default: UserMailer 76 | + attribute :repository, default: User::Repository 77 | + attribute :temporary_token, default: User::TemporaryToken 78 | + 79 | validates :repository, respond_to: [:exists?, :create!] 80 | + validates :temporary_token, respond_to: [:to] 81 | end 82 | 83 | input do 84 | @@ -34,7 +37,7 @@ 85 | .and_then(:create_user_token) 86 | } 87 | .and_then(:send_email_confirmation) 88 | - .and_expose(:user_registered, [:user]) 89 | + .and_expose(:user_registered, [:user, :user_token]) 90 | end 91 | 92 | private 93 | @@ -63,15 +66,14 @@ 94 | 95 | def create_user_token(user:, **) 96 | case deps.token_creation.call(user:) 97 | - in Solid::Success then Continue() 98 | + in Solid::Success(token:) then Continue(user_token: token.value) 99 | end 100 | end 101 | 102 | def send_email_confirmation(user:, **) 103 | - deps.mailer.with( 104 | - user:, 105 | - token: user.generate_token_for(:email_confirmation) 106 | - ).email_confirmation.deliver_later 107 | + token = deps.temporary_token.to(:email_confirmation, user) 108 | + 109 | + deps.mailer.with(token:, email: user.email).email_confirmation.deliver_later 110 | 111 | Continue() 112 | end 113 | ``` 114 | 115 |

⬆ back to top

116 | 117 | ## solid-process-2.60..solid-process-2.80 118 | 119 | ```diff 120 | attribute :mailer, default: UserMailer 121 | attribute :repository, default: User::Repository 122 | attribute :token_creation, default: User::Token::Creation 123 | - attribute :task_list_creation, default: Account::Task::List::Creation 124 | + attribute :account_creation, default: Account::Member::OwnerCreation 125 | 126 | - validates :repository, respond_to: [:exists?, :create!, :create_account!] 127 | + validates :repository, respond_to: [:exists?, :create!] 128 | end 129 | 130 | input do 131 | + attribute :uuid, :string, default: -> { ::UUID.generate } 132 | attribute :email, :string 133 | attribute :password, :string 134 | attribute :password_confirmation, :string 135 | @@ -18,6 +19,7 @@ 136 | end 137 | 138 | with_options presence: true do 139 | + validates :uuid, format: ::UUID::REGEXP 140 | validates :email, format: User::Email::REGEXP 141 | validates :password, confirmation: true, length: {minimum: User::Password::MINIMUM_LENGTH} 142 | end 143 | @@ -29,7 +31,6 @@ 144 | .and_then(:check_if_email_is_taken) 145 | .and_then(:create_user) 146 | .and_then(:create_user_account) 147 | - .and_then(:create_user_inbox) 148 | .and_then(:create_user_token) 149 | } 150 | .and_then(:send_email_confirmation) 151 | @@ -44,8 +45,8 @@ 152 | input.errors.any? ? Failure(:invalid_input, input:) : Continue() 153 | end 154 | 155 | - def create_user(email:, password:, password_confirmation:, **) 156 | - case deps.repository.create!(email:, password:, password_confirmation:) 157 | + def create_user(uuid:, email:, password:, password_confirmation:, **) 158 | + case deps.repository.create!(uuid:, email:, password:, password_confirmation:) 159 | in Solid::Success(user:) then Continue(user:) 160 | in Solid::Failure(user:) 161 | input.errors.merge!(user.errors) 162 | @@ -55,20 +56,14 @@ 163 | end 164 | 165 | def create_user_account(user:, **) 166 | - result = deps.repository.create_account!(user:) 167 | - 168 | - Continue(account: result[:account]) 169 | + case deps.account_creation.call(uuid: user.uuid) 170 | + in Solid::Success then Continue() 171 | end 172 | - 173 | - def create_user_inbox(account:, **) 174 | - case deps.task_list_creation.call(account:, inbox: true) 175 | - in Solid::Success(task_list:) then Continue() 176 | end 177 | - end 178 | 179 | def create_user_token(user:, **) 180 | case deps.token_creation.call(user:) 181 | - in Solid::Success(token:) then Continue() 182 | + in Solid::Success then Continue() 183 | end 184 | end 185 | ``` 186 | 187 |

⬆ back to top

188 | 189 | ## solid-process-2.20--2.40..solid-process-2.60 190 | 191 | ```diff 192 | class User::Registration < Solid::Process 193 | deps do 194 | attribute :mailer, default: UserMailer 195 | + attribute :repository, default: User::Repository 196 | attribute :token_creation, default: User::Token::Creation 197 | attribute :task_list_creation, default: Account::Task::List::Creation 198 | + 199 | + validates :repository, respond_to: [:exists?, :create!, :create_account!] 200 | end 201 | 202 | input do 203 | @@ -36,27 +39,25 @@ 204 | private 205 | 206 | def check_if_email_is_taken(email:, **) 207 | - input.errors.add(:email, "has already been taken") if User::Record.exists?(email:) 208 | + input.errors.add(:email, "has already been taken") if deps.repository.exists?(email:) 209 | 210 | input.errors.any? ? Failure(:invalid_input, input:) : Continue() 211 | end 212 | 213 | def create_user(email:, password:, password_confirmation:, **) 214 | - user = User::Record.create(email:, password:, password_confirmation:) 215 | - 216 | - return Continue(user:) if user.persisted? 217 | - 218 | + case deps.repository.create!(email:, password:, password_confirmation:) 219 | + in Solid::Success(user:) then Continue(user:) 220 | + in Solid::Failure(user:) 221 | input.errors.merge!(user.errors) 222 | 223 | Failure(:invalid_input, input:) 224 | end 225 | + end 226 | 227 | def create_user_account(user:, **) 228 | - account = Account::Record.create!(uuid: SecureRandom.uuid) 229 | + result = deps.repository.create_account!(user:) 230 | 231 | - account.memberships.create!(user:, role: :owner) 232 | - 233 | - Continue(account:) 234 | + Continue(account: result[:account]) 235 | end 236 | 237 | def create_user_inbox(account:, **) 238 | ``` 239 | 240 |

⬆ back to top

241 | 242 | ## solid-process-2.00..solid-process-2.20--2.40 243 | 244 | ```diff 245 | private 246 | 247 | def check_if_email_is_taken(email:, **) 248 | - input.errors.add(:email, "has already been taken") if User.exists?(email:) 249 | + input.errors.add(:email, "has already been taken") if User::Record.exists?(email:) 250 | 251 | input.errors.any? ? Failure(:invalid_input, input:) : Continue() 252 | end 253 | 254 | def create_user(email:, password:, password_confirmation:, **) 255 | - user = User.create(email:, password:, password_confirmation:) 256 | + user = User::Record.create(email:, password:, password_confirmation:) 257 | 258 | return Continue(user:) if user.persisted? 259 | 260 | @@ -52,7 +52,7 @@ 261 | end 262 | 263 | def create_user_account(user:, **) 264 | - account = Account.create!(uuid: SecureRandom.uuid) 265 | + account = Account::Record.create!(uuid: SecureRandom.uuid) 266 | 267 | account.memberships.create!(user:, role: :owner) 268 | ``` 269 | 270 |

⬆ back to top

271 | 272 | ## solid-process-1..solid-process-2.00 273 | 274 | ```diff 275 | class User::Registration < Solid::Process 276 | + deps do 277 | + attribute :mailer, default: UserMailer 278 | + attribute :token_creation, default: User::Token::Creation 279 | + attribute :task_list_creation, default: Account::Task::List::Creation 280 | + end 281 | + 282 | input do 283 | attribute :email, :string 284 | attribute :password, :string 285 | attribute :password_confirmation, :string 286 | + 287 | + before_validation do 288 | + self.email = email.downcase.strip 289 | end 290 | 291 | + with_options presence: true do 292 | + validates :email, format: User::Email::REGEXP 293 | + validates :password, confirmation: true, length: {minimum: User::Password::MINIMUM_LENGTH} 294 | + end 295 | + end 296 | + 297 | def call(attributes) 298 | rollback_on_failure { 299 | Given(attributes) 300 | + .and_then(:check_if_email_is_taken) 301 | .and_then(:create_user) 302 | .and_then(:create_user_account) 303 | .and_then(:create_user_inbox) 304 | @@ -19,6 +35,12 @@ 305 | 306 | private 307 | 308 | + def check_if_email_is_taken(email:, **) 309 | + input.errors.add(:email, "has already been taken") if User.exists?(email:) 310 | + 311 | + input.errors.any? ? Failure(:invalid_input, input:) : Continue() 312 | + end 313 | + 314 | def create_user(email:, password:, password_confirmation:, **) 315 | user = User.create(email:, password:, password_confirmation:) 316 | 317 | @@ -38,19 +60,19 @@ 318 | end 319 | 320 | def create_user_inbox(account:, **) 321 | - account.task_lists.inbox.create! 322 | - 323 | - Continue() 324 | + case deps.task_list_creation.call(account:, inbox: true) 325 | + in Solid::Success(task_list:) then Continue() 326 | end 327 | + end 328 | 329 | def create_user_token(user:, **) 330 | - user.create_token! 331 | - 332 | - Continue() 333 | + case deps.token_creation.call(user:) 334 | + in Solid::Success(token:) then Continue() 335 | end 336 | + end 337 | 338 | def send_email_confirmation(user:, **) 339 | - UserMailer.with( 340 | + deps.mailer.with( 341 | user:, 342 | token: user.generate_token_for(:email_confirmation) 343 | ).email_confirmation.deliver_later 344 | ``` 345 | 346 |

⬆ back to top

347 | 348 | ## solid-process-0..solid-process-1 349 | 350 | ```diff 351 | class User::Registration < Solid::Process 352 | input do 353 | - attribute :email 354 | - attribute :password 355 | - attribute :password_confirmation 356 | + attribute :email, :string 357 | + attribute :password, :string 358 | + attribute :password_confirmation, :string 359 | end 360 | 361 | def call(attributes) 362 | - user = User.new(attributes) 363 | + rollback_on_failure { 364 | + Given(attributes) 365 | + .and_then(:create_user) 366 | + .and_then(:create_user_account) 367 | + .and_then(:create_user_inbox) 368 | + .and_then(:create_user_token) 369 | + } 370 | + .and_then(:send_email_confirmation) 371 | + .and_expose(:user_registered, [:user]) 372 | + end 373 | 374 | - return Failure(:invalid_user, user:) if user.invalid? 375 | + private 376 | 377 | - ActiveRecord::Base.transaction do 378 | - user.save! 379 | + def create_user(email:, password:, password_confirmation:, **) 380 | + user = User.create(email:, password:, password_confirmation:) 381 | 382 | + return Continue(user:) if user.persisted? 383 | + 384 | + input.errors.merge!(user.errors) 385 | + 386 | + Failure(:invalid_input, input:) 387 | + end 388 | + 389 | + def create_user_account(user:, **) 390 | account = Account.create!(uuid: SecureRandom.uuid) 391 | 392 | - account.memberships.create!(user: user, role: :owner) 393 | + account.memberships.create!(user:, role: :owner) 394 | 395 | + Continue(account:) 396 | + end 397 | + 398 | + def create_user_inbox(account:, **) 399 | account.task_lists.inbox.create! 400 | 401 | + Continue() 402 | + end 403 | + 404 | + def create_user_token(user:, **) 405 | user.create_token! 406 | + 407 | + Continue() 408 | end 409 | 410 | + def send_email_confirmation(user:, **) 411 | UserMailer.with( 412 | - user: user, 413 | + user:, 414 | token: user.generate_token_for(:email_confirmation) 415 | ).email_confirmation.deliver_later 416 | 417 | - Success(:user_registered, user: user) 418 | + Continue() 419 | end 420 | end 421 | ``` 422 | 423 |

⬆ back to top

424 | -------------------------------------------------------------------------------- /docs/versions/model/solid-process-0.rb: -------------------------------------------------------------------------------- 1 | class User::Registration < Solid::Process 2 | input do 3 | attribute :email 4 | attribute :password 5 | attribute :password_confirmation 6 | end 7 | 8 | def call(attributes) 9 | user = User.new(attributes) 10 | 11 | return Failure(:invalid_user, user:) if user.invalid? 12 | 13 | ActiveRecord::Base.transaction do 14 | user.save! 15 | 16 | account = Account.create!(uuid: SecureRandom.uuid) 17 | 18 | account.memberships.create!(user: user, role: :owner) 19 | 20 | account.task_lists.inbox.create! 21 | 22 | user.create_token! 23 | end 24 | 25 | UserMailer.with( 26 | user: user, 27 | token: user.generate_token_for(:email_confirmation) 28 | ).email_confirmation.deliver_later 29 | 30 | Success(:user_registered, user: user) 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /docs/versions/model/solid-process-1.rb: -------------------------------------------------------------------------------- 1 | class User::Registration < Solid::Process 2 | input do 3 | attribute :email, :string 4 | attribute :password, :string 5 | attribute :password_confirmation, :string 6 | end 7 | 8 | def call(attributes) 9 | rollback_on_failure { 10 | Given(attributes) 11 | .and_then(:create_user) 12 | .and_then(:create_user_account) 13 | .and_then(:create_user_inbox) 14 | .and_then(:create_user_token) 15 | } 16 | .and_then(:send_email_confirmation) 17 | .and_expose(:user_registered, [:user]) 18 | end 19 | 20 | private 21 | 22 | def create_user(email:, password:, password_confirmation:, **) 23 | user = User.create(email:, password:, password_confirmation:) 24 | 25 | return Continue(user:) if user.persisted? 26 | 27 | input.errors.merge!(user.errors) 28 | 29 | Failure(:invalid_input, input:) 30 | end 31 | 32 | def create_user_account(user:, **) 33 | account = Account.create!(uuid: SecureRandom.uuid) 34 | 35 | account.memberships.create!(user:, role: :owner) 36 | 37 | Continue(account:) 38 | end 39 | 40 | def create_user_inbox(account:, **) 41 | account.task_lists.inbox.create! 42 | 43 | Continue() 44 | end 45 | 46 | def create_user_token(user:, **) 47 | user.create_token! 48 | 49 | Continue() 50 | end 51 | 52 | def send_email_confirmation(user:, **) 53 | UserMailer.with( 54 | user:, 55 | token: user.generate_token_for(:email_confirmation) 56 | ).email_confirmation.deliver_later 57 | 58 | Continue() 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /docs/versions/model/solid-process-2.00.rb: -------------------------------------------------------------------------------- 1 | class User::Registration < Solid::Process 2 | deps do 3 | attribute :mailer, default: UserMailer 4 | attribute :token_creation, default: User::Token::Creation 5 | attribute :task_list_creation, default: Account::Task::List::Creation 6 | end 7 | 8 | input do 9 | attribute :email, :string 10 | attribute :password, :string 11 | attribute :password_confirmation, :string 12 | 13 | before_validation do 14 | self.email = email.downcase.strip 15 | end 16 | 17 | with_options presence: true do 18 | validates :email, format: User::Email::REGEXP 19 | validates :password, confirmation: true, length: {minimum: User::Password::MINIMUM_LENGTH} 20 | end 21 | end 22 | 23 | def call(attributes) 24 | rollback_on_failure { 25 | Given(attributes) 26 | .and_then(:check_if_email_is_taken) 27 | .and_then(:create_user) 28 | .and_then(:create_user_account) 29 | .and_then(:create_user_inbox) 30 | .and_then(:create_user_token) 31 | } 32 | .and_then(:send_email_confirmation) 33 | .and_expose(:user_registered, [:user]) 34 | end 35 | 36 | private 37 | 38 | def check_if_email_is_taken(email:, **) 39 | input.errors.add(:email, "has already been taken") if User.exists?(email:) 40 | 41 | input.errors.any? ? Failure(:invalid_input, input:) : Continue() 42 | end 43 | 44 | def create_user(email:, password:, password_confirmation:, **) 45 | user = User.create(email:, password:, password_confirmation:) 46 | 47 | return Continue(user:) if user.persisted? 48 | 49 | input.errors.merge!(user.errors) 50 | 51 | Failure(:invalid_input, input:) 52 | end 53 | 54 | def create_user_account(user:, **) 55 | account = Account.create!(uuid: SecureRandom.uuid) 56 | 57 | account.memberships.create!(user:, role: :owner) 58 | 59 | Continue(account:) 60 | end 61 | 62 | def create_user_inbox(account:, **) 63 | case deps.task_list_creation.call(account:, inbox: true) 64 | in Solid::Success(task_list:) then Continue() 65 | end 66 | end 67 | 68 | def create_user_token(user:, **) 69 | case deps.token_creation.call(user:) 70 | in Solid::Success(token:) then Continue() 71 | end 72 | end 73 | 74 | def send_email_confirmation(user:, **) 75 | deps.mailer.with( 76 | user:, 77 | token: user.generate_token_for(:email_confirmation) 78 | ).email_confirmation.deliver_later 79 | 80 | Continue() 81 | end 82 | end 83 | -------------------------------------------------------------------------------- /docs/versions/model/solid-process-2.20--2.40.rb: -------------------------------------------------------------------------------- 1 | class User::Registration < Solid::Process 2 | deps do 3 | attribute :mailer, default: UserMailer 4 | attribute :token_creation, default: User::Token::Creation 5 | attribute :task_list_creation, default: Account::Task::List::Creation 6 | end 7 | 8 | input do 9 | attribute :email, :string 10 | attribute :password, :string 11 | attribute :password_confirmation, :string 12 | 13 | before_validation do 14 | self.email = email.downcase.strip 15 | end 16 | 17 | with_options presence: true do 18 | validates :email, format: User::Email::REGEXP 19 | validates :password, confirmation: true, length: {minimum: User::Password::MINIMUM_LENGTH} 20 | end 21 | end 22 | 23 | def call(attributes) 24 | rollback_on_failure { 25 | Given(attributes) 26 | .and_then(:check_if_email_is_taken) 27 | .and_then(:create_user) 28 | .and_then(:create_user_account) 29 | .and_then(:create_user_inbox) 30 | .and_then(:create_user_token) 31 | } 32 | .and_then(:send_email_confirmation) 33 | .and_expose(:user_registered, [:user]) 34 | end 35 | 36 | private 37 | 38 | def check_if_email_is_taken(email:, **) 39 | input.errors.add(:email, "has already been taken") if User::Record.exists?(email:) 40 | 41 | input.errors.any? ? Failure(:invalid_input, input:) : Continue() 42 | end 43 | 44 | def create_user(email:, password:, password_confirmation:, **) 45 | user = User::Record.create(email:, password:, password_confirmation:) 46 | 47 | return Continue(user:) if user.persisted? 48 | 49 | input.errors.merge!(user.errors) 50 | 51 | Failure(:invalid_input, input:) 52 | end 53 | 54 | def create_user_account(user:, **) 55 | account = Account::Record.create!(uuid: SecureRandom.uuid) 56 | 57 | account.memberships.create!(user:, role: :owner) 58 | 59 | Continue(account:) 60 | end 61 | 62 | def create_user_inbox(account:, **) 63 | case deps.task_list_creation.call(account:, inbox: true) 64 | in Solid::Success(task_list:) then Continue() 65 | end 66 | end 67 | 68 | def create_user_token(user:, **) 69 | case deps.token_creation.call(user:) 70 | in Solid::Success(token:) then Continue() 71 | end 72 | end 73 | 74 | def send_email_confirmation(user:, **) 75 | deps.mailer.with( 76 | user:, 77 | token: user.generate_token_for(:email_confirmation) 78 | ).email_confirmation.deliver_later 79 | 80 | Continue() 81 | end 82 | end 83 | -------------------------------------------------------------------------------- /docs/versions/model/solid-process-2.60.rb: -------------------------------------------------------------------------------- 1 | class User::Registration < Solid::Process 2 | deps do 3 | attribute :mailer, default: UserMailer 4 | attribute :repository, default: User::Repository 5 | attribute :token_creation, default: User::Token::Creation 6 | attribute :task_list_creation, default: Account::Task::List::Creation 7 | 8 | validates :repository, respond_to: [:exists?, :create!, :create_account!] 9 | end 10 | 11 | input do 12 | attribute :email, :string 13 | attribute :password, :string 14 | attribute :password_confirmation, :string 15 | 16 | before_validation do 17 | self.email = email.downcase.strip 18 | end 19 | 20 | with_options presence: true do 21 | validates :email, format: User::Email::REGEXP 22 | validates :password, confirmation: true, length: {minimum: User::Password::MINIMUM_LENGTH} 23 | end 24 | end 25 | 26 | def call(attributes) 27 | rollback_on_failure { 28 | Given(attributes) 29 | .and_then(:check_if_email_is_taken) 30 | .and_then(:create_user) 31 | .and_then(:create_user_account) 32 | .and_then(:create_user_inbox) 33 | .and_then(:create_user_token) 34 | } 35 | .and_then(:send_email_confirmation) 36 | .and_expose(:user_registered, [:user]) 37 | end 38 | 39 | private 40 | 41 | def check_if_email_is_taken(email:, **) 42 | input.errors.add(:email, "has already been taken") if deps.repository.exists?(email:) 43 | 44 | input.errors.any? ? Failure(:invalid_input, input:) : Continue() 45 | end 46 | 47 | def create_user(email:, password:, password_confirmation:, **) 48 | case deps.repository.create!(email:, password:, password_confirmation:) 49 | in Solid::Success(user:) then Continue(user:) 50 | in Solid::Failure(user:) 51 | input.errors.merge!(user.errors) 52 | 53 | Failure(:invalid_input, input:) 54 | end 55 | end 56 | 57 | def create_user_account(user:, **) 58 | result = deps.repository.create_account!(user:) 59 | 60 | Continue(account: result[:account]) 61 | end 62 | 63 | def create_user_inbox(account:, **) 64 | case deps.task_list_creation.call(account:, inbox: true) 65 | in Solid::Success(task_list:) then Continue() 66 | end 67 | end 68 | 69 | def create_user_token(user:, **) 70 | case deps.token_creation.call(user:) 71 | in Solid::Success(token:) then Continue() 72 | end 73 | end 74 | 75 | def send_email_confirmation(user:, **) 76 | deps.mailer.with( 77 | user:, 78 | token: user.generate_token_for(:email_confirmation) 79 | ).email_confirmation.deliver_later 80 | 81 | Continue() 82 | end 83 | end 84 | -------------------------------------------------------------------------------- /docs/versions/model/solid-process-2.80.rb: -------------------------------------------------------------------------------- 1 | class User::Registration < Solid::Process 2 | deps do 3 | attribute :mailer, default: UserMailer 4 | attribute :repository, default: User::Repository 5 | attribute :token_creation, default: User::Token::Creation 6 | attribute :account_creation, default: Account::Member::OwnerCreation 7 | 8 | validates :repository, respond_to: [:exists?, :create!] 9 | end 10 | 11 | input do 12 | attribute :uuid, :string, default: -> { ::UUID.generate } 13 | attribute :email, :string 14 | attribute :password, :string 15 | attribute :password_confirmation, :string 16 | 17 | before_validation do 18 | self.email = email.downcase.strip 19 | end 20 | 21 | with_options presence: true do 22 | validates :uuid, format: ::UUID::REGEXP 23 | validates :email, format: User::Email::REGEXP 24 | validates :password, confirmation: true, length: {minimum: User::Password::MINIMUM_LENGTH} 25 | end 26 | end 27 | 28 | def call(attributes) 29 | rollback_on_failure { 30 | Given(attributes) 31 | .and_then(:check_if_email_is_taken) 32 | .and_then(:create_user) 33 | .and_then(:create_user_account) 34 | .and_then(:create_user_token) 35 | } 36 | .and_then(:send_email_confirmation) 37 | .and_expose(:user_registered, [:user]) 38 | end 39 | 40 | private 41 | 42 | def check_if_email_is_taken(email:, **) 43 | input.errors.add(:email, "has already been taken") if deps.repository.exists?(email:) 44 | 45 | input.errors.any? ? Failure(:invalid_input, input:) : Continue() 46 | end 47 | 48 | def create_user(uuid:, email:, password:, password_confirmation:, **) 49 | case deps.repository.create!(uuid:, email:, password:, password_confirmation:) 50 | in Solid::Success(user:) then Continue(user:) 51 | in Solid::Failure(user:) 52 | input.errors.merge!(user.errors) 53 | 54 | Failure(:invalid_input, input:) 55 | end 56 | end 57 | 58 | def create_user_account(user:, **) 59 | case deps.account_creation.call(uuid: user.uuid) 60 | in Solid::Success then Continue() 61 | end 62 | end 63 | 64 | def create_user_token(user:, **) 65 | case deps.token_creation.call(user:) 66 | in Solid::Success then Continue() 67 | end 68 | end 69 | 70 | def send_email_confirmation(user:, **) 71 | deps.mailer.with( 72 | user:, 73 | token: user.generate_token_for(:email_confirmation) 74 | ).email_confirmation.deliver_later 75 | 76 | Continue() 77 | end 78 | end 79 | -------------------------------------------------------------------------------- /docs/versions/model/solid-process-2.95--2.99.rb: -------------------------------------------------------------------------------- 1 | class User::Registration < Solid::Process 2 | deps do 3 | attribute :token_creation, default: User::Token::Creation 4 | attribute :account_creation, default: Account::Member::OwnerCreation 5 | 6 | attribute :mailer, default: UserMailer 7 | attribute :repository, default: User::Repository 8 | attribute :temporary_token, default: User::TemporaryToken 9 | 10 | validates :repository, respond_to: [:exists?, :create!] 11 | validates :temporary_token, respond_to: [:to] 12 | end 13 | 14 | input do 15 | attribute :uuid, :string, default: -> { ::UUID.generate } 16 | attribute :email, :string 17 | attribute :password, :string 18 | attribute :password_confirmation, :string 19 | 20 | before_validation do 21 | self.email = email.downcase.strip 22 | end 23 | 24 | with_options presence: true do 25 | validates :uuid, format: ::UUID::REGEXP 26 | validates :email, format: User::Email::REGEXP 27 | validates :password, confirmation: true, length: {minimum: User::Password::MINIMUM_LENGTH} 28 | end 29 | end 30 | 31 | def call(attributes) 32 | rollback_on_failure { 33 | Given(attributes) 34 | .and_then(:check_if_email_is_taken) 35 | .and_then(:create_user) 36 | .and_then(:create_user_account) 37 | .and_then(:create_user_token) 38 | } 39 | .and_then(:send_email_confirmation) 40 | .and_expose(:user_registered, [:user, :user_token]) 41 | end 42 | 43 | private 44 | 45 | def check_if_email_is_taken(email:, **) 46 | input.errors.add(:email, "has already been taken") if deps.repository.exists?(email:) 47 | 48 | input.errors.any? ? Failure(:invalid_input, input:) : Continue() 49 | end 50 | 51 | def create_user(uuid:, email:, password:, password_confirmation:, **) 52 | case deps.repository.create!(uuid:, email:, password:, password_confirmation:) 53 | in Solid::Success(user:) then Continue(user:) 54 | in Solid::Failure(user:) 55 | input.errors.merge!(user.errors) 56 | 57 | Failure(:invalid_input, input:) 58 | end 59 | end 60 | 61 | def create_user_account(user:, **) 62 | case deps.account_creation.call(uuid: user.uuid) 63 | in Solid::Success then Continue() 64 | end 65 | end 66 | 67 | def create_user_token(user:, **) 68 | case deps.token_creation.call(user:) 69 | in Solid::Success(token:) then Continue(user_token: token.value) 70 | end 71 | end 72 | 73 | def send_email_confirmation(user:, **) 74 | token = deps.temporary_token.to(:email_confirmation, user) 75 | 76 | deps.mailer.with(token:, email: user.email).email_confirmation.deliver_later 77 | 78 | Continue() 79 | end 80 | end 81 | -------------------------------------------------------------------------------- /docs/versions/model/solid-process-3.rb: -------------------------------------------------------------------------------- 1 | class User::Registration < Solid::Process 2 | deps do 3 | attribute :token_creation, default: User::Token::Creation 4 | attribute :account_creation, default: Account::Member::OwnerCreation 5 | 6 | attribute :mailer, default: -> { User::Adapters.mailer } 7 | attribute :repository, default: -> { User::Adapters.repository } 8 | attribute :temporary_token, default: -> { User::Adapters.temporary_token } 9 | 10 | validates :mailer, respond_to: [:send_email_confirmation] 11 | validates :repository, respond_to: [:create!, :exists?] 12 | validates :temporary_token, respond_to: [:to] 13 | end 14 | 15 | input do 16 | attribute :uuid, :string, default: -> { ::UUID.generate } 17 | attribute :email, :string 18 | attribute :password, :string 19 | attribute :password_confirmation, :string 20 | 21 | before_validation do 22 | self.email = email.downcase.strip 23 | end 24 | 25 | with_options presence: true do 26 | validates :uuid, format: ::UUID::REGEXP 27 | validates :email, format: User::Email::REGEXP 28 | validates :password, confirmation: true, length: {minimum: User::Password::MINIMUM_LENGTH} 29 | end 30 | end 31 | 32 | def call(attributes) 33 | rollback_on_failure { 34 | Given(attributes) 35 | .and_then(:check_if_email_is_taken) 36 | .and_then(:create_user) 37 | .and_then(:create_user_account) 38 | .and_then(:create_user_token) 39 | } 40 | .and_then(:send_email_confirmation) 41 | .and_expose(:user_registered, [:user, :user_token]) 42 | end 43 | 44 | private 45 | 46 | def check_if_email_is_taken(email:, **) 47 | input.errors.add(:email, "has already been taken") if deps.repository.exists?(email:) 48 | 49 | input.errors.any? ? Failure(:invalid_input, input:) : Continue() 50 | end 51 | 52 | def create_user(uuid:, email:, password:, password_confirmation:, **) 53 | case deps.repository.create!(uuid:, email:, password:, password_confirmation:) 54 | in Solid::Success(user:) then Continue(user:) 55 | in Solid::Failure(user:) 56 | input.errors.merge!(user.errors) 57 | 58 | Failure(:invalid_input, input:) 59 | end 60 | end 61 | 62 | def create_user_account(user:, **) 63 | case deps.account_creation.call(uuid: user.uuid) 64 | in Solid::Success then Continue() 65 | end 66 | end 67 | 68 | def create_user_token(user:, **) 69 | case deps.token_creation.call(user:) 70 | in Solid::Success(token:) then Continue(user_token: token.value) 71 | end 72 | end 73 | 74 | def send_email_confirmation(user:, **) 75 | token = deps.temporary_token.to(:email_confirmation, user) 76 | 77 | deps.mailer.send_email_confirmation(token:, email: user.email) 78 | 79 | Continue() 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /docs/versions/model/solid-process-4.rb: -------------------------------------------------------------------------------- 1 | class User::Registration < Solid::Process 2 | deps do 3 | attribute :token_creation, default: User::Token::Creation 4 | attribute :account_creation, default: Account::Member::OwnerCreation 5 | 6 | attribute :mailer, default: -> { User::Adapters.mailer } 7 | attribute :repository, default: -> { User::Adapters.repository } 8 | attribute :temporary_token, default: -> { User::Adapters.temporary_token } 9 | 10 | validates :mailer, kind_of: User::Adapters::MailerInterface 11 | validates :repository, kind_of: User::Adapters::RepositoryInterface 12 | validates :temporary_token, kind_of: User::Adapters::TemporaryTokenInterface 13 | end 14 | 15 | input do 16 | attribute :uuid, :string, default: -> { ::UUID.generate } 17 | attribute :email, :string 18 | attribute :password, :string 19 | attribute :password_confirmation, :string 20 | 21 | before_validation do 22 | self.email = email.downcase.strip 23 | end 24 | 25 | with_options presence: true do 26 | validates :uuid, format: ::UUID::REGEXP 27 | validates :email, format: User::Email::REGEXP 28 | validates :password, confirmation: true, length: {minimum: User::Password::MINIMUM_LENGTH} 29 | end 30 | end 31 | 32 | def call(attributes) 33 | rollback_on_failure { 34 | Given(attributes) 35 | .and_then(:check_if_email_is_taken) 36 | .and_then(:create_user) 37 | .and_then(:create_user_account) 38 | .and_then(:create_user_token) 39 | } 40 | .and_then(:send_email_confirmation) 41 | .and_expose(:user_registered, [:user, :user_token]) 42 | end 43 | 44 | private 45 | 46 | def check_if_email_is_taken(email:, **) 47 | input.errors.add(:email, "has already been taken") if deps.repository.exists?(email:) 48 | 49 | input.errors.any? ? Failure(:invalid_input, input:) : Continue() 50 | end 51 | 52 | def create_user(uuid:, email:, password:, password_confirmation:, **) 53 | case deps.repository.create!(uuid:, email:, password:, password_confirmation:) 54 | in Solid::Success(user:) then Continue(user:) 55 | in Solid::Failure(user:) 56 | input.errors.merge!(user.errors) 57 | 58 | Failure(:invalid_input, input:) 59 | end 60 | end 61 | 62 | def create_user_account(user:, **) 63 | case deps.account_creation.call(uuid: user.uuid) 64 | in Solid::Success then Continue() 65 | end 66 | end 67 | 68 | def create_user_token(user:, **) 69 | case deps.token_creation.call(user:) 70 | in Solid::Success(token:) then Continue(user_token: token.value) 71 | end 72 | end 73 | 74 | def send_email_confirmation(user:, **) 75 | token = deps.temporary_token.to(:email_confirmation, user) 76 | 77 | deps.mailer.send_email_confirmation(token:, email: user.email) 78 | 79 | Continue() 80 | end 81 | end 82 | --------------------------------------------------------------------------------