Hi Folks, i ran into something today with the Injector that I didn’t have a good solve for right away, so I thought I’d ask. Scenario: we need to initialize a class with a unique property that changes based on context.
All examples are using Ruby 2.7.
class Container
register :client do
Service::Client.new
end
end
Import = Dry::AutoInject(Container)
class Action
include import[:client]
def call(data)
# the action instance needs to pass on some specific config
# known by the context of the caller
client.call(config, data)
end
end
Our current container uses the default kwyd arguments injection strategy, but I couldn’t seem to get around ruby syntax violations. For example:
class Client
end
class Container
extend Dry::Container::Mixin
register(:client) do
Client.new
end
end
Import = Dry::AutoInject(Container)
class Action
include Import[:client]
def initialize(config:); end
end
action = Action.new(config: {})
raises:
in `initialize': unknown keyword: :client (ArgumentError)
and since Action
does not inherit, I can’t use the keyword in the subclass, filter it out, and pass it up with super
(which I’ve done in subclass cases historically).
thusfar my two best options are of the variety:
class Action
include Import[:client]
attr_accessor :config
end
config = {}
action.new.tap { _1.config = config }
any other thoughts on initialization strategies?
Thanks in advance!
Hey Jed. I’m not sure I fully follow what you are trying to do here regarding the Client
but, from the examples you’ve provided, I think the piece you are missing is ensuring Action
gets constructed with both your config
keyword argument and your injected client
dependency by passing your dependencies to super
.
I rewrote your code snippets, above, as a runable snippet. Note that I wrote this in Ruby 3.1.x syntax to save on vertical space. Regardless, I think all you’ll need to study is how I setup the Action
constructor.
Here’s the solution:
#! /usr/bin/env ruby
# frozen_string_literal: true
# Save as `snippet`, then `chmod 755 snippet`, and run as `./snippet`.
require "bundler/inline"
gemfile true do
source "https://rubygems.org"
gem "amazing_print"
gem "debug"
gem "dry-container"
gem "dry-auto_inject"
end
class Client
def call(config, data) = ap "I'm the client and I just got: Config: #{config}, Data: #{data}."
end
class Container
extend Dry::Container::Mixin
register(:client) { Client.new }
end
Import = Dry::AutoInject Container
class Action
include Import[:client]
def initialize config:, **dependencies # Important, so AutoInject and still provide client as a keyword.
super(**dependencies)
@config = config
end
def call(data) = client.call config, data
private
attr_reader :config
end
Action.new(config: {a: 1}).call "test_data"
Running the above will output as:
Fetching gem metadata from https://rubygems.org/...
Resolving dependencies...
"I'm the client and I just got: Config: {:a=>1}, Data: test_data."
Does the above fix your issue?
@bkuhlmann Yes, I think this will solve the issue! My mistake was not destructuring the arguments in the init. Thanks for the quick reply! Jed
1 Like
Just an update here, we were able to refactor our code to use this solution and it works “as advertised”. Thanks for the help!
1 Like