Git Product home page Git Product logo

dry-system's Introduction

dry-system Gem Version CI Status

Links

Supported Ruby versions

This library officially supports the following Ruby versions:

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

License

See LICENSE file.

dry-system's People

Contributors

actions-user avatar alassek avatar amhol avatar arielvalentin avatar blelump avatar cgeorgii avatar cllns avatar davydovanton avatar deepj avatar dry-bot avatar esparta avatar flash-gordon avatar fxn avatar gustavocaso avatar ianks avatar mensfeld avatar mpokrywka avatar oeoeaio avatar olleolleolle avatar pusewicz avatar rickenharp avatar rx avatar sagmor avatar skryukov avatar solnic avatar szajbus avatar timriley avatar v-kolesnikov avatar wafcio avatar wuarmin 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

dry-system's Issues

Can't use auto-registration and stubs in test mode

I have setup my container to auto register everything under my lib/foo folder which, if I understood correctly, is triggered when calling finalize! method on the container.

However, calling the finalize! method does not allow me to stub anymore.
How do you, auto register classes but also allows for stubs in test mode?

Relevant code:

module Foo
  class Container < Dry::System::Container
    configure do |config|
      config.name              = :foo
      config.root              = Pathname(File.dirname(__FILE__)).join('../../')
      config.default_namespace = 'foo'
      config.auto_register     = %w[lib/foo]
    end

    load_paths!('lib')
  end

  Container.finalize!
end

In my spec_helper.rb:

require 'dry/system/stubs'
require 'foo'

RSpec.configure do |config|
  config.before :suite do
    Foo::Container.enable_stubs!
    Foo::Container.stub :repo, Foo::SomeRepo.new
  end
end

Outputs:

An error occurred in a `before(:suite)` hook.
Failure/Error: Foo::Container.enable_stubs!

RuntimeError:
  can't modify frozen Class
# /Users/gottfrois/.rvm/gems/ruby-2.4.2/gems/dry-container-0.6.0/lib/dry/container/stub.rb:44:in `extend_object'
# /Users/gottfrois/.rvm/gems/ruby-2.4.2/gems/dry-container-0.6.0/lib/dry/container/stub.rb:44:in `extend'
# /Users/gottfrois/.rvm/gems/ruby-2.4.2/gems/dry-container-0.6.0/lib/dry/container/stub.rb:44:in `enable_stubs!'
# /Users/gottfrois/.rvm/gems/ruby-2.4.2/gems/dry-system-0.9.1/lib/dry/system/stubs.rb:27:in `enable_stubs!'
# ./spec/spec_helper.rb:14:in `block (2 levels) in <top (required)>'

Allow to have optional settings

The following code seems to not be working as expected:

Dry::System.register_component(:foo, provider: :core) do
  settings do
    key :foo, Types::Strict::String
    key :bar, Types::Strict::String.optional
  end
end

Then inside a container:

boot :foo, from: :core do
  configure do |config|
    config.foo = "..."
  end
end

When loading my app, I get:

Dry::Struct::Error: [Configuration.new] :bar is missing in Hash input

[discussion] Find a way to have component registered even though it was previously required

Hi!

I've recently entered an issue of which it seems some changes need to be applied here in order to get it to work. I mean the possiblity to register component even though it was required somewhere else. Consider inheritance as an example – either base class has to be registered first so the order of registering components must be explicit or an extra require statement is needed in the subclass so the parent constant is known. Although Kernel.require(component.path) && yield is very clever I suppose it may be inconsistent due to the guard statement present at the method beggining – in essence, if the component exists, go away. Actually the block shouldn't ever be performed if key associated to given component already exists. However, I may not be familiar with all of the use cases, but simple change Kernel.require(component.path); yield vastly changes the whole registration process.

From what I've found, the yield concerns only use cases when component has to be registered, e.g. here and here, however in this case guard forbids registration if a component already exists so we're covered, or at least that's my way of thinking.

The current container apporach prefers aggregation, whereas in certain cases inheritance might be a solution as well.

What do you guys think about it ?

ps. I ran Kernel.require(component.path); yield in my local environment and didn't get into any troubles. The dry-system specs have passed as well.

Can not set a false boolean value in settings

When setting a boolean value to false in settings area
image
That setting should be set to false.
image

It seems that the current setting can not be set as false, instead I get an error.
image

A clear and concise description of what the bug is.

Just set a setting as boolean with the value false.

Also found the problem:
image

It seems that value false is considered as a non-value and it is assigned with Undefined
https://github.com/dry-rb/dry-system/blob/master/lib/dry/system/settings.rb

Provide detailed steps to reproduce, an executable script would be best.

I would expect that the current setting should set as false and not throw errors.

A clear and concise description of what you expected to happen.

Your environment

  • Affects my production application: YES
  • Ruby version: 2.4.2
  • OS: Ubuntu 20.04

Removing namespace from component identifier broken in some cases

When you have an identifier which has parts that include the namespace, you'll end up with a broken component identifier. Here's a simple example:

> Dry::System::Component.new(:mailer, namespace: "mail", separator: ".")
=> #<Dry::System::Component identifier="r" path="mailer">

