You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Avoid #steps boilerplate by prepending around #call (#11)
We get rid of the necessity to wrap the operations' flow with the
`#steps`'s block by prepending a module that decorates the `#call`
method.
If before we had to do:
```ruby
class CreateUser < Dry::Operation
def call(input)
steps do
attributes = step validate(input)
step create(attributes)
end
end
# ...
end
```
Now we can do:
```ruby
class CreateUser < Dry::Operation
def call(input)
attributes = step validate(input)
step create(attributes)
end
# ...
end
```
We want to provide that as the default behavior to improve the
ergonomics of the library. However, we also want to provide a way to
customize or opt out of this magic behavior.
After discarding dynamic inheritance because of DX concerns (see
#9), we opt for implementing
a couple of class-level methods to tweak the defaults.
`.operate_on` allows to customize the method to decorate. E.g., this is
how we decorate `#run` instead of `#call`:
```ruby
class CreateUser < Dry::Operation
operate_on :run # Several methods can be passed as arguments
def run(input)
attributes = step validate(input)
step create(attributes)
end
# ...
end
```
On the other hand, `.skip_prepending` allows to opt out of the default
`#call` decoration:
```ruby
class CreateUser < Dry::Operation
skip_prepending
def call(input)
steps do
attributes = step validate(input)
step create(attributes)
end
end
# ...
end
```
To have `#call` decorated by default but still be something
configurable, we need to rely on Ruby's `.method_added` hook. Notice
that for any other method specified by `.operate_on` we could just
include the prepender module and avoid going through the hook. However,
we opt for still using the hook to have a single way of doing things.
Both `.operate_on` and `.skip_prepending` tweaks are inherited by
subclasses, so it's possible to do something like:
```ruby
class BaseOperation < Dry::Operation
operate_on :run
end
class CreateUser < BaseOperation
def run(input)
attributes = step validate(input)
step create(attributes)
end
# ...
end
```
Both methods raise an exception when called after any method has been
prepended. This is to avoid misunderstandings like trying to skip
prepending after the `.method_added` hook has been triggered:
```ruby
class CreateUser < Dry::Operation
def call(input)
steps do
attributes = step validate(input)
step create(attributes)
end
end
skip_prepending # At this point, `#call` would have already been prepended
# ...
end
```
Similarly, `.operate_on` raises an exception when called after the
method has already been defined.
```ruby
class CreateUser < Dry::Operation
def run(input)
attributes = step validate(input)
step create(attributes)
end
operate_on :run # At this point, `.method_added` won't be called for `#run`
# ...
end
```
Those checks are reset when a subclass is defined to allow for
redefinitions or changes in the configuration.
0 commit comments