GithubHelp home page GithubHelp logo

dry-rb / dry-container Goto Github PK

View Code? Open in Web Editor NEW
333.0 25.0 41.0 445 KB

A simple, configurable object container implemented in Ruby

Home Page: https://dry-rb.org/gems/dry-container

License: MIT License

Ruby 97.16% HTML 2.84%
dry-rb ruby rubygem

dry-container's Introduction

dry-container Gem Version CI Status

Links

Supported Ruby versions

This library officially supports the following Ruby versions:

  • MRI >= 3.0.0
  • jruby >= 9.4 (not tested on CI)

License

See LICENSE file.

dry-container's People

Contributors

actions-user avatar amhol avatar andrewcroome avatar artofhuman avatar blafri avatar cllns avatar cthulhu666 avatar deepj avatar dry-bot avatar endash avatar flash-gordon avatar gabrielmalakias avatar gabteles avatar gardwired avatar gustavocaso avatar igor-alexandrov avatar ivoanjo avatar jbourassa avatar jeremyf avatar joevandyk avatar k0va1 avatar mcls avatar mensfeld avatar mikekreuzer avatar olleolleolle avatar raventid avatar sagmor avatar solnic avatar timriley avatar wojciechko avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

dry-container's Issues

Reseting registry when testing

Hi! Thanks for a great gem!

I have a question regarding testing. Is it possible to somehow unregister objects from the registry? I have a problem with leaking test object added to the registry.

Before I had this code:

    require 'dry/container/stub'
    Fortnox::API::Registry.enable_stubs!
    Fortnox::API::Registry.stub(:test, Model::Test)

Now I get an error: cannot stub "test" - no such key in container. When I try adding the test object to the registry before stubbing, as you suggest in the documentation, the first test passes but the second fails with:

     Dry::Container::Error:
       There is already an item registered with the key "test"

Also, I found below code in your own test suite. Unfortunately, I cannot do the same in my test since my registry includes other objects that I do not want to throw away :)

        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

Getting an `ArgumentError` missing keywords on Ruby 2.7.3

Describe the bug

After updating dry-container from 0.8.0 to 0.10.0, I'm getting an ArgumentError missing keywords in mixin.rb

dry-container-0.10.0/lib/dry/container/mixin.rb:221:in `key?': wrong number of arguments (given 2, expected 1) (ArgumentError)

My environment

  • Ruby version: 2.7.3
  • Rails version: 7.x
  • OS: Mojave 12.4

Proposal: Meta information for container objects

I think it'll be helpful to store some meta-information about dependency in a container. Some ideas where you can use this information:

  • Aliases for DI frameworks (dry-auto_inject in our case);
  • Store graph of all dependencies in a dependency. After we can calculate what we need to load and check before current dep and it will provide to detect "dependency deadlock" (when A dependency load B dependency and B dependency load A);
  • Rules for DI framework (hello java). For example, it can allow loading dependency in specific scope:
container.register('users.repositories.users', only_in: 'users.operations.*') { ... }

module Books
  module Operations
    class Create
      include Import['users.repositories.users'] # => raise error on booting
    end
  end
end
  • Passing context into dependencies
  • Etc

WDYT? I can implement simple PoC for meta information, and after that we can start play with it if you like the idea

Fix incorrect version listed in docs

Describe the bug

Noticed, when trying to install Dry Container 0.8.0, that it doesn't exist. Only 0.7.2 is the latest version at the time of this writing. By the way, this seems to be an oddity with other Dry libraries in case it's of interest. It seems the generated documentation is ahead of what is available.

To Reproduce

Visit the landing page (also notice 0.8 is in the URL as well). Here's a screenshot for illustration:

Firefox-ps0t0imc

Expected behavior

Would expect 0.7.2 to be listed instead of 0.8.0 as found on RubyGems.

My environment

Using Firefox as my browser.

Including mixin into a class with initializer doesn't work

Hello all,

I noticed that the following doesn't work:

class X
  include Dry::Container::Mixin
  def initialize
  end
end

X.new.register :foo, -> {}
# =>
# NoMethodError: undefined method `key?' for nil:NilClass
#        from /home/malte/.rbenv/versions/2.2.4/lib/ruby/gems/2.2.0/gems/dry-container-0.5.0/lib/dry/container/registry.rb:33:in `block in call'

More specifically, I wanted to register items in the initializer. Any reason why this was implemented this way, relying on the module being able to define the class initializer?

Thank you!

After updating to rails 7 , getting this error