this is supposed to behave like this:

> Dry::System::Component.new(:mailer, namespace: "mail", separator: ".")
=> #<Dry::System::Component identifier="mailer" path="mail/mailer">

Misspelled plugin name causes confusing exception

Describe the bug

When a plugin name is misspelled, you'll end up with a confusing exception.

To Reproduce

class MyContainer < Dry::System::Container
  use :roargh
end

Results in:

NoMethodError:
  undefined method `load_dependencies' for nil:NilClass
# .gem/ruby/2.4.1/gems/dry-system-0.12.0/lib/dry/system/plugins.rb:86:in `use'

Expected behavior

A meaningful error should be raised saying "Plugin :roargh does not exist"

In tests cannot resolve anything from container

In my spec_helper.rb I require my boot.rb and I finalize the app:

require File.expand_path("../../boot", __FILE__)
require "dry/container/stub"
Application.enable_stubs!
Application.finalize!

In a test I keep getting: RuntimeError: can't modify frozen #<Class:Application>:

require 'spec_helper'

describe :sandbox_authorization do
  it 'should fail' do
    result = Application['operations.sandbox_authorization'].call true
    expect(result).to be(false)
  end
end

Should I not finalize! the app? How do I get all my app for testing then? I thought about booting but I don't know how I'd mix booting with auto registered folders. If I have to put constantly use to load all other parts of the system I don't understand what's the use. I think I'm missing how to use this gem and had to remove it for a while because I literally was unable to make testing work with either Rspec or Minitest :( Can you let me knkow what I'm missing or if there's a bug somewhere or both?

Why are duplicate keys disallowed?

@solnic just wondering what your rationale was for this behavior?

    it 'raises when identifier/path has duplicated keys' do
      expect { Dry::System::Component.new('foo.bar.foo', namespace: 'foo') }
        .to raise_error(Dry::System::InvalidComponentError, /foo\.bar\.foo/)
    end

I came across this today when I had what felt like a reasonable component within one of my systems: BulkData::Parsers::BulkData. I've renamed it to work around the issue but it'd be good to understand what the rationale/technical limitation is here. Whatever we end up doing, we could document the outcome :)

Setting from multi-line ENV value

Dry-System settings component can't fetch multi-line value from .env file.

Gem dotenv supports it: https://github.com/bkeepers/dotenv#multi-line-values, so we can implement the same functionality.

I even think further - plugins (such settings) should be more external and maybe are not a part of the core of dry-system because it much harder to support stable core and plugins in the same time.

Couldn't use `Import` module in my codebase

I have Import = Container.injector in my import.rb. And when I tried to create my own namespace with Import module, I've got an error Import is not a module

module Import
  module Parsers
    class SpeakersParser < Operation
      #...
    end
  end
end

Is it any possible solutions not to rename one of Imports?

Add Container#shutdown

As mentioned in the issue #90

The lifecycle process is missing the stop method. It has been addressed in the PR #91

@solnic mentioned also It would be interesting to have a shutdown method that goes through all booted components and stop them.

Move boot-related methods out of `Component`

As noted in #44:

I noticed a few structural things I'd like to improve later. I'm not sure if e.g. Component#bootable? and #boot_file? should exist - it feels like that's the component knowing too much about other parts of the system.

Rename Container.require to something else

Container.require having the same name as Kernel.require means awkwardness requiring from the load path inside bootable components, e.g.

MyApp::Container.boot :settings, from: :system do
  before(:init) do
    # TODO: we need to rename `Container.require` to something else
    ::Kernel.require "types"
  end

  settings do
    # Declare settings here using `Types` module (required above)
  end
end

Having to do ::Kernel.require inside the bootable component DSL is surprising and will likely cause people issues. And in general, require is so widely used in Ruby that we shouldn't try and offer some specialised behaviour behind the same name.

Move config to dry-web

Right now, dry-component’s config is a little under-baked, suffering from a few different things:

  • The actual .yml files must exist on the filesystem before config keys can exist, even if config values are available in ENV since the .yml files act as the canonical list of the available keys. This causes problems when making 12-factor-style deployments, since we have to keep this config file checked in, which means it makes it awkward to use secret config values during development.
  • Config files are looked up relative to the component’s root, and in a typical app where multiple components are installed, this means config files are spread around the filesystem, which is unexpected
  • Config files being named after the component’s name also makes them harder to find
  • And, biggest of all, in a typical dry-web app, only the config for the top-level component is really used. This means we have a ton of sub-components scattered around the place with never-used config systems.

I do recognise that the config system in its current state wasn’t intended to be the final solution :) However, I think because of the issues above it may be a point of confusion for people coming to build a dry-web app.

I want to propose an interim step while we develop a more complete config system:

Remove config from dry-component and move it to dry-web.

This would mean that a dry-web app would have a single point of configuration, which would be accessible from any dry-component used within it. We’d make this available on Dry::Web::Container, and I think it’d be smart to make the config class configurable just like @AMHOL’s done with the dry-component loader. That way we could provide a very simple default config class, and allow people to replace it with their own (e.g. in our apps at Icelab we’re using a config class where the keys are explicitly specified and validated for appropriate values).

