regsitered assets don't seem to be called

Given the following setup:

require "dry/system/container"
require 'ostruct'

class Connection
  def call
    OpenStruct.new(test: "test")
  end
end

class Container < Dry::System::Container
  register(:connection, call: true) { Connection.new }
end

Import = Container.injector

class Tester
  include Import[:connection]

  def call
    connection.test
  end
end

t = Tester.new
puts t.call

I get the following error: test.rb:19:in private method test' called for #<Connection:0x000000015695f720> (NoMethodError)

if I change the register to:

register(:connection) { Connection.new.call }

I get the result I am expecting: test

Curious, What am I doing wrong?

Does it work when you do Container[:connection]?

Edit:

I forgot to update the code, please ignore the original response below.

it does not work via container resolution: when setup like so:

  register(:connection, call: true) { Connection.new }
[1] pry(main)> Container[:connection]
=> #<Connection:0x0000000137b21e10>
[2] pry(main)> Container[:connection].test
NoMethodError: private method `test' called for #<Connection:0x0000000137a62600>
from (pry):2:in `<main>'

Original response:

@solnic , yes it does!

[1] pry(main)> Container[:connection]
=> #
[2] pry(main)> Container[:connection].test
=> “test”

I think this might come down to a misunderstanding of the call: option for register, and what exactly the registered item is in your examples.

Let’s take this registration of yours as a starting point:

register(:connection, call: true) { Connection.new }

It might be helpful to restructure it a little to make it clear just what the registered item will be in this case:

register(:connection, call: true, Proc.new { Connection.new })

The registered item in this case is actually this Proc object, which is what dry-container will turn a provided block into behind the scenes. (Reminder: dry-container is what dry-system uses under the hood).

From here, what call: true tells dry-container to do is to call that registered proc whenever you resolve your registered component. And in your example, calling Proc.new { Connection.new } will return a new instance of your Connection.

In fact, any time you provide a block (or indeed a Proc object) to register, dry-container will set call: true for you automatically, so these are both equivalent to the examples above:

# Both equivalent, since `call: true` is set for all registered Procs
register(:connection) { Connection.new }
register(:connection, Proc.new { Connection.new })

Now to help understand this completely, if we inverted the call: option and set it to false, here’s what would happen:

MyContainer.register(:connection, call: false) { Connection.new }

MyContainer.resolve(:connection)      # => Supplied Proc instance, e.g. #<Proc:0x00000001120786c8>
MyContainer.resolve(:connection).call # => A Connection instance, e.g. #<Connection:0x000000011210e4c0>

What this all should hopefully show is that when you’re registering items with a block, what call: true means (and why its set to true by default for blocks) is that it will call the block’s proc itself to return whatever you’re returning from the block. What it won’t do is send #call to that object. This is your job to do as the user of the container, once you have resolved that object.

So from here I’d recommend you take one of two options:

  1. Register your connection with the block like you’ve been currently doing, and make an extra step within your application code to .call that Connection object once you’ve resolved it from the container
  2. Or initialize that Connection eagerly somewhere, and then send .call it and register that resulting object directly on the container

There might actually be other options here, but it’s a bit hard for me to guess at that without knowing a bit more about your circumstances. If you could share more, I’d be happy to offer more suggestions!

Either way, hopefully this helps clear things up a little about how dry-container callable registrations work :slight_smile:

Thanks for the detailed reply Tim, I have been using dry-container for about a year and generally speaking its defaults seem exactly what I want. But recently I’ve been exploring some of the other ways to resolve dependencies. To your point, here is what I was trying to solve. I have connection class that wraps a Faraday client:

class Connection
  extend Forwardable

  def initialize
    settings = Container.resolve(:settings)
    @conn = Faraday.new do |f|
      ...
    end
  end

  def_delegators :@conn, :post, :put, :get, :delete
end

I’d like to be able to resolve this directly without having to call first:

# this seems awkward
Container.resolve(:connection).call.post("/some/route", data)

# beter
Container.resolve(:connection).post("/some/route", data)

often I solve this by doing the setup directly in the container:

register(:connection) do
  Faraday.new do |f|
    ...
  end
end

but in this case it felt better to wrap it in a class and was surprised that the call: true didn’t do quite what I wanted. I didn’t realize that was the default when supplying a block, but how you explained it, it makes sense. How do other folks solve this?

Thanks,

I’ve never used call option, so sorry for my misguided question :sweat_smile: I assumed that it works differently BTW. Now when I think about it, I have mixed feelings. This is confusing a bit so maybe it should not allow call: true + a block?