This library officially supports the following Ruby versions:
- MRI
>= 3.0
- jruby
>= 9.4
(not tested on CI)
See LICENSE
file.
Application framework with state management and built-in dependency injection support
Home Page: https://dry-rb.org/gems/dry-system
License: MIT License
This library officially supports the following Ruby versions:
>= 3.0
>= 9.4
(not tested on CI)See LICENSE
file.
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)>'
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
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.
When setting a boolean value to false in settings area
That setting should be set to false.
It seems that the current setting can not be set as false, instead I get an error.
A clear and concise description of what the bug is.
Just set a setting as boolean with the value false.
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
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">
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"
See hanami/hanami#1019 (comment)
It's limiting to have to rely on Dry::System::Container.configure
having been run.
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?
@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 :)
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.
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 Import
s?
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.
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.
Right now, dry-component’s config is a little under-baked, suffering from a few different things:
.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.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.
Is class inheritance intentionally discouraged with dry-system? Like, instead shared behavior should be Import
ed by each individual child class?
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.
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">
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
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.
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
dry-system uses the inflecto gem for inferring the class names from file names; according to this thread dry-inflector would be a better choice.
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.
Refs #41
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
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.
We need a convention (FS location and files named based on namespace?) to pick up "manual registration" files and require them if we can't otherwise find a component when trying to lazy load it.
See: https://discuss.dry-rb.org/t/question-about-dry-system-and-auto-register/160/2
"...I tried using a path separator in the name_space to follow it through. Looks like it can only work with a single name, as the components path gets separated into an array and then the array rejects anything matching the namespace."
I get dry-component-0.0.1/lib/dry/component/config.rb:9:in
load': 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.
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
We need a way to tell the system that it's in test mode and components might be stubbed so we can't freeze a container upon finalization.
refs #32
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...
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?
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
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.
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)
dry-inflector#constantize
, but I think it makes more sense in the context of dry-system
.cc @timriley
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?
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 :)
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)
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)
We should copy the behaviour of #resolve
on a non-finalized container:
# @api public
def resolve(key)
load_component(key) unless finalized?
super
end
And do the same for key?
. This would help unit testing of Dry::Transaction
objects (i.e. alongside a non-finalized container), which check with the container for a key?
before attempting to resolve an object from it.
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.
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:
A more complete reproduction script is at https://gist.github.com/dmaze/308034415354fb0ec2e537157b3a39b8.
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?
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?
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.
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. :)
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:
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.
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.
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.
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?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.