With this in place, I think a dry-web app will be easier to understand, and then we can more gradually work towards an per-component config approach. There are a number of things that I think would be helpful there, like a component specifying what config keys it requires, and then those keys being plucked out of a top-level config object that is passed to the component when it is booted. But these are the things that’d take a little experimentation to get right, which is why I think it’d be good to settle on a reasonable interim arrangement :)

What do you think, @solnic, @AMHOL? This is one of the things I'd love to straighten out a little before RDRC.

class inheritance

Is class inheritance intentionally discouraged with dry-system? Like, instead shared behavior should be Imported by each individual child class?

Consider alternative default namespace approaches

In implementing this feature and having to turn around the quick-fire fix in #22, I realised there might be some better ways to do this.

I was thinking that it might be better for the default namespace handling to be done on the resolution side rather than the registration side. dry-component could have its own resolver that is default namespace aware, so it would know to convert "foo" to "main.foo" if "main" was the default namespace and there was a matching registration.

For a convenience, this approach seems a lot cleaner and more efficient than getting involved in the actual file loading process. It also means all the container registrations remain "canonical" and fully matching their corresponding file paths, etc.

I was also thinking that it might be good if we can make it explicit when a user intends to access a container registration as in the shorthand format (i.e. with the default namespace removed):

# the leading `.` here implies the shorthand format
include Admin::Import[".users.repository"]

In this case, if Admin::Import is configured with "admin" as the default namespace, then it would know 100% that ".users.repository" should be converted to "admin.users.repository" and looked up accordingly.

What do you think? This feels like it would remove any possible ambiguity, which'd be a good thing.

Component path brokes when set a namespace that is not included in the identifier

When setting a component with a namespace that is not present in the identifier the path get mess up

# Wrong
Dry::System::Component.new(:mailer, namespace: "mail")
#<Dry::System::Component identifier="mailer" path="mailer">

# Right
Dry::System::Component.new("mail/mailer", namespace: "mail")
#<Dry::System::Component identifier="mailer" path="mail/mailer">

# Right
 Dry::System::Component.new("mail.mailer", namespace: "mail")
#<Dry::System::Component identifier="mailer" path="mail/mailer">

Auto register and already required files

Hi all ✌️

We've encountered some unexpected behaviour with auto register.

Let's say I have Services folder with 2 files:

#1.rb
class One
end
#2.rb
require './1.rb'
class Two
end

Both files are inside of some auto register folder. And in this case load order matters, cause if 2.rb is auto registered (and so required) first, 1.rb will not be registered at all because Kernel.require(component.path) will return false. Preventing this code from registering component.

So, maybe Kernel.require(component.path) && yield should be replaced with:

Kernel.require(component.path)
yield

Otherwise why it is done with &&?

Thanks for help

`require_component': could not resolve require file

Container Definition:

# frozen_string_literal: true
require 'dry/component/container'

module Dependencies
  # The Ioc Container for the app
  class Container < Dry::Component::Container

    configure do |config|
      config.root = Pathname.new('./alertbus')
    end
  end
end

Dependency Declaration:

# frozen_string_literal: true
require 'dry/component/container'
require './app/services/fae_email_notifier'
require './config/initializers/dependencies/container'

module Dependencies
  #register app_config variables
  Dependencies::Container.register(
    :dashboard_domain, -> { APP_CONFIG[:dashboard_url] })
  Dependencies::Container.register(
    :mailer_domain, -> { APP_CONFIG[:mailer_domain] })

  #register services
  Dependencies::Container.register(
    'services.fae_email_notifier',
    Services::FaeEmailNotifier.new(
    dashboard_domain: 'test',
    mailer_domain: 'test')
  )

  Application_Container = Dependencies::Container::Inject
end

Injection in use:

# frozen_string_literal: true
# This workers converts transcodes zips and notifies personnel about FAE
# video requests that have been completed.
module Services
class FaeVideoRequestWorker < BaseWorker
  include Dependencies::Application_Container['services.fae_email_notifier']

Error:

container.rb:163:in `require_component': could not resolve require file for services.fae_email_notifier

So i had this rather simple bit of code working before the latest version bump when import_module was removed from DryContainer and replaced with the Inject method. Notice how its really not much more complex than the dry-component example docs on the dry-component webpage. Now im getting errors when running the test for the associated worker code which is the only thing using dry atm. Want to add that the dry component documentation is now wrong on http://dry-rb.org/gems/dry-component. I think what dry is lacking right now is detailed documentation on how to get it set up on applications in practice. Which makes it rather difficult to use especially when the api is changing heavily and use documentation is not kept up to date.

cannot load such file -- dry/events/publisher (LoadError)

dry-events seems to be required but not listed in the gemspec of the gem resulting in the following exception when booting my app:

