{"id":18942339,"url":"https://github.com/bugcrowd/adama","last_synced_at":"2025-04-15T21:31:33.843Z","repository":{"id":62552978,"uuid":"95283216","full_name":"bugcrowd/adama","owner":"bugcrowd","description":"Adama - Command and Invoker Pattern For Getting Things Done","archived":false,"fork":false,"pushed_at":"2023-08-10T15:58:02.000Z","size":37,"stargazers_count":13,"open_issues_count":0,"forks_count":4,"subscribers_count":46,"default_branch":"master","last_synced_at":"2024-08-09T10:03:05.498Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bugcrowd.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-06-24T07:40:06.000Z","updated_at":"2024-08-09T10:03:05.499Z","dependencies_parsed_at":"2022-11-03T04:15:24.358Z","dependency_job_id":null,"html_url":"https://github.com/bugcrowd/adama","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bugcrowd%2Fadama","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bugcrowd%2Fadama/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bugcrowd%2Fadama/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bugcrowd%2Fadama/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bugcrowd","download_url":"https://codeload.github.com/bugcrowd/adama/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":223685533,"owners_count":17185869,"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":[],"created_at":"2024-11-08T12:32:28.500Z","updated_at":"2024-11-08T12:32:29.148Z","avatar_url":"https://github.com/bugcrowd.png","language":"Ruby","readme":"# Commander Adama\n\n![Adama](https://raw.githubusercontent.com/wiki/bugcrowd/adama/images/adama.jpg)\n\nAdama is a bare bones command pattern library inspired by Collective Idea's [Interactor](https://github.com/collectiveidea/interactor) gem.\n\nCommands are small classes that represent individual units of work. Each command is executed by a client \"calling\" it. An invoker is a class responsible for the execution of one or more commands.\n\n## Getting Started\n\nAdd Commander Adama to your Gemfile and `bundle install`.\n\n```ruby\ngem 'adama'\n```\n\n## Usage\n\n### Command\n\nTo create a command, include the `Adama::Command` module in your command's class definition:\n\n```ruby\nclass DestroyCylons\n  include Adama::Command\nend\n```\n\nIncluding the `Adama::Command` module extends the class with the `.call` class method. So you would execute the command like this:\n\n```ruby\nDestroyCylons.call(captain: :apollo)\n```\n\nThe above `.call` method creates an instance of the `DestroyCylons` class, then calls the `#call` instance method. If the `#call` method fails, the `#rollback` method is then called.\n\nAt this point our command `DestroyCylons` doesn't do much. As explained above, the `Adama::Command` module has two instance methods: `call` and `rollback`. By default these methods are empty and should be overridden like this:\n\n```ruby\nclass DestroyCylons\n  include Adama::Command\n\n  validate_presence_of :captain\n\n  def call\n    got_destroy_cylons(captain)\n  end\n\n  def rollback\n    retreat_and_jump_away()\n  end\nend\n```\n\nEach validated attribute is available as an attr_accessor on the instance of the command, so you can reference them directly in the `#call` method. within the `#call` and `#rollback` instance methods due to an `attr_reader` in the `Adama::Command` module.\n\n### Invoker\n\nTo create an invoker, include the `Adama::Invoker` module in your invoker's class definition:\n\n```ruby\nclass RebuildHumanRace\n  include Adama::Invoker\nend\n```\n\nBecause the `Adama::Invoker` module extends `Adama::Command` you can execute an invoker in the exact same way you execute a command, with the `.call` class method:\n\n```ruby\nRebuildHumanRace.call(captain: :apollo, president: :laura)\n```\n\nThe `Adama::Invoker` module _also_ extends your invoker class with the `.invoke` class method, which allows you to specify a list of commands to run in sequence, e.g.:\n\n```ruby\nclass RebuildHumanRace\n  include Adama::Invoker\n\n  invoke(\n    GetArrowOfApollo,\n    DestroyCylons,\n    FindEarth,\n  )\nend\n```\n\nNow, when you run `RebuildHumanRace.call(captain: :apollo, president: :laura)` it will execute `GetArrowOfApollo`, then `DestroyCylons`, then finally `FindEarth` commands in order.\n\nIf there is an error in any of those commands, the invoker will call `FindEarth.rollback`, then `DestroyCylons.rollback`, then `GetArrowOfApollo.rollback` leaving everything just as it was in the beginning.\n\n#### Instance Invoker List\n\nTypically your Invoker class takes responsibility for an immutable set of actions, however sometimes it's handy to be able to adjust the invoked commands on the fly while keeping the error handling and rollback functionality of the invoker.\n\ne.g.\n\n```ruby\nclass FightCylons\n  include Adama::Invoker\nend\n\nattack1 = Invoker.new(captain: :apollo, lieutenant:  :starbuck).invoke(Advance, Strafe, Fire)\nattack2 = Invoker.new(captain: :apollo, lieutenant:  :starbuck).invoke(Advance, Fire)\n\nattack1.run\nattack2.run\n```\n\nIt's important to see that we're using the `#run` instance method on the Invoker instance (as the `.call` class method would). This ensures we execute the invoker in the error / rollback handler. Calling the `#call` instance method directly would simply execute each command, without any Invoker level error handling.\n\n### Errors\n\n`Adama::Command#call` or `Adama::Invoker#call` will *always* raise an error of type `Adama::Errors::BaseError`.\n\nMore specifically:\n\nIf a command fails, it will raise `Adama::Errors::CommandError`.\n\nIf a command fails while being called in an invoker, the commands will be rolled back and the invoker will raise `Adama::Errors::InvokerError`.\n\nIf a command fails while rolling back within the invoker, the invoker will raise `Adama::Errors::InvokerRollbackError`.\n\nThe base error type `Adama::Errors::Adama` is designed to be initialized with three optional keyword args:\n\n`error` - the original exception that was rescued in the command or invoker.\n`command` - the failed command instance.\n`invoker` - the failed invoker instance, set if the command or rollback failed in an invoker.\n\n```ruby\nmodule Adama\n  module Errors\n    class BaseError \u003c StandardError\n      attr_reader :error, :command, :invoker\n\n      def initialize(error: nil, command: nil, invoker: nil)\n        @error = error\n        @command = command\n        @invoker = invoker\n      end\n    end\n  end\nend\n```\n\n### TODOS\n\nI'm contemplating adding support for per-command validation, potentially through the [dry-validation](https://github.com/dry-rb/dry-validation) gem,\n\n## Development\n\nAfter 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.\n\nTo install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).\n\n## Contributing\n\nBug reports and pull requests are welcome on GitHub at https://github.com/bugcrowd/adama. So Say We All.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbugcrowd%2Fadama","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbugcrowd%2Fadama","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbugcrowd%2Fadama/lists"}