{"id":26063867,"url":"https://github.com/fdutey/shirinji","last_synced_at":"2025-04-11T12:43:04.074Z","repository":{"id":52887309,"uuid":"117701966","full_name":"fdutey/shirinji","owner":"fdutey","description":"Dependency injections made easy with Ruby","archived":false,"fork":false,"pushed_at":"2024-10-18T08:20:10.000Z","size":43,"stargazers_count":8,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-10T18:18:44.050Z","etag":null,"topics":["dependency-injection","inversion-of-control","ruby"],"latest_commit_sha":null,"homepage":"https://github.com/fdutey/shirinji","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/fdutey.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"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":"2018-01-16T15:30:25.000Z","updated_at":"2025-03-07T03:42:09.000Z","dependencies_parsed_at":"2022-08-21T01:20:22.539Z","dependency_job_id":null,"html_url":"https://github.com/fdutey/shirinji","commit_stats":null,"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fdutey%2Fshirinji","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fdutey%2Fshirinji/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fdutey%2Fshirinji/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fdutey%2Fshirinji/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/fdutey","download_url":"https://codeload.github.com/fdutey/shirinji/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248402538,"owners_count":21097331,"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","inversion-of-control","ruby"],"created_at":"2025-03-08T17:26:02.088Z","updated_at":"2025-04-11T12:43:04.040Z","avatar_url":"https://github.com/fdutey.png","language":"Ruby","readme":"# Shirinji\n\n[![Gem Version](https://badge.fury.io/rb/shirinji.svg)](\nhttps://badge.fury.io/rb/shirinji\n)\n[![Build Status](https://travis-ci.org/fdutey/shirinji.svg?branch=master)](\nhttps://travis-ci.org/fdutey/shirinji\n)\n[![Maintainability](\nhttps://api.codeclimate.com/v1/badges/4b1c0010788d70581680/maintainability)\n](https://codeclimate.com/github/fdutey/shirinji/maintainability)\n\nDependencies Injection made clean and easy for Ruby.\n\n## Supported ruby versions\n\n- 2.7.x\n- 3.0.x\n\n## Principles\n\nRemove hard dependencies between your objects and delegate object tree building\nto an unobtrusive framework with cool convention over configuration. \n\nShirinji relies on a mapping of beans and a resolver. When you resolve a bean,\nit will return (by default) an instance of the class associated to the bean,\nwith all the bean dependencies resolved.\n\n```ruby\nclass FooService\n  attr_reader :bar_service\n  \n  def initialize(bar_service:)\n    @bar_service = bar_service\n  end\n  \n  def call(obj)\n    obj.foo = 123\n    \n    bar_service.call(obj)\n  end\nend\n\nmap = Shirinji::Map.new do\n  bean(:foo_service, klass: 'FooService')\n  bean(:bar_service, klass: 'BarService')\nend\n\nresolver = Shirinji::Resolver.new(map)\n\nresolver.resolve(:foo_service)\n# =\u003e \u003c#FooService @bar_service=\u003c#BarService\u003e\u003e \n```\n\nShirinji is unobtrusive. Basically, any of your objects can be used \noutside of its context.\n\n```ruby\nbar_service = BarService.new\nfoo_service = FooService.new(bar_service: bar_service)\n# =\u003e \u003c#FooService @bar_service=\u003c#BarService\u003e\u003e\n\n# tests\n\nRSpec.describe FooService do\n  let(:bar_service) { double(call: nil) }\n  let(:service) { described_class.new(bar_service: bar_service) }\n  \n  describe '.call' do\n    # ...\n  end\nend\n```\n\n## Constructor arguments\n\nShirinji relies on constructor to inject dependencies. It's considering that\nobjects that receive dependencies should be immutables and those dependencies\nshould not change during your program lifecycle.\n\nShirinji doesn't accept anything else than named parameters. This way,\narguments order doesn't matter and it makes everybody's life easier. \n\n## Name resolution\n\nBy default, when you try to resolve a bean, Shirinji will look for a bean named \naccordingly for each constructor parameter. \n\nIt's possible to locally override this behaviour though by using `attr` macro.\n\n```ruby\nclass FooService\n  attr_reader :bar_service\n  \n  def initialize(my_service:)\n    @bar_service = my_service\n  end\nend\n\nmap = Shirinji::Map.new do\n  bean(:foo_service, klass: 'FooService') do\n    attr :my_service, ref: :bar_service\n  end\n  \n  bean(:bar_service, klass: 'BarService')\nend\n\nresolver = Shirinji::Resolver.new(map)\n\nresolver.resolve(:foo_service)\n# =\u003e \u003c#FooService @bar_service=\u003c#BarService\u003e\u003e\n```\n\n## Caching and singletons\n\nShirinji provides a caching mecanism to help you improve memory consumption.\nThis cache is safe as long as your beans remains immutable (they should always\nbe). \n\nThe consequence is that any cached instance is actually a singleton. Singleton\nis no more a property of your class but of it's environment, improving the\nreusability of your code. \n\nSingleton is the default access mode for a bean.\n\n```ruby\nmap = Shirinji::Map.new do\n  bean(:bar_service, klass: 'BarService', access: :instance)\n  bean(:foo_service, klass: 'FooService', access: :singleton)\n  # same as bean(:foo_service, klass: 'FooService')\nend\n\nresolver = Shirinji::Resolver.new(map)\n\nresolver.resolve(:foo_service).object_id #=\u003e 1\nresolver.resolve(:foo_service).object_id #=\u003e 1\n\nresolver.resolve(:bar_service).object_id #=\u003e 2 \nresolver.resolve(:bar_service).object_id #=\u003e 3 \n```\n\nCache can be reset with the simple command `resolver.reset_cache`, which can be\nuseful when using a development console like rails console ([shirinji-rails](\nhttps://github.com/fdutey/shirinji-rails) is attaching cache reset to `reload!` \ncommand).\n\n## Other type of beans\n\nDependencies injection doesn't apply only to classes. You can actually inject\nanything and therefore, Shirinji allows you to declare anything as a dependency.\nTo achieve that, use the key `value` instead of `class`.\n\n```ruby\nmodule MyApp\n  def self.config\n    @config\n  end\n  \n  def self.load!\n    @config = OpenStruct.new  \n  end\nend\n\nclass FooService\n  attr_reader :config\n  \n  def initialize(config:)\n    @config = config\n  end\nend\n\nMyApp.load!\n\nmap = Shirinji::Map.new do\n  bean(:config, value: Proc.new { MyApp.config })\n  \n  bean(:foo_service, klass: 'FooService')\nend\n\nresolver = Shirinji::Resolver.new(map)\n\nresolver.resolve(:foo_service)\n#=\u003e \u003c#FooService @config=\u003c#OpenStruct ...\u003e ...\u003e\n```\n\nA value can be anything. `Proc` will be lazily evaluated. It also obeys the \ncache mechanism described before.\n\n## Skip construction mechanism\n\nIn some cases, you need a dependency to be injected as a class and not an \ninstance. In such case, you could use value beans, returning the class itself, \nbut you would lose the benefit of scopes (see below). \nInstead, Shirinji provides a parameter to skip the object construction.\n\nA real life example is a Job where `deliver_now` and `deliver_later` are \nclass methods.\n\n```ruby\nmap = Shirinji::Map.new do\n  bean(:foo_job, klass: 'FooJob', construct: false)\nend\n\nresolver = Shirinji::Resolver.new(map)\n\nresolver.resolve(:foo_job) #=\u003e FooJob\n```\n\n## Scopes\n\nBuilding complex objects mapping leads to lot of repetition. That's why Shirinji \nalso provides a scope mechanism to help you dry your code.\n\n```ruby\nmap = Shirinji::Map.new do\n  scope module: :Services, suffix: :service, klass_suffix: :Service do\n    bean(:foo, klass: 'Foo')\n    # same as bean(:foo_service, klass: 'Services::FooService')\n    \n    scope module: :User, prefix: :user do\n      bean(:bar, klass: 'Bar')\n      # same as bean(:user_bar_service, klass: 'Services::User::BarService')\n    end \n  end\nend\n```\n\nScopes also come with an `auto_klass` attribute to save even more time for \ncommon cases\n\n```ruby\nmap = Shirinji::Map.new do\n  scope module: :Services, \n        suffix: :service, \n        klass_suffix: :Service, \n        auto_klass: true do\n    bean(:foo)\n    # same as bean(:foo_service, klass: 'Services::FooService')\n  end\nend\n```\n\nScopes also provides an `auto_prefix` option\n\n```ruby\nmap = Shirinji::Map.new do\n  scope module: :Services, \n        suffix: :service, \n        klass_suffix: :Service, \n        auto_klass: true do\n        \n    # Do not use auto prefix on root scope or every bean will be prefixed\n    # with `services_`\n    scope auto_prefix: true do\n      bean(:foo)\n      # same as bean(:foo_service, klass: 'Services::FooService')\n      \n      scope module: :User do\n        # same as scope module: :User, prefix: :user\n  \n        bean(:bar)\n        # same as bean(:user_bar_service, klass: 'Services::User::BarService')\n      end\n    end\n  end\nend\n```\n\nFinally, for mailers / jobs ..., Scopes allow you to specify a global value\nfor `construct`\n\n```ruby\nmap = Shirinji::Map.new do\n  scope module: :Jobs, \n        suffix: :job, \n        klass_suffix: :Job, \n        auto_klass: true, \n        construct: false do\n    bean(:foo)\n    # bean(:foo_job, klass: 'Jobs::FooJob', construct: false)\n  end\nend\n```\n\nScopes do not carry property `access`\n\n## Code splitting\n\nWhen a project grows, dependencies grows too. Keeping them into one single file\nleads to headaches. One possible solution to keep everything under control is\nto split your dependencies into many files.\n\nTo include a \"sub-map\" into another one, you can use `include_map` method.\n\n```ruby\n# dependencies/services.rb\nShirinji::Map.new do\n  bean(:foo_service, klass: 'FooService')\nend\n\n# dependencies/queries.rb\nShirinji::Map.new do\n  bean(:foo_query, klass: 'FooQuery')\nend\n\n# dependencies.rb\n\nroot = Pathname.new(File.expand_path('../dependencies', __FILE__))\n\nShirinji::Map.new do\n  bean(:config, value: -\u003e { MyApp.config })\n  \n  # paths must be absolute \n  include_map(root.join('queries.rb'))\n  include_map(root.join('services.rb'))\nend\n```\n\n## Notes\n\n- It is absolutely mandatory for your beans to be stateless to use the singleton \n  mode. If they're not, you will probably run into trouble as your objects \n  behavior will depend on their history, leading to unpredictable effects.\n- Shirinji only works with named arguments. It will raise `ArgumentError` if you \n  try to use it with \"standard\" method arguments.\n  \n## TODOS\n\n- solve absolute paths problems for `include_map` (`instance_eval` is a problem)\n\n## Contributing\n\nBug reports and pull requests are welcome on GitHub at \nhttps://github.com/fdutey/shirinji.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffdutey%2Fshirinji","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffdutey%2Fshirinji","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffdutey%2Fshirinji/lists"}