Skip to content

Commit 8697d91

Browse files
committed
Update README.md
1 parent ed373fe commit 8697d91

File tree

1 file changed

+203
-1
lines changed

1 file changed

+203
-1
lines changed

README.md

Lines changed: 203 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,12 @@ Use it to enable the [Railway Oriented Programming](https://fsharpforfunandprofi
7878
- [`config.pattern_matching.disable!(:nil_as_valid_value_checking)`](#configpattern_matchingdisablenil_as_valid_value_checking)
7979
- [`config.feature.disable!(:expectations)`](#configfeaturedisableexpectations)
8080
- [`BCDD::Result.config`](#bcddresultconfig)
81+
- [BCDD::Result#and\_then!](#bcddresultand_then)
82+
- [Dependency Injection](#dependency-injection-1)
83+
- [Configuration](#configuration-1)
84+
- [Analysis: Why is `and_then!` an Anti-pattern?](#analysis-why-is-and_then-an-anti-pattern)
85+
- [`#and_then` versus `#and_then!`](#and_then-versus-and_then)
86+
- [Analysis: Why is `#and_then` the antidote/standard?](#analysis-why-is-and_then-the-antidotestandard)
8187
- [About](#about)
8288
- [Development](#development)
8389
- [Contributing](#contributing)
@@ -2079,10 +2085,206 @@ BCDD::Result.config.feature.options
20792085
# "BCDD::Result",
20802086
# "BCDD::Result::Context"
20812087
# ]
2082-
# }
2088+
# },
2089+
# :and_then!=>{
2090+
# :enabled=>false,
2091+
# :affects=>[
2092+
# "BCDD::Result",
2093+
# "BCDD::Result::Context"
2094+
# ]
2095+
# },
20832096
# }
20842097
```
20852098

2099+
<p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
2100+
2101+
## BCDD::Result#and_then!
2102+
2103+
In the Ruby ecosystem, several gems facilitate operation composition using classes and modules. Two notable examples are the `interactor` gem and the `u-case` gem.
2104+
2105+
**`interactor` gem example**
2106+
2107+
```ruby
2108+
class PlaceOrder
2109+
include Interactor::Organizer
2110+
2111+
organize CreateOrder,
2112+
PayOrder,
2113+
SendOrderConfirmation,
2114+
NotifyAdmins
2115+
end
2116+
```
2117+
2118+
**`u-case` gem example**
2119+
2120+
```ruby
2121+
class PlaceOrder < Micro::Case
2122+
flow CreateOrder, PayOrder, SendOrderConfirmation, NotifyAdmins
2123+
end
2124+
2125+
# Alternative approach
2126+
class PlaceOrder < Micro::Case
2127+
def call!
2128+
call(CreateOrder)
2129+
.then(PayOrder)
2130+
.then(SendOrderConfirmation)
2131+
.then(NotifyAdmins)
2132+
end
2133+
end
2134+
```
2135+
2136+
To facilitate migration for users accustomed to the above approaches, `bcdd-result` includes the `BCDD::Result#and_then!`/`BCDD::Result::Context#and_then!` methods, which will invoke the method `call` of the given operation and expect it to return a `BCDD::Result`/`BCDD::Result::Context` object.
2137+
2138+
```ruby
2139+
BCDD::Result.configure do |config|
2140+
config.feature.enable!(:and_then!)
2141+
end
2142+
2143+
class PlaceOrder
2144+
include BCDD::Result::Context.mixin
2145+
2146+
def call(**input)
2147+
Given(input)
2148+
.and_then!(CreateOrder.new)
2149+
.and_then!(PayOrder.new)
2150+
.and_then!(SendOrderConfirmation.new)
2151+
.and_then!(NotifyAdmins.new)
2152+
end
2153+
end
2154+
```
2155+
2156+
<p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
2157+
2158+
#### Dependency Injection
2159+
2160+
Like `#and_then`, `#and_then!` also supports an additional argument for dependency injection.
2161+
2162+
**In BCDD::Result**
2163+
2164+
```ruby
2165+
class PlaceOrder
2166+
include BCDD::Result.mixin
2167+
2168+
def call(input, logger:)
2169+
Given(input)
2170+
.and_then!(CreateOrder.new, logger)
2171+
# Further method chaining...
2172+
end
2173+
end
2174+
```
2175+
2176+
**In BCDD::Result::Context**
2177+
2178+
```ruby
2179+
class PlaceOrder
2180+
include BCDD::Result::Context.mixin
2181+
2182+
def call(logger:, **input)
2183+
Given(input)
2184+
.and_then!(CreateOrder.new, logger:)
2185+
# Further method chaining...
2186+
end
2187+
end
2188+
```
2189+
2190+
<p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
2191+
2192+
#### Configuration
2193+
2194+
```ruby
2195+
BCDD::Result.configure do |config|
2196+
config.feature.enable!(:and_then!)
2197+
2198+
config.and_then!.default_method_name_to_call = :perform
2199+
end
2200+
```
2201+
2202+
**Explanation:**
2203+
2204+
- `enable!(:and_then!)`: Activates the `and_then!` feature.
2205+
2206+
- `default_method_name_to_call`: Sets a default method other than `:call` for `and_then!`.
2207+
2208+
<p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
2209+
2210+
#### Analysis: Why is `and_then!` an Anti-pattern?
2211+
2212+
The `and_then!` approach, despite its brevity, introduces several issues:
2213+
2214+
- **Lack of Clarity:** The input/output relationship between the steps is not apparent.
2215+
2216+
- **Steps Coupling:** Each operation becomes interdependent (high coupling), complicating implementation and compromising the reusability of these operations.
2217+
2218+
We recommend cautious use of `#and_then!`. Due to these issues, it is turned off by default and considered an antipattern.
2219+
2220+
It should be a temporary solution, primarily for assisting in migration from another to gem to `bcdd-result`.
2221+
2222+
<p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
2223+
2224+
#### `#and_then` versus `#and_then!`
2225+
2226+
The main difference between the `#and_then` and `#and_then!` is that the latter does not check the result source. However, as a drawback, the result source will change.
2227+
2228+
Attention: to ensure the correct behavior, do not mix `#and_then` and `#and_then!` in the same result chain.
2229+
2230+
<p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
2231+
2232+
#### Analysis: Why is `#and_then` the antidote/standard?
2233+
2234+
The `BCDD::Result#and_then`/`BCDD::Result::Context#and_then` methods diverge from the above approach by requiring explicit invocation and mapping of the outcomes at each process step. This approach has the following advantages:
2235+
2236+
- **Clarity:** The input/output relationship between the steps is apparent and highly understandable.
2237+
2238+
- **Steps uncoupling:** Each operation becomes independent (low coupling). You can even map a failure result to a success (and vice versa).
2239+
2240+
See this example to understand what your code should look like:
2241+
2242+
```ruby
2243+
class PlaceOrder
2244+
include BCDD::Result::Context.mixin(config: { addon: { continue: true } })
2245+
2246+
def call(**input)
2247+
Given(input)
2248+
.and_then(:create_order)
2249+
.and_then(:pay_order)
2250+
.and_then(:send_order_confirmation)
2251+
.and_then(:notify_admins)
2252+
.and_expose(:order_placed, %i[order])
2253+
end
2254+
2255+
private
2256+
2257+
def create_order(customer:, products:)
2258+
CreateOrder.new.call(customer:, products:).handle do |on|
2259+
on.success { |output| Continue(order: output[:order]) }
2260+
on.failure { |error| Failure(:order_creation_failed, error:) }
2261+
end
2262+
end
2263+
2264+
def pay_order(customer:, order:, payment_method:, **)
2265+
PayOrder.new.call(customer:, payment_method:, order:).handle do |on|
2266+
on.success { |output| Continue(payment: output[:payment]) }
2267+
on.failure { |error| Failure(:order_payment_failed, error:) }
2268+
end
2269+
end
2270+
2271+
def send_order_confirmation(customer:, order:, payment:, **)
2272+
SendOrderConfirmation.new.call(customer:, order:, payment:).handle do |on|
2273+
on.success { Continue() }
2274+
on.failure { |error| Failure(:order_confirmation_failed, error:) }
2275+
end
2276+
end
2277+
2278+
def notify_admins(customer:, order:, payment:, **)
2279+
NotifyAdmins.new.call(customer:, order:, payment:)
2280+
2281+
Continue()
2282+
end
2283+
end
2284+
```
2285+
2286+
<p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
2287+
20862288
## About
20872289

20882290
[Rodrigo Serradura](https://github.com/serradura) created this project. He is the B/CDD process/method creator and has already made similar gems like the [u-case](https://github.com/serradura/u-case) and [kind](https://github.com/serradura/kind/blob/main/lib/kind/result.rb). This gem is a general-purpose abstraction/monad, but it also contains key features that serve as facilitators for adopting B/CDD in the code.

0 commit comments

Comments
 (0)