/Users/gottfrois/.rvm/gems/ruby-2.4.2/gems/activesupport-4.2.10/lib/active_support/dependencies.rb:274:in `require': cannot load such file -- dry/events/publisher (LoadError)
	from /Users/gottfrois/.rvm/gems/ruby-2.4.2/gems/activesupport-4.2.10/lib/active_support/dependencies.rb:274:in `block in require'
	from /Users/gottfrois/.rvm/gems/ruby-2.4.2/gems/activesupport-4.2.10/lib/active_support/dependencies.rb:240:in `load_dependency'
	from /Users/gottfrois/.rvm/gems/ruby-2.4.2/gems/activesupport-4.2.10/lib/active_support/dependencies.rb:274:in `require'
	from /Users/gottfrois/.rvm/gems/ruby-2.4.2/gems/dry-system-0.9.0/lib/dry/system/plugins/monitoring.rb:3:in `<top (required)>'

on require 'dry/system/container' using 0.9.0

Generate dependency graph for all objects in application container

I created a simple proof of concept for generation graph of all dependencies in container: link to gist file.

For this I used "custom" auto_inject register for creating included_keys class method with all dependencies.

After that I detect all classes with this method and generate "AST" with associations between classes.

With this changes we can detect unused dependencies in container, build GUI graph for easily understand system (like UML diagrams) and ect.

We can start build something like this for dry-system, WDYT? I can create a simple plan with small steps for this feature if you want.

Cannot use logging plugin's logger component in a dependency boot file

Describe the bug

I am not able to access the logger provided by the logging plugin from a dependency boot file. It seems that bootable dependencies are only able to use other bootable dependencies, perhaps?

To Reproduce

Use the logging plugin:

# system/container.rb

class Application < Dry::System::Container
  use :logging

  # ...
end

And try to use it within a bootable dependency's start block as explained in the docs.

# system/boot/db.rb

Application.boot(:db) do |app|
  start do
    use :logger

    register(DB.new(ENV['DB_URL'], logger: app[:logger]))
  end
end

Expected behavior

The above code works -- the logger can be used from the boot file and provided to the bootable dependency.

Actual behaviour

The system fails to boot, with the following error message:

Dry::System::InvalidComponentIdentifierError: component identifier +logger+ is invalid or boot file is missing
  /usr/local/bundle/gems/dry-system-0.17.0/lib/dry/system/booter/component_registry.rb:30:in `[]'
  /usr/local/bundle/gems/dry-system-0.17.0/lib/dry/system/booter.rb:142:in `with_component'
  /usr/local/bundle/gems/dry-system-0.17.0/lib/dry/system/booter.rb:95:in `start'
  /usr/local/bundle/gems/dry-system-0.17.0/lib/dry/system/container.rb:351:in `start'
  /usr/local/bundle/gems/dry-system-0.17.0/lib/dry/system/lifecycle.rb:91:in `block in use'
  /usr/local/bundle/gems/dry-system-0.17.0/lib/dry/system/lifecycle.rb:90:in `each'
  /usr/local/bundle/gems/dry-system-0.17.0/lib/dry/system/lifecycle.rb:90:in `use'
  /app/system/boot/db.rb:3:in `block (2 levels) in <top (required)>'
  /usr/local/bundle/gems/dry-system-0.17.0/lib/dry/system/lifecycle.rb:115:in `trigger!'
  /usr/local/bundle/gems/dry-system-0.17.0/lib/dry/system/lifecycle.rb:80:in `start'
  /usr/local/bundle/gems/dry-system-0.17.0/lib/dry/system/lifecycle.rb:52:in `block in call'
  /usr/local/bundle/gems/dry-system-0.17.0/lib/dry/system/lifecycle.rb:50:in `each'
  /usr/local/bundle/gems/dry-system-0.17.0/lib/dry/system/lifecycle.rb:50:in `call'
  /usr/local/bundle/gems/dry-system-0.17.0/lib/dry/system/components/bootable.rb:99:in `start'
  /usr/local/bundle/gems/dry-system-0.17.0/lib/dry/system/booter.rb:99:in `block (2 levels) in start'
  /usr/local/bundle/gems/dry-system-0.17.0/lib/dry/system/booter.rb:86:in `block (2 levels) in init'
  /usr/local/bundle/gems/dry-system-0.17.0/lib/dry/system/booter.rb:125:in `block in call'
  /usr/local/bundle/gems/dry-system-0.17.0/lib/dry/system/booter.rb:149:in `with_component'
  /usr/local/bundle/gems/dry-system-0.17.0/lib/dry/system/booter.rb:122:in `call'
  /usr/local/bundle/gems/dry-system-0.17.0/lib/dry/system/booter.rb:84:in `block in init'
  /usr/local/bundle/gems/dry-system-0.17.0/lib/dry/system/booter.rb:149:in `with_component'
  /usr/local/bundle/gems/dry-system-0.17.0/lib/dry/system/booter.rb:83:in `init'
  /usr/local/bundle/gems/dry-system-0.17.0/lib/dry/system/booter.rb:98:in `block in start'
  /usr/local/bundle/gems/dry-system-0.17.0/lib/dry/system/booter.rb:149:in `with_component'
  /usr/local/bundle/gems/dry-system-0.17.0/lib/dry/system/booter.rb:95:in `start'
  /usr/local/bundle/gems/dry-system-0.17.0/lib/dry/system/booter.rb:66:in `block in finalize!'
  /usr/local/bundle/gems/dry-system-0.17.0/lib/dry/system/booter/component_registry.rb:16:in `each'
  /usr/local/bundle/gems/dry-system-0.17.0/lib/dry/system/booter/component_registry.rb:16:in `each'
  /usr/local/bundle/gems/dry-system-0.17.0/lib/dry/system/booter.rb:65:in `finalize!'
  /usr/local/bundle/gems/dry-system-0.17.0/lib/dry/system/container.rb:328:in `finalize!'

Your environment

  • Affects my production application: No
  • Ruby version: 2.7.1
  • OS: macOS Catalina

Gem load is failing with `NoMethodError: undefined method `constructor_type' for Dry::System::Settings::Configuration:Class`

