functional-light-service 6.1.0 → 6.2.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 +4 -4
- data/.gitignore +2 -0
- data/CHANGELOG.md +19 -0
- data/README.md +96 -11
- data/functional-light-service.gemspec +3 -1
- data/lib/functional-light-service/action.rb +48 -0
- data/lib/functional-light-service/configuration.rb +12 -2
- data/lib/functional-light-service/context/key_verifier.rb +1 -1
- data/lib/functional-light-service/context.rb +15 -2
- data/lib/functional-light-service/errors.rb +1 -0
- data/lib/functional-light-service/i18n/localization_adapter.rb +50 -0
- data/lib/functional-light-service/localization_adapter.rb +19 -28
- data/lib/functional-light-service/localization_map.rb +9 -0
- data/lib/functional-light-service/organizer/reduce_case.rb +50 -0
- data/lib/functional-light-service/organizer/reduce_if_else.rb +23 -0
- data/lib/functional-light-service/organizer/reduce_until.rb +1 -1
- data/lib/functional-light-service/organizer/reduce_while.rb +31 -0
- data/lib/functional-light-service/organizer/scoped_reducable.rb +2 -2
- data/lib/functional-light-service/organizer/with_reducer_log_decorator.rb +2 -1
- data/lib/functional-light-service/organizer.rb +18 -3
- data/lib/functional-light-service/version.rb +1 -1
- data/lib/functional-light-service.rb +5 -0
- data/spec/acceptance/organizer/add_to_context_spec.rb +24 -0
- data/spec/acceptance/organizer/context_failure_and_skipping_spec.rb +22 -0
- data/spec/acceptance/organizer/execute_spec.rb +21 -0
- data/spec/acceptance/organizer/reduce_case_spec.rb +65 -0
- data/spec/acceptance/organizer/reduce_if_else_spec.rb +60 -0
- data/spec/acceptance/organizer/reduce_while_spec.rb +96 -0
- data/spec/acceptance/skip_all_remaining_spec.rb +139 -0
- data/spec/action_optional_expected_keys_spec.rb +107 -0
- data/spec/context/inspect_spec.rb +13 -3
- data/spec/context_spec.rb +12 -0
- data/spec/i18n_localization_adapter_spec.rb +83 -0
- data/spec/localization_adapter_spec.rb +66 -83
- data/spec/spec_helper.rb +3 -0
- data/spec/test_doubles.rb +28 -0
- metadata +26 -15
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 53e332561acc819e0d2d6488a38adeb3600aadcf31c100e474e1d1b0a6397157
|
|
4
|
+
data.tar.gz: 6500b3cdbf6895ce0e165eb36cc3dc7634ed4fc341cd42ceea3a5553eaf6033d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7b64b265d6d63225baca32509169796242f4003c4000e429f4ab9758a310ae4b1a4668553512b40bc9d070a61cd2566ef0eb1efc8cdad3e06cda7b5b478a1854
|
|
7
|
+
data.tar.gz: 959de9564732c9acd2108c746b36be9da3445ea969a7940943c5c88543044d25fd5e3c5df5f6f6b749aa54f950a9a9a511320d992d0536d901eff116f70d2431
|
data/.gitignore
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,22 @@
|
|
|
1
|
+
## 6.2.0 (2026-07-18)
|
|
2
|
+
|
|
3
|
+
Port selettivo delle feature di light-service upstream 0.14-0.21 (niente Rails
|
|
4
|
+
support; solo aggiunte retro-compatibili).
|
|
5
|
+
|
|
6
|
+
### Added
|
|
7
|
+
- skip_all_remaining!: salta tutto il resto dell'organizer, scope annidati compresi; non viene resettato da reduce_if/iterate/ecc. (upstream 0.21) ( 2026-07-18 ) [ sphynx79]
|
|
8
|
+
- reduce_while: macro dell'organizer che controlla la condizione prima di ogni step (upstream 0.21) ( 2026-07-18 ) [ sphynx79]
|
|
9
|
+
- reduce_if_else: come reduce_if ma con ramo else (upstream 0.17) ( 2026-07-18 ) [ sphynx79]
|
|
10
|
+
- reduce_case: dispatch sugli step in base a un valore del context, kwargs :value/:when/:else (upstream 0.18) ( 2026-07-18 ) [ sphynx79]
|
|
11
|
+
- expects :key, default: valore-o-lambda per chiavi attese opzionali; kwarg diverso da :default solleva UnusableExpectKeyDefaultError (upstream 0.17) ( 2026-07-18 ) [ sphynx79]
|
|
12
|
+
- execute accetta un blocco oltre alla lambda (upstream master) ( 2026-07-18 ) [ sphynx79]
|
|
13
|
+
- :organized_by tra le chiavi riservate del KeyVerifier: errore piu chiaro in caso di collisione (upstream 0.14) ( 2026-07-18 ) [ sphynx79]
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
- add_to_context registra anche gli accessor delle chiavi aggiunte (ctx.mia_chiave); chiavi che collidono con metodi del Context ora sollevano ReservedKeysInContextError invece di passare in silenzio (upstream 0.16) ( 2026-07-18 ) [ sphynx79]
|
|
17
|
+
- i18n non e' piu una dipendenza runtime: di default i simboli si risolvono col nuovo adapter built-in (LocalizationMap + Configuration.locale); l'adapter I18n trasloca in FunctionalLightService::I18n::LocalizationAdapter e viene selezionato solo se l'applicazione carica la gem i18n (upstream 0.19/0.20) ( 2026-07-18 ) [ sphynx79]
|
|
18
|
+
|
|
19
|
+
|
|
1
20
|
## 6.1.0 (2026-07-03)
|
|
2
21
|
|
|
3
22
|
### Added
|
data/README.md
CHANGED
|
@@ -419,6 +419,28 @@ end
|
|
|
419
419
|
In the example above, the organizer invokes four actions.
|
|
420
420
|
The first two run successfully; the third calls skip_remaining!, so the fourth is never executed, yet the overall context stays successful.
|
|
421
421
|
|
|
422
|
+
### Skipping everything, including nested scopes
|
|
423
|
+
|
|
424
|
+
`skip_remaining!` is scoped: constructs like `reduce_if` or `iterate` reset it
|
|
425
|
+
at their boundary, so it only exits the **current** scope. When you need to stop
|
|
426
|
+
the whole organizer from inside a nested construct, use
|
|
427
|
+
`context.skip_all_remaining!` — it is never reset, so every remaining step (in
|
|
428
|
+
the current scope and in the outer ones) is skipped while the context stays
|
|
429
|
+
successful:
|
|
430
|
+
|
|
431
|
+
```ruby
|
|
432
|
+
class StopsEverythingAction
|
|
433
|
+
extend FunctionalLightService::Action
|
|
434
|
+
expects :item
|
|
435
|
+
|
|
436
|
+
executed do |context|
|
|
437
|
+
if context.item.poison_pill?
|
|
438
|
+
context.skip_all_remaining!("Poison pill found, stopping the pipeline")
|
|
439
|
+
end
|
|
440
|
+
end
|
|
441
|
+
end
|
|
442
|
+
```
|
|
443
|
+
|
|
422
444
|
## Benchmarking Actions with Around Advice
|
|
423
445
|
|
|
424
446
|
When you need to profile a pipeline, adding timing code inside every single
|
|
@@ -620,6 +642,33 @@ end
|
|
|
620
642
|
|
|
621
643
|
Want to see it in practice? Check out [this spec](spec/action_expects_and_promises_spec.rb) test file.
|
|
622
644
|
|
|
645
|
+
### Default values for expected keys
|
|
646
|
+
|
|
647
|
+
An expected key can declare a `default`, used when the key is missing from the
|
|
648
|
+
context (also when the action runs inside an organizer). The default can be a
|
|
649
|
+
static value or a lambda receiving the context:
|
|
650
|
+
|
|
651
|
+
```ruby
|
|
652
|
+
class GreetsSomeoneAction
|
|
653
|
+
extend FunctionalLightService::Action
|
|
654
|
+
|
|
655
|
+
expects :name
|
|
656
|
+
expects :greeting, :default => "Hello"
|
|
657
|
+
expects :message, :default => ->(ctx) { "#{ctx[:greeting]}, #{ctx[:name]}!" }
|
|
658
|
+
|
|
659
|
+
executed do |context|
|
|
660
|
+
puts context.message
|
|
661
|
+
end
|
|
662
|
+
end
|
|
663
|
+
|
|
664
|
+
GreetsSomeoneAction.execute(:name => "Rick") # ⇒ "Hello, Rick!"
|
|
665
|
+
```
|
|
666
|
+
|
|
667
|
+
Note that `expects` accepts a single key when a default is given, and any
|
|
668
|
+
keyword other than `default` raises `UnusableExpectKeyDefaultError` at class
|
|
669
|
+
definition time. Keys already reachable through an alias are considered
|
|
670
|
+
present, so their default is not applied.
|
|
671
|
+
|
|
623
672
|
## Key Aliases
|
|
624
673
|
|
|
625
674
|
Need to wire together actions that use different key names?
|
|
@@ -830,10 +879,30 @@ For a full example, see [this acceptance test](spec/acceptance/rollback_spec.rb)
|
|
|
830
879
|
|
|
831
880
|
## Localizing Messages
|
|
832
881
|
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
adapter
|
|
882
|
+
Symbols passed to `fail!`/`succeed!` are looked up through a localization
|
|
883
|
+
adapter. Two adapters ship with the gem:
|
|
884
|
+
|
|
885
|
+
- **Built-in adapter** (default): resolves messages from
|
|
886
|
+
`FunctionalLightService::LocalizationMap.instance`, a plain hash keyed by
|
|
887
|
+
`Configuration.locale` (default `:en`) — no extra dependency needed:
|
|
888
|
+
|
|
889
|
+
```ruby
|
|
890
|
+
FunctionalLightService::LocalizationMap.instance[:en] = {
|
|
891
|
+
:foo_action => {
|
|
892
|
+
:light_service => {
|
|
893
|
+
:failures => { :exceeded_api_limit => "Exceeded API limit" },
|
|
894
|
+
:successes => { :api_call_ok => "All good" }
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
```
|
|
899
|
+
|
|
900
|
+
- **I18n adapter**: selected automatically when your application loads the
|
|
901
|
+
`i18n` gem (it is no longer a runtime dependency of this gem — add it to
|
|
902
|
+
your own Gemfile if you want I18n-backed lookups).
|
|
903
|
+
|
|
904
|
+
If your app needs something more advanced, you can swap in a custom
|
|
905
|
+
localization adapter.
|
|
837
906
|
|
|
838
907
|
```ruby
|
|
839
908
|
class FooAction
|
|
@@ -906,7 +975,7 @@ configuration:
|
|
|
906
975
|
FunctionalLightService::Configuration.localization_adapter = MyLocalizer.new
|
|
907
976
|
|
|
908
977
|
# lib/my_localizer.rb
|
|
909
|
-
class MyLocalizer < FunctionalLightService::LocalizationAdapter
|
|
978
|
+
class MyLocalizer < FunctionalLightService::I18n::LocalizationAdapter
|
|
910
979
|
# change default scope to: "light_service.failures.<class_path>"
|
|
911
980
|
def i18n_scope_from_class(action_class, type)
|
|
912
981
|
"light_service.#{type.pluralize}.#{action_class.name.underscore}"
|
|
@@ -984,15 +1053,18 @@ your actions.
|
|
|
984
1053
|
|
|
985
1054
|
| Construct | Declarative “equivalent” | What it does (in one line) |
|
|
986
1055
|
| ------------------------------------------------------------------ | ------------------------ | ------------------------------------------------------------------------------------------- |
|
|
987
|
-
| [reduce_until](spec/acceptance/organizer/reduce_until_spec.rb) | `
|
|
988
|
-
| [
|
|
1056
|
+
| [reduce_until](spec/acceptance/organizer/reduce_until_spec.rb) | `until` loop | Keeps reducing the listed steps **until** the lambda returns `true`. |
|
|
1057
|
+
| [reduce_while](spec/acceptance/organizer/reduce_while_spec.rb) | `while` guard | Checks the lambda **before each step** and stops as soon as it returns `false`. |
|
|
1058
|
+
| [reduce_if](spec/acceptance/organizer/reduce_if_spec.rb) | `if` | Reduces its sub‑steps **only if** the lambda returns `true`. |
|
|
1059
|
+
| [reduce_if_else](spec/acceptance/organizer/reduce_if_else_spec.rb) | `if/else` | Reduces the first list of steps when the lambda is `true`, the second one otherwise. |
|
|
1060
|
+
| [reduce_case](spec/acceptance/organizer/reduce_case_spec.rb) | `case/when` | Dispatches to the steps matching a context value (`:value`, `:when`, `:else` kwargs). |
|
|
989
1061
|
| [iterate](spec/acceptance/organizer/iterate_spec.rb) | `each` loop | Loops over a collection key; each element is exposed under the **singular** name. |
|
|
990
|
-
| [execute](spec/acceptance/organizer/execute_spec.rb) | one‑off lambda | Runs an inline lambda for quick context tweaks (add keys, transform values, etc.).
|
|
1062
|
+
| [execute](spec/acceptance/organizer/execute_spec.rb) | one‑off lambda | Runs an inline lambda or block for quick context tweaks (add keys, transform values, etc.). |
|
|
991
1063
|
| [with_callback](spec/acceptance/organizer/with_callback_spec.rb) | streaming callback | Defers execution like a SAX parser—great for huge inputs without loading everything in RAM. |
|
|
992
|
-
| [add_to_context](spec/acceptance/organizer/add_to_context_spec.rb) | N/A (context inject) | Injects key–value pairs into the context
|
|
1064
|
+
| [add_to_context](spec/acceptance/organizer/add_to_context_spec.rb) | N/A (context inject) | Injects key–value pairs into the context (defining accessors) before the next steps run. |
|
|
993
1065
|
| [add_aliases](spec/acceptance/organizer/add_aliases_spec.rb) | key aliasing | Creates an alias so actions can read/write the same value under different names. |
|
|
994
1066
|
|
|
995
|
-
All
|
|
1067
|
+
All ten are covered by acceptance tests in spec/acceptance/organizer/*_spec.rb.
|
|
996
1068
|
|
|
997
1069
|
**Tip**: When iterating, the collection must already be in the context.
|
|
998
1070
|
iterate(:items) expects context[:items]; it then places each element under
|
|
@@ -1003,10 +1075,23 @@ iterate(:items, [ProcessItem])
|
|
|
1003
1075
|
# Inside ProcessItem → context.item
|
|
1004
1076
|
```
|
|
1005
1077
|
|
|
1006
|
-
Need a quick context mutation?
|
|
1078
|
+
Need a quick context mutation? Use execute, with a lambda or a block:
|
|
1007
1079
|
|
|
1008
1080
|
```ruby
|
|
1009
1081
|
execute(->(c) { c[:some_values] = c.some_hash.values })
|
|
1082
|
+
# or
|
|
1083
|
+
execute { |c| c[:some_values] = c.some_hash.values }
|
|
1084
|
+
```
|
|
1085
|
+
|
|
1086
|
+
Need to branch on a context value? Use reduce_case:
|
|
1087
|
+
|
|
1088
|
+
```ruby
|
|
1089
|
+
reduce_case :value => :status,
|
|
1090
|
+
:when => {
|
|
1091
|
+
:active => [NotifiesUserAction],
|
|
1092
|
+
:archived => [ArchivesRecordAction]
|
|
1093
|
+
},
|
|
1094
|
+
:else => [RaisesUnknownStatusAction]
|
|
1010
1095
|
```
|
|
1011
1096
|
|
|
1012
1097
|
## ContextFactory for Faster Action Testing
|
|
@@ -16,11 +16,13 @@ Gem::Specification.new do |gem|
|
|
|
16
16
|
gem.required_ruby_version = '>= 3.1.0'
|
|
17
17
|
|
|
18
18
|
gem.add_runtime_dependency("dry-inflector", ">= 0.2.1", "< 2")
|
|
19
|
-
gem.add_runtime_dependency("i18n", "~> 1.8", ">= 1.8.11")
|
|
20
19
|
# logger non e' piu una default gem da Ruby 3.5/4.0: senza questa
|
|
21
20
|
# dichiarazione `require 'logger'` fallisce sotto Bundler
|
|
22
21
|
gem.add_runtime_dependency("logger", "~> 1.5")
|
|
23
22
|
|
|
23
|
+
# i18n e' opzionale a runtime: l'adapter I18n viene usato solo se
|
|
24
|
+
# l'applicazione ospite carica la gem (vedi Configuration)
|
|
25
|
+
gem.add_development_dependency("i18n", "~> 1.8", ">= 1.8.11")
|
|
24
26
|
gem.add_development_dependency("rake", "~> 13.0")
|
|
25
27
|
gem.add_development_dependency("rspec", "~> 3.13")
|
|
26
28
|
gem.add_development_dependency("simplecov", "~> 0.22")
|
|
@@ -17,6 +17,12 @@ module FunctionalLightService
|
|
|
17
17
|
|
|
18
18
|
module Macros
|
|
19
19
|
def expects(*args)
|
|
20
|
+
if expect_key_having_default?(args)
|
|
21
|
+
available_defaults[args.first] = args.last[:default]
|
|
22
|
+
|
|
23
|
+
args = [args.first]
|
|
24
|
+
end
|
|
25
|
+
|
|
20
26
|
expected_keys.concat(args)
|
|
21
27
|
end
|
|
22
28
|
|
|
@@ -66,11 +72,53 @@ module FunctionalLightService
|
|
|
66
72
|
private
|
|
67
73
|
|
|
68
74
|
def create_action_context(context)
|
|
75
|
+
# I default vanno applicati anche quando l'action gira dentro un
|
|
76
|
+
# organizer (il context è già un Context): prima dell'early return.
|
|
77
|
+
# Il guard sull'ivar evita lavoro (e scritture lazy) nel percorso caldo
|
|
78
|
+
apply_expects_defaults(context) if @available_defaults
|
|
79
|
+
|
|
69
80
|
return context if context.is_a? FunctionalLightService::Context
|
|
70
81
|
|
|
71
82
|
FunctionalLightService::Context.make(context)
|
|
72
83
|
end
|
|
73
84
|
|
|
85
|
+
def apply_expects_defaults(context)
|
|
86
|
+
usable_defaults(context).each do |ctx_key, default|
|
|
87
|
+
context[ctx_key] = extract_default(default, context)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def available_defaults
|
|
92
|
+
@available_defaults ||= {}
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def expect_key_having_default?(key)
|
|
96
|
+
return false unless key.size == 2 && key.last.is_a?(Hash)
|
|
97
|
+
return true if key.last.key?(:default)
|
|
98
|
+
|
|
99
|
+
bad_key = key.last.keys.first
|
|
100
|
+
err_msg = "Specify defaults with a `default` key. You have #{bad_key}."
|
|
101
|
+
raise UnusableExpectKeyDefaultError, err_msg
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def missing_expected_keys(context)
|
|
105
|
+
# context.key? risolve anche gli alias: `expected_keys - context.keys`
|
|
106
|
+
# (upstream) darebbe falsi mancanti sulle chiavi aliasate
|
|
107
|
+
expected_keys.reject { |key| context.key?(key) }
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def usable_defaults(context)
|
|
111
|
+
available_defaults.slice(
|
|
112
|
+
*(missing_expected_keys(context) & available_defaults.keys)
|
|
113
|
+
)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def extract_default(default, context)
|
|
117
|
+
return default unless default.respond_to?(:call)
|
|
118
|
+
|
|
119
|
+
default.call(context)
|
|
120
|
+
end
|
|
121
|
+
|
|
74
122
|
def all_keys
|
|
75
123
|
expected_keys + promised_keys
|
|
76
124
|
end
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
module FunctionalLightService
|
|
4
4
|
class Configuration
|
|
5
5
|
class << self
|
|
6
|
-
attr_writer :logger, :localization_adapter
|
|
6
|
+
attr_writer :logger, :localization_adapter, :locale
|
|
7
7
|
|
|
8
8
|
def logger
|
|
9
9
|
@logger = _default_logger unless instance_variable_defined?("@logger")
|
|
@@ -11,7 +11,17 @@ module FunctionalLightService
|
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
def localization_adapter
|
|
14
|
-
|
|
14
|
+
# La gem i18n non è una dipendenza: l'adapter I18n viene scelto solo
|
|
15
|
+
# se la costante è già stata caricata dall'applicazione ospite
|
|
16
|
+
@localization_adapter ||= if Module.const_defined?('I18n')
|
|
17
|
+
FunctionalLightService::I18n::LocalizationAdapter.new
|
|
18
|
+
else
|
|
19
|
+
LocalizationAdapter.new
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def locale
|
|
24
|
+
@locale ||= :en
|
|
15
25
|
end
|
|
16
26
|
|
|
17
27
|
private
|
|
@@ -116,7 +116,7 @@ module FunctionalLightService
|
|
|
116
116
|
def reserved_keys
|
|
117
117
|
# _aliases/_before_actions/_after_actions sono chiavi infrastrutturali
|
|
118
118
|
# scritte da Organizer.with nel context
|
|
119
|
-
%i[message error_code current_action
|
|
119
|
+
%i[message error_code current_action organized_by
|
|
120
120
|
_aliases _before_actions _after_actions].freeze
|
|
121
121
|
end
|
|
122
122
|
end
|
|
@@ -14,6 +14,7 @@ module FunctionalLightService
|
|
|
14
14
|
outcome = Success(:message => '', :error => nil))
|
|
15
15
|
@outcome = outcome
|
|
16
16
|
@skip_remaining = false
|
|
17
|
+
@skip_all_remaining = false
|
|
17
18
|
context.to_hash.each { |k, v| self[k] = v }
|
|
18
19
|
end
|
|
19
20
|
# rubocop:enable Lint/MissingSuper
|
|
@@ -46,6 +47,10 @@ module FunctionalLightService
|
|
|
46
47
|
@skip_remaining
|
|
47
48
|
end
|
|
48
49
|
|
|
50
|
+
def skip_all_remaining?
|
|
51
|
+
@skip_all_remaining
|
|
52
|
+
end
|
|
53
|
+
|
|
49
54
|
def reset_skip_remaining!
|
|
50
55
|
# Resetta soltanto il flag: l'esito (e il suo messaggio) non vanno persi
|
|
51
56
|
@skip_remaining = false
|
|
@@ -100,8 +105,15 @@ module FunctionalLightService
|
|
|
100
105
|
@skip_remaining = true
|
|
101
106
|
end
|
|
102
107
|
|
|
108
|
+
# Variante non azzerabile di skip_remaining!: salta tutto il resto
|
|
109
|
+
# dell'organizer, scope annidati compresi (scoped_reduce non la resetta)
|
|
110
|
+
def skip_all_remaining!(message = nil)
|
|
111
|
+
@outcome = Success(:message => message)
|
|
112
|
+
@skip_all_remaining = true
|
|
113
|
+
end
|
|
114
|
+
|
|
103
115
|
def stop_processing?
|
|
104
|
-
failure? || skip_remaining?
|
|
116
|
+
failure? || skip_remaining? || skip_all_remaining?
|
|
105
117
|
end
|
|
106
118
|
|
|
107
119
|
# Registra le chiavi come accessor consentiti: la lettura/scrittura passa
|
|
@@ -174,7 +186,8 @@ module FunctionalLightService
|
|
|
174
186
|
|
|
175
187
|
def inspect
|
|
176
188
|
"#{self.class}(#{self}, success: #{success?}, message: #{check_nil(message)}, error_code: " \
|
|
177
|
-
"#{check_nil(error_code)}, skip_remaining: #{@skip_remaining},
|
|
189
|
+
"#{check_nil(error_code)}, skip_remaining: #{@skip_remaining}, " \
|
|
190
|
+
"skip_all_remaining: #{@skip_all_remaining}, aliases: #{aliases})"
|
|
178
191
|
end
|
|
179
192
|
|
|
180
193
|
private
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'dry/inflector'
|
|
4
|
+
|
|
5
|
+
module FunctionalLightService
|
|
6
|
+
module I18n
|
|
7
|
+
class LocalizationAdapter
|
|
8
|
+
def failure(message_or_key, action_class, i18n_options = {})
|
|
9
|
+
find_translated_message(message_or_key,
|
|
10
|
+
action_class,
|
|
11
|
+
i18n_options,
|
|
12
|
+
:type => :failure)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def success(message_or_key, action_class, i18n_options = {})
|
|
16
|
+
find_translated_message(message_or_key,
|
|
17
|
+
action_class,
|
|
18
|
+
i18n_options,
|
|
19
|
+
:type => :success)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def find_translated_message(message_or_key,
|
|
25
|
+
action_class,
|
|
26
|
+
i18n_options,
|
|
27
|
+
type)
|
|
28
|
+
if message_or_key.is_a?(Symbol)
|
|
29
|
+
translate(message_or_key, action_class, i18n_options.merge(type))
|
|
30
|
+
else
|
|
31
|
+
message_or_key
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def translate(key, action_class, options = {})
|
|
36
|
+
type = options.delete(:type)
|
|
37
|
+
|
|
38
|
+
scope = i18n_scope_from_class(action_class, type)
|
|
39
|
+
options[:scope] = scope
|
|
40
|
+
|
|
41
|
+
::I18n.t(key, **options)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def i18n_scope_from_class(action_class, type)
|
|
45
|
+
inflector = Dry::Inflector.new
|
|
46
|
+
"#{inflector.underscore(action_class.name)}.light_service.#{inflector.pluralize(type)}"
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -1,48 +1,39 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require '
|
|
3
|
+
require 'dry/inflector'
|
|
4
4
|
|
|
5
5
|
module FunctionalLightService
|
|
6
|
+
# Adapter built-in basato su LocalizationMap: nessuna dipendenza da I18n.
|
|
7
|
+
# Viene selezionato da Configuration quando la costante ::I18n non esiste
|
|
6
8
|
class LocalizationAdapter
|
|
7
|
-
|
|
9
|
+
INFLECTOR = Dry::Inflector.new
|
|
10
|
+
|
|
11
|
+
def failure(message_or_key, action_class, options = {})
|
|
8
12
|
find_translated_message(message_or_key,
|
|
9
|
-
action_class,
|
|
10
|
-
|
|
11
|
-
:type => :failure)
|
|
13
|
+
INFLECTOR.underscore(action_class.to_s),
|
|
14
|
+
options.merge(:type => :failures))
|
|
12
15
|
end
|
|
13
16
|
|
|
14
|
-
def success(message_or_key, action_class,
|
|
17
|
+
def success(message_or_key, action_class, options = {})
|
|
15
18
|
find_translated_message(message_or_key,
|
|
16
|
-
action_class,
|
|
17
|
-
|
|
18
|
-
:type => :success)
|
|
19
|
+
INFLECTOR.underscore(action_class.to_s),
|
|
20
|
+
options.merge(:type => :successes))
|
|
19
21
|
end
|
|
20
22
|
|
|
21
23
|
private
|
|
22
24
|
|
|
23
|
-
def find_translated_message(message_or_key,
|
|
24
|
-
action_class,
|
|
25
|
-
i18n_options,
|
|
26
|
-
type)
|
|
25
|
+
def find_translated_message(message_or_key, action_class, options)
|
|
27
26
|
if message_or_key.is_a?(Symbol)
|
|
28
|
-
|
|
27
|
+
FunctionalLightService::LocalizationMap.instance.dig(
|
|
28
|
+
FunctionalLightService::Configuration.locale,
|
|
29
|
+
action_class.to_sym,
|
|
30
|
+
:light_service,
|
|
31
|
+
options[:type],
|
|
32
|
+
message_or_key
|
|
33
|
+
)
|
|
29
34
|
else
|
|
30
35
|
message_or_key
|
|
31
36
|
end
|
|
32
37
|
end
|
|
33
|
-
|
|
34
|
-
def translate(key, action_class, options = {})
|
|
35
|
-
type = options.delete(:type)
|
|
36
|
-
|
|
37
|
-
scope = i18n_scope_from_class(action_class, type)
|
|
38
|
-
options[:scope] = scope
|
|
39
|
-
|
|
40
|
-
I18n.t(key, **options)
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
def i18n_scope_from_class(action_class, type)
|
|
44
|
-
inflector = Dry::Inflector.new
|
|
45
|
-
"#{inflector.underscore(action_class.name)}.light_service.#{inflector.pluralize(type)}"
|
|
46
|
-
end
|
|
47
38
|
end
|
|
48
39
|
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module FunctionalLightService
|
|
4
|
+
module Organizer
|
|
5
|
+
class ReduceCase
|
|
6
|
+
extend ScopedReducable
|
|
7
|
+
|
|
8
|
+
class Arguments
|
|
9
|
+
attr_reader :value, :when, :else
|
|
10
|
+
|
|
11
|
+
def initialize(**args)
|
|
12
|
+
validate_arguments(**args)
|
|
13
|
+
@value = args[:value]
|
|
14
|
+
@when = args[:when]
|
|
15
|
+
@else = args[:else]
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
# rubocop:disable Style/MultilineIfModifier
|
|
21
|
+
def validate_arguments(**args)
|
|
22
|
+
raise(
|
|
23
|
+
ArgumentError,
|
|
24
|
+
"Expected keyword arguments: [:value, :when, :else]. Given: #{args.keys}"
|
|
25
|
+
) unless args.keys.intersection(mandatory_arguments).count == mandatory_arguments.count
|
|
26
|
+
end
|
|
27
|
+
# rubocop:enable Style/MultilineIfModifier
|
|
28
|
+
|
|
29
|
+
def mandatory_arguments
|
|
30
|
+
%i[value when else]
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def self.run(organizer, **args)
|
|
35
|
+
arguments = Arguments.new(**args)
|
|
36
|
+
|
|
37
|
+
->(ctx) do
|
|
38
|
+
return ctx if ctx.stop_processing?
|
|
39
|
+
|
|
40
|
+
matched_case = arguments.when.keys.find { |k| k.eql?(ctx[arguments.value]) }
|
|
41
|
+
steps = arguments.when[matched_case] || arguments.else
|
|
42
|
+
|
|
43
|
+
ctx = scoped_reduce(organizer, ctx, steps)
|
|
44
|
+
|
|
45
|
+
ctx
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module FunctionalLightService
|
|
4
|
+
module Organizer
|
|
5
|
+
class ReduceIfElse
|
|
6
|
+
extend ScopedReducable
|
|
7
|
+
|
|
8
|
+
def self.run(organizer, condition_block, if_steps, else_steps)
|
|
9
|
+
->(ctx) do
|
|
10
|
+
return ctx if ctx.stop_processing?
|
|
11
|
+
|
|
12
|
+
ctx = if condition_block.call(ctx)
|
|
13
|
+
scoped_reduce(organizer, ctx, if_steps)
|
|
14
|
+
else
|
|
15
|
+
scoped_reduce(organizer, ctx, else_steps)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
ctx
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module FunctionalLightService
|
|
4
|
+
module Organizer
|
|
5
|
+
class ReduceWhile
|
|
6
|
+
def self.run(organizer, condition_block, steps)
|
|
7
|
+
->(ctx) do
|
|
8
|
+
return ctx if ctx.stop_processing?
|
|
9
|
+
|
|
10
|
+
reset_skip(ctx)
|
|
11
|
+
|
|
12
|
+
Array(steps).each do |step|
|
|
13
|
+
break unless condition_block.call(ctx)
|
|
14
|
+
|
|
15
|
+
ctx = organizer.with(ctx).reduce([step])
|
|
16
|
+
break if ctx.stop_processing?
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
reset_skip(ctx)
|
|
20
|
+
|
|
21
|
+
ctx
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.reset_skip(ctx)
|
|
26
|
+
ctx.reset_skip_remaining! unless ctx.failure? || ctx.skip_all_remaining?
|
|
27
|
+
end
|
|
28
|
+
private_class_method :reset_skip
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -4,9 +4,9 @@ module FunctionalLightService
|
|
|
4
4
|
module Organizer
|
|
5
5
|
module ScopedReducable
|
|
6
6
|
def scoped_reduce(organizer, ctx, steps)
|
|
7
|
-
ctx.reset_skip_remaining! unless ctx.failure?
|
|
7
|
+
ctx.reset_skip_remaining! unless ctx.failure? || ctx.skip_all_remaining?
|
|
8
8
|
ctx = organizer.with(ctx).reduce([steps])
|
|
9
|
-
ctx.reset_skip_remaining! unless ctx.failure?
|
|
9
|
+
ctx.reset_skip_remaining! unless ctx.failure? || ctx.skip_all_remaining?
|
|
10
10
|
|
|
11
11
|
ctx
|
|
12
12
|
end
|
|
@@ -93,7 +93,8 @@ module FunctionalLightService
|
|
|
93
93
|
end
|
|
94
94
|
|
|
95
95
|
def skip_remaining?(context)
|
|
96
|
-
context.respond_to?(:skip_remaining?) && context.skip_remaining?
|
|
96
|
+
(context.respond_to?(:skip_remaining?) && context.skip_remaining?) ||
|
|
97
|
+
(context.respond_to?(:skip_all_remaining?) && context.skip_all_remaining?)
|
|
97
98
|
end
|
|
98
99
|
|
|
99
100
|
def write_skip_remaining_log(context, action)
|