Skip to content

Commit

Permalink
narration: wip
Browse files Browse the repository at this point in the history
  • Loading branch information
alessandro-fazzi committed Sep 9, 2024
1 parent aefe739 commit 99cb4ed
Show file tree
Hide file tree
Showing 9 changed files with 1,284 additions and 17 deletions.
122 changes: 114 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@
# Shy::Interactor

I think `interactor` gem could be substituted with 50 lines of ruby and a moderate use of functional patterns.

Long live Interactor.
Refer to the [narration/README.md](narration document) for the long,
follow-through discussion.

## Installation

Expand All @@ -17,9 +16,116 @@ Install the gem and add to the application's Gemfile by executing:

$ bundle add shy-interactor --github "alessandro-fazzi/shy-interactor"

## What does it do

Building interactors (service objects) with different capabilities and composing
them together.

- *Interactor*-like interactors, but without using `OpenStruct`
- creating context as `Struct` with dry-ed syntax
- creating a struct context starting from a `Hash` (with refinements)
- adding methods to struct context even using the dry-ed syntax or hash
refinements conversion method
- validating context
- with active model
- hash context
- struct context
- with dry-validation
- hash context
- struct context
- arbitrary context
- interactors composition (orchestration) without the use of a dedicated class,
but using ruby's own functional composition
- railway interactors
- basic Result::Success/Result::Failure monads
- owner tracking in Result (useful for knowing which interactor has failed
in a pipeline)
-

## Usage

Tests should demonstrate usage. Some interesting spotlights follows.
Tests should demonstrate usage.

```ruby
TestContext#test_can_build_a_context_hash
TestContext#test_can_add_new_methods_to_context_struct_at_build_time
TestContext#test_can_build_a_context_struct
TestContext#test_can_build_context_struct_with_prefilled_members
TestContext#test_can_build_a_context_hash_with_prefilled_members
TestInteractorRailway#test_railway_interactors_can_be_composed
TestInteractorRailway#test_returns_a_success_monad
TestInteractorRailway#test_composed_railway_interactors_correctly_fail
TestInteractorRailway#test_success_can_be_resolved_to_a_value
TestInteractorRailway#test_calling_with_kwargs_will_produce_an_hash_as_argument
TestInteractorRailway#test_failure_has_a_message
TestInteractorRailway#test_failure_monad_has_predicate_method
TestInteractorRailway#test_failure_can_be_resolved_to_a_value
TestInteractorRailway#test_failure_resolves_to_self
TestInteractorRailway#test_returns_a_result_monad
TestInteractorRailway#test_can_be_called_with_an_arbitrary_argument
TestInteractorRailway#test_returns_a_failure_monad
TestInteractorRailway#test_success_monad_has_predicate_method
TestInteractorRailway#test_success_resolves_to_the_returned_value
TestInteractorRailway#test_can_use_our_refinements_to_produce_a_struct_as_initial_result_and_it_works
TestInteractorRailway#test_railway_interactor_does_not_support_rollback
TestHashRefinements#test_can_add_methods_while_creating_the_struct
TestHashRefinements#test_can_add_methods_while_creating_the_context
TestHashRefinements#test_can_transform_an_hash_into_a_struct
TestHashRefinements#test_can_transform_an_hash_into_a_context
TestConfig#test_configuration_can_be_updated_with_configure_convenience_method
TestConfig#test_configuration_can_be_updated_directly_through_accessor
TestConfig#test_default_logger_is_configured_by_default
TestConfig#test_the_main_module_has_a_config_object
TestConfig#test_configuration_is_freezable
TestLogger#test_each_execution_is_logged
TestLogger#test_context_is_logged_too
TestLogger#test_log_format
TestActiveModelContract#test_validation_fails_the_context
TestActiveModelContract#test_validation_in_composition
TestActiveModelContract#test_validates_only_declared_attributes
TestActiveModelContract#test_respond_to_validate
TestActiveModelContract#test_validation_in_composition_should_trigger_rollback_for_previous_interactors
TestActiveModelContract#test_validation_works_when_context_is_a_struct
TestActiveModelContract#test_validation
TestInteractor#test_proc_can_be_used_in_composition
TestInteractor#test_interactor_can_be_rolled_back_when_composed
TestInteractor#test_context_knows_which_interactor_has_failed
TestInteractor#test_outcome_will_be_a_failure_when_an_error_occurred
TestInteractor#test_that_it_has_a_version_number
TestInteractor#test_interactor_can_be_rolled_back
TestInteractor#test_an_interactor_returns_a_result
TestInteractor#test_outcome_will_be_a_success_when_no_error_occurred
TestInteractor#test_it_is_possible_to_compose_interactors_in_reversed_order
TestInteractor#test_main_module_is_defined
TestInteractor#test_with_proc_is_possible_to_simulate_an_around_hook
TestInteractor#test_it_is_possible_to_use_a_struct_as_context
TestInteractor#test_it_is_possible_to_compose_interactors
TestInteractor#test_it_is_possibile_to_compose_interactor_with_sub_compositions
TestInteractor#test_when_using_struct_context_you_must_declare_all_members_in_advance
TestInteractor#test_it_is_possible_to_call_it_with_an_existent_context
TestInteractor#test_callable_method_could_be_customized
TestInteractor#test_when_doing_a_single_interactor_call_kwargs_could_be_used_to_initialize_context
TestInteractor#test_manual_validation
TestInteractor#test_with_proc_is_possible_to_simulate_an_around_hook_also_in_composition
TestInteractor#test_context_is_mutated_by_the_interactor
TestInteractor#test_when_using_composition_kwargs_could_be_used_to_initialize_context
TestDryValidationContract#test_validation_fails_the_context
TestDryValidationContract#test_validation_in_composition
TestDryValidationContract#test_railway_contract_validation
TestDryValidationContract#test_respond_to_validate
TestDryValidationContract#test_validates_only_declared_attributes
TestDryValidationContract#test_validation
TestDryValidationContract#test_railway_contract_validation_when_result_is_a_struct
TestDryValidationContract#test_type_validation
TestDryValidationContract#test_validation_in_composition_should_trigger_rollback_for_previous_interactors
TestDryValidationContract#test_validation_works_when_context_is_a_struct
TestDryValidationContract#test_railway_validation_adds_owner_to_failure_monad_when_it_fails
```

