{"id":21646180,"url":"https://github.com/phallguy/scorpion","last_synced_at":"2025-10-06T09:31:35.345Z","repository":{"id":34969416,"uuid":"39046158","full_name":"phallguy/scorpion","owner":"phallguy","description":"Simple IoC for ruby","archived":false,"fork":false,"pushed_at":"2025-01-16T06:42:23.000Z","size":381,"stargazers_count":16,"open_issues_count":12,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-01-16T07:40:51.600Z","etag":null,"topics":["dependency-injection","ruby-on-rails"],"latest_commit_sha":null,"homepage":null,"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/phallguy.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","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":"2015-07-14T01:18:06.000Z","updated_at":"2025-01-16T06:42:24.000Z","dependencies_parsed_at":"2024-12-31T18:28:04.933Z","dependency_job_id":null,"html_url":"https://github.com/phallguy/scorpion","commit_stats":{"total_commits":161,"total_committers":5,"mean_commits":32.2,"dds":0.6086956521739131,"last_synced_commit":"82e869fdf7eaf4c0c8fc35911692ab46632b7c78"},"previous_names":[],"tags_count":42,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phallguy%2Fscorpion","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phallguy%2Fscorpion/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phallguy%2Fscorpion/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phallguy%2Fscorpion/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/phallguy","download_url":"https://codeload.github.com/phallguy/scorpion/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":235519892,"owners_count":19003200,"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":["dependency-injection","ruby-on-rails"],"created_at":"2024-11-25T06:38:17.921Z","updated_at":"2025-10-06T09:31:29.912Z","avatar_url":"https://github.com/phallguy.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Scorpion\n\n[![Gem Version](https://badge.fury.io/rb/scorpion-ioc.svg)](http://badge.fury.io/rb/scorpion-ioc)\n[![Code Climate](https://codeclimate.com/github/phallguy/scorpion.png)](https://codeclimate.com/github/phallguy/scorpion)\n[![Test Coverage](https://codeclimate.com/github/phallguy/scorpion/badges/coverage.svg)](https://codeclimate.com/github/phallguy/scorpion/coverage)\n[![Inch CI](https://inch-ci.org/github/phallguy/scorpion.svg?branch=master)](https://inch-ci.org/github/phallguy/scorpion)\n[![Circle CI](https://circleci.com/gh/phallguy/scorpion.svg?style=svg)](https://circleci.com/gh/phallguy/scorpion)\n\nAdd IoC to rails with minimal fuss and ceremony.\n\n(Also check out [shog](http://github.com/phallguy/shog) for better rails logs)\n\n\u003c!-- vim-markdown-toc GFM --\u003e\n\n* [Dependency Injection](#dependency-injection)\n  * [Why might you _Want_ a DI FRamework?](#why-might-you-_want_-a-di-framework)\n      * [Setter/Default Injection](#setterdefault-injection)\n      * [Constructor/Ignorant Injection](#constructorignorant-injection)\n    * [Using a Framework...like Scorpion](#using-a-frameworklike-scorpion)\n* [Using Scorpion](#using-scorpion)\n  * [Objects](#objects)\n  * [Configuration](#configuration)\n    * [Classes](#classes)\n    * [Modules](#modules)\n    * [Builders](#builders)\n    * [Hunting Delegates](#hunting-delegates)\n    * [Singletons](#singletons)\n  * [Nests](#nests)\n  * [Rails](#rails)\n    * [ActionController](#actioncontroller)\n    * [ActiveJob](#activejob)\n    * [ActiveRecord](#activerecord)\n* [Contributing](#contributing)\n* [License](#license)\n\n\u003c!-- vim-markdown-toc --\u003e\n\n## Dependency Injection\n\nDependency injection helps to break explicit dependencies between objects making\nit much easier to maintain a [single\nresponsibility](https://en.wikipedia.org/wiki/Single_responsibility_principle)\nand reduce [coupling](https://en.wikipedia.org/wiki/Coupling_(computer_programming))\nin our class designs. This leads to more testable code and code that is more\nresilient to change.\n\nSeveral have argued that the dynamic properties of Ruby make Dependency\nInjection _frameworks_ irrelevant. Some argue that you can build in defaults and\nmake them overridable, or just use module mixins.\n\nMost of these counter arguments focus on testing, and given how easy it is to\nmock objects in Ruby, you don't really need a framework. If testing were the\nonly virtue they'd be spot on. Despite its virtues DI doesn't come without its\nown problems. However for larger projects that you expect to be long-lived, a DI\nframework may help manage the complexity.\n\nFor a deeper background on Dependency Injection consider the\n[Wikipedia](https://en.wikipedia.org/wiki/Dependency_injection) article on the\nsubject.\n\n### Why might you _Want_ a DI FRamework?\n\nAssuming you've embraced the general concept of DI why would you want to use a\nframework. Lets consider the alternatives.\n\n##### Setter/Default Injection\n\n```ruby\nclass Hunter\n    def weapon\n      @weapon ||= Weapon.new\n    end\n    def weapon=( value )\n      @weapon = value\n    end\nend\n```\n\nIn this scenario the Hunter class knows how to create a weapon and provides a\nsane default, but allows the dependency to be overridden if needed.\n\n**PROS**\n\n- Very simple to understand and debug\n- Provides basic flexibility\n- The dependency is clearly defined.\n\n**CONS**\n\n- Still coupled to a specific type of Weapon.\n- If multiple classes use this approach and you decide to upgrade your armory,\n  you'd have to modify every line that creates new weapons. The factory pattern\n  can be used to address  such a dependency.\n- No global method of replacing a Weapon class with a specialized or augmented\n  class. For example a ThreadLockedWeapon.\n\n##### Constructor/Ignorant Injection\n\n```ruby\nclass Hunter\n  def initialize( weapon )\n    @weapon = weapon\n  end\nend\n```\n\nHere Hunters can use any weapon and can be designed to an interface Weapon that\ndoes not have an implementation yet.\n\n**PROS**\n\n- Provides flexibility\n- Work can proceed concurrently on Hunter and Weapon classes by different\n  engineers  on the team.\n\n**CONS**\n\n- Hard to reason about Hunters and Weapons as a whole.\n- The dependency is not clearly defined - what is a weapon?\n- It pushes the responsibility of constructing dependencies onto the consumer of\n  the class. If the class is used in multiple places this becomes a maintenance\n  chore when changes are required.\n- It becomes tedious to use classes resulting in repeated boilerplate code that\n  distracts from the primary responsibility of the calling code.\n\n\n#### Using a Framework...like Scorpion\n\nUsing a good framework can help conserve the pros of each method while\nminimizing the cons. A DI framework works like an automatic factory system\nresolving dependencies cleanly like a factory but without all the effort to\ncreate custom factories.\n\nA good framework should\n\n- Make dependencies clear\n- Require a minimal amount of configuration or ceremony\n\n```ruby\nclass Hunter\n  depend_on do\n    weapon Weapon\n  end\n\n  # or\n  attr_dependency :weapon, Weapon\nend\n```\n\nHere the dependency is clearly defined - and even creates accessors for getting\nand setting the weapon. When a Hunter is created its dependencies are also\ncreated - and any of their dependencies and so on. Usage is equally simple\n\n```ruby\nhunter = scorpion.fetch Hunter\nhunter.weapon   # =\u003e a Weapon\n```\n\nOverriding the kind of weapons used by hunters.\n\n```ruby\nclass Axe \u003c Weapon; end\n\nscorpion.prepare do\n  hunt_for Axe\nend\n\nhunter = scorpion.fetch Hunter\nhunter.weapon # =\u003e an Axe\n```\n\nOverriding hunters!\n\n```ruby\nclass Axe \u003c Weapon; end\nclass Predator \u003c Hunter; end\n\nscorpion.prepare do\n  hunt_for Predator\n  hunt_for Axe\nend\n\nhunter = scorpion.fetch Hunter\nhunter        # =\u003e Predator\nhunter.weapon # =\u003e an Axe\n```\n\n\n## Using Scorpion\n\nOut of the box Scorpion does not need any configuration and will work\nimmediately. You can hunt for any Class even if it hasn't been configured.\n\n```ruby\n  hash = Scorpion.instance.fetch Hash\n  hash # =\u003e {}\n```\n\n### Objects\n\nScorpions feed their [Scorpion Objects](lib/scorpion/object.rb) - any object that\nshould be fed its dependencies when it is created. Simply include the\nScorpion::Object module into your class to benefit from Scorpion injections.\n\n```ruby\nclass Keeper\n  include Scorpion::Object\n\n  depend_on do\n    lunch FastFood\n  end\nend\n\nclass Zoo\n  include Scorpion::Object\n\n  depend_on do\n    keeper Zoo::Keeper\n    vet Zoo::Vet, lazy: true\n  end\n\n  # or with like attr_accessor\n  attr_dependency :keeper, Zook::Keeper\n  attr_dependency :vet, Zoo::Vet, lazy: true\nend\n\nzoo = scorpion.fetch Zoo\nzoo.keeper       # =\u003e an instance of a Zoo::Keeper\nzoo.vet?         # =\u003e false it hasn't been hunted down yet\nzoo.vet          # =\u003e an instance of a Zoo::Vet\nzoo.keeper.lunch # =\u003e an instance of FastFood\n```\n\nAll of your classes should be objects! And any dependency that is also a Object will\nbe fed.\n\n### Configuration\n\nA good scorpion should be prepared to hunt. An effort that describes what the\nscorpion hunts for and how it should be found. Scorpion uses Classes and Modules\nas the primary means of identifying dependency in favor of opaque labels or strings.\nThis serves two benefits:\n\n1. The type of object expected by the dependency is clearly identified making it\n   easier to understand what the concrete dependencies really are.\n2. Types (Classes \u0026 Modules) explicitly declare the expected behavioral contract\n   of an object's dependencies.\n\n#### Classes\n\nMost scorpion hunts will be for an instance of a specific class (or a more\nderived class). In the absence of any configuration, Scorpion will simply create\nan instance of the specific class requested.\n\n```ruby\nscorpion.fetch Hash   # =\u003e Hash.new\n\nscorpion.prepare do\n  hunt_for Object::HashWithIndifferentAccess\nend\n\nscorpion.fetch Hash   # =\u003e Object::HashWithIndifferentAccess.new\n```\n\n#### Modules\n\nModules can be hunted for in two ways.\n\n1. If a Class has been prepared for hunting that includes the module, it will\n   be used to satisfy requests for that module\n2. If no Class is found, the Module itself will be returned.\n\n```ruby\nmodule Sharp\n  module_function\n  def poke; self.class.name end\nend\n\nclass Sword\n  include Sharp\nend\n\npoker = scorpion.fetch Sharp\npoker.poke     # =\u003e \"Module\"\n\nscorpion.prepare do\n  hunt_for Sword\nend\n\npoker = scorpion.fetch Sharp\npoker.poke     # =\u003e \"Sword\"\n```\n\n#### Builders\n\nSometimes resolving the correct dependencies is a bit more dynamic. In those\ncases you can use a builder block to hunt for dependency.\n\n```ruby\nclass Samurai \u003c Sword; end\nclass Broad \u003c Sword; end\n\nscorpion.prepare do\n  hunt_for Sword do |scorpion|\n    scorpion.spawn Random.rand( 2 ) == 1 ? Samurai : Broad\n  end\nend\n```\n\nObjects may also define their own .create methods that receive a scorpion and\narguments.\n\n```ruby\nclass City\n  def self.create( scorpion, name )\n    klass = \n      if name == \"New York\"\n        BigCity\n      else\n        SmallCity\n      end\n\n    scorpion.new klass, name\n  end\n\n  def initialize( name )\n    @name = name\n  end\nend\n\nclass BigCity \u003c City; end\nclass SmallCity \u003c City; end\n\n```\n\n#### Hunting Delegates\n\nFor really complex dependencies you may want to delegate the effort to retrieve\nthe dependencies to another type - a factory module for example. Scorpion\nallows you to delegate hunting dependency using the `:with` option.\n\n```ruby\nmodule ChocolateFactory\n    module_function\n\n    def call( scorpion, *args, \u0026block )\n      case args.first\n      when Nuget        then scorpion.spawn Snickers, *args, \u0026block\n      when Butterscotch then scorpion.spawn Butterfinger, *args, \u0026block\n      when Coconut      then scorpion.spawn Garbage, *args, \u0026block\n      end\n    end\nend\n\nscorpion.prepare do\n  hunt_for Candy, with: ChocolateFactory\nend\n\nscorpion.fetch Candy, Nuget.new  #=\u003e Snickers.new Nugget.new\n```\n\nAny object that responds to `#call( scorpion, *args, \u0026block )` can be used as\na hunting delegate.\n\n#### Singletons\n\nScorpion allows you to capture dependency and feed the same instance to everyone that\nasks for a matching dependency.\n\nDI singletons are different then global singletons in that each scorpion can\nhave a unique instance of the class that it shares with all of its objects. This\nallows, for example, global variable like support per-request without polluting\nthe global namespace or dealing with thread concurrency issues.\n\n\n```ruby\nclass Logger; end\n\nscorpion.prepare do\n  capture Logger\nend\n\nscorpion.fetch Logger  # =\u003e Logger.new\nscorpion.fetch Logger  # =\u003e Previously captured logger\n```\n\n\u003e Captured dependencies are not shared with child scorpions (for example when\n\u003e conceiving scorpions from a [Nest](Nests)). To share captured dependency with\n\u003e children use `share`.\n\n### Nests\n\nA scorpion nest is where a mother scorpion lives and conceives young -\nduplicates of the mother but maintaining their own state. The scorpion nest is\nused by the Rails integration to give each request its own scorpion.\n\nAll preparation  performed by the mother is shared with all the children it\nconceives so that configuration is established when the application starts.\n\n```ruby\nnest.prepare do\n  hunt_for Logger\nend\n\nscorpion = nest.conceive\nscorpion.fetch Logger  # =\u003e Logger.new\n```\n\n### Rails\n\n#### ActionController\n\nScorpion provides simple integration for rails controllers to establish a\nscorpion for each request.\n\n```ruby\n# user_service.rb\nclass UserService\n  def find( username ) ... end\nend\n\n# config/initializers/nest.rb\nrequire 'scorpion'\n\nScorpion.prepare do\n  capture UserService  # Share with all the objects that are spawned in _this_ request\n\n  share do\n    capture Logger  # Share with every request\n  end\nend\n\n# application_controller.rb\nrequire 'scorpion'\n\nclass ApplicationController \u003c ActionController::Base\n  depend_on do\n    users UserService, lazy: true\n  end\nend\n\n# users_controller.rb\nclass UsersController \u003c ApplicationController\n  def show\n    user = users.find( \"batman\" )\n    logger.write \"Found a user: #{ user }\"\n  end\nend\n```\n\n#### ActiveJob\n\nSimliar to support for controllers, Scorpion provides support for dependency\ninjection into ActiveJob objects.\n\n```ruby\n\n# avatar_job.rb\nclass AvatarJob \u003c ActiveJob::Base\n  depend_on do\n    users UserService, lazy: true\n    logger Logger\n  end\n\n  def perform( id )\n    user = users.find( id )\n    logger.write \"Found a user: #{ user }\"\n  end\nend\n```\n\n#### ActiveRecord\n\nScorpion enhances ActiveRecord models to support resolving dependencies from\na scorpion and sharing that scorpion with all associations.\n\n\u003e Consider using a SOA framework like [Shamu](https://github.com/phallguy/shamu)\n\u003e for managing complex resource relationships.\n\n```ruby\nclass User \u003c ActiveRecord::Base\n  depend_on do\n    credentials Service::Auth::Credentials\n  end\n\n  def check_password( password )\n    credentials.check encoded_password, password\n  end\nend\n\nclass SessionsController \u003c ActionController::Base\n  def create\n    user = User.with_scorpion( scorpion ).find params[:id]\n    user = scorpion( User ).find params[:id]\n    sign_in if user.check_password( params[:password] )\n  end\nend\n```\n\n\n## Contributing\n\n1. Fork it ( https://github.com/phallguy/scorpion/fork )\n2. Create your feature branch (`git checkout -b my-new-feature`)\n3. Commit your changes (`git commit -am 'Add some feature'`)\n4. Push to the branch (`git push origin my-new-feature`)\n5. Create a new Pull Request\n\n\n## License\n\n[The MIT License (MIT)](http://opensource.org/licenses/MIT)\n\nCopyright (c) 2015 Paul Alexander\n\n[@phallguy](http://twitter.com/phallguy) / http://phallguy.com\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fphallguy%2Fscorpion","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fphallguy%2Fscorpion","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fphallguy%2Fscorpion/lists"}