dry-container-0.7.2/0000755000175000017500000000000013617277563014243 5ustar utkarshutkarshdry-container-0.7.2/CONTRIBUTING.md0000644000175000017500000000304213617277563016473 0ustar utkarshutkarsh# Issue Guidelines ## Reporting bugs If you found a bug, report an issue and describe what's the expected behavior versus what actually happens. If the bug causes a crash, attach a full backtrace. If possible, a reproduction script showing the problem is highly appreciated. ## Reporting feature requests Report a feature request **only after discussing it first on [discourse.dry-rb.org](http://discourse.dry-rb.org)** where it was accepted. Please provide a concise description of the feature, don't link to a discussion thread, and instead summarize what was discussed. ## Reporting questions, support requests, ideas, concerns etc. **PLEASE DON'T** - use [discourse.dry-rb.org](http://discourse.dry-rb.org) instead. # Pull Request Guidelines A Pull Request will only be accepted if it addresses a specific issue that was reported previously, or fixes typos, mistakes in documentation etc. Other requirements: 1) Do not open a pull request if you can't provide tests along with it. If you have problems writing tests, ask for help in the related issue. 2) Follow the style conventions of the surrounding code. In most cases, this is standard ruby style. 3) Add API documentation if it's a new feature 4) Update API documentation if it changes an existing feature 5) Bonus points for sending a PR to [github.com/dry-rb/dry-rb.org](github.com/dry-rb/dry-rb.org) which updates user documentation and guides # Asking for help If these guidelines aren't helpful, and you're stuck, please post a message on [[discourse.dry-rb.org](http://discourse.dry-rb.org). dry-container-0.7.2/.travis.yml0000644000175000017500000000126313617277563016356 0ustar utkarshutkarshlanguage: ruby dist: trusty sudo: required cache: bundler bundler_args: --without console script: - bundle exec rake spec after_success: - '[ -d coverage ] && bundle exec codeclimate-test-reporter' rvm: - 2.4.6 - 2.5.5 - 2.6.3 - jruby-9.2.7.0 - truffleruby matrix: allow_failures: - rvm: truffleruby env: global: - JRUBY_OPTS='--dev -J-Xmx1024M' - COVERAGE='true' notifications: email: false webhooks: urls: - https://webhooks.gitter.im/e/19098b4253a72c9796db on_success: change # options: [always|never|change] default: always on_failure: always # options: [always|never|change] default: always on_start: false # default: false dry-container-0.7.2/.rubocop.yml0000644000175000017500000000061613617277563016520 0ustar utkarshutkarsh# Generated by `rubocop --auto-gen-config` inherit_from: .rubocop_todo.yml Metrics/LineLength: Max: 120 Style/Documentation: Enabled: false Lint/HandleExceptions: Exclude: - rakelib/*.rake Lint/NestedMethodDefinition: Exclude: - lib/dry/container/mixin.rb Metrics/MethodLength: Exclude: - lib/dry/container/mixin.rb Style/FileName: Exclude: - lib/dry-container.rb dry-container-0.7.2/dry-container.gemspec0000644000175000017500000000173413617277563020373 0ustar utkarshutkarshrequire File.expand_path('../lib/dry/container/version', __FILE__) Gem::Specification.new do |spec| spec.name = 'dry-container' spec.version = ::Dry::Container::VERSION spec.authors = ['Andy Holland'] spec.email = ['andyholland1991@aol.com'] spec.summary = 'A simple container intended for use as an IoC container' spec.homepage = 'https://github.com/dry-rb/dry-container' spec.license = 'MIT' spec.files = `git ls-files -z`.split("\x0") spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) spec.require_paths = ['lib'] spec.required_ruby_version = '>= 2.3.0' spec.add_runtime_dependency 'concurrent-ruby', '~> 1.0' spec.add_runtime_dependency 'dry-configurable', '~> 0.1', '>= 0.1.3' spec.add_development_dependency 'bundler' spec.add_development_dependency 'rake' spec.add_development_dependency 'rspec' end dry-container-0.7.2/Rakefile0000644000175000017500000000040113617277563015703 0ustar utkarshutkarsh#!/usr/bin/env rake require 'bundler/gem_tasks' $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), 'lib')) require 'rspec/core' require 'rspec/core/rake_task' task default: :spec desc 'Run all specs in spec directory' RSpec::Core::RakeTask.new(:spec) dry-container-0.7.2/CHANGELOG.md0000644000175000017500000000630013617277563016053 0ustar utkarshutkarsh## v0.7.2 - 2019-07-09 ## Added * `.resolve` accepts an optional fallback block, similar to how `Hash#fetch` works ([flash-gordon](https://github.com/flash-gordon)) ```ruby container.resolve('missing_key') { :fallback } # => :fallback ``` * `.decorate` can (again) work with static values. Also, it can take a block instead of `with` ([flash-gordon](https://github.com/flash-gordon)) ```ruby container.register('key', 'value') container.decorate('key') { |v| "<'#{v}'>" } container.resolve('key') # => "<'value'>" ``` [Compare v0.7.1...0.7.2](https://github.com/dry-rb/dry-container/compare/v0.7.1...v0.7.2) ## v0.7.1 - 2019-06-07 ## Fixed * Added `Mixin#dup` and `Mixin#clone`, now copies don't share underlying containers (flash-gordon) [Compare v0.7.0...0.7.1](https://github.com/dry-rb/dry-container/compare/v0.7.0...v0.7.1) ## v0.7.0 - 2019-02-05 ## Changed * [BREAKING] Now only Ruby 2.3 and above is supported ([flash-gordon](https://github.com/flash-gordon)) ## Fixed * Symbols are now coerced to strings when resolving stubbed dependencies ([cthulhu666](https://github.com/cthulhu666)) * Stubbing keys not present in container will raise an error ([flash-gordon](https://github.com/flash-gordon)) This means after upgrading you may see errors like this ``` ArgumentError (cannot stub "something" - no such key in container) ``` Be sure you register dependencies before using them. The new behavior will likely save quite a bit of time when debugging ill-configured container setups. ## Added * Namespace DSL resolves keys relative to the current namespace, see the corresponding [changes](https://github.com/dry-rb/dry-container/pull/47) ([yuszuv](https://github.com/yuszuv)) * Registered objects can be decorated with the following API ([igor-alexandrov](https://github.com/igor-alexandrov)) ```ruby class CreateUser def call(params) # ... end end container.register('create_user') { CreateUser.new } container.decorate('create_user', with: ShinyLogger.new) # Now subsequent resolutions will return a wrapped object container.resolve('create_user') # => #]> ``` * Freezing a container now prevents further registrations ([flash-gordon](https://github.com/flash-gordon)) ## Internal * Handling container items was generalized in [#34](https://github.com/dry-rb/dry-container/pull/34) ([GabrielMalakias](https://github.com/GabrielMalakias)) [Compare v0.6.0...0.7.0](https://github.com/dry-rb/dry-container/compare/v0.6.0...v0.7.0) ## v0.6.0 - 2016-12-09 ## Added * `Dry::Container::Mixin#each` - provides a means of seeing what all is registered in the container ([jeremyf](https://github.com/jeremyf)) ## Fixed * Including mixin into a class with a custom initializer ([maltoe](https://github.com/maltoe)) [Compare v0.5.0...v0.6.0](https://github.com/dry-rb/dry-container/compare/v0.5.0...v0.6.0) ## v0.5.0 - 2016-08-31 ## Added * `memoize` option to `#register` - memoizes items on first resolve ([ivoanjo](https://github.com/ivoanjo)) ## Fixed * `required_ruby_version` set to `>= 2.0.0` ([artofhuman](https://github.com/artofhuman)) [Compare v0.4.0...v0.5.0](https://github.com/dry-rb/dry-container/compare/v0.4.0...v0.5.0) dry-container-0.7.2/.codeclimate.yml0000644000175000017500000000130713617277563017316 0ustar utkarshutkarshengines: rubocop: enabled: true checks: Rubocop/Metrics/LineLength: enabled: true max: 120 Rubocop/Style/Documentation: enabled: false Rubocop/Lint/HandleExceptions: enabled: true exclude: - rakelib/*.rake Rubocop/Lint/NestedMethodDefinition: enabled: true exclude: - lib/dry/container/mixin.rb Rubocop/Metrics/MethodLength: enabled: true exclude: - lib/dry/container/mixin.rb Rubocop/Style/FileName: enabled: true exclude: - lib/dry-container.rb ratings: paths: - lib/**/*.rb exclude_paths: - spec/**/* - examples/**/* dry-container-0.7.2/.gitignore0000644000175000017500000000013713617277563016234 0ustar utkarshutkarsh.DS_Store coverage /.bundle vendor/bundle bin/ tmp/ .idea/ Gemfile.lock spec/examples.txt pkg/ dry-container-0.7.2/Gemfile0000644000175000017500000000050513617277563015536 0ustar utkarshutkarshsource 'https://rubygems.org' gemspec group :test do platforms :mri do gem 'codeclimate-test-reporter', require: false gem 'simplecov', require: false end end group :tools do gem 'rubocop' gem 'guard' gem 'guard-rspec' gem 'guard-rubocop' gem 'listen', '3.0.6' gem 'pry-byebug', platform: :mri end dry-container-0.7.2/.rubocop_todo.yml0000644000175000017500000000060413617277563017542 0ustar utkarshutkarsh# This configuration was generated by `rubocop --auto-gen-config` # on 2015-06-16 19:51:19 +0100 using RuboCop version 0.32.0. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. dry-container-0.7.2/README.md0000644000175000017500000000213713617277563015525 0ustar utkarshutkarsh[gem]: https://rubygems.org/gems/dry-container [travis]: https://travis-ci.org/dry-rb/dry-container [maintainability]: https://codeclimate.com/github/dry-rb/dry-container/maintainability [test_coverage]: https://codeclimate.com/github/dry-rb/dry-container/test_coverage [inch]: http://inch-ci.org/github/dry-rb/dry-container [chat]: https://dry-rb.zulipchat.com # dry-container [![Join the chat at https://dry-rb.zulipchat.com](https://img.shields.io/badge/dry--rb-join%20chat-%23346b7a.svg)][chat] [![Gem Version](https://img.shields.io/gem/v/dry-container.svg)][gem] [![Build Status](https://img.shields.io/travis/dry-rb/dry-container.svg)][travis] [![Maintainability](https://api.codeclimate.com/v1/badges/97faf9446cb5811100e7/maintainability)][maintainability] [![Test Coverage](https://api.codeclimate.com/v1/badges/97faf9446cb5811100e7/test_coverage)][test_coverage] [![API Documentation Coverage](http://inch-ci.org/github/dry-rb/dry-container.svg)][inch] A simple, configurable container implemented in Ruby. ## Links [Documentation](http://dry-rb.org/gems/dry-container/) ## License See `LICENSE` file. dry-container-0.7.2/.rspec0000644000175000017500000000005013617277563015353 0ustar utkarshutkarsh--color --require ./spec/spec_helper.rb dry-container-0.7.2/LICENSE0000644000175000017500000000207513617277563015254 0ustar utkarshutkarshThe MIT License (MIT) Copyright (c) 2015-2017 dry-container Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. dry-container-0.7.2/spec/0000755000175000017500000000000013617277563015175 5ustar utkarshutkarshdry-container-0.7.2/spec/spec_helper.rb0000644000175000017500000001013413617277563020012 0ustar utkarshutkarsh# require 'pathname' if RUBY_ENGINE == 'ruby' && ENV['COVERAGE'] == 'true' require 'yaml' rubies = YAML.load(File.read(File.join(__dir__, '..', '.travis.yml')))['rvm'] latest_mri = rubies.select { |v| v =~ /\A\d+\.\d+.\d+\z/ }.max if RUBY_VERSION == latest_mri require 'simplecov' SimpleCov.start do add_filter '/spec/' end end end begin require 'pry-byebug' rescue LoadError end # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration RSpec.configure do |config| config.expect_with :rspec do |expectations| # This option will default to `true` in RSpec 4. It makes the `description` # and `failure_message` of custom matchers include text for helper methods # defined using `chain`, e.g.: # be_bigger_than(2).and_smaller_than(4).description # # => "be bigger than 2 and smaller than 4" # ...rather than: # # => "be bigger than 2" expectations.include_chain_clauses_in_custom_matcher_descriptions = true end config.mock_with :rspec do |mocks| # Prevents you from mocking or stubbing a method that does not exist on # a real object. This is generally recommended, and will default to # `true` in RSpec 4. mocks.verify_partial_doubles = true end # This option will default to `:apply_to_host_groups` in RSpec 4 (and will # have no way to turn it off -- the option exists only for backwards # compatibility in RSpec 3). It causes shared context metadata to be # inherited by the metadata hash of host groups and examples, rather than # triggering implicit auto-inclusion in groups with matching metadata. config.shared_context_metadata_behavior = :apply_to_host_groups # This allows you to limit a spec run to individual examples or groups # you care about by tagging them with `:focus` metadata. When nothing # is tagged with `:focus`, all examples get run. RSpec also provides # aliases for `it`, `describe`, and `context` that include `:focus` # metadata: `fit`, `fdescribe` and `fcontext`, respectively. config.filter_run_when_matching :focus # Allows RSpec to persist some state between runs in order to support # the `--only-failures` and `--next-failure` CLI options. We recommend # you configure your source control system to ignore this file. config.example_status_persistence_file_path = 'spec/examples.txt' # Limits the available syntax to the non-monkey patched syntax that is # recommended. For more details, see: # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode config.disable_monkey_patching! # This setting enables warnings. It's recommended, but in some cases may # be too noisy due to issues in dependencies. config.warnings = true # Many RSpec users commonly either run the entire suite or an individual # file, and it's useful to allow more verbose output when running an # individual spec file. if config.files_to_run.one? # Use the documentation formatter for detailed output, # unless a formatter has already been configured # (e.g. via a command-line flag). config.default_formatter = 'doc' end # Print the n slowest examples and example groups at the # end of the spec run, to help surface which specs are running # particularly slow. config.profile_examples = 3 # Run specs in random order to surface order dependencies. If you find an # order dependency and want to debug it, you can fix the order by providing # the seed, which is printed after each run. # --seed 1234 config.order = :random # Seed global randomization in this process using the `--seed` CLI option. # Setting this allows you to use `--seed` to deterministically reproduce # test failures related to randomization by passing the same `--seed` value # as the one that triggered the failure. Kernel.srand config.seed end require 'dry/container' require 'dry/container/stub' Dir[Pathname(__FILE__).dirname.join('support/**/*.rb').to_s].each do |file| require file end dry-container-0.7.2/spec/integration/0000755000175000017500000000000013617277563017520 5ustar utkarshutkarshdry-container-0.7.2/spec/integration/mixin_spec.rb0000644000175000017500000000133713617277563022207 0ustar utkarshutkarshRSpec.describe Dry::Container::Mixin do describe 'extended' do let(:klass) do Class.new { extend Dry::Container::Mixin } end let(:container) { klass } it_behaves_like 'a container' end describe 'included' do let(:klass) do Class.new { include Dry::Container::Mixin } end let(:container) { klass.new } it_behaves_like 'a container' context 'into a class with a custom .initialize method' do let(:klass) do Class.new do include Dry::Container::Mixin def initialize; end end end it 'does not fail on missing member variable' do expect { container.register :key, -> {} }.to_not raise_error end end end end dry-container-0.7.2/spec/integration/container_spec.rb0000644000175000017500000000066713617277563023052 0ustar utkarshutkarshRSpec.describe Dry::Container do let(:klass) { Dry::Container } let(:container) { klass.new } it_behaves_like 'a container' describe 'inheritance' do it 'sets up a container for a child class' do parent = Class.new { extend Dry::Container::Mixin } child = Class.new(parent) parent.register(:foo, 'foo') child.register(:foo, 'foo') expect(parent[:foo]).to_not be(child[:foo]) end end end dry-container-0.7.2/spec/support/0000755000175000017500000000000013617277563016711 5ustar utkarshutkarshdry-container-0.7.2/spec/support/shared_examples/0000755000175000017500000000000013617277563022055 5ustar utkarshutkarshdry-container-0.7.2/spec/support/shared_examples/container.rb0000644000175000017500000004503313617277563024371 0ustar utkarshutkarshRSpec.shared_examples 'a container' do describe 'configuration' do describe 'registry' do describe 'default' do it { expect(klass.config.registry).to be_a(Dry::Container::Registry) } end describe 'custom' do let(:custom_registry) { double('Registry') } let(:key) { :key } let(:item) { :item } let(:options) { {} } before do klass.configure do |config| config.registry = custom_registry end allow(custom_registry).to receive(:call) end after do # HACK: Have to reset the configuration so that it doesn't # interfere with other specs klass.configure do |config| config.registry = Dry::Container::Registry.new end end subject! { container.register(key, item, options) } it do expect(custom_registry).to have_received(:call).with( container._container, key, item, options ) end end end describe 'resolver' do describe 'default' do it { expect(klass.config.resolver).to be_a(Dry::Container::Resolver) } end describe 'custom' do let(:custom_resolver) { double('Resolver') } let(:item) { double('Item') } let(:key) { :key } before do klass.configure do |config| config.resolver = custom_resolver end allow(custom_resolver).to receive(:call).and_return(item) end after do # HACK: Have to reset the configuration so that it doesn't # interfere with other specs klass.configure do |config| config.resolver = Dry::Container::Resolver.new end end subject! { container.resolve(key) } it { expect(custom_resolver).to have_received(:call).with(container._container, key) } it { is_expected.to eq(item) } end end describe 'namespace_separator' do describe 'default' do it { expect(klass.config.namespace_separator).to eq('.') } end describe 'custom' do let(:custom_registry) { double('Registry') } let(:key) { 'key' } let(:namespace_separator) { '-' } let(:namespace) { 'one' } before do klass.configure do |config| config.namespace_separator = namespace_separator end container.namespace(namespace) do register('key', 'item') end end after do # HACK: Have to reset the configuration so that it doesn't # interfere with other specs klass.configure do |config| config.namespace_separator = '.' end end subject! { container.resolve([namespace, key].join(namespace_separator)) } it { is_expected.to eq('item') } end end end context 'with default configuration' do describe 'registering a block' do context 'without options' do context 'without arguments' do it 'registers and resolves an object' do container.register(:item) { 'item' } expect(container.keys).to eq(['item']) expect(container.key?(:item)).to be true expect(container.resolve(:item)).to eq('item') end end context 'with arguments' do it 'registers and resolves a proc' do container.register(:item) { |item| item } expect(container.resolve(:item).call('item')).to eq('item') end it 'does not call a proc on resolving if one accepts an arbitrary number of keyword arguments' do container.register(:item) { |*| 'item' } expect(container.resolve(:item)).to be_a_kind_of Proc expect(container.resolve(:item).call).to eq('item') end end end context 'with option call: false' do it 'registers and resolves a proc' do container.register(:item, call: false) { 'item' } expect(container.keys).to eq(['item']) expect(container.key?(:item)).to be true expect(container.resolve(:item).call).to eq('item') expect(container[:item].call).to eq('item') end end end describe 'registering a proc' do context 'without options' do context 'without arguments' do it 'registers and resolves an object' do container.register(:item, proc { 'item' }) expect(container.keys).to eq(['item']) expect(container.key?(:item)).to be true expect(container.resolve(:item)).to eq('item') expect(container[:item]).to eq('item') end end context 'with arguments' do it 'registers and resolves a proc' do container.register(:item, proc { |item| item }) expect(container.keys).to eq(['item']) expect(container.key?(:item)).to be true expect(container.resolve(:item).call('item')).to eq('item') expect(container[:item].call('item')).to eq('item') end end end context 'with option call: false' do it 'registers and resolves a proc' do container.register(:item, proc { 'item' }, call: false) expect(container.keys).to eq(['item']) expect(container.key?(:item)).to be true expect(container.resolve(:item).call).to eq('item') expect(container[:item].call).to eq('item') end end context 'with option memoize: true' do it 'registers and resolves a proc' do container.register(:item, proc { 'item' }, memoize: true) expect(container[:item]).to be container[:item] expect(container.keys).to eq(['item']) expect(container.key?(:item)).to be true expect(container.resolve(:item)).to eq('item') expect(container[:item]).to eq('item') end it 'only resolves the proc once' do resolved_times = 0 container.register(:item, proc { resolved_times += 1 }, memoize: true) expect(container.resolve(:item)).to be 1 expect(container.resolve(:item)).to be 1 end context 'when receiving something other than a proc' do it do expect { container.register(:item, 'Hello!', memoize: true) }.to raise_error(Dry::Container::Error) end end end end describe 'registering an object' do context 'without options' do it 'registers and resolves the object' do item = 'item' container.register(:item, item) expect(container.keys).to eq(['item']) expect(container.key?(:item)).to be true expect(container.resolve(:item)).to be(item) expect(container[:item]).to be(item) end end context 'with option call: false' do it 'registers and resolves an object' do item = -> { 'test' } container.register(:item, item, call: false) expect(container.keys).to eq(['item']) expect(container.key?(:item)).to be true expect(container.resolve(:item)).to eq(item) expect(container[:item]).to eq(item) end end end describe 'registering with the same key multiple times' do it do container.register(:item, proc { 'item' }) expect { container.register(:item, proc { 'item' }) }.to raise_error(Dry::Container::Error) end end describe 'resolving with a key that has not been registered' do it do expect(container.key?(:item)).to be false expect { container.resolve(:item) }.to raise_error(Dry::Container::Error) end end describe 'mixing Strings and Symbols' do it do container.register(:item, 'item') expect(container.resolve('item')).to eql('item') end end describe '#merge' do let(:key) { :key } let(:other) { Dry::Container.new } before do other.register(key) { :item } end context 'without namespace argument' do subject! { container.merge(other) } it { expect(container.resolve(key)).to be(:item) } it { expect(container[key]).to be(:item) } end context 'with namespace argument' do subject! { container.merge(other, namespace: namespace) } context 'when namespace is nil' do let(:namespace) { nil } it { expect(container.resolve(key)).to be(:item) } it { expect(container[key]).to be(:item) } end context 'when namespace is not nil' do let(:namespace) { 'namespace' } it { expect(container.resolve("#{namespace}.#{key}")).to be(:item) } it { expect(container["#{namespace}.#{key}"]).to be(:item) } end end end describe '#key?' do let(:key) { :key } before do container.register(key) { :item } end subject! { container.key?(resolve_key) } context 'when key exists in container' do let(:resolve_key) { key } it { is_expected.to be true } end context 'when key does not exist in container' do let(:resolve_key) { :random } it { is_expected.to be false } end end describe '#keys' do let(:keys) { [:key_1, :key_2] } let(:expected_keys) { ['key_1', 'key_2'] } before do keys.each do |key| container.register(key) { :item } end end subject! { container.keys } it 'returns stringified versions of all registered keys' do is_expected.to match_array(expected_keys) end end describe '#each_key' do let(:keys) { [:key_1, :key_2] } let(:expected_keys) { ['key_1', 'key_2'] } let!(:yielded_keys) { [] } before do keys.each do |key| container.register(key) { :item } end end subject! do container.each_key { |key| yielded_keys << key } end it 'yields stringified versions of all registered keys to the block' do expect(yielded_keys).to match_array(expected_keys) end it 'returns the container' do is_expected.to eq(container) end end describe '#each' do let(:keys) { [:key_1, :key_2] } let(:expected_key_value_pairs) { [['key_1', 'value_for_key_1'], ['key_2', 'value_for_key_2']] } let!(:yielded_key_value_pairs) { [] } before do keys.each do |key| container.register(key) { "value_for_#{key}" } end end subject! do container.each { |key, value| yielded_key_value_pairs << [key, value] } end it 'yields stringified versions of all registered keys to the block' do expect(yielded_key_value_pairs).to match_array(expected_key_value_pairs) end it 'returns the container' do is_expected.to eq(expected_key_value_pairs) end end describe '#decorate' do require 'delegate' let(:key) { :key } let(:decorated_class_spy) { spy(:decorated_class_spy) } let(:decorated_class) { Class.new } context 'for callable item' do before do allow(decorated_class_spy).to receive(:new) { decorated_class.new } container.register(key, memoize: memoize) { decorated_class_spy.new } container.decorate(key, with: SimpleDelegator) end context 'memoize false' do let(:memoize) { false } it 'does not call the block until the key is resolved' do expect(decorated_class_spy).not_to have_received(:new) container.resolve(key) expect(decorated_class_spy).to have_received(:new) end specify do expect(container[key]).to be_instance_of(SimpleDelegator) expect(container[key].__getobj__).to be_instance_of(decorated_class) expect(container[key]).not_to be(container[key]) expect(container[key].__getobj__).not_to be(container[key].__getobj__) end end context 'memoize true' do let(:memoize) { true } specify do expect(container[key]).to be_instance_of(SimpleDelegator) expect(container[key].__getobj__).to be_instance_of(decorated_class) expect(container[key]).to be(container[key]) end end end context 'for not callable item' do describe 'wrapping' do before do container.register(key, call: false) { "value" } container.decorate(key, with: SimpleDelegator) end it 'expected to be an instance of SimpleDelegator' do expect(container.resolve(key)).to be_instance_of(SimpleDelegator) expect(container.resolve(key).__getobj__.call).to eql("value") end end describe 'memoization' do before do @called = 0 container.register(key, 'value') container.decorate(key) do |value| @called += 1 "<#{value}>" end end it 'decorates static value only once' do expect(container.resolve(key)).to eql('') expect(container.resolve(key)).to eql('') expect(@called).to be(1) end end end context 'with an instance as a decorator' do let(:decorator) do double.tap do |decorator| allow(decorator).to receive(:call) { |input| "decorated #{input}" } end end before do container.register(key) { "value" } container.decorate(key, with: decorator) end it 'expected to pass original value to decorator#call method' do expect(container.resolve(key)).to eq("decorated value") end end end describe 'namespace' do context 'when block does not take arguments' do before do container.namespace('one') do register('two', 2) end end subject! { container.resolve('one.two') } it 'registers items under the given namespace' do is_expected.to eq(2) end end context 'when block takes arguments' do before do container.namespace('one') do |c| c.register('two', 2) end end subject! { container.resolve('one.two') } it 'registers items under the given namespace' do is_expected.to eq(2) end end context 'with nesting' do before do container.namespace('one') do namespace('two') do register('three', 3) end end end subject! { container.resolve('one.two.three') } it 'registers items under the given namespaces' do is_expected.to eq(3) end end context 'with nesting and when block takes arguments' do before do container.namespace('one') do |c| c.register('two', 2) c.register('three', c.resolve('two')) end end subject! { container.resolve('one.three') } it 'resolves items relative to the namespace' do is_expected.to eq(2) end end end describe 'import' do it 'allows importing of namespaces' do ns = Dry::Container::Namespace.new('one') do register('two', 2) end container.import(ns) expect(container.resolve('one.two')).to eq(2) end it 'allows importing of nested namespaces' do ns = Dry::Container::Namespace.new('two') do register('three', 3) end container.namespace('one') do import(ns) end expect(container.resolve('one.two.three')).to eq(3) end end end describe 'stubbing' do before do container.enable_stubs! container.register(:item, 'item') container.register(:foo, 'bar') end after do container.unstub end it 'keys can be stubbed' do container.stub(:item, 'stub') expect(container.resolve(:item)).to eql('stub') expect(container[:item]).to eql('stub') end it 'only other keys remain accesible' do container.stub(:item, 'stub') expect(container.resolve(:foo)).to eql('bar') expect(container[:foo]).to eql('bar') end it 'keys can be reverted back to their original value' do container.stub(:item, 'stub') container.unstub(:item) expect(container.resolve(:item)).to eql('item') expect(container[:item]).to eql('item') end describe 'with block argument' do it 'executes the block with the given stubs' do expect { |b| container.stub(:item, 'stub', &b) }.to yield_control end it 'keys are stubbed only while inside the block' do container.stub(:item, 'stub') do expect(container.resolve(:item)).to eql('stub') end expect(container.resolve(:item)).to eql('item') end end describe 'mixing Strings and Symbols' do it do container.stub(:item, 'stub') expect(container.resolve('item')).to eql('stub') end end it 'raises an error when key is missing' do expect { container.stub(:non_existing, 'something') }. to raise_error(ArgumentError, 'cannot stub "non_existing" - no such key in container') end end describe '.freeze' do before do container.register(:foo, 'bar') end it 'allows to freeze a container so that nothing can be registered later' do container.freeze error = RUBY_VERSION >= '2.5' ? FrozenError : RuntimeError expect { container.register(:baz, 'quux') }.to raise_error(error) expect(container).to be_frozen end it 'returns self back' do expect(container.freeze).to be(container) end end describe '.dup' do it "returns a copy that doesn't share registered keys with the parent" do container.dup.register(:foo, 'bar') expect(container.key?(:foo)).to be false end end describe '.clone' do it "returns a copy that doesn't share registered keys with the parent" do container.clone.register(:foo, 'bar') expect(container.key?(:foo)).to be false end it 're-uses frozen container' do expect(container.freeze.clone).to be_frozen expect(container.clone._container).to be(container._container) end end describe '.resolve' do it 'accepts a fallback block' do expect(container.resolve('missing') { :fallback }).to be(:fallback) end end end dry-container-0.7.2/rakelib/0000755000175000017500000000000013617277563015654 5ustar utkarshutkarshdry-container-0.7.2/rakelib/rubocop.rake0000644000175000017500000000055713617277563020200 0ustar utkarshutkarshbegin require 'rubocop/rake_task' Rake::Task[:default].enhance [:rubocop] RuboCop::RakeTask.new do |task| task.options << '--display-cop-names' end namespace :rubocop do desc 'Generate a configuration file acting as a TODO list.' task :auto_gen_config do exec 'bundle exec rubocop --auto-gen-config' end end rescue LoadError end dry-container-0.7.2/lib/0000755000175000017500000000000013617277563015011 5ustar utkarshutkarshdry-container-0.7.2/lib/dry-container.rb0000644000175000017500000000003013617277563020105 0ustar utkarshutkarshrequire 'dry/container' dry-container-0.7.2/lib/dry/0000755000175000017500000000000013617277563015607 5ustar utkarshutkarshdry-container-0.7.2/lib/dry/container/0000755000175000017500000000000013617277563017571 5ustar utkarshutkarshdry-container-0.7.2/lib/dry/container/version.rb0000644000175000017500000000012613617277563021602 0ustar utkarshutkarshmodule Dry class Container # @api public VERSION = '0.7.2'.freeze end end dry-container-0.7.2/lib/dry/container/registry.rb0000644000175000017500000000242213617277563021766 0ustar utkarshutkarshrequire 'dry/container/item/factory' module Dry class Container # Default registry for registering items with the container # # @api public class Registry # @private def initialize @_mutex = ::Mutex.new end # Register an item with the container to be resolved later # # @param [Concurrent::Hash] container # The container # @param [Mixed] key # The key to register the container item with (used to resolve) # @param [Mixed] item # The item to register with the container # @param [Hash] options # @option options [Symbol] :call # Whether the item should be called when resolved # # @raise [Dry::Container::Error] # If an item is already registered with the given key # # @return [Mixed] # # @api public def call(container, key, item, options) key = key.to_s.dup.freeze @_mutex.synchronize do if container.key?(key) raise Error, "There is already an item registered with the key #{key.inspect}" end container[key] = factory.call(item, options) end end def factory @factory ||= ::Dry::Container::Item::Factory.new end end end end dry-container-0.7.2/lib/dry/container/mixin.rb0000644000175000017500000001670013617277563021246 0ustar utkarshutkarsh require 'concurrent/hash' module Dry class Container PREFIX_NAMESPACE = lambda do |namespace, key, config| [namespace, key].join(config.namespace_separator) end EMPTY_HASH = {}.freeze # Mixin to expose Inversion of Control (IoC) container behaviour # # @example # # class MyClass # extend Dry::Container::Mixin # end # # MyClass.register(:item, 'item') # MyClass.resolve(:item) # => 'item' # # class MyObject # include Dry::Container::Mixin # end # # container = MyObject.new # container.register(:item, 'item') # container.resolve(:item) # => 'item' # # # @api public module Mixin # @private def self.extended(base) hooks_mod = ::Module.new do def inherited(subclass) subclass.instance_variable_set(:@_container, @_container.dup) super end end base.class_eval do extend ::Dry::Configurable extend hooks_mod setting :registry, ::Dry::Container::Registry.new setting :resolver, ::Dry::Container::Resolver.new setting :namespace_separator, '.' @_container = ::Concurrent::Hash.new end end # @private module Initializer def initialize(*args, &block) @_container = ::Concurrent::Hash.new super end end # @private def self.included(base) base.class_eval do extend ::Dry::Configurable prepend Initializer setting :registry, ::Dry::Container::Registry.new setting :resolver, ::Dry::Container::Resolver.new setting :namespace_separator, '.' def config self.class.config end end end # Register an item with the container to be resolved later # # @param [Mixed] key # The key to register the container item with (used to resolve) # @param [Mixed] contents # The item to register with the container (if no block given) # @param [Hash] options # Options to pass to the registry when registering the item # @yield # If a block is given, contents will be ignored and the block # will be registered instead # # @return [Dry::Container::Mixin] self # # @api public def register(key, contents = nil, options = EMPTY_HASH, &block) if block_given? item = block options = contents if contents.is_a?(::Hash) else item = contents end config.registry.call(_container, key, item, options) self end # Resolve an item from the container # # @param [Mixed] key # The key for the item you wish to resolve # @yield # Fallback block to call when a key is missing. Its result will be returned # @yieldparam [Mixed] key Missing key # # @return [Mixed] # # @api public def resolve(key, &block) config.resolver.call(_container, key, &block) end # Resolve an item from the container # # @param [Mixed] key # The key for the item you wish to resolve # # @return [Mixed] # # @api public # @see Dry::Container::Mixin#resolve def [](key) resolve(key) end # Merge in the items of the other container # # @param [Dry::Container] other # The other container to merge in # @param [Symbol, nil] namespace # Namespace to prefix other container items with, defaults to nil # # @return [Dry::Container::Mixin] self # # @api public def merge(other, namespace: nil) if namespace _container.merge!( other._container.each_with_object(::Concurrent::Hash.new) do |a, h| h[PREFIX_NAMESPACE.call(namespace, a.first, config)] = a.last end ) else _container.merge!(other._container) end self end # Check whether an item is registered under the given key # # @param [Mixed] key # The key you wish to check for registration with # # @return [Bool] # # @api public def key?(key) config.resolver.key?(_container, key) end # An array of registered names for the container # # @return [Array] # # @api public def keys config.resolver.keys(_container) end # Calls block once for each key in container, passing the key as a parameter. # # If no block is given, an enumerator is returned instead. # # @return [Dry::Container::Mixin] self # # @api public def each_key(&block) config.resolver.each_key(_container, &block) self end # Calls block once for each key/value pair in the container, passing the key and the registered item parameters. # # If no block is given, an enumerator is returned instead. # # @return [Enumerator] # # @api public # # @note In discussions with other developers, it was felt that being able to iterate over not just # the registered keys, but to see what was registered would be very helpful. This is a step # toward doing that. def each(&block) config.resolver.each(_container, &block) end # Decorates an item from the container with specified decorator # # @return [Dry::Container::Mixin] self # # @api public def decorate(key, with: nil, &block) key = key.to_s original = _container.delete(key) do raise Error, "Nothing registered with the key #{key.inspect}" end if with.is_a?(Class) decorator = with.method(:new) elsif block.nil? && !with.respond_to?(:call) raise Error, "Decorator needs to be a Class, block, or respond to the `call` method" else decorator = with || block end _container[key] = original.map(decorator) self end # Evaluate block and register items in namespace # # @param [Mixed] namespace # The namespace to register items in # # @return [Dry::Container::Mixin] self # # @api public def namespace(namespace, &block) ::Dry::Container::NamespaceDSL.new( self, namespace, config.namespace_separator, &block ) self end # Import a namespace # # @param [Dry::Container::Namespace] namespace # The namespace to import # # @return [Dry::Container::Mixin] self # # @api public def import(namespace) namespace(namespace.name, &namespace.block) self end # Freeze the container. Nothing can be registered after freezing # # @api public def freeze super _container.freeze self end # @private no, really def _container @_container end # @api public def dup copy = super copy.instance_variable_set(:@_container, _container.dup) copy end # @api public def clone copy = super unless copy.frozen? copy.instance_variable_set(:@_container, _container.dup) end copy end end end end dry-container-0.7.2/lib/dry/container/namespace.rb0000644000175000017500000000164313617277563022056 0ustar utkarshutkarshmodule Dry class Container # Create a namespace to be imported # # @example # # ns = Dry::Container::Namespace.new('name') do # register('item', 'item') # end # # container = Dry::Container.new # # container.import(ns) # # container.resolve('name.item') # => 'item' # # # @api public class Namespace # @return [Mixed] The namespace (name) attr_reader :name # @return [Proc] The block to be executed when the namespace is imported attr_reader :block # Create a new namespace # # @param [Mixed] name # The name of the namespace # @yield # The block to evaluate when the namespace is imported # # @return [Dry::Container::Namespace] # # @api public def initialize(name, &block) @name = name @block = block end end end end dry-container-0.7.2/lib/dry/container/error.rb0000644000175000017500000000013613617277563021247 0ustar utkarshutkarshmodule Dry class Container # @api public Error = Class.new(StandardError) end end dry-container-0.7.2/lib/dry/container/stub.rb0000644000175000017500000000211613617277563021073 0ustar utkarshutkarshmodule Dry class Container module Stub # Overrides resolve to look into stubbed keys first # # @api public def resolve(key) _stubs.fetch(key.to_s) { super } end # Add a stub to the container def stub(key, value, &block) unless key?(key) raise ArgumentError, "cannot stub #{ key.to_s.inspect } - no such key in container" end _stubs[key.to_s] = value if block yield unstub(key) end self end # Remove stubbed keys from the container def unstub(*keys) keys = _stubs.keys if keys.empty? keys.each { |key| _stubs.delete(key.to_s) } end # Stubs have already been enabled turning this into a noop def enable_stubs! # DO NOTHING end private # Stubs container def _stubs @_stubs ||= {} end end module Mixin # Enable stubbing functionality into the current container def enable_stubs! extend ::Dry::Container::Stub end end end end dry-container-0.7.2/lib/dry/container/namespace_dsl.rb0000644000175000017500000000240513617277563022715 0ustar utkarshutkarshrequire 'delegate' module Dry class Container # @api private class NamespaceDSL < ::SimpleDelegator # DSL for defining namespaces # # @param [Dry::Container::Mixin] container # The container # @param [String] namespace # The namespace (name) # @param [String] namespace_separator # The namespace separator # @yield # The block to evaluate to define the namespace # # @return [Mixed] # # @api private def initialize(container, namespace, namespace_separator, &block) @namespace = namespace @namespace_separator = namespace_separator super(container) if block.arity.zero? instance_eval(&block) else yield self end end def register(key, *args, &block) super(namespaced(key), *args, &block) end def namespace(namespace, &block) super(namespaced(namespace), &block) end def import(namespace) namespace(namespace.name, &namespace.block) self end def resolve(key) super(namespaced(key)) end private def namespaced(key) [@namespace, key].join(@namespace_separator) end end end end dry-container-0.7.2/lib/dry/container/resolver.rb0000644000175000017500000000453313617277563021764 0ustar utkarshutkarshmodule Dry class Container # Default resolver for resolving items from container # # @api public class Resolver # Resolve an item from the container # # @param [Concurrent::Hash] container # The container # @param [Mixed] key # The key for the item you wish to resolve # @yield # Fallback block to call when a key is missing. Its result will be returned # @yieldparam [Mixed] key Missing key # # @raise [Dry::Conainer::Error] # If the given key is not registered with the container (and no block provided) # # # @return [Mixed] # # @api public def call(container, key) item = container.fetch(key.to_s) do if block_given? return yield(key) else raise Error, "Nothing registered with the key #{key.inspect}" end end item.call end # Check whether an items is registered under the given key # # @param [Concurrent::Hash] container # The container # @param [Mixed] key # The key you wish to check for registration with # # @return [Bool] # # @api public def key?(container, key) container.key?(key.to_s) end # An array of registered names for the container # # @return [Array] # # @api public def keys(container) container.keys end # Calls block once for each key in container, passing the key as a parameter. # # If no block is given, an enumerator is returned instead. # # @return Hash # # @api public def each_key(container, &block) container.each_key(&block) end # Calls block once for each key in container, passing the key and the registered item parameters. # # If no block is given, an enumerator is returned instead. # # @return Key, Value # # @api public # @note In discussions with other developers, it was felt that being able to iterate over not just # the registered keys, but to see what was registered would be very helpful. This is a step # toward doing that. def each(container, &block) container.map { |key, value| [key, value.call] }.each(&block) end end end end dry-container-0.7.2/lib/dry/container/item.rb0000644000175000017500000000175613617277563021065 0ustar utkarshutkarshmodule Dry class Container # Base class to abstract Memoizable and Callable implementations # # @api abstract # class Item # @return [Mixed] the item to be solved later attr_reader :item # @return [Hash] the options to memoize, call or no. attr_reader :options # @api abstract def initialize(item, options = {}) @item = item @options = { call: item.is_a?(::Proc) && item.parameters.empty? }.merge(options) end # @api abstract def call raise NotImplementedError end # @private def value? !callable? end # @private def callable? options[:call] end # Build a new item with transformation applied # # @private def map(func) if callable? self.class.new(-> { func.(item.call) }, options) else self.class.new(func.(item), options) end end end end end dry-container-0.7.2/lib/dry/container/item/0000755000175000017500000000000013617277563020527 5ustar utkarshutkarshdry-container-0.7.2/lib/dry/container/item/memoizable.rb0000644000175000017500000000214513617277563023202 0ustar utkarshutkarshrequire 'dry/container/item' module Dry class Container class Item # Memoizable class to store and execute item calls # # @api public # class Memoizable < Item # @return [Mutex] the stored mutex attr_reader :memoize_mutex # Returns a new Memoizable instance # # @param [Mixed] item # @param [Hash] options # # @raise [Dry::Container::Error] # # @return [Dry::Container::Item::Base] def initialize(item, options = {}) super raise_not_supported_error unless callable? @memoize_mutex = ::Mutex.new end # Returns the result of item call using a syncronized mutex # # @return [Dry::Container::Item::Base] def call memoize_mutex.synchronize do @memoized_item ||= item.call end end private # @private def raise_not_supported_error raise ::Dry::Container::Error, 'Memoize only supported for a block or a proc'.freeze end end end end end dry-container-0.7.2/lib/dry/container/item/factory.rb0000644000175000017500000000116013617277563022521 0ustar utkarshutkarshrequire 'dry/container/item/memoizable' require 'dry/container/item/callable' module Dry class Container class Item # Factory for create an Item to register inside of container # # @api public class Factory # Creates an Item Memoizable or Callable # @param [Mixed] item # @param [Hash] options # # @raise [Dry::Container::Error] # # @return [Dry::Container::Item::Base] def call(item, options = {}) options[:memoize] ? Memoizable.new(item, options) : Callable.new(item, options) end end end end end dry-container-0.7.2/lib/dry/container/item/callable.rb0000644000175000017500000000055613617277563022621 0ustar utkarshutkarshrequire 'dry/container/item' module Dry class Container class Item # Callable class to returns a item call # # @api public # class Callable < Item # Returns the result of item call or item # # @return [Mixed] def call callable? ? item.call : item end end end end end dry-container-0.7.2/lib/dry/container.rb0000644000175000017500000000155413617277563020123 0ustar utkarshutkarshrequire 'dry-configurable' require 'dry/container/error' require 'dry/container/namespace' require 'dry/container/registry' require 'dry/container/resolver' require 'dry/container/namespace_dsl' require 'dry/container/mixin' require 'dry/container/version' # A collection of micro-libraries, each intended to encapsulate # a common task in Ruby module Dry # Inversion of Control (IoC) container # # @example # # container = Dry::Container.new # container.register(:item, 'item') # container.resolve(:item) # => 'item' # # container.register(:item1, -> { 'item' }) # container.resolve(:item1) # => 'item' # # container.register(:item2, -> { 'item' }, call: false) # container.resolve(:item2) # => # # # @api public class Container include ::Dry::Container::Mixin end end