In `test/shy/test_helper.rb` a lot of interactor classes are defined to support
the test suites: they are decent examples to read.

Some interesting spotlights follows.

### Validation

Expand Down Expand Up @@ -83,7 +189,7 @@ class InteractorWithDryValidationContract
def call(ctx); end

contract do
params do
schema do
required(:test)
end
end
Expand Down Expand Up @@ -132,9 +238,9 @@ POC relies on Ruby's own functional composition.
# => 3
```
Including `Shy::Interactor` module will make the descendant respond to `#>>` method (on class)
like a callable object handling the _context_. Moreover any callable object accepting a sole
argument (the _context_) can be added in the composition chain.
Including `Shy::Interactor` module will make the descendant respond to `.>>` method
like a proc object handling the _context_. Moreover any proc object
accepting a sole argument (the _context_) can be added in the composition chain.
> [!IMPORTANT]
> When using an arbitrary callable object, be sure to always return the context at the
Expand Down
20 changes: 11 additions & 9 deletions examples/a_more_functional_cat.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,19 @@
gem "shy-interactor", github: "alessandro-fazzi/shy-interactor"
end

class Cat # rubocop:disable Style/Documentation
module Heterotroph # rubocop:disable Style/Documentation
def hungry? = eaten.size < 2
module Heterotroph # rubocop:disable Style/Documentation
def hungry? = eaten.size < 2

def sated? = eaten.size >= 2
end
def sated? = eaten.size >= 2
end

class Cat # rubocop:disable Style/Documentation
def self.LetItLive(food:) # rubocop:disable Naming/MethodName
context = Shy::Interactor::Context.Struct(food:, eaten: [], walked: false) do
context = Shy::Interactor::Context.Struct(
food:,
eaten: [],
walked: false
) do
extend Heterotroph
end

Expand All @@ -42,7 +46,7 @@ class Eat # rubocop:disable Style/Documentation
include Shy::Interactor

def call(ctx)
return ctx unless ctx.hungry?
return ctx if ctx.sated?

ctx[:eaten] << ctx[:food]
end
Expand All @@ -52,8 +56,6 @@ class Poop # rubocop:disable Style/Documentation
include Shy::Interactor

def call(ctx)
return ctx unless ctx.sated?

ctx[:eaten].replace []
p "Meow"
end
Expand Down
Loading

0 comments on commit 99cb4ed

Please sign in to comment.