README.md in light-service-ext-0.1.1 vs README.md in light-service-ext-0.1.2
- old
+ new
@@ -1,11 +1,14 @@
-# LightServiceExt
+![LightService](https://raw.githubusercontent.com/adomokos/light-service/master/resources/light-service.png)
-Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/light-service-ext`. To experiment with that code, run `bin/console` for an interactive prompt.
+# Light Service Extensions
-TODO: Delete this and the text above, and describe your gem
+Aims to enhance [light-service](https://github.com/adomokos/light-service) to enhance this powerful and flexible service skeleton framework with an emphasis on simplicity
+## Console
+run `bin/console` for an interactive prompt.
+
## Installation
Add this line to your application's Gemfile:
```ruby
@@ -18,12 +21,197 @@
Or install it yourself as:
$ gem install light-service-ext
-## Usage
+## ApplicationContext
-TODO: Write usage instructions here
+> Adds useful defaults to the organizer/orchestrator context
+- `:input` ~> values originally provided to organizer get moved here for better isolation
+- `:params`
+ - stores values `filtered` and `mapped` from original `input`
+ - outcomes/return values provided by any action that implements `LightServiceExt::ApplicationAction`
+- `:errors`
+ - validation errors processed by `LightServiceExt::ApplicationValidatorAction` [dry-validation](https://github.com/dry-rb/dry-validation) contract
+ - manually added by an action e.g. `{ errors: { email: 'not found' } }`
+- `:successful_actions` ~> provides a list of actions processed mostly useful for debugging purposes
+- `:api_responses` ~> contains a list of external API interactions mostly for recording/debugging purposes
+- `:allow_raise_on_failure` ~> determines whether or not to throw a `RaiseOnContextError` error up the stack in the case of validation errors and/or captured exceptions
+- `:outcome` denotes the current status of the organizer with one of the following flags:
+ - `LightServiceExt::Outcome::COMPLETE`
+
+Example
+
+````ruby
+input = { order: order }
+overrides = {} # optionally override `params`, `errors` and `allow_raise_on_failure`
+LightServiceExt::ApplicationContext.make_with_defaults(input, overrides)
+
+# => { input: { order: order },
+# params: {},
+# errors: {},
+# successful_actions: [],
+# api_responses: [],
+# allow_raise_on_failure: true
+# }
+````
+
+#### Useful methods
+
+- `.add_params(**params)`
+ - Adds given args to context's `params` field
+ - e.g. `add_params(user_id: 1) # => { params: { user_id: 1 } }`
+- `.add_errors(**errors)`
+ - Adds given args to to context's `errors` field
+ - Fails and returns from current action/organizer's context
+ - e.g. `add_to_errors(email: 'not found') # => { errors: { email: 'not found' } }`
+
+
+## ApplicationOrganizer
+
+> Adds the following support
+
+### Useful methods
+
+- `.reduce_if_success(<list of actions>)` prevents execution of action/step in the case of context failure or `:errors` present
+- `.with_context(&block)` calls given block with `:ctx` argument
+- `.execute_if` ~> Useful if you want the current `Organizer` to act as a `Orchestrator` and call another organizer
+ - *ONLY* modifies the current organizer/orchestrator's as a result of executing `organizer_or_action_class_or_proc` if manually applied by a given `result_callback` Proc
+ - Executed `steps` do modify the current organizer/orchestrator's context without the need for manual intervention
+ - Arguments:
+ - `condition_block` (required) ~> given block is called with current `context` argument
+ - `organizer_or_action_class_or_proc` (required) ~> only executed if `condition_block` evaluates to `true`
+ - must be one of `ApplicationOrganizer`, `ApplicationAction`, `Proc`
+ - `apply_ctx_transform` (optional)
+ - given block is called prior to `organizer_or_action_class_or_proc` being executed
+ - e.g. `apply_ctx_transform: -> (context) { context[:params][:user_id] = record(context)&.id }`
+ - returned value gets passed to `organizer_or_action_class_or_proc` call
+ - `result_callback` (optional)
+ - given block is called after `organizer_or_action_class_or_proc` has been executed
+ - Useful in the case where you want to augment the current organizer's context based on the context returned from the `organizer_or_action_class_or_proc` call
+ - e.g. `result_callback: -> (ctx:, result:) { ctx[:params] = result[:params] }`
+ - `ctx:` represents the main `organizer/orchestrator's` context
+ - `result:` represents the context returned from the executed `organizer_or_action_class_or_proc`
+ - `steps` (optional) ~> calls current `organizer/orchestrator's` actions/steps and called once `organizer_or_action_class_or_proc` has been processed
+ - *PLEASE NOTE* called regardless of the result from the `organizer_or_action_class_or_proc` call unless you *manually* fail the current context or add `:errors`
+
+#### Error Handling
+> Provided by `.with_error_handler`
+
+- Records errors via `issue_error_report!` into context as exemplified below:
+```ruby
+ {
+ errors: {
+ base: "some-exception-message",
+ internal_only: {
+ type: 'ArgumentError',
+ message: "`user_id` must be a number",
+ exception: "ArgumentError : `user_id` must be a number",
+ backtrace: [], # filtered backtrace via `[ActiveSupport::BacktraceCleaner](https://api.rubyonrails.org/classes/ActiveSupport/BacktraceCleaner.html)`
+ error: original_captured_exception
+ }
+ }
+ }
+```
+
+- Captures `model validation` exceptions and record the messages to the organizer's `:errors` context field
+ - Supports the following exceptions by default
+ - `ActiveRecord::Errors`
+ - `ActiveModel::Errors`
+- Raises any non validation errors up the stack
+
+#### API Responses
+- records api responses set by an action's `:api_response` context field
+- Stored inside of the organizer's `:api_responses` field
+
+#### Retrieve Record
+> Allows for a block to be defined on an organizer in order to retrieve the model record
+
+Example
+
+```ruby
+class TaxCalculator < LightServiceExt::ApplicationOrganizer
+ self.retrieve_record = -> (ctx:) { User.find_by(email: ctx.params[:email]) }
+
+ def self.call(input:)
+ user = record(ctx: input) # `.record` method executes proc provided to `retrieve_record`
+ input = { user: user }.merge(user: user)
+ reduce_with({ input: input }, steps)
+ end
+end
+```
+
+#### Failing The Context
+- Prevents further action's been executed in the following scenarios:
+ - All actions complete determined by organizer's `:outcome` context field set to `LightServiceExt::Outcome::COMPLETE`
+
+### ApplicationAction
+
+#### Useful methods
+- TODO
+
+#### Invoked Action
+- *NOTE* Action's `executed` block gets called by the underlying `LightService::Action`
+ - this means in order to call your action's methods you need to invoke it from `invoked_action:` instead of `self`
+- `invoked_action:` added to current action's context before it gets executed
+ - Consist of an instance of the current action that implements `LightServiceExt::ApplicationAction`
+
+## ApplicationContract
+
+- Enhances `Dry::Validation::Contract` with the following methods:
+ - `#keys` ~> returns names of params defined
+ - `#t` ~> returns translation messages in context with the current organizer
+ - Arguments:
+ - `key` e.g. :not_found
+ - `base_path:` e.g. :user
+ - `**opts` options passed into underlying Rails i18n translate call
+ - E.g. `t(:not_found, base_path: 'business_create', scope: 'user')` would execute
+ - => `I18n.t('business_create.user.not_found', opts.except(:scope))`
+
+## ApplicationValidatorAction
+
+> Responsible for mapping, filtering and validating the context `input:` field
+
+- `executed` block does the following:
+ - Appends `params:` field to the current context with the mapped and filtered values
+ - Appends errors returned from a `ApplicationContract` [dry-validation](https://github.com/dry-rb/dry-validation) contract to the current context's `errors:` field
+ - *NOTE* fails current context if `errors:` present
+
+##### Useful Accessors
+
+- `.contract_class` ~> sets the [dry-validation](https://github.com/dry-rb/dry-validation) contract to be applied by the current validator action
+- `.params_mapper_class` ~> sets the mapper class that must implement `.map_from(context)` and return mapped `:input` values
+
+## ContextError
+
+> Provides all the information related to an exception/validation errors captured by the current organizer
+
+#### Useful methods
+- `#error_info` ~> `ErrorInfo` instance
+- `#context` ~> state of context provided
+- `#error` ~> original exception
+- `#message` ~> summarizes which action failed etc.
+
+## ErrorInfo
+- Summarize captured exception
+
+#### Useful accessors
+- `non_fatal_errors` ~> takes a list of error class names considered to be non fatal exceptions
+
+#### Useful methods
+- `#error` ~> captured exception
+- `#type` ~> exception class name e.g. `ArgumentError`
+- `#message` ~> error message
+- `title` ~> combined error class name and error message e.g. `ArgumentError : email must be present`
+- `#fatal_error?`
+- `#error_summary` ~> summarizes exception with message and cleaned backtrace via `ActiveSupport::BacktraceCleaner`
+
+## Regex
+
+#### Useful methods
+- `.match?(type, value)` e.g. `LightServiceExt::Regex.match?(email:, 'email@domain.com')`
+ - supported `type`:
+ - :email
## Development
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.