Users/macbookpro/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/dry-container-0.11.0/lib/dry/container/mixin.rb:155:in `register': undefined method `call' for {:default=>#<Dry::Container::Registry:0x0000000104e2c458 @_mutex=#<Thread::Mutex:0x0000000104e2c368>>}:Hash (NoMethodError)

        config.registry.call(_container, key, item, options)
                       ^^^^^
	from /Users/macbookpro/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/dry-auto_inject-0.9.0/lib/dry/auto_inject/strategies/args.rb:72:in `<class:Strategies>'
	from /Users/macbookpro/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/dry-auto_inject-0.9.0/lib/dry/auto_inject/strategies/args.rb:8:in `<module:AutoInject>'
	from /Users/macbookpro/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/dry-auto_inject-0.9.0/lib/dry/auto_inject/strategies/args.rb:7:in `<module:Dry>'
	from /Users/macbookpro/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/dry-auto_inject-0.9.0/lib/dry/auto_inject/strategies/args.rb:6:in `<top (required)>'
	from /Users/macbookpro/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/dry-auto_inject-0.9.0/lib/dry/auto_inject/strategies.rb:19:in `require'
	from /Users/macbookpro/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/dry-auto_inject-0.9.0/lib/dry/auto_inject/strategies.rb:19:in `<top (required)>'
	from /Users/macbookpro/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/dry-auto_inject-0.9.0/lib/dry/auto_inject/builder.rb:3:in `require'
	from /Users/macbookpro/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/dry-auto_inject-0.9.0/lib/dry/auto_inject/builder.rb:3:in `<top (required)>'
	from /Users/macbookpro/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/dry-auto_inject-0.9.0/lib/dry/auto_inject.rb:3:in `require'
	from /Users/macbookpro/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/dry-auto_inject-0.9.0/lib/dry/auto_inject.rb:3:in `<top (required)>'
	from /Users/macbookpro/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/warden-jwt_auth-0.5.0/lib/warden/jwt_auth.rb:4:in `require'
	from /Users/macbookpro/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/warden-jwt_auth-0.5.0/lib/warden/jwt_auth.rb:4:in `<top (required)>'
	from /Users/macbookpro/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/devise-jwt-0.8.1/lib/devise/jwt.rb:6:in `require'
	from /Users/macbookpro/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/devise-jwt-0.8.1/lib/devise/jwt.rb:6:in `<top (required)>'
	from /Users/macbookpro/Desktop/development/dashboard/config/application.rb:5:in `require'
	from /Users/macbookpro/Desktop/development/dashboard/config/application.rb:5:in `<top (required)>'
	from /Users/macbookpro/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/railties-7.0.4.3/lib/rails/commands/server/server_command.rb:137:in `require'
	from /Users/macbookpro/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/railties-7.0.4.3/lib/rails/commands/server/server_command.rb:137:in `block in perform'
	from <internal:kernel>:90:in `tap'
	from /Users/macbookpro/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/railties-7.0.4.3/lib/rails/commands/server/server_command.rb:134:in `perform'
	from /Users/macbookpro/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/thor-1.2.1/lib/thor/command.rb:27:in `run'
	from /Users/macbookpro/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/thor-1.2.1/lib/thor/invocation.rb:127:in `invoke_command'
	from /Users/macbookpro/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/thor-1.2.1/lib/thor.rb:392:in `dispatch'
	from /Users/macbookpro/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/railties-7.0.4.3/lib/rails/command/base.rb:87:in `perform'
	from /Users/macbookpro/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/railties-7.0.4.3/lib/rails/command.rb:48:in `invoke'
	from /Users/macbookpro/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/railties-7.0.4.3/lib/rails/commands.rb:18:in `<top (required)>'
	from bin/rails:4:in `require'
	from bin/rails:4:in `<main>'
  • Ruby version: 3.1.2 , rails 7

What do think about introduction of interceptors for instantiated services?

I thinking about preparation of PR for addition of interceptors. It would be interested to know how likely PR with this kind of feature could be merged.

This feature already present in different implementations of containers in Ruby and other languages.
Essentially this is a wrapper around instance of service added before returning the service to a client.

Interceptors are great way to implement service instantiation tracers, loggers, exception handlers, authorization checks and many other auxiliary functionality which correspond the instance.

Simple resolver customization causing errors

Using the current code and documentation, a container having a custom resolver won't be able to use #keys, #each, #each_key or #key?, with the error that a Proc doesn't have those methods.

# current documentation for memory
class ContainerObject
  include Dry::Container::Mixin

  configure do |config|
    config.registry = ->(container, key, item, options) { container[key] = item }
    config.resolver = ->(container, key) { container[key] }
  end
end

The workaround I found is by subclassing Dry::Container::Resolver like this:

class ResolverObject < Dry::Container::Resolver
  def call(container, key)
    container[key]
  end
end

class ContainerObject
  include Dry::Container::Mixin

  configure do |config|
    config.registry = ->(container, key, item, options) { container[key] = item }
    config.resolver = ResolverObject.new
  end
end

But I don't understand the benefit of having those four methods delegated to the resolver as they are methods of the container?

Relevant lines of code concerned:

Test failures

