Thoughts on dry-auto_inject with classes/constatnts

Hi,

I am testing dry auto inject right now.
I did something like this:

Dependency = Dry::AutoInject(Class.new do
  extend Dry::Container::Mixin

  register InterfaceA do
    InterfaceAImpl.new
  end
end)

And a class B can use the dependency like this:

include Dependency[InterfaceA]

It works fine, but it seems like a coincidence.
If we take a look into lib/dry/auto_inject/dependency_map.rb

VALID_NAME = /([a-z_][a-zA-Z_0-9]*)$/ 

is defined, which causes the depencency map to be
created with the key ‘nterfaceA’ (starting upper cased letter is removed) and in case I have a namespaced class the namespace is removed.

So my question is what do you think about usage of constants/classes?
What I like about that is, that it seems kind of like an interface.
I declare dependency on an interface and I do not care about the registered implementation class.
I can look up the interface class and see which methods will be available.

In case of support, I would like to adjust the name_for method in dependency_map.rb
to handle classes and especially namespaces well.

Best regards

1 Like

Ah yeah, nice catch.
Was using in the same way as you do, and can confirm the key name gets improperly truncated.

The idea is to use strings as identifiers exclusively, the only reason why we do #to_s is because it’s also nice to be able to use symbols, but internally dry-container stores everything under string identifiers. This is not going to change. I even think we should change dry-container in 1.0.0 to only allow symbols and strings in register. Using constants for identifiers creates more coupling than is needed, a string is much simpler to handle than a constant.

1 Like

In this case can we adjust the name_for method to at least handle strings like
‘InterfaceA’ -> interface_a
‘Namespace::InterfaceA’ -> namespace_interface_a

@solnic Could you please reply if your are fine with strings like
“InterfaceA” and “Namespace::InterfaceA”?

I’ve been in this code, so I think I can answer this for you. The convention is to use underscore-inflected strings with period delimiters, so in your case interface_a and namespace.interface_a, however this is not enforced so you could certainly store things in that format.

However, breaking with this convention will cause all sorts of major/minor problems.

First with the namespace helper, it will continue to assume period-delimiters unless you override that.

namespace :Namespace do
  register(:InterfaceA) { Namespace::InterfaceA.new }
  # registers Namespace.InterfaceA
end

As long as you treat these namespaced strings as a flat namespace within Dry::Container it should be okay.

Second, injecting this dependency will work, like so:

include Dependency[:InterfaceA]

But I would guess that the namespaced version would break without additional guidance:

include Dependency[InterfaceA: "Namespace::InterfaceA"]

I would recommend against doing this because it would be really confusing to inject instance methods that look like constants. The underscore <-> classify inflection is extremely commonplace in Ruby, and this would communicate more clearly what you are doing. Let the conventions work for you rather than against.

In this case can we adjust the name_for method to at least handle strings like
‘InterfaceA’ → interface_a
‘Namespace::InterfaceA’ → namespace_interface_a

I don’t understand why you would want to do this. It destroys information; now you can’t tell that it’s not NamespaceInterfaceA. The convention is what it is for good reasons.

If you just want autoloaded constants, use zeitwerk.

If you don’t want to type out the registry names, use Dry::System components.