I was reading the doc and wanted to play with this gem from console, installed the gem, then fired up a pry console, but the gem load from the console is failing with NoMethodError: undefined method constructor_type for Dry::System::Settings::Configuration:Class

Console output:

[1] pry(main)> require 'dry-struct'
=> true
[2] pry(main)> Dry::Types
=> Dry::Types
[3] pry(main)> require 'dry-core'
=> true
[4] pry(main)> require 'dry-system'
NoMethodError: undefined method `constructor_type' for Dry::System::Settings::Configuration:Class
Did you mean?  constructor
from /Users/USERNAME/.rbenv/versions/2.4.3/lib/ruby/gems/2.4.0/gems/dry-system-0.9.2/lib/dry/system/settings.rb:35:in `<class:Configuration>'
[5] pry(main)> require 'rubygems'
=> false
[6] pry(main)> p Gem::Specification.select { |gem| gem.name == 'dry-system' }.map { |gem| [gem.name, gem.version.to_s] }.flatten
["dry-system", "0.9.2"]
=> ["dry-system", "0.9.2"]

I definitely have the gem installed, other gems from dry-rb are loading properly. I am using this version 2.4.3p205 (2017-12-14 revision 61247) [x86_64-darwin16] of ruby.

Any pointer on what I can do to debug further will be great.

Thanks in advance.

Root has to be a pathname not a String as indicated in the example

I get dry-component-0.0.1/lib/dry/component/config.rb:9:inload': undefined method join' for "app":String (NoMethodError) when I try the example code:

class Application < Dry::Component::Container
  configure do |config|
    config.root = 'app'

    # we set 'lib' relative to `root` as a path which contains class definitions
    # that can be auto-registered
    config.auto_register = 'lib'
  end

  # this alters $LOAD_PATH hence the `!`
  load_paths!('lib')
end

I made the app string a pathname config.root = Pathname.new('app') and it works. Clearly it expect a pathname or assumes it's being instantiated elsewhere. Please let me know if I'm missing something or it's a real bug.

dry-system 0.16.0 not compatible with dry-configurable 0.11.1

Describe the bug

The specs for v0.16.0 this library do not pass when using dry-configurable 0.11.1, 11 examples fail with the following message:

Failure/Error: config.settings << _settings[name]

NoMethodError:
  undefined method `settings' for #<Dry::Configurable::Config:0x00007fc757259f38>
  Did you mean?  _settings

To Reproduce

Clone this repository, check out v0.16.0 (or current master), run bundler, run specs.

Expected behavior

The specs for this library should pass.

Possible solution

diff --git a/lib/dry/system/container.rb b/lib/dry/system/container.rb
index cb99fd3..3d72da2 100644
--- a/lib/dry/system/container.rb
+++ b/lib/dry/system/container.rb
@@ -104,7 +104,7 @@ module Dry
         # @api public
         def setting(name, *args, &block)
           super(name, *args, &block)
-          config.settings << _settings[name]
+          config._settings << _settings[name]
           self
         end

Your environment

  • Affects my production application: NO, have not upgraded to dry-configurable 0.11.1
  • Ruby version: 2.6.5
  • OS: MacOS

How `stop` callback is invoked?

Consider following example from documentation

Application.finalize(:persistence) do |container|
  start do
    require 'sequel'
    container.register('persistence.db', Sequel.connect(ENV['DB_URL']))
  end

  stop do
    db.disconnect
  end
end

When stop callback (which calls db.disconnect) will be called? Is there a method to stop all components at once (like finalize! which starts all components)

This case is not covered in documentation...

Example apps

Been trying to implement this in a rack app running Grape::API without much luck. Been copying the structure of the rodakase and lotuskase examples, but getting a bunch of errors. Maybe because the usage guide on this page suggests another folder structure than what is used in those examples (f.e the container is defined in a subdirectory other than boot in rodakase but not on this page).
Are there any plans for an example app that is using dry-component instead of Rodakase::Container?

Improve clarity of the error thrown by Dry::System::Loader#constant when a constant is not found

Hello!

I want to suggest a minor but meaningful improvement to the clarity of the error that gets raised when Dry::System::Loader cannot load a constant.

Disclaimer: I am a dry newbie so apologies in advance if what I'm proposing doesn't make sense in the broader scheme of dry-system.

As a developer
When I create a file that will be loaded through Dry::System
And the file I created has some typo in the name of the constant (or the path)
I expect Dry::System to point me directly to the file that cannot be loaded

Examples

Let's say I create a file that is meant to be injected somewhere else and I have configured my app to auto-register files living on lib. However, when I create the file, I make a typo on the constant name (or on the name of any directory on the path to the file).

# lib/admin/articles/commands/create_article.rb
module Admin
  module Articles
    module Command # <= I missed an "s"
      class CreateArticle
         # ...
      end
    end
  end
end

When I load my application, I get a NameError that comes from dry-inflector.

3: from .../gems/dry-system-0.17.0/lib/dry/system/loader.rb:52:in `call'
2: from .../gems/dry-system-0.17.0/lib/dry/system/loader.rb:67:in `constant'
1: from .../gems/dry-inflector-0.2.0/lib/dry/inflector.rb:90:in `constantize'
.../gems/dry-inflector-0.2.0/lib/dry/inflector.rb:90:in `const_get': uninitialized constant Admin::Articles::Commands (NameError)

However, the NameError doesn't directly point me to the source of the problem. Instead, I get pointed to Admin::Articles::Commands; but I may have many files under that namespace.

Proposed Solution

Doing something like this in Dry::System::Loader#constant, can make the error reporting better:

def constant
  stringified_const_name = inflector.camelize(path)
  inflector.constantize(stringified_const_name)
rescue NameError
  raise NameError, "uninitialized constant #{stringified_const_name}"
end
gems/dry-system-0.17.0/lib/dry/system/loader.rb:69:in `rescue in constant': uninitialized constant Admin::Articles::Commands::CreateArticle (NameError)