Hi,
There are test failures when tried to build on a Debian system.
Here are the logs:

┌──────────────────────────────────────────────────────────────────────────────┐
│ Run tests for ruby2.5 from debian/ruby-tests.rake                            │
└──────────────────────────────────────────────────────────────────────────────┘

RUBYLIB=. GEM_PATH=debian/ruby-dry-container/usr/share/rubygems-integration/all:/home/utkarsh/.gem/ruby/2.5.0:/var/lib/gems/2.5.0:/usr/lib/ruby/gems/2.5.0:/usr/share/rubygems-integration/2.5.0:/usr/share/rubygems-integration/all:/usr/lib/x86_64-linux-gnu/rubygems-integration/2.5.0 ruby2.5 -S rake -f debian/ruby-tests.rake
/usr/bin/ruby2.5 /usr/bin/rspec --pattern ./spec/\*\*/\*_spec.rb --format documentation

An error occurred while loading ./spec/spec_helper.rb.
Failure/Error:
  Dir[Pathname(__FILE__).dirname.join('support/**/*.rb').to_s].each do |file|
    require file
  end

NoMethodError:
  undefined method `Pathname' for main:Object
# ./spec/spec_helper.rb:102:in `<top (required)>'

An error occurred while loading ./spec/integration/container_spec.rb.
Failure/Error: it_behaves_like 'a container'

ArgumentError:
  Could not find shared examples "a container"
# ./spec/integration/container_spec.rb:5:in `block in <top (required)>'
# ./spec/integration/container_spec.rb:1:in `<top (required)>'

An error occurred while loading ./spec/integration/container_spec.rb.
Failure/Error: it_behaves_like 'a container'

ArgumentError:
  Could not find shared examples "a container"
# ./spec/integration/container_spec.rb:5:in `block in <top (required)>'
# ./spec/integration/container_spec.rb:1:in `<top (required)>'

An error occurred while loading ./spec/integration/mixin_spec.rb.
Failure/Error: it_behaves_like 'a container'

ArgumentError:
  Could not find shared examples "a container"
# ./spec/integration/mixin_spec.rb:8:in `block (2 levels) in <top (required)>'
# ./spec/integration/mixin_spec.rb:2:in `block in <top (required)>'
# ./spec/integration/mixin_spec.rb:1:in `<top (required)>'

An error occurred while loading ./spec/integration/mixin_spec.rb.
Failure/Error: it_behaves_like 'a container'

ArgumentError:
  Could not find shared examples "a container"
# ./spec/integration/mixin_spec.rb:8:in `block (2 levels) in <top (required)>'
# ./spec/integration/mixin_spec.rb:2:in `block in <top (required)>'
# ./spec/integration/mixin_spec.rb:1:in `<top (required)>'
No examples found.
No examples found.

Randomized with seed 63721

Randomized with seed 63721


Top 0 slowest examples (0 seconds, 0.0% of total time):

Finished in 0.00025 seconds (files took 0.17628 seconds to load)
0 examples, 0 failures, 3 errors occurred outside of examples

Finished in 0.00025 seconds (files took 0.17628 seconds to load)
0 examples, 0 failures, 3 errors occurred outside of examples

Randomized with seed 63721

Randomized with seed 63721


/usr/bin/ruby2.5 /usr/bin/rspec --pattern ./spec/\*\*/\*_spec.rb --format documentation failed
ERROR: Test "ruby2.5" failed. Exiting.

Stubbing non-existant container values

I ran into an problem writing a test with a stubbed container value where the container in question was empty when the test is run in isolation. This caused a hard-to-find issue where some code in dry-transaction was calling #key? on the container, and it was returning false even though a stubbed value had been registered.

Is this something that should be supported, or is the current code right to assume that only existing registered values can be stubbed?

If we allow non-registered values to be stubbed, I think we'll either need a different approach to stubbing or the stubbing API will need to be fleshed out to better support the full API of Dry::Container::Mixin.

Add ability to iterate over items registered in a namespace

I have a use-case where I have several "parser" objects registered in a namespace within a container, and I'd like to iterate over each of those objects to find the first that works.

Examples

I have a container like this:

class MyContainer
  extend Dry::Container::Mixin

  namespace :parsers do
    register(:first)    { MyParser.new }
    register(:another)  { MyOtherParser.new }
    register(:fallback) { MyFallbackParser.new }
  end

  namespace :more_things do
    # ...
  end
end

In my code, I want to try each one till I get one that works:

MyContainer
  .namespaced(:parsers) # Something like this
  .lazy
  .map { |parser| parser.call }
  .detect(&:success?)
  .presence || Failure("no valid parser found")

I can work around it by doing:

MyContainer
  .each
  .select { |k,_| k.start_with? "parsers." }
  .map(&:last)

... but that seems more cumbersome than it needs to be.

Resources

