{"id":15652121,"url":"https://github.com/rewinfrey/actionlogic","last_synced_at":"2025-04-30T20:07:52.475Z","repository":{"id":2029930,"uuid":"45516884","full_name":"rewinfrey/ActionLogic","owner":"rewinfrey","description":"A business logic abstraction gem that provides structure to the organization and composition of business logic (Ruby)","archived":false,"fork":false,"pushed_at":"2022-06-09T17:46:10.000Z","size":3341,"stargazers_count":33,"open_issues_count":6,"forks_count":3,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-04-30T20:07:45.824Z","etag":null,"topics":["abstraction","business-logic","ruby"],"latest_commit_sha":null,"homepage":"http://rickwinfrey.com/ActionLogic/","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rewinfrey.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":null,"code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-11-04T05:16:19.000Z","updated_at":"2025-02-05T18:12:50.000Z","dependencies_parsed_at":"2022-08-29T15:20:21.958Z","dependency_job_id":null,"html_url":"https://github.com/rewinfrey/ActionLogic","commit_stats":null,"previous_names":["rewinfrey/action_logic"],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rewinfrey%2FActionLogic","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rewinfrey%2FActionLogic/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rewinfrey%2FActionLogic/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rewinfrey%2FActionLogic/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rewinfrey","download_url":"https://codeload.github.com/rewinfrey/ActionLogic/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251774896,"owners_count":21641731,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["abstraction","business-logic","ruby"],"created_at":"2024-10-03T12:41:25.268Z","updated_at":"2025-04-30T20:07:52.442Z","avatar_url":"https://github.com/rewinfrey.png","language":"Ruby","readme":"# ActionLogic\n\n[![Build Status](https://travis-ci.org/rewinfrey/ActionLogic.svg?branch=master)](https://travis-ci.org/rewinfrey/ActionLogic)\n[![Gem Version](https://badge.fury.io/rb/action_logic.svg)](https://badge.fury.io/rb/action_logic)\n[![Code Climate](https://codeclimate.com/github/rewinfrey/action_logic/badges/gpa.svg)](https://codeclimate.com/github/rewinfrey/action_logic)\n[![coverage](https://codecov.io/github/rewinfrey/ActionLogic/coverage.svg?branch=master)](https://codecov.io/github/rewinfrey/ActionLogic?branch=master)\n[![License](https://img.shields.io/badge/license-MIT-green.svg)](http://opensource.org/licenses/MIT)\n\n### Introduction\n\nThis is a business logic abstraction gem that provides structure to the organization and composition of business logic in a Ruby or Rails application. `ActionLogic` is inspired by gems like [ActiveInteraction](https://github.com/orgsync/active_interaction), [DecentExposure](https://github.com/hashrocket/decent_exposure), [Interactor](https://github.com/collectiveidea/interactor), [Light-Service](https://github.com/adomokos/light-service), [Mutations](https://github.com/cypriss/mutations), [Surrounded](https://github.com/saturnflyer/surrounded), [Trailblazer](https://github.com/apotonick/trailblazer) and [Wisper](https://github.com/krisleech/wisper).\n\nWhy another business logic abstraction gem? `ActionLogic` provides teams of various experience levels with a minimal yet powerful set of abstractions that promote easy to write and easy to understand code. By using `ActionLogic`, teams can more quickly and easily write business logic that honors the SOLID principles, is easy to test and easy to reason about, and provides a flexible foundation from which teams can model and define their application's business domains by focusing on reusable units of work that can be composed and validated with one another.\n\n### Contents\n\n* [Backstory](#backstory)\n* [Overview](#overview)\n* [`ActionContext`](#actioncontext)\n* [`ActionTask`](#actiontask)\n* [`ActionUseCase`](#actionusecase)\n* [`ActionCoordinator`](#actioncoordinator)\n* [Succeeding an `ActionContext`](#succeeding-an-actioncontext)\n* [Failing an `ActionContext`](#failing-an-actioncontext)\n* [Halting an `ActionContext`](#halting-an-actioncontext)\n* [Custom `ActionContext` Status](#custom-actioncontext)\n* [Error Handling](#error-handling)\n* [Attribute Validations](#attribute-validations)\n* [Type Validations](#type-validations)\n* [Custom Type Validations](#custom-type-validations)\n* [Presence Validations](#presence-validations)\n* [Custom Presence Validations](#custom-presence-validations)\n* [Before Validations](#before-validations)\n* [After Validations](#after-validations)\n* [Around Validations](#around-validations)\n* [Benchmarking](#benchmarking)\n\t* [Enable Benchmarking](#enable-benchmarking)\n\t* [Benchmark Logging](#benchmark-logging)\n\t* [Benchmark Log Formatting](#benchmark-log-formatting)\n\t* [Custom Benchmark Handling](#custom-benchmark-handling)\n* [Installation](#installation)\n* [Contributing](#contributing)\n\n### Backstory\n\nConsider a traditional e-commerce Rails application. Users can shop online and add items to their shopping cart until they are ready to check out.\nThe happy path scenario might go something like this: the user submits their order form, an orders controller action records the order in the database,\nsubmits the order total to a payment processor, waits for a response from the payment processor, and upon a success response from the payment processor sends\nan order confirmation email to the user, the order is sent internally to the warehouse for fulfillment which requires creating various records in the database,\nand finally the server responds to the initial POST request with a rendered html page including a message indicating the order was successfully processed. In this\nwork flow there are at least 7 distinct steps or tasks that must be satisfied in order for the application's business logic to be considered correct according\nto specifications.\n\nAlthough this flow works well for most users, there are other users whose credit card information might be expired or users who might attempt to check out when\nyour application's payment processor service is down. Additional edge case scenarios start to pop up in error logs as exception emails fill up your inbox.\nWhat happens when that user that is notorious for having 100 tabs open forgets to complete the checkout process and submits a two week old order form that\nincludes an item that your e-commerce store no longer stocks? What happens if an item is sold out? The edge cases and exception emails pile up, and as each one comes in\nyou add more and more logic to that controller action.\n\nWhat once was a simple controller action designed with only the happy path of a successful checkout in mind has now become 100 lines long with 5 to 10 levels\nof nested if statements.  You think on it for awhile and consider not only the technical challenges of refactoring this code, but you'd also like to make this code\nreusable and modular. You want this code to be easy to test and easy to maintain. You want to honor the SOLID principles by writing classes that are singularly focused\nand easy to extend. You reason these new classes should only have to change if the business logic they execute changes. You see that there are relationships between the\nentities and you see the possibility of abstractions that allow entities of similar types to interact nicely with each other. You begin thinking about interfaces and the\nLiskov Substitution Principle, and eventually your mind turns towards domains and data modeling. Where does it end you wonder?\n\nBut you remember your team. It's a team of people all wanting to do their best, and represent a variety of backgrounds and experiences. Each person has varying degress of familiarity\nwith different types of abstractions and approaches, and you wonder what abstractions might be as easy to work with for a new developer as they are for an experienced developer?\nYou consider DSL's you've used in the past and wonder what is that ideal balance between magic and straightforward OOP design?\n\nAs more and more questions pile up in the empty space of your preferred text editor, you receive another exception email for a new problem with the order flow. The questions about\nhow to refactor this code transform into asking questions about how can you edit the existing code to add the new fix? Add a new nested if statement? You do what you can given the\nconstraints you're faced with, and add another 5 lines and another nested if statement. You realize there is not enough time to make this refactor happen, and you've got to push the\nfix out as soon as possible. Yet, as you merge your feature branch in master and deploy a hotfix, you think surely there must be a better way.\n\n`ActionLogic` was born from many hours thinking about these questions and considering how it might be possible to achieve a generic set of abstractions to help guide\nbusiness logic that would promote the SOLID principles and be easy for new and experienced developers to understand and extend. It's not a perfect abstraction (as nothing is),\nbut *can* help simplify your application's business logic by encouraging you to consider the smallest units of work required for your business logic while offering features\nlike type and presence validation that help reduce or eliminate boiler plate, defensive code (nil checks anyone?). However, as with all general purpose libraries, your mileage\nwill vary.\n\n### Overview\n\nThere are three levels of abstraction provided by `ActionLogic`:\n\n* [`ActionTask` (a concrete unit of work)](#action_task)\n* [`ActionUseCase` (organizes two or more `ActionTasks`)](#action_use_case)\n* [`ActionCoordinator` (coordinates two or more `ActionUseCases`)](#action_coordinator)\n\nEach level of abstraction operates with a shared, mutable data structure referred to as a `context` and is an instance of `ActionContext`. This shared `context` is threaded\nthrough each `ActionTask`, `ActionUseCase` and / or `ActionCoordinator` until all work is completed. The resulting `context` is returned to the original caller\n(typically in a Rails application this will be a controller action). In the problem described above we might have an `ActionUseCase` for organizing the checkout order flow,\nand each of the distinct steps would be represented by a separate `ActionTask`. However, overtime it may make more sense to split apart the singular `ActionUseCase` for the order\nflow into smaller `ActionUseCases` that are isolated by their domain (users, payment processor, inventory / warehouse, email, etc.). Considering that we limit our `ActionUseCases` to\nsingle domains, then the `ActionCoordinator` abstraction would allow us to coordinate communication between the `ActionUseCases` and their `ActionTasks` to fulfill the necessary\nwork required when a user submits a checkout order form.\n\nThe diagram below illustrates how the `ActionTask`, `ActionUseCase` and `ActionCoordinator` abstractions work together, and the role of `ActionContext` as the primary, single input:\n\n\u003cimg src=\"https://raw.githubusercontent.com/rewinfrey/action_logic/master/resources/overview_diagram.png\" /\u003e\n\n### ActionContext\n\nThe glue that binds the three layers of abstraction provided in `ActionLogic` is `ActionContext`. Anytime an `ActionTask`, `ActionUseCase` or `ActionCoordinator` is invoked\nan instance of `ActionContext` is created and passed as an input parameter to the receiving execution context. Because each of the three abstractions works in the same way\nwith `ActionContext`, it is intended to be a relatively simple \"learn once understand everywhere\" abstraction.\n\nInstances of `ActionContext` are always referred to within the body of `call` methods defined in any `ActionTask`, `ActionUseCase` or `ActionCoordinator` as `context`. An\ninstance of `ActionContext` is a thin wrapper around Ruby's standard library [`OpenStruct`](http://ruby-doc.org/stdlib-2.0.0/libdoc/ostruct/rdoc/OpenStruct.html). This allows\ninstances of `ActionContext` to be maximally flexible. Arbitrary attributes can be defined on a `context` and their values can be of any type.\n\nIn addition to allowing arbitrary attributes and values to be defined on a `context`, instances of `ActionContext` also conform to a set of simple rules:\n\n* Every `context` instance is instantiated with a default `status` of `:success`\n* A `context` responds to `success?` which returns true if the `status` is `:success`\n* A `context` responds to `fail!` which sets the `status` to `:failure`\n* A `context` responds to `fail?` which returns true if the `status` is `:failure`\n* A `context` rseponds to `halt!` which sets the `status` to `:halted`\n* A `context` responds to `halted?` which returns true if the `status` is `:halted`\n\nEnough with the words, let's look at some code! The following shows an instance of `ActionContext` and its various abilities:\n\n```ruby\ncontext = ActionLogic::ActionContext.new\n\ncontext # =\u003e #\u003cActionLogic::ActionContext status=:success\u003e\n\n# default status is `:success`:\ncontext.status # =\u003e :success\n\n# defining a new attribute called `name` with the value `\"Example\"`:\ncontext.name = \"Example\"\n\n# retrieving the value of the `name` attribute:\ncontext.name # =\u003e \"Example\"\n\n# you can set attributes to anything, including Procs:\ncontext.lambda_example = -\u003e { \"here\" }\n\ncontext.lambda_example # =\u003e #\u003cProc:0x007f8d6b0a9ba0@-:11 (lambda)\u003e\n\ncontext.lambda_example.call # =\u003e \"here\"\n\n# contexts can be failed:\ncontext.fail!\n\ncontext.status # =\u003e :failure\n\ncontext.failure? # =\u003e true\n\n# contexts can also be halted:\ncontext.halt!\n\ncontext.status # =\u003e :halted\n\ncontext.halted? # =\u003e true\n```\n\nNow that we have seen what `ActionContext` can do, let's take a look at the lowest level of absraction in `ActionLogic` that consumes instances of `ActionContext`, the `ActionTask`\nabstraction.\n\n### ActionTask\n\nAt the core of every `ActionLogic` work flow is an `ActionTask`. These classes are the lowest level of abstraction in `ActionLogic` and are where concrete work is performed. All `ActionTasks` conform to the same structure and incorporate all features of `ActionLogic` including validations and error handling.\n\nTo implement an `ActionTask` class you must define a `call` method. You can also specify any before, after or around validations or an error handler. The following code example demonstrates an `ActionTask` class that includes before and after validations, and also demonstrates how an `ActionTask` is invoked :\n\n```ruby\nclass ActionTaskExample\n  include ActionLogic::ActionTask\n\n  validates_before :expected_attribute1 =\u003e { :type =\u003e String },\n                   :expected_attribute2 =\u003e { :type =\u003e Fixnum, :presence =\u003e true }\n  validates_after  :example_attribute1 =\u003e { :type =\u003e String, :presence =\u003e -\u003e(example_attribute1) { !example_attribute1.empty? } }\n\n  def call\n    # adds `example_attribute1` to the shared `context` with the value \"Example value\"\n    context.example_attribute1 = \"New value from context attributes: #{context.expected_attribute1} #{context.expected_attribute2}\"\n  end\nend\n\n# ActionTasks are invoked by calling an `execute` static method directly on the class with an optional hash of key value pairs:\nresult = ActionTaskExample.execute(:expected_attribute1 =\u003e \"example\", :expected_attribute2 =\u003e 123)\n\n# The result object is the shared context object (an instance of ActionContext):\nresult # =\u003e #\u003cActionLogic::ActionContext expected_attribute1=\"example\", expected_attribute2=123, status=:success, example_attribute1=\"New value from context attributes: example 123\"\u003e\n```\n\nThe `ActionTaskExample` is invoked using the static method `execute` which takes an optional hash of attributes that is converted into an `ActionContext`.\nAssuming the before validations are satisfied, the `call` method is invoked. In the body of the `call` method the `ActionTask` can access the shared `ActionContext`\ninstance via a `context` object. This shared `context` object allows for getting and setting attributes as needed. When the `call` method returns, the `context`\nis validated against any defined after validations, and the `context` is then returned to the caller.\n\nThe diagram below is a visual representation of how an `ActionTask` is evaluted when its `execute` method is invoked from a caller:\n\n\u003cimg src=\"https://raw.githubusercontent.com/rewinfrey/action_logic/master/resources/action_task_diagram.png\" /\u003e\n\nAlthough this example is for the `ActionTask` abstraction, `ActionUseCase` and `ActionCoordinator` follow the same pattern. The difference is that `ActionUseCase`\nis designed to organize multiple `ActionTasks`, and `ActionCoordinator` is designed to organize many `ActionUseCases`.\n\n### ActionUseCase\n\nAs business logic grows in complexity the number of steps or tasks required to fulfill that business logic tends to increase. Managing this complexity is a problem every team must face.\nAbstractions can help teams of varying experience levels work together and promote code that remains modular and simple to understand and extend. `ActionUseCase` represents a layer of\nabstraction that organizes multiple `ActionTasks` and executes each `ActionTask` in the order they are defined. Each task receives the same shared `context` so tasks can be composed together.\n\nTo implement an `ActionUseCase` class you must define a `call` method and a `tasks` method. You also can specify any before, after or around validations or an error handler.\nThe following is an example showcasing how an `ActionUseCase` class organizes the execution of multiple `ActionTasks` and defines before and after validations on the shared `context`:\n\n```ruby\nclass ActionUseCaseExample\n  include ActionLogic::ActionUseCase\n\n  validates_before :expected_attribute1 =\u003e { :type =\u003e String },\n                   :expected_attribute2 =\u003e { :type =\u003e Fixnum, :presence =\u003e true }\n  validates_after  :example_task1    =\u003e { :type =\u003e TrueClass, :presence =\u003e true },\n                   :example_task2    =\u003e { :type =\u003e TrueClass, :presence =\u003e true },\n                   :example_task3    =\u003e { :type =\u003e TrueClass, :presence =\u003e true },\n                   :example_usecase1 =\u003e { :type =\u003e TrueClass, :presence =\u003e true }\n\n  # The `call` method is invoked prior to invoking any of the ActionTasks defined by the `tasks` method.\n  # The purpose of the `call` method allows us to prepare the shared `context` prior to invoking the ActionTasks.\n  def call\n    context # =\u003e #\u003cActionLogic::ActionContext expected_attribute1=\"example\", expected_attribute2=123, status=:success\u003e\n    context.example_usecase1 = true\n  end\n\n  def tasks\n    [ActionTaskExample1,\n     ActionTaskExample2,\n     ActionTaskExample3]\n  end\nend\n\nclass ActionTaskExample1\n  include ActionLogic::ActionTask\n  validates_after :example_task1 =\u003e { :type =\u003e TrueClass, :presence =\u003e true }\n\n  def call\n    context # =\u003e #\u003cActionLogic::ActionContext expected_attribute1=\"example\", expected_attribute2=123, status=:success, example_usecase1=true\u003e\n    context.example_task1 = true\n  end\nend\n\nclass ActionTaskExample2\n  include ActionLogic::ActionTask\n  validates_after :example_task2 =\u003e { :type =\u003e TrueClass, :presence =\u003e true }\n\n  def call\n    context # =\u003e #\u003cActionLogic::ActionContext expected_attribute1=\"example\", expected_attribute2=123, status=:success, example_usecase1=true, example_task1=true\u003e\n    context.example_task2 = true\n  end\nend\n\nclass ActionTaskExample3\n  include ActionLogic::ActionTask\n  validates_after :example_task3 =\u003e { :type =\u003e TrueClass, :presence =\u003e true }\n\n  def call\n    context # =\u003e #\u003cActionLogic::ActionContext expected_attribute1=\"example\", expected_attribute2=123, status=:success, example_usecase1=true, example_task1=true, example_task2=true\u003e\n    context.example_task3 = true\n  end\nend\n\n# To invoke the ActionUseCaseExample, we call its execute method with the required attributes:\nresult = ActionUseCaseExample.execute(:expected_attribute1 =\u003e \"example\", :expected_attribute2 =\u003e 123)\n\nresult # =\u003e #\u003cActionLogic::ActionContext expected_attribute1=\"example\", expected_attribute2=123, status=:success, example_usecase1=true, example_task1=true, example_task2=true, example_task3=true\u003e\n```\n\nBy following the value of the shared `context` from the `ActionUseCaseExample` to each of the `ActionTask` classes, it is possible to see how the shared `context`\nis mutated to accomodate the various attributes and their values each execution context adds to the `context`. It also reveals the order in which the `ActionTasks`\nare evaluated, and indicates that the `call` method of the `ActionUseCaseExample` is invoked prior to any of the `ActionTasks` defined in the `tasks` method.\n\nTo help visualize the flow of execution when an `ActionUseCase` is invoked, this diagram aims to illustrate the relationship between `ActionUseCase` and `ActionTasks`\nand the order in which operations are performed:\n\n\u003cimg src=\"https://raw.githubusercontent.com/rewinfrey/action_logic/master/resources/action_use_case_diagram.png\" /\u003e\n\n### ActionCoordinator\n\nSometimes the behavior we wish our Ruby or Rails application to provide requires us to coordinate work between various domains of our application's business logic.\nThe `ActionCoordinator` abstraction is intended to help coordinate multiple `ActionUseCases` by allowing you to define a plan of which `ActionUseCases` to invoke\ndepending on the outcome of each `ActionUseCase` execution. The `ActionCoordinator` abstraction is the highest level of abstraction in `ActionLogic`.\n\nTo implement an `ActionCoordinator` class, you must define a `call` method in addition to a `plan` method. The purpose of the `plan` method is to define a state\ntransition map that links together the various `ActionUseCase` classes the `ActionCoordinator` is organizing, as well as allowing you to define error or halt\nscenarios based on the result of each `ActionUseCase`. The following code example demonstrates a simple `ActionCoordinator`:\n\n```ruby\nclass ActionCoordinatorExample\n  include ActionLogic::ActionCoordinator\n\n  def call\n    context.required_attribute1 = \"required attribute 1\"\n    context.required_attribute2 = \"required attribute 2\"\n  end\n\n  def plan\n    {\n      ActionUseCaseExample1 =\u003e { :success =\u003e ActionUseCaseExample2,\n                                 :failure =\u003e ActionUseCaseFailureExample },\n      ActionUseCaseExample2 =\u003e { :success =\u003e nil,\n                                 :failure =\u003e ActionUseCaseFailureExample },\n      ActionUseCaseFailureExample =\u003e { :success =\u003e nil }\n    }\n  end\nend\n\nclass ActionUseCaseExample1\n  include ActionLogic::ActionUseCase\n\n  validates_before :required_attribute1 =\u003e { :type =\u003e String }\n\n  def call\n    context # =\u003e #\u003cActionLogic::ActionContext status=:success, required_attribute1=\"required attribute 1\", required_attribute2=\"required attribute 2\"\u003e\n    context.example_usecase1 = true\n  end\n\n  # Normally `tasks` would define multiple tasks, but in this example, I've used one ActionTask to keep the overall code example smaller\n  def tasks\n    [ActionTaskExample1]\n  end\nend\n\nclass ActionUseCaseExample2\n  include ActionLogic::ActionUseCase\n\n  validates_before :required_attribute2 =\u003e { :type =\u003e String }\n\n  def call\n    context # =\u003e #\u003cActionLogic::ActionContext status=:success, required_attribute1=\"required attribute 1\", required_attribute2=\"required attribute 2\", example_usecase1=true, example_task1=true\u003e\n    context.example_usecase2 = true\n  end\n\n  # Normally `tasks` would define multiple tasks, but in this example, I've used one ActionTask to keep the overall code example smaller\n  def tasks\n    [ActionTaskExample2]\n  end\nend\n\n# In this example, we are not calling ActionUseCaseFailureExample, but is used to illustrate the purpose of the `plan` of our ActionCoordinator\n# in the event of a failure in one of the consumed `ActionUseCases`\nclass ActionUseCaseFailureExample\n  include ActionLogic::ActionUseCase\n\n  def call\n  end\n\n  def tasks\n    [ActionTaskLogFailure,\n     ActionTaskEmailFailure]\n  end\nend\n\nclass ActionTaskExample1\n  include ActionLogic::ActionTask\n  validates_after :example_task1 =\u003e { :type =\u003e TrueClass, :presence =\u003e true }\n\n  def call\n    context # =\u003e #\u003cActionLogic::ActionContext status=:success, required_attribute1=\"required attribute 1\", required_attribute2=\"required attribute 2\", example_usecase1=true\u003e\n    context.example_task1 = true\n  end\nend\n\nclass ActionTaskExample2\n  include ActionLogic::ActionTask\n  validates_after :example_task2 =\u003e { :type =\u003e TrueClass, :presence =\u003e true }\n\n  def call\n    context # =\u003e #\u003cActionLogic::ActionContext status=:success, required_attribute1=\"required attribute 1\", required_attribute2=\"required attribute 2\", example_usecase1=true, example_task1=true, example_usecase2=true\u003e\n    context.example_task2 = true\n  end\nend\n\nresult = ActionCoordinatorExample.execute\n\nresult # =\u003e #\u003cActionLogic::ActionContext status=:success, required_attribute1=\"required attribute 1\", required_attribute2=\"required attribute 2\", example_usecase1=true, example_task1=true, example_usecase2=true, example_task2=true\u003e\n```\n\n\u003cimg src=\"https://raw.githubusercontent.com/rewinfrey/action_logic/master/resources/action_coordinator_diagram.png\" /\u003e\n\n### Succeeding an `ActionContext`\nBy default, the value of the `status` attribute of instances of `ActionContext` is `:success`. Normally this is useful information for the caller of an `ActionTask`,\n`ActionUseCase` or `ActionCoordinator` because it informs the caller that the various execution context(s) were successful. In other words, a `:success` status\nindicates that none of the execution contexts had a failure or halted execution.\n\n### Failing an `ActionContext`\nUsing `context.fail!` does two important things: it immediately stops the execution of any proceeding business logic (prevents any additional `ActionTasks` from executing)\nand also sets the status of the `context` as `:failure`. This status is most applicable to the caller or an `ActionCoordinator` that might have a plan specifically for a `:failure`\nstatus of a resulting `ActionUseCase`.\n\nThe following is a simple example to show how a `context` is failed within a `call` method:\n\n```ruby\nclass ActionTaskExample\n  include ActionLogic::ActionTask\n\n  def call\n    if failure_condition?\n      context.fail!\n    end\n  end\n\n  def failure_condition?\n    true\n  end\nend\n\nresult = ActionTaskExample.execute\n\nresult # =\u003e #\u003cActionLogic::ActionContext status=:failure, message=\"\"\u003e\n```\n\nWhen failing a `context` it is possible to also specify a message:\n\n```ruby\nclass ActionTaskExample\n  include ActionLogic::ActionTask\n\n  def call\n    if failure_condition?\n      context.fail! \"Something was invalid\"\n    end\n  end\n\n  def failure_condition?\n    true\n  end\nend\n\nresult = ActionTaskExample.execute\n\nresult # =\u003e #\u003cActionLogic::ActionContext status=:failure, message=\"Something was invalid\"\u003e\n\nresult.message # =\u003e \"Something was invalid\"\n```\n\nFrom the above example we see how it is possible to `fail!` a `context` while also specifying a clarifying message about the failure condition. Later, we retrieve\nthat failure message via the `message` attribute defined on the returned `context`.\n\n### Halting an `ActionContext`\nLike, failing a context, Using `context.halt!` does two important things: it immediately halts the execution of any proceeding business logic (prevents any additional `ActionTasks`\nfrom executing) and also sets the status of the `context` as `:halted`. The caller may use that information to define branching logic or an `ActionCoordinator` may use that\ninformation as part of its `plan`.\n\nHowever, unlike failing a `context`, halting is designed to indicate that no more processing is required, but otherwise execution was successful.\n\nThe following is a simple example to show how a `context` is halted within a `call` method:\n\n```ruby\nclass ActionTaskExample\n  include ActionLogic::ActionTask\n\n  def call\n    if halt_condition?\n      context.halt!\n    end\n  end\n\n  def halt_condition?\n    true\n  end\nend\n\nresult = ActionTaskExample.execute\n\nresult # =\u003e #\u003cActionLogic::ActionContext status=:halted, message=\"\"\u003e\n```\n\nWhen failing a `context` it is possible to also specify a message:\n\n```ruby\nclass ActionTaskExample\n  include ActionLogic::ActionTask\n\n  def call\n    if halt_condition?\n      context.halt! \"Something required a halt\"\n    end\n  end\n\n  def halt_condition?\n    true\n  end\nend\n\nresult = ActionTaskExample.execute\n\nresult # =\u003e #\u003cActionLogic::ActionContext status=:halted, message=\"Something required a halt\"\u003e\n\nresult.message # =\u003e \"Something required a halt\"\n```\n\nFrom the above example we see how it is possible to `halt!` a `context` while also specifying a clarifying message about the halt condition. Later, we retrieve\nthat halt message via the `message` attribute defined on the returned `context`.\n\n### Custom `ActionContext` Status\nIt is worthwhile to point out that you should not feel limited to only using the three provided statuses of `:success`, `:failure` or `:halted`. It is easy to implement your\nown system of statuses if you prefer. For example, consider a system that is used to defining various status codes or disposition codes to indicate the result of some business\nlogic. Instances of `ActionContext` can be leveraged to indicate these disposition codes by using the `status` attribute, or by defining custom attributes. You are encouraged\nto expirement and play with the flexibility provided to you by `ActionContext` in determining what is optimal for your given code contexts and your team.\n\n```ruby\nclass RailsControllerExample \u003c ApplicationController\n  def create\n    case create_use_case.status\n      when :disposition_1 then ActionUseCaseSuccess1.execute(create_use_case)\n      when :disposition_2 then ActionUseCaseSuccess2.execute(create_use_case)\n      when :disposition_9 then ActionUseCaseFailure.execute(create_use_case)\n      else\n        ActionUseCaseDefault.execute(create_use_case)\n    end\n  end\n\n  private\n\n  def create_use_case\n    @create_use_case ||= ActionUseCaseExample.execute(params)\n  end\nend\n```\n\nAlthough this contrived example would be ideal for an `ActionCoordinator` (because the result of `ActionUseCaseExample` drives the execution of the next `ActionUseCase`), this\nexample serves to show that `status` can be used with custom disposition codes to drive branching behavior.\n\n### Error Handling\nDuring execution of an `ActionTask`, `ActionUseCase` or `ActionCoordinator` you may wish to define custom behavior for handling errors. Within any of these classes\nyou can define an `error` method that receives as its input the error exception. Invoking an `error` method does not make any assumptions about the `status` of the\nunderlying `context`. Execution of the `ActionTask`, `ActionUseCase` or `ActionCoordinator` also stops after the `error` method returns, and execution of the work\nflow continues as normal unless the `context` is failed or halted.\n\nThe following example is a simple illustration of how an `error` method is invoked for an `ActionTask`:\n\n```ruby\nclass ActionTaskExample\n  include ActionLogic::ActionTask\n\n  def call\n    context.before_raise = true\n    raise \"Something broke\"\n    context.after_raise = true\n  end\n\n  def error(e)\n    context.error = \"the error is passed in as an input parameter: #{e.class}\"\n  end\nend\n\nresult = ActionTaskExample.execute\n\n# the status of the context is not mutated\nresult.status # =\u003e :success\n\nresult.error # =\u003e \"the error is passed in as an input parameter: RuntimeError\"\n\nresult.before_raise # =\u003e true\n\nresult.after_raise # =\u003e nil\n```\n\nIt is important to note that defining an `error` method is **not** required. If at any point in the execution of an `ActionTask`, `ActionUseCase` or `ActionCoordinator`\nan uncaught exception is thrown **and** an `error` method is **not** defined, the exception is raised to the caller.\n\n### Attribute Validations\nThe most simple and basic type of validation offered by `ActionLogic` is attribute validation. To require that an attribute be defined on an instance of `ActionContext`, you\nneed only specify the name of the attribute and an empty hash with one of the three validation types (before, after or around):\n\n```ruby\nclass ActionTaskExample\n  include ActionLogic::ActionTask\n\n  validates_before :required_attribute1 =\u003e {}\n\n  def call\n  end\nend\n\nresult = ActionTaskExample.execute(:required_attribute1 =\u003e true)\n\nresult.status # =\u003e :success\n\nresult.required_attribute1 # =\u003e true\n```\n\nHowever, in the above example, if we were to invoke the `ActionTaskExample` without the `required_attribute1` parameter, the before validation would fail and raise\nan `ActionLogic::MissingAttributeError` and also detail which attribute is missing:\n\n```ruby\nclass ActionTaskExample\n  include ActionLogic::ActionTask\n\n  validates_before :required_attribute1 =\u003e {}\n\n  def call\n  end\nend\n\nActionTaskExample.execute # ~\u003e context: ActionTaskExample message: [:required_attribute1] (ActionLogic::MissingAttributeError)\n```\n\nAttribute validations are defined in the same way regardless of the timing of the validation ([before](#before-validations), [after](#after-validations) or\n[around](#around-validations)). Please refer to the relevant sections for examples of their usage.\n\n### Type Validations\nIn addition to attribute validations, `ActionLogic` also allows you to validate against the type of the value of the attribute you expect to be defined in an instance\nof `ActionContext`. To understand the default types `ActionLogic` validates against, please see the following example:\n\n```ruby\nclass ActionTaskExample\n  include ActionLogic::ActionTask\n\n  validates_after :integer_test =\u003e { :type =\u003e Fixnum },\n                  :float_test   =\u003e { :type =\u003e Float },\n                  :string_test  =\u003e { :type =\u003e String },\n                  :truthy_test  =\u003e { :type =\u003e TrueClass },\n                  :falsey_test  =\u003e { :type =\u003e FalseClass },\n                  :hash_test    =\u003e { :type =\u003e Hash },\n                  :array_test   =\u003e { :type =\u003e Array },\n                  :symbol_test  =\u003e { :type =\u003e Symbol },\n                  :nil_test     =\u003e { :type =\u003e NilClass }\n\n  def call\n    context.integer_test = 123\n    context.float_test   = 1.0\n    context.string_test  = \"test\"\n    context.truthy_test  = true\n    context.falsey_test  = false\n    context.hash_test    = {}\n    context.array_test   = []\n    context.symbol_test  = :symbol\n    context.nil_test     = nil\n  end\nend\n\nresult = ActionTaskExample.execute\n\nresult # =\u003e #\u003cActionLogic::ActionContext status=:success,\n            #                            integer_test=123,\n            #                            float_test=1.0,\n            #                            string_test=\"test\",\n            #                            truthy_test=true,\n            #                            falsey_test=false,\n            #                            hash_test={},\n            #                            array_test=[],\n            #                            symbol_test=:symbol,\n            #                            nil_test=nil\u003e\n```\n\nIt's important to point out that Ruby's `true` and `false` are not `Boolean` but `TrueClass` and `FalseClass` respectively. Additionally, `nil`'s type is `NilClass` in Ruby.\nAlso potentially surprising to some is that Ruby's integer type is of class `Fixnum`, but floats are of class `Float`.\n\nAs we saw with attribute validations, if an attribute's value does not conform to the type expected, `ActionLogic` will raise an `ActionLogic::AttributeTypeError`\nwith a detailed description about which attribute's value failed the validation:\n\n```ruby\nclass ActionTaskExample\n  include ActionLogic::ActionTask\n\n  validates_after :integer_test =\u003e { :type =\u003e Fixnum }\n\n  def call\n    context.integer_test = 1.0\n  end\nend\n\nActionTaskExample.execute # ~\u003e context: ActionTaskExample message: Attribute: integer_test with value: 1.0 was expected to be of type Fixnum but is Float (ActionLogic::AttributeTypeError)\n```\n\nIn addition to the above default types it is possible to also validate against user defined types.\n\n### Custom Type Validations\nIf you would like to validate the type of attributes on a given `context` with your application's classes, `ActionLogic` is happy to provide that functionality.\n\nLet's consider the following example:\n\n```ruby\nclass ExampleClass\nend\n\nclass ActionTaskExample\n  include ActionLogic::ActionTask\n\n  validates_after :example_attribute =\u003e { :type =\u003e ExampleClass }\n\n  def call\n    context.example_attribute = ExampleClass.new\n  end\nend\n\nresult = ActionTaskExample.execute\n\nresult # =\u003e #\u003cActionLogic::ActionContext status=:success, example_attribute=#\u003cExampleClass:0x007f95d1922bd8\u003e\u003e\n```\n\nIn the above example, a custom class `ExampleClass` is defined. In order to type validate against this class, the required format for the name of the class is simply\nthe class constant `ExampleClass`.\n\nIf a custom type validation fails, `ActionLogic` provides the same `ActionLogic::AttributeTypeError` with a detailed explanation about what attribute is in violation\nof the type validation:\n\n```ruby\nclass ExampleClass\nend\n\nclass OtherClass\nend\n\nclass ActionTaskExample\n  include ActionLogic::ActionTask\n\n  validates_after :example_attribute =\u003e { :type =\u003e ExampleClass }\n\n  def call\n    context.example_attribute = OtherClass.new\n  end\nend\n\nActionTaskExample.execute # ~\u003e context: ActionTaskExample message: Attribute: example_attribute with value: #\u003cOtherClass:0x007fb5ca04edb8\u003e was expected to be of type ExampleClass but is OtherClass (ActionLogic::AttributeTypeError)\n```\n\nAttribute and type validations are very helpful, but in some situations this is not enough. Additionally, `ActionLogic` provides presence validation so you can also verify that\na given attribute on a context not only has the correct type, but also has a value that is considered `present`.\n\n### Presence Validations\n\n`ActionLogic` also allows for presence validation for any attribute on an instance of `ActionContext`. Like other validations, presence validations can be specified in before, after or\naround validations.\n\nBy default, presence validations simply check to determine if an attribute's value is not `nil` or is not `false`. To define a presence validation, you need only specify `:presence =\u003e true`\nfor the attribute you wish to validate against:\n\n```ruby\nclass ActionTaskExample\n  include ActionLogic::ActionTask\n\n  validates_before :example_attribute =\u003e { :presence =\u003e true }\n\n  def call\n  end\nend\n\nresult = ActionTaskExample.execute(:example_attribute =\u003e 123)\n\nresult # =\u003e #\u003cActionLogic::ActionContext example_attribute=123, status=:success\u003e\n```\n\nHowever, if a presence validation fails, `ActionLogic` will raise an `ActionLogic::PresenceError` with a detailed description about the attribute failing the presence validation\nand why:\n\n```ruby\nclass ActionTaskExample\n  include ActionLogic::ActionTask\n\n  validates_before :example_attribute =\u003e { :presence =\u003e true }\n\n  def call\n  end\nend\n\nActionTaskExample.execute(:example_attribute =\u003e nil) # ~\u003e context: ActionTaskExample message: Attribute: example_attribute is missing value in context but presence validation was specified (ActionLogic::PresenceError)\n```\n\n### Custom Presence Validations\n\nSometimes when wanting to validate presence of an attribute with an aggregate type (like `Array` or `Hash`), we may want to validate that such a type is not empty. If\nyou wish to validate presence for a type that requires inspecting the value of the attribute, `ActionLogic` allows you the ability to define a custom `Proc` to validate\nan attribute's value against.\n\n```ruby\nclass ActionTaskExample\n  include ActionLogic::ActionTask\n\n  validates_before :example_attribute =\u003e { :presence =\u003e -\u003e(attribute) { attribute.any? } }\n\n  def call\n  end\nend\n\nresult = ActionTaskExample.execute(:example_attribute =\u003e [\"element1\", \"element2\", \"element3\"])\n\nresult # =\u003e #\u003cActionLogic::ActionContext example_attribute=[\"element1\", \"element2\", \"element3\"], status=:success\u003e\n```\n\nIn the example above, we define a lambda that accepts as input the value of the attribute on the `context`. In this case, we are interested in verifying that\n`example_attribute` is not an empty `Array` or an empty `Hash`. This passes our before validation because `ActionTaskExample` is invoked with an `example_attribute`\nwhose value is an array consisting of three elements.\n\nHowever, if a custom presence validation fails, `ActionLogic` will raise an `ActionLogic::PresenceError` with a detailed description about the attribute failing\nthe custom presence validation:\n\n```ruby\nclass ActionTaskExample\n  include ActionLogic::ActionTask\n\n  validates_before :example_attribute =\u003e { :presence =\u003e -\u003e(attribute) { attribute.any? } }\n\n  def call\n  end\nend\n\nActionTaskExample.execute(:example_attribute =\u003e []) # ~\u003e context: ActionTaskExample message: Attribute: example_attribute is missing value in context but custom presence validation was specified (ActionLogic::PresenceError)\n```\n\nIn the above example, we have failed to pass the presence validation for `example_attribute` because the value of `example_attribute` is an empty array. When\nthe custom presence validation lambda is called, the lambda returns `false` and the `ActionLogic::PresenceError` is thrown, with an error message indicating\nthe attribute that failed the presence validation while also indicating that a custom presence validation was specified.\n\n### Before Validations\n\nIf you combine Rails ActionController's `before_filter` and ActiveModel's `validates` then you have approximately what `ActionLogic` defines as `validates_before`.\nBefore validations can be defined in any of the `ActionLogic` abstractions (`ActionTask`, `ActionUseCase` and `ActionCoordinator`). In each abstraction a `validates_before`\noperation is performed *before* invoking the `call` method.\n\nBefore validations allow you to specify a required attribute and optionally its type and / or presence. The following example illustrates how to specify a before\nvalidation on a single attribute:\n\n```ruby\nclass ActionTaskExample\n  include ActionLogic::ActionTask\n\n  validates_before :example_attribute =\u003e { :type =\u003e Array, :presence =\u003e -\u003e(attribute) { attribute.any? } }\n\n  def call\n  end\nend\n\nresult = ActionTaskExample.execute(:example_attribute =\u003e [1, 2, 3])\n\nresult # =\u003e #\u003cActionLogic::ActionContext example_attribute=[1, 2, 3], status=:success\u003e\n```\n\nThe following example illustrates how to specify a before validation for multiple attributes:\n\n```ruby\nclass ActionTaskExample\n  include ActionLogic::ActionTask\n\n  validates_before :example_attribute =\u003e { :type =\u003e Array, :presence =\u003e -\u003e(attribute) { attribute.any? } },\n                   :example_attribute2 =\u003e { :type =\u003e Fixnum }\n\n  def call\n  end\nend\n\nresult = ActionTaskExample.execute(:example_attribute =\u003e [1, 2, 3], :example_attribute2 =\u003e 1)\n\nresult # =\u003e #\u003cActionLogic::ActionContext example_attribute=[1, 2, 3], example_attribute2=1, status=:success\u003e\n```\n\n### After Validations\n\nIf you combine Rails ActionController's `after_filter` and ActiveModel's `validates` then you have approximately what `ActionLogic` defines as `validates_after`.\nAfter validations can be defined in any of the `ActionLogic` abstractions (`ActionTask`, `ActionUseCase` and `ActionCoordinator`). In each abstraction a `validates_after`\noperation is performed *after* invoking the `call` method.\n\nAfter validations allow you to specify a required attribute and optionally its type and / or presence. The following example illustrates how to specify an after\nvalidation on a single attribute:\n\n```ruby\nclass ActionTaskExample\n  include ActionLogic::ActionTask\n\n  validates_after :example_attribute =\u003e { :type =\u003e Array, :presence =\u003e -\u003e(attribute) { attribute.any? } }\n\n  def call\n    context.example_attribute = [1, 2, 3]\n  end\nend\n\nresult = ActionTaskExample.execute\n\nresult # =\u003e #\u003cActionLogic::ActionContext example_attribute=[1, 2, 3], status=:success\u003e\n```\nThe following example illustrates how to specify an after validation for multiple attributes:\n\n```ruby\nclass ActionTaskExample\n  include ActionLogic::ActionTask\n\n  validates_after :example_attribute =\u003e { :type =\u003e Array, :presence =\u003e -\u003e(attribute) { attribute.any? } },\n                  :example_attribute2 =\u003e { :type =\u003e Fixnum }\n\n  def call\n    context.example_attribute = [1, 2, 3]\n    context.example_attribute2 = 1\n  end\nend\n\nresult = ActionTaskExample.execute\n\nresult # =\u003e #\u003cActionLogic::ActionContext example_attribute=[1, 2, 3], example_attribute2=1, status=:success\u003e\n```\n\n### Around Validations\n\nIf you combine Rails ActionController's `around_filter` and ActiveModel's `validates` then you have approximately what `ActionLogic` defines as `validates_around`.\nAround validations can be defined in any of the `ActionLogic` abstractions (`ActionTask`, `ActionUseCase` and `ActionCoordinator`). In each abstraction a `validates_around`\noperation is performed *before* and *after* invoking the `call` method.\n\nAround validations allow you to specify a required attribute and optionally its type and / or presence. The following example illustrates how to specify an around\nvalidation on a single attribute:\n\n```ruby\nclass ActionTaskExample\n  include ActionLogic::ActionTask\n\n  validates_around :example_attribute =\u003e { :type =\u003e Array, :presence =\u003e -\u003e(attribute) { attribute.any? } }\n\n  def call\n  end\nend\n\nresult = ActionTaskExample.execute(:example_attribute =\u003e [1, 2, 3])\n\nresult # =\u003e #\u003cActionLogic::ActionContext example_attribute=[1, 2, 3], status=:success\u003e\n```\nThe following example illustrates how to specify an around validation for multiple attributes:\n\n```ruby\nclass ActionTaskExample\n  include ActionLogic::ActionTask\n\n  validates_around :example_attribute =\u003e { :type =\u003e Array, :presence =\u003e -\u003e(attribute) { attribute.any? } },\n                   :example_attribute2 =\u003e { :type =\u003e Fixnum }\n\n  def call\n  end\nend\n\nresult = ActionTaskExample.execute(:example_attribute =\u003e [1, 2, 3], :example_attribute2 =\u003e 1)\n\nresult # =\u003e #\u003cActionLogic::ActionContext example_attribute=[1, 2, 3], example_attribute2=1, status=:success\u003e\n```\n\n### Benchmarking\n\nAt some point you may want to benchmark and profile the performance of your code. `ActionLogic` allows for benchmarking that\nrange from simple defaults to highly customizable options depending on your use case and needs.\n\n### Enable Benchmarking\n\nBecause benchmarking negatively impacts performance, we must explicitly tell `ActionLogic` that we want to benchmark (otherwise\nit defaults to ignore benchmarking). To do this, we configure `ActionLogic` using the `configure` method. With the provided\n`config` object, we explicitly enable benchmarking by setting `config.benchmark = true`:\n\n```ruby\nActionLogic.configure do |config|\n  config.benchmark = true\nend\n```\n\n### Benchmark Logging\n\nAdditionally, `ActionLogic` writes a benchmark log to `$stdout` by default, or you can override this default configuration\nby specifying a log file. To do this, you configure `ActionLogic` to use a `File` object for logging benchmark results via the\n`ActionLogic.configure` method:\n\n```ruby\nActionLogic.configure do |config|\n  config.benchmark = true\n  config.benchmark_log = File.open(\"benchmark.log\", \"w\")\nend\n```\n\n### Benchmark Log Formatting\n\nBy default, `ActionLogic` formats benchmark logs in the following format:\n\n```\ncontext:ValidateAroundPresenceTestUseCase user_time:0.000000 system_time:0.000000 total_time:0.000000 real_time:0.000135\n...\n```\n\nThe default format is intended to be machine readable for easy parsing and is not intended to be used for human reading.\nHowever, if you wish to change the format of the log output, `ActionLogic` allows you to override the default formatter by\nallowing you to provide your own formatter:\n\n```ruby\nActionLogic.configure do |config|\n  config.benchmark = true\n  config.benchmark_log = File.open(\"benchmark.log\", \"w\")\n  config.benchmark_formatter = YourCustomFormatter\nend\n```\n\nWhere `YourCustomFormatter` subclasses `ActionLogic::ActionBenchmark::DefaultFormatter`:\n\n```ruby\nclass CustomFormatter \u003c ActionLogic::ActionBenchmark::DefaultFormatter\n\n  def log_coordinator(benchmark_result, execution_context_name)\n    benchmark_log.puts(\"The ActionCoordinator #{execution_context_name} took #{benchmark_result.real} to complete.\")\n  end\n\n  def log_use_case(benchmark_result, execution_context_name)\n    benchmark_log.puts(\"The ActionUseCase #{execution_context_name} took #{benchmark_result.real} to complete.\")\n  end\n\n  def log_task(benchmark_result, execution_context_name)\n    benchmark_log.puts(\"The ActionTask #{execution_context_name} took #{benchmark_result.real} to complete.\")\n  end\n\nend\n```\n\nFrom the example above, you can see that a custom formatter is required to define three methods: `log_coordinator`, `log_use_case` and `log_task`. The `log_t cqcoordinator`\nmethod is called when a `ActionCoordinator` context is benchmarked. The `use_case` and `task` methods are invoked when `ActionUseCase` and `ActionTask`\ncontexts are benchmarked, respectively.\n\nEach of the three log methods receives two input parameters: `benchmark_result` and `execution_context_name` where `benchmark_result` is a Ruby\nstandard library `Benchmark` result object, and `execution_context_name` is the class name of the `ActionLogic` context.\n\nOnce configured, you can verify that the formatter outputs to the specified log file by executing your `ActionLogic` contexts\nand verifying that the log file is written to with the correct format:\n\n```\nThe ActionUseCase TestUseCase2 took 0.00011722202179953456 to complete.\nThe ActionTask TestTask3 took 4.570698365569115e-05 to complete.\n...\n```\n\n### Custom Benchmark Handling\n\nBy default, `ActionLogic` benchmarks execution contexts using Ruby's `Benchmark` module. If you are content with a `Benchmark` result object, then\nyou do not need to specify a custom benchmark handler. However, if you wish to have maximum control, or you require something different than Ruby's\n`Benchmark` module, you can define a custom handler like so:\n\n```ruby\nclass CustomHandler\n  def call\n    # custom logic\n    yield\n    # custom logic\n  end\nend\n```\n\nYour custom handler is free to define any custom logic, but you must yield during the body of the `call` method. This is what triggers the execution\ncontext and will allow your custom handler to measure the length of execution. If you do not yield, the relevant `ActionCoordinator`, `ActionUseCase`\nor `ActionTask` will not be executed and will result in no execution to benchmark.\n\nAdditionally, you must register your custom handler with `ActionLogic` using `ActionLogic.configure`:\n\n```ruby\nActionLogic.configure do |config|\n  config.benchmark = true\n  config.benchmark_log = File.open(\"benchmark.log\", \"w\")\n  config.benchmark_handler = CustomHandler.new\nend\n```\n\n### Installation\n\nAdd `ActionLogic` to your project's Gemfile:\n\n`gem 'action_logic'`\n\nDon't forget to bundle:\n\n`$ bundle`\n\n### Contributing\n\nInterested in contributing to `ActionLogic`? If so that is awesome! \u003c3\nPlease see the [contributing doc](https://github.com/rewinfrey/ActionLogic/blob/master/CONTRIBUTING.md) for details.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frewinfrey%2Factionlogic","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frewinfrey%2Factionlogic","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frewinfrey%2Factionlogic/lists"}