Extra Notes

  • I don't know if this should be implemented here or in dry-inflector#constantize, but I think it makes more sense in the context of dry-system.

cc @timriley

Support conditional booting of providers

I found the interesting case for improvement.

In my hanami app I have server and rake "instances". For rake I need to use aws dependency but in server booting, I don't need it. In this case, it will be better for me to load aws only for rake env.

In hanami I can use Hanami.app?(:rake) (docs). And it looks like this:

if Hanami.app?(:rake)
  Container.boot :rake_test do |container|
    start do
      container.register :rake_test, Object.new
    end
  end
end

Also, I updated rakefile:

ENV['HANAMI_APPS'] = 'rake'

So, what I suggest. Create a optional argument for booting dependency, something like this:

Container.boot(:rake_test, condition: -> { Hanami.app?(:rake) }) do |container|
  start do
    container.register :rake_test, Object.new
  end
end

And in this case, dry-system will boot dependency only with true in condition. Also, it can be related to #73 but I'm not sure.

WDYT?

cc @solnic @timriley @GustavoCaso

Question: Why default to register a instance and not the class?

I was wondering why you do this. Maybe there's a reason in how Rodakase originally used the container, but I'm not sure. I'm trying to understand the intention a little better to avoid suggesting something against what you wanted to achieve.

Besides the default instantiation in the Loader, I don't know why you used the load path instead of using full paths on requires to avoid collisions. If I have many containers this is a real risk, but I don't know if you only intended for one component container per app instead of many inside the same body of code. Lotus-container uses a namespace for the container by using a module.

Could you briefly clarify what the intention was? Maybe it's just a small working library you extracted from Rodakase and hadn't considered that, however probably it's the other way around and you wanted it that way for a reason. By knowing this better I can probably contribute or provide better feedback :)

Plugins raise a confusing exception when an external dep is missing

Current

irb(main):005:0> Dry::System::Plugins.registry[:monitoring].load_dependencies
Traceback (most recent call last):
        7: from bin/console:23:in `<main>'
        6: from <internal:prelude>:145:in `irb'
        5: from (irb):5:in `<main>'
        4: from .../dry-system-0.13.0/lib/dry/system/plugins.rb:32:in `load_dependencies'
        3: from .../dry-system-0.13.0/lib/dry/system/plugins.rb:32:in `each'
        2: from .../dry-system-0.13.0/lib/dry/system/plugins.rb:33:in `block in load_dependencies'
        1: from .../dry-system-0.13.0/lib/dry/system/plugins.rb:37:in `rescue in block in load_dependencies'
Dry::System::PluginDependencyMissing (dry-system plugin :monitoring failed to load its dependencies: cannot load such file -- dry/events/publisher)

Expected

irb(main):005:0> Dry::System::Plugins.registry[:monitoring].load_dependencies
Traceback (most recent call last):
        7: from bin/console:23:in `<main>'
        6: from <internal:prelude>:145:in `irb'
        5: from (irb):5:in `<main>'
        4: from .../dry-system-0.13.0/lib/dry/system/plugins.rb:32:in `load_dependencies'
        3: from .../dry-system-0.13.0/lib/dry/system/plugins.rb:32:in `each'
        2: from .../dry-system-0.13.0/lib/dry/system/plugins.rb:33:in `block in load_dependencies'
        1: from .../dry-system-0.13.0/lib/dry/system/plugins.rb:37:in `rescue in block in load_dependencies'
Dry::System::PluginDependencyMissing (dry-system plugin :monitoring failed to load its dependencies: cannot load such file -- dry/events/publisher - add dry-events to your Gemfile)

Default environment variable values aren't honored

I have a script that says, in part:

class Application < Dry::System::Container
  use(:env)
  boot(:settings, from: :system) do
    settings do
      key :an_int, Types::Coercible::Int.default(17)
    end
  end
end

If I run this script and set an environment variable AN_INT, then Application[:settings].an_int gets set to the value of the variable. If I don't set the variable then I get an error:

TypeError: can't convert nil into Integer
                  Integer at org/jruby/RubyKernel.java:434
                     call at org/jruby/RubyMethod.java:115
                     call at .../gems/dry-types-0.12.2/lib/dry/types/constructor.rb:47
          block in coerce at .../gems/dry-types-0.12.2/lib/dry/types/hash/schema.rb:99
(...)

I expect the script to work without reporting an error, and for Application[:settings].an_int to get its default value of 17.

https://gist.github.com/dmaze/308034415354fb0ec2e537157b3a39b8 has a more complete reproduction script.

Missing and incorrect environment variables give opaque errors

I have a sample script that says, in part,

class Application < Dry::System::Container
  use(:env)
  boot(:settings, from: :system) do
    settings do
      key :an_int, Types::Coercible::Int.default(17)
    end
  end
end

If I run it like

AN_INT=20 ./dry-rb-env.rb

then it picks up and parses the environment variable correctly. But if I use a non-integer value

AN_INT=foo ./dry-rb-env.rb

I get a very long backtrace, that doesn't mention the variable name at all:

ArgumentError: invalid value for Integer(): "foo"
                  Integer at org/jruby/RubyKernel.java:434
                     call at org/jruby/RubyMethod.java:115
                     call at .../gems/dry-types-0.12.2/lib/dry/types/constructor.rb:47
          block in coerce at .../gems/dry-types-0.12.2/lib/dry/types/hash/schema.rb:99

Similarly, if I make the setting type not have a .default() and don't set the environment variable, I get a

TypeError: can't convert nil into Integer

Since this is probably end-user error and not developer error, it would be nice if:

  1. A missing or incorrect environment variable didn't generate a backtrace
  2. These error messages included the variable name

A more complete reproduction script is at https://gist.github.com/dmaze/308034415354fb0ec2e537157b3a39b8.

Share an example within a gem?

Hey guys,

I'm having a hard time configuring dry-system within a new gem context. The 2 examples provided in this repo are pretty close but they lack the fact that when creating a new gem, the lib folder look something like this:

lib
├── foo
│   └── version.rb
└── foo.rb

Where we can then namespace services objects using dry-transactions within a Transactions module:

lib
├── foo
│   ├── transactions
│   │   └── do_something.rb
│   └── version.rb
└── foo.rb

But i'm confused on hot to setup the configure hook of dry-system. This is what I have so far:

# system/container.rb
module Foo
  class Container < Dry::System::Container
    configure do |config|
      config.root = Pathname(File.dirname(__FILE__)).join('../../')
      config.auto_register = %w(lib/foo/transactions)
    end

    load_paths!('lib/foo')
  end
end

but when testing in console with:

Foo::Container['transactions.do_something']
NameError: uninitialized constant Transactions::DoSomething

It's missing the gem namespace Foo.
Any idea what i'm missing?

Would be nice to include in this repo a full example using a gem as the file structure, what do you think?

Turn `Import` into a code-loader

Right now we rely on require to load files, this is honestly good-enough for me, but I know people are complaining about deeply nested dir structures ie apps/admin/lib/admin which just looks odd to somebody who's not used to relying on require + LOAD_PATHs. Another issue is that you don't have app's namespace available so each file must define it, which means nesting instead of class Admin::Something.

To improve this, it seems like using import modules to require files and set up namespaces would be a good idea. I think something like this should be possible:

# apps/admin/system/import.rb
require_relative 'container'

module Admin
  Import = Container.injector do |c|
    c.auto_require = true
    c.auto_namespace = true
  end
end

Then, you can have this:

# apps/admin/lib/products/operations/create.rb
require 'admin/import'

# namespaces are created on-the-fly so they are already available
class Admin::Products::Operations::Create
  # this will be translated to:
  #   require "#{Container.root}/lib/product_repo"
  include Admin::Import["product_repo"]  
end

Auto-namespaces might be a little too much though, as scanning dirs and defining constants can slow things down, so it's something to experiment with. Auto-requires should be very simple to do though, and it will simplify dir structure so that's gonna be a win for many folks.

WDYT @timriley?

Using bare `register` with block arg inside bootable component (e.g. with `namespace: true`) does not result in a block-style registration on the top-level container

e.g.

MyApp::Container.boot :models, namespace: true do |system|
  start do
    register "foo" do
      puts "Resolving foo!"
      Object.new
    end
  end
end

When the container boots, this will print "Resolving foo!" exactly 1 time, and every resolution of "models.foo" afterwards will return the exact same object.

It appears that the magical merging of this DSL-specific container into the top-level container throws away our intention here, which is for the block to run every time the object is resolved.

Should there be extra built in loaders?

Hello.

After playing a lot with dry-system I found that some classes or objects are not to be instantiated. For example, schemas build with dry-validation are to be called with input and not instantiated. Reading through the tests I found the option for a custom loader and this is what I have now:

require 'dry/system/container'