@solnic asked my to open a feature request from this convo in zulip.

Freeze a Container?

I'd like to be able to freeze a container after it is set up, so that it won't accept new keys anymore.

Right now, this works:

MyContainer._container.freeze
MyContainer.register("test", "bail")

FrozenError (can't modify frozen Concurrent::Hash)

But as the comment says, _container is pretty private.

Is the above usage ok, or should the be a new feature?

Strings and Symbols

I believe it would make sense to cast keys into strings

Right now the following happens if you switch between Symbols and Strings:

container = Dry::Container.new
container.register(:foo, 'bar')
container[:foo]
# => "bar"
container['foo']
# => Dry::Container::Error: Nothing registered with the key "foo"

The confusion appears when you start to namespace things, for example:

container = Dry::Container.new
container.namespace(:test) do
  register(:foo, 'bar')
end

# Now it's a string
container['test.foo']
# => "bar"

I think it would be clearer if things got stored as strings by default.
If you think that's better I can push a PR with the change.

Allow overwriting of component keys in container

I have a dry system container that has dependencies organized by namespace. I also want to create keys for arrays of component types i can operate on with an enum. What would be the best way of doing this? Each dependency has a method like below so I can call the method in an aggregate way.

class Foo::Bar
def self.register!
        Container.register('foo.bar', Foo::Bar)
        bars = (Container['foo.bars'] rescue []) << Foo::Bar
        Container.register('foo.bars', bars)
end
end

class Foo::Bar2
def self.register!
        Container.register('foo.bar2', Foo::Bar2)
        bars = (Container['foo.bars'] rescue []) << Foo::Bar2
        Container.register('foo.bars', bars)
end
end

..

Foo::Bar.register!
Foo::Bar2.register!
Container.finalize!

OPTION 1 Container['foo.bars'] #should equal [Foo::Bar, Foo::Bar2]
OPTION 2 Container['foo'] #should equal [Foo::Bar, Foo::Bar2]

What would be a good way of achieving this in a dry fashion? Is this currently possible?

Cannot install 0.8.0 in JRuby

Describe the bug

When attempting to install dry-container 0.8.0 on JRuby, I'm consistently getting an error:

ERROR:  Error installing dry-container:
	dry-container-0.8.0 requires Ruby version >= 2.6.0. The current ruby version is 2.5.7.0.

dry-core, which has similar code in the gemspec, installs fine.

To Reproduce

  1. Install JRuby
  2. Try to install dry-container with jruby -S gem install dry-container --version 0.8.0

Expected behavior

dry-container 0.8.0 is installed

My environment

I checked it with JRuby versions 9.2.16.0 and 9.2.17.0 on Linux and Mac. Tried gem update --system but did not help.

The only thing I see is that in dry-container rubygems page required ruby version on the right is >= 2.6.0 while with dry-core it says >= 2.5.0. So maybe a wrong file was pushed to RubyGems somehow?

Getting an `ArgumentError` missing keywords on Ruby 3

Describe the bug

I have been using dry-container & dry-auto_inject since use ruby 2.3, everything works well until ruby 2.7, when I try upgrade to ruby 3.0 I'm getting an ArgumentError missing keywords.

ArgumentError (wrong number of arguments (given 1, expected 0; required keywords: status_message, data, meta)):
  
app/services/version1/list_result.rb:7:in `initialize'

To Reproduce

Ruby <= 2.7 & Rails <= 6.1.x ✅
Ruby 3.0.1 & Rails 6.1.x ❌

class ListResult
   attr_reader :status,
                    :status_message,
                    :data,
                    :meta

        SUCCESS = 1

        def initialize(status_message:, data:, meta:)
            @status = SUCCESS
            @status_message = status_message
            @data = data
            @meta = meta
        end
end

try to Register

require 'dry-container'
require 'dry-auto_inject'

module MyClass
    class DiContainer
        extend Dry::Container::Mixin
        
       register :list_result do
            ListResult
        end
    end
    
    # dependency injection
    INJECT = Dry::AutoInject(MyClass::DiContainer)
end

Try inject to the service

require 'my_class/di_container'
module Version1
   class MyService
       include MyClass::INJECT[:list_result]
       
      def get_data
            list_result.new(status_message: '', data: [], meta: {})
      end
   end
end

Expected behavior

Manual call without DI

ListResult.new(status_message: '', data: [], meta: {})
=> #<ListResult:0x00007f8d40a70970 @data=[], @meta={}, @status=1, @status_message="">

My environment

  • Affects my production application: YES (not tested yet but I think it will be impact to production due to development issue)
  • Ruby version: 3.0.1
  • Dry Container : 0.8.0
  • OS: Mojave 10.14.6

Support for mocking

I think it would be useful to have a public API for mocking. Currently I mutate internal _container object in a couple special places in my test suite where there's really no other way than to mock a dependency via container.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.