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.