Dependency injection into Dry::Struct descendant class


#1

I would like to figure how to inject a dependency into a Dry::Struct descendant class.

Here’s the code:

require 'dry-struct'

module Types
  include Dry::Types.module
end

class User < Dry::Struct
  attribute :name, Types::Strict::String
  attribute :age,  Types::Strict::Int.constrained(gteq: 18)
end

Now I want to inject a logger in the simplest way possible. I would not like to use dry-container partly because I didn’t grasp it yet, and partly because I still adhere to ‘you don’t need a dependency injection framework for dependency injection’ opinion. I’d like to keep it simple.

So, without dry-struct, I’d go with:

class User
  def initialize logger
    @logger = logger
  end
end

User.new(Logger.new(STDOUT))

But sure enough, this approach doesn’t work for Dry::Struct successors.


#2

I came up with adding an extra attribute:

require 'dry-struct'

module Types
  include Dry::Types.module
end

class User < Dry::Struct
  attribute :name, Types::Strict::String
  attribute :age,  Types::Strict::Int.constrained(gteq: 18)
  attribute :logger, Types::Any

  def log_info(message)
    logger.info message
  end
end
irb(main):001:0> user = User.new(name: 'Peter', age: 20, logger: Logger.new(STDOUT))
=> #<User name="Peter" age=20 logger=#<Logger:0x007f987813f6b0 @level=0, @progname=nil, @default_formatter=#<Logger::Formatter:0x007f987813f660 @datetime_format=nil>, @formatter=nil, @logdev=#<Logger::LogDevice:0x007f987813f610 @shift_period_suffix=nil, @shift_size=nil, @shift_age=nil, @filename=nil, @dev=#<IO:<STDOUT>>, @mon_owner=nil, @mon_count=0, @mon_mutex=#<Thread::Mutex:0x007f987813f598>>>>
irb(main):002:0> user.log_info 'hello'
I, [2017-08-23T14:06:23.437863 #6990]  INFO -- : hello
=> true

#3

Please try not to. Structs are meant to be used as pure data structures, they should not depend on any external systems.


#4

My struct got a bit beefy, because I added a whole bunch of methods to it. So, I took the wrong way? In the case above, where do I put user's methods, the things user can do?


#5

@solnic As far as I understand, I shouldn’t name the dry-struct class a User, but rather UserData. And I should have have a separate User class with all the methods.

And then… do I inject UserData instance into User constructor? If so, what’s the benefit of separation of the two classes?


#6

If you call it UserData then what User is supposed to do? Usually, operations that should be logged are located in places of a different kind, e.g. service objects, functional objects and alike. You might want to rearrange your code according to these concepts.