class CallableLoader < Dry::System::Loader
  def call(*args)
    constant.respond_to?(:call) ? constant : constant.new(*args)
  end
end

class Application < Dry::System::Container
  configure do |config|
    config.name = :app
    # root will default to the PWD. Tipically one level app.
    #config.root = Pathname.pwd.join('app')
    config.auto_register = ['app/steps', 'app/operations']
    config.loader = CallableLoader
  end

  load_paths!('lib', 'app')
end

Import = Application.injector

I think this is becoming more common when the dry gems start to work together and I was wondering if this isn't something that the gem could provide as a built-in loader so that I can do something like config.loader = Dry::System::CallableLoader. Maybe there are two or three common loaders that cover most cases? I wanted to open the question because interacting with dry-validations is quite common, or also adding operations that implement self.call so they're not to be instantiated? Maybe I don't entirely understand the proper usage and there's no point in this but hopefully the discussion will be helpful. :)

Support skipping auto-registration for particular files via configuration code

In #45 (comment), @solnic, you mentioned this:

We may also consider adding a configuration option ie config.auto_registration_exclude { |dir| dir.include?('entities') } or something like that.

I'm looking to start adding this now, so it can complement the magic comments behaviour and then we can consider a new dry-system release.

However, I feel hesitant to add another setting like config.auto_registration_exclude, for a few reasons:

  1. Firstly, dry-configurable won't make it easy to have a block as the value for the setting like your example. Since it uses blocks for nested settings, we'd have to ask users to provide a proc as the value, which doesn't feel as natural
  2. I'm also wary about "configuration-creep". If we can avoid a setting at the top-level of the container, I think that'd be good.
  3. The user may not want this setting to apply to all their auto-registration paths.
  4. Lastly, and perhaps most critically, the auto-registrar class is a setting itself, and if the default auto-registrar was replaced, it may not even want to pay any attention to a setting like auto_registration_exclude.

So instead of adding another container setting, I wonder if we could reuse the .auto_register!(path, &block) method we alreay have, just extending the block so it supports auto-registration configuration for that particular path. So instead of:

auto_register!('lib') do |component|
  # some custom initialization logic
  constant = component.loader.constant
  constant.create
end

We make it this:

auto_register!('lib') do |config|
  # Both of these configuration blocks are optional

  config.instance do |component|
    # some custom initialization logic
    constant = component.loader.constant
    constant.create
  end

  config.exclude do |component|
    component.path =~ /entities/
  end
end

What do you think? Since this configuration is coupled to our default Dry::System::AutoRegistrar, it feels like a more appropriate place for it.

Does not register component named differently then it's filename

Example bellow would not work:

# in ./system/boot/hell.rb
Container.finalize :heaven do |container|
  container.register('heaven', 'string')
end

I spent several hours struggling with this issue.
If this is necessary, having a warning or error on boot would be helpful.

Invoking undefined methods does not cause exceptions.

Today, I stumbled upon some weird behaviour while trying to use dry-system. Here's the code in question:

# system/container
require "dry/system/container"

class Container < Dry::System::Container
  configure do |config|
    config.auto_register = "lib"

    load_paths!("lib")
  end
end

# system/boot/db.rb
Container.finalize(:db) do |container|

  _unexisting_method # THIS SHOULD FAIL

  use :logger
  require "sequel"

  Sequel.database_timezone = :utc
  Sequel.application_timezone = :local
  db = Sequel.connect(ENV["DB_URL"])
  db.loggers << logger

  container.register(:db, db)
end

# system/boot.rb
require_relative "container"
Container.finalize!

Result:

require_relative "system/boot"

# SUCCESS
Container["db"].tables   #=> schema_info

Even though I tried to invoke the undefined method, no exceptions were raised.

Config: core_dir and root relation

When core_dir and root folders are not related, Dry::Component fails to require file.

- config
  |- boot
    - config.rb
  boot.rb
- lib
config.root = Pathname.new('lib')
config.core_dir = '../config'

Fails to require here:
https://github.com/dry-rb/dry-component/blob/master/lib/dry/component/container.rb#L119

Though file existence checked here:
https://github.com/dry-rb/dry-component/blob/master/lib/dry/component/container.rb#L211

LoadError: cannot load such file -- config/boot/config
/usr/local/lib/ruby/gems/2.3.0/gems/dry-component-0.3.0/lib/dry/component/container.rb:119:in `require'
/usr/local/lib/ruby/gems/2.3.0/gems/dry-component-0.3.0/lib/dry/component/container.rb:119:in `block in require'
/usr/local/lib/ruby/gems/2.3.0/gems/dry-component-0.3.0/lib/dry/component/container.rb:118:in `each'
/usr/local/lib/ruby/gems/2.3.0/gems/dry-component-0.3.0/lib/dry/component/container.rb:118:in `require'
/usr/local/lib/ruby/gems/2.3.0/gems/dry-component-0.3.0/lib/dry/component/container.rb:108:in `boot'
/usr/local/lib/ruby/gems/2.3.0/gems/dry-component-0.3.0/lib/dry/component/container.rb:96:in `boot!'

So, this is a bug, or I should never use core_dir location out of root location?

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.