solid-adapters 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.rubocop.yml +3 -0
- data/.standard.yml +5 -0
- data/CHANGELOG.md +10 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/LICENSE.txt +21 -0
- data/README.md +418 -0
- data/Rakefile +14 -0
- data/examples/README.md +15 -0
- data/examples/anti_corruption_layer/README.md +217 -0
- data/examples/anti_corruption_layer/Rakefile +30 -0
- data/examples/anti_corruption_layer/app/models/payment/charge_credit_card.rb +36 -0
- data/examples/anti_corruption_layer/config.rb +19 -0
- data/examples/anti_corruption_layer/lib/payment_gateways/adapters/circle_up.rb +19 -0
- data/examples/anti_corruption_layer/lib/payment_gateways/adapters/pay_friend.rb +19 -0
- data/examples/anti_corruption_layer/lib/payment_gateways/contract.rb +15 -0
- data/examples/anti_corruption_layer/lib/payment_gateways/response.rb +5 -0
- data/examples/anti_corruption_layer/lib/payment_gateways.rb +11 -0
- data/examples/anti_corruption_layer/vendor/circle_up/client.rb +11 -0
- data/examples/anti_corruption_layer/vendor/pay_friend/client.rb +11 -0
- data/examples/ports_and_adapters/README.md +157 -0
- data/examples/ports_and_adapters/Rakefile +66 -0
- data/examples/ports_and_adapters/app/models/user/record/repository.rb +13 -0
- data/examples/ports_and_adapters/app/models/user/record.rb +7 -0
- data/examples/ports_and_adapters/config.rb +32 -0
- data/examples/ports_and_adapters/db/setup.rb +16 -0
- data/examples/ports_and_adapters/lib/user/creation.rb +19 -0
- data/examples/ports_and_adapters/lib/user/data.rb +5 -0
- data/examples/ports_and_adapters/lib/user/repository.rb +14 -0
- data/examples/ports_and_adapters/test/user_test/repository.rb +21 -0
- data/lib/solid/adapters/configurable/options.rb +44 -0
- data/lib/solid/adapters/configurable.rb +19 -0
- data/lib/solid/adapters/core/config.rb +35 -0
- data/lib/solid/adapters/core/proxy.rb +25 -0
- data/lib/solid/adapters/interface.rb +57 -0
- data/lib/solid/adapters/proxy.rb +11 -0
- data/lib/solid/adapters/version.rb +7 -0
- data/lib/solid/adapters.rb +28 -0
- metadata +85 -0
@@ -0,0 +1,217 @@
|
|
1
|
+
<small>
|
2
|
+
|
3
|
+
> `MENU` [README](../../README.md) | [Examples](../README.md)
|
4
|
+
|
5
|
+
</small>
|
6
|
+
|
7
|
+
## 🛡️ Anti-Corruption Layer Example <!-- omit from toc -->
|
8
|
+
|
9
|
+
- [The ACL](#the-acl)
|
10
|
+
- [🤔 How does it work?](#-how-does-it-work)
|
11
|
+
- [📜 The Contract](#-the-contract)
|
12
|
+
- [🔄 The Adapters](#-the-adapters)
|
13
|
+
- [⚖️ What is the benefit of doing this?](#️-what-is-the-benefit-of-doing-this)
|
14
|
+
- [How much to do this (create ACL)?](#how-much-to-do-this-create-acl)
|
15
|
+
- [Is it worth the overhead of contract checking at runtime?](#is-it-worth-the-overhead-of-contract-checking-at-runtime)
|
16
|
+
- [🏃♂️ How to run the application?](#️-how-to-run-the-application)
|
17
|
+
|
18
|
+
The **Anti-Corruption Layer**, or ACL, is a pattern that isolates and protects a system from legacy or dependencies out of its control. It acts as a mediator, translating and adapting data between different components, ensuring they communicate without corrupting each other's data or logic.
|
19
|
+
|
20
|
+
To illustrate this pattern, let's see an example of an application that uses third-party API to charge a credit card.
|
21
|
+
|
22
|
+
Let's start seeing the code structure of this example:
|
23
|
+
|
24
|
+
```
|
25
|
+
├── Rakefile
|
26
|
+
├── config.rb
|
27
|
+
├── app
|
28
|
+
│ └── models
|
29
|
+
│ └── payment
|
30
|
+
│ └── charge_credit_card.rb
|
31
|
+
├── lib
|
32
|
+
│ ├── payment_gateways
|
33
|
+
│ │ ├── adapters
|
34
|
+
│ │ │ ├── circle_up.rb
|
35
|
+
│ │ │ └── pay_friend.rb
|
36
|
+
│ │ ├── contract.rb
|
37
|
+
│ │ └── response.rb
|
38
|
+
│ └── payment_gateways.rb
|
39
|
+
└── vendor
|
40
|
+
├── circle_up
|
41
|
+
│ └── client.rb
|
42
|
+
└── pay_friend
|
43
|
+
└── client.rb
|
44
|
+
```
|
45
|
+
|
46
|
+
The files and directories are organized as follows:
|
47
|
+
|
48
|
+
- `Rakefile` runs the application.
|
49
|
+
- `config.rb` file contains the configurations.
|
50
|
+
- `app` directory contains the domain model where the business process to charge a credit card is implemented.
|
51
|
+
- `lib` directory contains the payment gateways contract and adapters.
|
52
|
+
- `vendor` directory contains the third-party API clients.
|
53
|
+
|
54
|
+
## The ACL
|
55
|
+
|
56
|
+
The ACL is implemented in the `PaymentGateways` module (see `lib/payment_gateways.rb`). It translates the third-party APIs (see `vendor`) into something known by the application's domain model. Through this module, the application can charge a credit card without knowing the details/internals of the vendors.
|
57
|
+
|
58
|
+
### 🤔 How does it work?
|
59
|
+
|
60
|
+
The `PaymentGateways::ChargeCreditCard` class (see `app/models/payment/charge_credit_card.rb`) uses`PaymentGateways::Contract` to ensure the `payment_gateway` object implements the required and known interface (input and output) to charge a credit card.
|
61
|
+
|
62
|
+
```ruby
|
63
|
+
module Payment
|
64
|
+
class ChargeCreditCard
|
65
|
+
include ::Solid::Output.mixin(config: { addon: { continue: true } })
|
66
|
+
|
67
|
+
attr_reader :payment_gateway
|
68
|
+
|
69
|
+
def initialize(payment_gateway)
|
70
|
+
@payment_gateway = ::PaymentGateways::Contract.new(payment_gateway)
|
71
|
+
end
|
72
|
+
|
73
|
+
def call(amount:, details: {})
|
74
|
+
Given(amount:)
|
75
|
+
.and_then(:validate_amount)
|
76
|
+
.and_then(:charge_credit_card, details:)
|
77
|
+
.and_expose(:payment_charged, %i[payment_id])
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
def validate_amount(amount:)
|
83
|
+
return Continue() if amount.is_a?(::Numeric) && amount.positive?
|
84
|
+
|
85
|
+
Failure(:invalid_amount, erros: ['amount must be positive'])
|
86
|
+
end
|
87
|
+
|
88
|
+
def charge_credit_card(amount:, details:)
|
89
|
+
response = payment_gateway.charge_credit_card(amount:, details:)
|
90
|
+
|
91
|
+
Continue(payment_id: ::SecureRandom.uuid) if response.success?
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
```
|
96
|
+
|
97
|
+
#### 📜 The Contract
|
98
|
+
|
99
|
+
The `PaymentGateways::Contract` defines the interface of the payment gateways. It is implemented by the `PaymentGateways::Adapters::CircleUp` and `PaymentGateways::Adapters::PayFriend` adapters.
|
100
|
+
|
101
|
+
```ruby
|
102
|
+
module PaymentGateways
|
103
|
+
class Contract < ::Solid::Adapters::Proxy
|
104
|
+
def charge_credit_card(params)
|
105
|
+
params => { amount: Numeric, details: Hash }
|
106
|
+
|
107
|
+
outcome = object.charge_credit_card(params)
|
108
|
+
|
109
|
+
outcome => Response[true | false]
|
110
|
+
|
111
|
+
outcome
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
```
|
116
|
+
|
117
|
+
In this case, the contract will ensure the input by using the `=>` pattern-matching operator, which will raise an exception if it does not match the expected types. After that, it calls the adapter's `charge_credit_card` method and ensures the output is a `PaymentGateways::Response` by using the `=>` operator again.
|
118
|
+
|
119
|
+
The response (see `lib/payment_gateways/response.rb`) will ensure the ACL, as it is the object known/exposed to the application.
|
120
|
+
|
121
|
+
```ruby
|
122
|
+
module PaymentGateways
|
123
|
+
Response = ::Struct.new(:success?)
|
124
|
+
end
|
125
|
+
```
|
126
|
+
|
127
|
+
#### 🔄 The Adapters
|
128
|
+
|
129
|
+
Let's see the payment gateways adapters:
|
130
|
+
|
131
|
+
`lib/payment_gateways/adapters/circle_up.rb`
|
132
|
+
|
133
|
+
```ruby
|
134
|
+
module PaymentGateways
|
135
|
+
class Adapters::CircleUp
|
136
|
+
attr_reader :client
|
137
|
+
|
138
|
+
def initialize
|
139
|
+
@client = ::CircleUp::Client.new
|
140
|
+
end
|
141
|
+
|
142
|
+
def charge_credit_card(params)
|
143
|
+
params => { amount:, details: }
|
144
|
+
|
145
|
+
response = client.charge_cc(amount, details)
|
146
|
+
|
147
|
+
Response.new(response.ok?)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
```
|
152
|
+
|
153
|
+
`lib/payment_gateways/adapters/pay_friend.rb`
|
154
|
+
|
155
|
+
```ruby
|
156
|
+
module PaymentGateways
|
157
|
+
class Adapters::PayFriend
|
158
|
+
attr_reader :client
|
159
|
+
|
160
|
+
def initialize
|
161
|
+
@client = ::PayFriend::Client.new
|
162
|
+
end
|
163
|
+
|
164
|
+
def charge_credit_card(params)
|
165
|
+
params => { amount:, details: }
|
166
|
+
|
167
|
+
response = client.charge(amount:, payment_data: details, payment_method: 'credit_card')
|
168
|
+
|
169
|
+
Response.new(response.status == 'success')
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
```
|
174
|
+
|
175
|
+
You can see that each third-party API has its way of charging a credit card, so the adapters are responsible for translating the input/output from the third-party APIs to the output known by the application (the `PaymentGateways::Response`).
|
176
|
+
|
177
|
+
## ⚖️ What is the benefit of doing this?
|
178
|
+
|
179
|
+
The benefit of doing this is that the core business logic is decoupled from the legacy/external dependencies, which makes it easier to test and promote changes in the code.
|
180
|
+
|
181
|
+
Using this example, if the third-party APIs change, we just need to implement a new adapter and make the business processes (`Payment::ChargeCreditCard`) use it. The business processes will not be affected as it is protected by the ACL.
|
182
|
+
|
183
|
+
### How much to do this (create ACL)?
|
184
|
+
|
185
|
+
Use this pattern when there is a real need to decouple the core business logic from external dependencies.
|
186
|
+
|
187
|
+
You can start with a simple implementation (without ACL) and refactor it to use this pattern when the need arises.
|
188
|
+
|
189
|
+
### Is it worth the overhead of contract checking at runtime?
|
190
|
+
|
191
|
+
You can eliminate the overhead by disabling the `Solid::Adapters::Proxy` class, which is a proxy that forwards all the method calls to the object it wraps.
|
192
|
+
|
193
|
+
When it is disabled, the `Solid::Adapters::Proxy.new` returns the given object so that the method calls are made directly to it.
|
194
|
+
|
195
|
+
To disable it, set the configuration to false:
|
196
|
+
|
197
|
+
```ruby
|
198
|
+
Solid::Adapters.configuration do |config|
|
199
|
+
config.proxy_enabled = false
|
200
|
+
end
|
201
|
+
```
|
202
|
+
|
203
|
+
## 🏃♂️ How to run the application?
|
204
|
+
|
205
|
+
In the same directory as this `README`, run:
|
206
|
+
|
207
|
+
```bash
|
208
|
+
rake
|
209
|
+
|
210
|
+
# -- CircleUp --
|
211
|
+
#
|
212
|
+
# #<Solid::Output::Success type=:payment_charged value={:payment_id=>"2df767d0-af83-4657-b28d-6605044ffe2c"}>
|
213
|
+
#
|
214
|
+
# -- PayFriend --
|
215
|
+
#
|
216
|
+
# #<Solid::Output::Success type=:payment_charged value={:payment_id=>"dd2af4cc-8484-4f6a-bc35-f7a5e6917ecc"}>
|
217
|
+
```
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
if RUBY_VERSION <= '3.1'
|
4
|
+
puts 'This example requires Ruby 3.1 or higher.'
|
5
|
+
exit! 1
|
6
|
+
end
|
7
|
+
|
8
|
+
require_relative 'config'
|
9
|
+
|
10
|
+
task :default do
|
11
|
+
puts '====================='
|
12
|
+
puts 'Anti Corruption Layer'
|
13
|
+
puts '====================='
|
14
|
+
|
15
|
+
puts
|
16
|
+
puts '-- CircleUp --'
|
17
|
+
puts
|
18
|
+
|
19
|
+
circle_up_gateway = PaymentGateways::Adapters::CircleUp.new
|
20
|
+
|
21
|
+
p Payment::ChargeCreditCard.new(circle_up_gateway).call(amount: 100)
|
22
|
+
|
23
|
+
puts
|
24
|
+
puts '-- PayFriend --'
|
25
|
+
puts
|
26
|
+
|
27
|
+
pay_friend_gateway = PaymentGateways::Adapters::PayFriend.new
|
28
|
+
|
29
|
+
p Payment::ChargeCreditCard.new(pay_friend_gateway).call(amount: 200)
|
30
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'securerandom'
|
4
|
+
|
5
|
+
module Payment
|
6
|
+
class ChargeCreditCard
|
7
|
+
include ::Solid::Output.mixin(config: { addon: { continue: true } })
|
8
|
+
|
9
|
+
attr_reader :payment_gateway
|
10
|
+
|
11
|
+
def initialize(payment_gateway)
|
12
|
+
@payment_gateway = ::PaymentGateways::Contract.new(payment_gateway)
|
13
|
+
end
|
14
|
+
|
15
|
+
def call(amount:, details: {})
|
16
|
+
Given(amount:)
|
17
|
+
.and_then(:validate_amount)
|
18
|
+
.and_then(:charge_credit_card, details:)
|
19
|
+
.and_expose(:payment_charged, %i[payment_id])
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def validate_amount(amount:)
|
25
|
+
return Continue() if amount.is_a?(::Numeric) && amount.positive?
|
26
|
+
|
27
|
+
Failure(:invalid_amount, erros: ['amount must be positive'])
|
28
|
+
end
|
29
|
+
|
30
|
+
def charge_credit_card(amount:, details:)
|
31
|
+
response = payment_gateway.charge_credit_card(amount:, details:)
|
32
|
+
|
33
|
+
Continue(payment_id: ::SecureRandom.uuid) if response.success?
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "bundler/inline"
|
4
|
+
|
5
|
+
$LOAD_PATH.unshift(__dir__)
|
6
|
+
|
7
|
+
gemfile do
|
8
|
+
source "https://rubygems.org"
|
9
|
+
|
10
|
+
gem "solid-result", "~> 2.0"
|
11
|
+
gem "solid-adapters", path: "../../"
|
12
|
+
end
|
13
|
+
|
14
|
+
require "vendor/pay_friend/client"
|
15
|
+
require "vendor/circle_up/client"
|
16
|
+
|
17
|
+
require "lib/payment_gateways"
|
18
|
+
|
19
|
+
require "app/models/payment/charge_credit_card"
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PaymentGateways
|
4
|
+
class Adapters::CircleUp
|
5
|
+
attr_reader :client
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@client = ::CircleUp::Client.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def charge_credit_card(params)
|
12
|
+
params => { amount:, details: }
|
13
|
+
|
14
|
+
response = client.charge_cc(amount, details)
|
15
|
+
|
16
|
+
Response.new(response.ok?)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PaymentGateways
|
4
|
+
class Adapters::PayFriend
|
5
|
+
attr_reader :client
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@client = ::PayFriend::Client.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def charge_credit_card(params)
|
12
|
+
params => { amount:, details: }
|
13
|
+
|
14
|
+
response = client.charge(amount:, payment_data: details, payment_method: 'credit_card')
|
15
|
+
|
16
|
+
Response.new(response.status == 'success')
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PaymentGateways
|
4
|
+
class Contract < ::Solid::Adapters::Proxy
|
5
|
+
def charge_credit_card(params)
|
6
|
+
params => { amount: Numeric, details: Hash }
|
7
|
+
|
8
|
+
outcome = object.charge_credit_card(params)
|
9
|
+
|
10
|
+
outcome => Response[true | false]
|
11
|
+
|
12
|
+
outcome
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PaymentGateways
|
4
|
+
require_relative 'payment_gateways/contract'
|
5
|
+
require_relative 'payment_gateways/response'
|
6
|
+
|
7
|
+
module Adapters
|
8
|
+
require_relative 'payment_gateways/adapters/circle_up'
|
9
|
+
require_relative 'payment_gateways/adapters/pay_friend'
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,157 @@
|
|
1
|
+
<small>
|
2
|
+
|
3
|
+
> `MENU` [README](../../README.md) | [Examples](../README.md)
|
4
|
+
|
5
|
+
</small>
|
6
|
+
|
7
|
+
## 🔌 Ports and Adapters Example <!-- omit from toc -->
|
8
|
+
|
9
|
+
- [⚖️ What is the benefit of doing this?](#️-what-is-the-benefit-of-doing-this)
|
10
|
+
- [How much to do this (create Ports and Adapters)?](#how-much-to-do-this-create-ports-and-adapters)
|
11
|
+
- [Is it worth the overhead of contract checking at runtime?](#is-it-worth-the-overhead-of-contract-checking-at-runtime)
|
12
|
+
- [🏃♂️ How to run the application?](#️-how-to-run-the-application)
|
13
|
+
|
14
|
+
Ports and Adapters is an architectural pattern that separates the application's core logic (Ports) from external dependencies (Adapters).
|
15
|
+
|
16
|
+
This example shows how to implement a simple application using this pattern and the gem `solid-adapters`.
|
17
|
+
|
18
|
+
Let's start seeing the code structure:
|
19
|
+
|
20
|
+
```
|
21
|
+
├── Rakefile
|
22
|
+
├── config.rb
|
23
|
+
├── db
|
24
|
+
├── app
|
25
|
+
│ └── models
|
26
|
+
│ └── user
|
27
|
+
│ ├── record
|
28
|
+
│ │ └── repository.rb
|
29
|
+
│ └── record.rb
|
30
|
+
├── lib
|
31
|
+
│ └── user
|
32
|
+
│ ├── creation.rb
|
33
|
+
│ ├── data.rb
|
34
|
+
│ └── repository.rb
|
35
|
+
└── test
|
36
|
+
└── user_test
|
37
|
+
└── repository.rb
|
38
|
+
```
|
39
|
+
|
40
|
+
The files and directories are organized as follows:
|
41
|
+
|
42
|
+
- `Rakefile` runs the application.
|
43
|
+
- `config.rb` file contains the configuration of the application.
|
44
|
+
- `db` directory contains the database. It is not part of the application, but it is used by the application.
|
45
|
+
- `app` directory contains "Rails" components.
|
46
|
+
- `lib` directory contains the core business logic.
|
47
|
+
- `test` directory contains the tests.
|
48
|
+
|
49
|
+
The application is a simple "user management system". It unique core functionality is to create users.
|
50
|
+
|
51
|
+
Now we understand the code structure, let's see the how the pattern is implemented.
|
52
|
+
|
53
|
+
### The Port
|
54
|
+
|
55
|
+
In this application, there is only one business process: `User::Creation` (see `lib/user/creation.rb`), which relies on the `User::Repository` (see `lib/user/repository.rb`) to persist the user.
|
56
|
+
|
57
|
+
The `User::Repository` is an example of **port**, because it is an interface/contract that defines how the core business logic will persist user records.
|
58
|
+
|
59
|
+
```ruby
|
60
|
+
module User::Repository
|
61
|
+
include Solid::Adapters::Interface
|
62
|
+
|
63
|
+
module Methods
|
64
|
+
def create(name:, email:)
|
65
|
+
name => String
|
66
|
+
email => String
|
67
|
+
|
68
|
+
super.tap { _1 => ::User::Data[id: Integer, name: String, email: String] }
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
```
|
73
|
+
|
74
|
+
### The Adapters
|
75
|
+
|
76
|
+
The `User::Repository` is implemented by two adapters:
|
77
|
+
|
78
|
+
- `User::Record::Repository` (see `app/models/user/record/repository.rb`) is an adapter that persists user records in the database (through the `User::Record`, that is an `ActiveRecord` model).
|
79
|
+
|
80
|
+
- `UserTest::Repository` (see `test/user_test/repository.rb`) is an adapter that persists user records in memory (through the `UserTest::Data`, that is a simple in-memory data structure).
|
81
|
+
|
82
|
+
## ⚖️ What is the benefit of doing this?
|
83
|
+
|
84
|
+
The benefit of doing this is that the core business logic is decoupled from the external dependencies, which makes it easier to test and promote changes in the code.
|
85
|
+
|
86
|
+
For example, if we need to change the persistence layer (start to send the data to a REST API or a Redis DB), we just need to implement a new adapter and make the business processes (`User::Creation`) use it.
|
87
|
+
|
88
|
+
### How much to do this (create Ports and Adapters)?
|
89
|
+
|
90
|
+
Use this pattern when there is a real need to decouple the core business logic from external dependencies.
|
91
|
+
|
92
|
+
You can start with a simple implementation (without Ports and Adapters) and refactor it to use this pattern when the need arises.
|
93
|
+
|
94
|
+
### Is it worth the overhead of contract checking at runtime?
|
95
|
+
|
96
|
+
You can eliminate the overhead by disabling the `Solid::Adapters::Interface`, which is enabled by default.
|
97
|
+
|
98
|
+
When it is disabled, the `Solid::Adapters::Interface` won't prepend the interface methods module to the adapter, which means that the adapter won't be checked against the interface.
|
99
|
+
|
100
|
+
To disable it, set the configuration to false:
|
101
|
+
|
102
|
+
```ruby
|
103
|
+
Solid::Adapters.configuration do |config|
|
104
|
+
config.interface_enabled = false
|
105
|
+
end
|
106
|
+
```
|
107
|
+
|
108
|
+
## 🏃♂️ How to run the application?
|
109
|
+
|
110
|
+
In the same directory as this `README`, run:
|
111
|
+
|
112
|
+
```bash
|
113
|
+
rake # or rake SOLID_ADAPTERS_ENABLED=enabled
|
114
|
+
|
115
|
+
# or
|
116
|
+
|
117
|
+
rake SOLID_ADAPTERS_ENABLED=false
|
118
|
+
```
|
119
|
+
|
120
|
+
**Proxy enabled**
|
121
|
+
|
122
|
+
```bash
|
123
|
+
rake # or rake SOLID_ADAPTERS_ENABLED=enabled
|
124
|
+
|
125
|
+
# Output sample:
|
126
|
+
#
|
127
|
+
# -- Valid input --
|
128
|
+
#
|
129
|
+
# Created user: #<struct User::Data id=1, name="Jane", email="jane@foo.com">
|
130
|
+
# Created user: #<struct User::Data id=1, name="John", email="john@bar.com">
|
131
|
+
#
|
132
|
+
# -- Invalid input --
|
133
|
+
#
|
134
|
+
# rake aborted!
|
135
|
+
# NoMatchingPatternError: nil: String === nil does not return true (NoMatchingPatternError)
|
136
|
+
# /.../lib/user/repository.rb:9:in `create'
|
137
|
+
# /.../lib/user/creation.rb:12:in `call'
|
138
|
+
# /.../Rakefile:36:in `block in <top (required)>'
|
139
|
+
```
|
140
|
+
|
141
|
+
**Proxy disabled**
|
142
|
+
|
143
|
+
```bash
|
144
|
+
rake SOLID_ADAPTERS_ENABLED=false
|
145
|
+
|
146
|
+
# Output sample:
|
147
|
+
#
|
148
|
+
# -- Valid input --
|
149
|
+
#
|
150
|
+
# Created user: #<struct User::Data id=1, name="Jane", email="jane@foo.com">
|
151
|
+
# Created user: #<struct User::Data id=1, name="John", email="john@bar.com">
|
152
|
+
#
|
153
|
+
# -- Invalid input --
|
154
|
+
#
|
155
|
+
# Created user: #<struct User::Data id=2, name="Jane", email=nil>
|
156
|
+
# Created user: #<struct User::Data id=3, name="", email=nil>
|
157
|
+
```
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
if RUBY_VERSION <= "3.1"
|
4
|
+
puts "This example requires Ruby 3.1 or higher."
|
5
|
+
exit! 1
|
6
|
+
end
|
7
|
+
|
8
|
+
require_relative "config"
|
9
|
+
|
10
|
+
require_relative "test/user_test/repository"
|
11
|
+
|
12
|
+
task :default do
|
13
|
+
puts
|
14
|
+
puts "------------------"
|
15
|
+
puts "Ports and Adapters"
|
16
|
+
puts "------------------"
|
17
|
+
|
18
|
+
# -- User creation instances
|
19
|
+
|
20
|
+
db_creation = User::Creation.new(repository: User::Record::Repository)
|
21
|
+
|
22
|
+
memory_creation = User::Creation.new(repository: UserTest::Repository.new)
|
23
|
+
|
24
|
+
puts
|
25
|
+
puts "-- Valid input --"
|
26
|
+
puts
|
27
|
+
|
28
|
+
db_creation.call(name: "Jane", email: "jane@foo.com")
|
29
|
+
|
30
|
+
memory_creation.call(name: "John", email: "john@bar.com")
|
31
|
+
|
32
|
+
puts
|
33
|
+
puts "-- Invalid input --"
|
34
|
+
puts
|
35
|
+
|
36
|
+
db_creation.call(name: "Jane", email: nil)
|
37
|
+
|
38
|
+
memory_creation.call(name: "", email: nil)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Output sample: rake SOLID_ADAPTERS_ENABLED=true
|
42
|
+
#
|
43
|
+
# -- Valid input --
|
44
|
+
#
|
45
|
+
# Created user: #<struct User::Data id=1, name="Jane", email="jane@foo.com">
|
46
|
+
# Created user: #<struct User::Data id=1, name="John", email="john@bar.com">
|
47
|
+
#
|
48
|
+
# -- Invalid input --
|
49
|
+
#
|
50
|
+
# rake aborted!
|
51
|
+
# NoMatchingPatternError: nil: String === nil does not return true (NoMatchingPatternError)
|
52
|
+
# /.../lib/user/repository.rb:9:in `create'
|
53
|
+
# /.../lib/user/creation.rb:12:in `call'
|
54
|
+
# /.../Rakefile:36:in `block in <top (required)>'
|
55
|
+
|
56
|
+
# Output sample: rake SOLID_ADAPTERS_ENABLED=false
|
57
|
+
#
|
58
|
+
# -- Valid input --
|
59
|
+
#
|
60
|
+
# Created user: #<struct User::Data id=1, name="Jane", email="jane@foo.com">
|
61
|
+
# Created user: #<struct User::Data id=1, name="John", email="john@bar.com">
|
62
|
+
#
|
63
|
+
# -- Invalid input --
|
64
|
+
#
|
65
|
+
# Created user: #<struct User::Data id=2, name="Jane", email=nil>
|
66
|
+
# Created user: #<struct User::Data id=3, name="", email=nil>
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module User
|
4
|
+
module Record::Repository
|
5
|
+
extend ::User::Repository
|
6
|
+
|
7
|
+
def self.create(name:, email:)
|
8
|
+
record = Record.create!(name:, email:)
|
9
|
+
|
10
|
+
::User::Data.new(id: record.id, name: record.name, email: record.email)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "bundler/inline"
|
4
|
+
|
5
|
+
$LOAD_PATH.unshift(__dir__)
|
6
|
+
|
7
|
+
gemfile do
|
8
|
+
source "https://rubygems.org"
|
9
|
+
|
10
|
+
gem "sqlite3", "~> 1.7"
|
11
|
+
gem "activerecord", "~> 7.1", ">= 7.1.2", require: "active_record"
|
12
|
+
gem "solid-adapters", path: "../../"
|
13
|
+
end
|
14
|
+
|
15
|
+
require "active_support/all"
|
16
|
+
|
17
|
+
require "db/setup"
|
18
|
+
|
19
|
+
::Solid::Adapters.configuration do |config|
|
20
|
+
enabled = ENV.fetch("SOLID_ADAPTERS_ENABLED", "true") != "false"
|
21
|
+
|
22
|
+
config.interface_enabled = enabled
|
23
|
+
end
|
24
|
+
|
25
|
+
module User
|
26
|
+
require "lib/user/data"
|
27
|
+
require "lib/user/repository"
|
28
|
+
require "lib/user/creation"
|
29
|
+
end
|
30
|
+
|
31
|
+
require "app/models/user/record"
|
32
|
+
require "app/models/user/record/repository"
|