{"id":15293336,"url":"https://github.com/pirminis/apparatus","last_synced_at":"2025-03-24T13:44:38.410Z","repository":{"id":187520352,"uuid":"676688949","full_name":"pirminis/apparatus","owner":"pirminis","description":"Take control of your business logic.","archived":false,"fork":false,"pushed_at":"2023-08-20T16:01:29.000Z","size":19,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-03-03T10:47:57.335Z","etag":null,"topics":["architectural-patterns","entity-component-system","ruby","ruby-gem"],"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/pirminis.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-08-09T19:24:13.000Z","updated_at":"2023-08-20T15:39:49.000Z","dependencies_parsed_at":"2024-12-02T02:37:15.661Z","dependency_job_id":null,"html_url":"https://github.com/pirminis/apparatus","commit_stats":null,"previous_names":["pirminis/apparatus"],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pirminis%2Fapparatus","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pirminis%2Fapparatus/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pirminis%2Fapparatus/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pirminis%2Fapparatus/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pirminis","download_url":"https://codeload.github.com/pirminis/apparatus/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245284369,"owners_count":20590306,"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":["architectural-patterns","entity-component-system","ruby","ruby-gem"],"created_at":"2024-09-30T16:46:21.925Z","updated_at":"2025-03-24T13:44:38.379Z","avatar_url":"https://github.com/pirminis.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# What is apparatus?\n\nHave you ever worked on code that evolves so quickly that not only it gives you anxiety, but becomes technical debt over time?\n\nApparatus is a very simple architectural way to solve this. It uses composition over inheritance and is a (very) simple ECS (entity-component-system) implementation.\n\nIt decouples data and logic.\n\n## What does it do?\n\nIt ensures your complex code parts are highly structured and easy to understand and modify:\n- code is organized and tidy by default\n- data is always separated from logic\n- adding, removing, modifying data and logic is easy\n- complete freedom how data is shaped\n- very easy to to see what is being done at a glance\n\n## When to use it?\n\nOnly use this gem for the business logic that is very volatile:\n- there are or will be a lot of changes in the business logic\n- feature requests come frequently and are aplenty\n- you still have no idea how to structure your complex piece of code\n\n## Installation\n\n### Using Rubygems:\n\n```sh\ngem install apparatus\n```\n\n### Using Bundler:\n\nAdd the following to your Gemfile:\n\n```sh\ngem \"apparatus\"\n```\n\n## Usage\n\n### Basic example\n\nLet's build code that outputs delivery methods to the standard output. We have a imaginary cart and three shipping methods.\n\n```ruby\nrequire \"apparatus\"\n\n# aliases (optional)\nEntity = Apparatus::Entity\nSystem = Apparatus::System\n\n# component classes\nType = Struct.new(:value)\nName = Struct.new(:value)\nIdentifier = Struct.new(:value)\nPrice = Struct.new(:value)\nWeightInKg = Struct.new(:value)\nBoolean = Struct.new(:value)\n\n# entities\ndelivery = Entity.new({\n  type: Type.new(\"delivery_method\"),\n  identifier: Identifier.new(\"delivery\"),\n  name: Name.new(\"Delivery by courier\"),\n  price: Price.new(3.99)\n})\npickup = Entity.new({\n  type: Type.new(\"delivery_method\"),\n  identifier: Identifier.new(\"pickup\"),\n  name: Name.new(\"Pickup in closest store\"),\n  price: Price.new(0.0)\n})\npigeon = Entity.new({\n  type: Type.new(\"delivery_method\"),\n  identifier: Identifier.new(\"pigeon\"),\n  name: Name.new(\"Delivery by pigeon\"),\n  price: Price.new(50.0)\n})\ncart = Entity.new({\n  type: Type.new(\"cart\"),\n  total_price: Price.new(27.0),\n  total_weight: WeightInKg.new(5.0)\n})\n\n# systems\nclass PrintShippingMethods \u003c System\n  def run\n    entities.each do |entity|\n      next if !entity.has?(:type, :price)\n\n      next if entity[:type].value.to_s != \"delivery_method\"\n\n      name, price, enabled = entity[:name], entity[:price], entity[:enabled]\n\n      next if enabled \u0026\u0026 !enabled.value\n\n      puts \"#{name.value} (#{price.value} €)\"\n    end\n  end\nend\n\nclass EnableDeliveryMethods \u003c System\n  def run\n    cart = entities.find { _1.has?(:type) \u0026\u0026 _1[:type].value.to_s == \"cart\" }\n\n    return if !cart\n\n    delivery_methods = entities.select do |entity|\n      entity.has?(:type, :identifier) \u0026\u0026\n        entity[:type].value.to_s == \"delivery_method\"\n    end\n\n    delivery_methods.each do |delivery_method|\n      delivery_method[:enabled] = delivery_method[:identifier].value.to_s == \"pigeon\" ?\n                                    Boolean.new(cart[:total_weight].value \u003c 0.1) :\n                                    Boolean.new(true)\n    end\n  end\nend\n\n# apparatus itself\napparatus = Apparatus::Body.new\n\napparatus.add_entities(delivery, pickup, pigeon, cart)\napparatus.add_systems(\n  EnableDeliveryMethods,\n  PrintShippingMethods\n)\n\n# run all systems\napparatus.run\n```\n\nIf you run this code, the output is following:\n```\nDelivery by courier (3.99)\nPickup in closest store (0.0)\n```\n\nDelivery by pigeon is missing, because the cart weight is too heavy and pigeon cannot bring you (imaginary) package.\n\nHowever if you comment the line `EnableDeliveryMethods,` and run the code again, the output changes to:\n```\nDelivery by courier (3.99)\nPickup in closest store (0.0)\nDelivery by pigeon (50.0)\n```\n\n## Noteworthy things about the basic example\n\nI think you should notice some positive things about the basic example:\n- Component classes are really simple classes that can be of any shape that is useful to you. I chose `Struct`, but it can be anything: custom class, String, Integer, etc.\n- An entity is just a collection of components\n- A system just processes entities one by one\n- When apparatus is run, each system is run one by one\n- Nowhere did I call a loose method on a data object: data is data and has no logic; all logic lies within systems\n- I can easily disable any amount of systems if needed without breaking the apparatus\n- The whole implementation is very linear and easy to read\n- Code is easy to debug (just use `puts` or `byebug`)\n\n## What apparatus is not\n\nThis is not THE SOLUTION.\n\nThis gem is not meant to be used always, everywhere. It shines when you have suspicion that the incoming feature will be extremely volatile (business logic will change a lot or complexity will grow, there might be a lot of feature requests, etc).\n\n## Drawbacks in the implementation\n\nIf you check the source code of this gem, it is absurdly simple. However, with simplicity come the drawbacks:\n- No magic\n- No queries or cached queries\n- Almost no helper methods\n- If you have not encountered ECS, then apparatus might seem strange\n\n## Alternatives\n\nThere are alternatives that have a lot more features:\n- https://github.com/guitsaru/draco\n- https://github.com/ged/chione\n- https://github.com/jtuttle/baku\n\n## Development\n\nAfter checking out the repo, run `bin/setup` to install dependencies. Then, run `make check` to run the checks (rubocop + 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 the created tag, 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/pirminis/apparatus.\n\n## License\n\nThe gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpirminis%2Fapparatus","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpirminis%2Fapparatus","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpirminis%2Fapparatus/lists"}