These are chat archives for dry-rb/chat

31st
Aug 2018
Jonah
@jonahx
Aug 31 2018 00:37

this gem allows you to make use of the dependency inversion principle, which would normally be ignored in idiomatic Ruby libraries in favour of hard-coded dependencies and/or wide interfaces.

what are wide interfaces?

Tim Riley
@timriley
Aug 31 2018 00:38
Classes with too many methods
Jonah
@jonahx
Aug 31 2018 00:39
why does injecting deps manually cause that?
i see the connection to hard-coded deps, but not the connection to wide interfaces?
Jonah
@jonahx
Aug 31 2018 01:03

New question:
Would it be fair to say that this:

Import = Dry::AutoInject(MyContainer)

class CreateUser
  include Import["users_repository"]

  def call(user_attrs)
    users_repository.create(user_attrs)
  end
end

is syntactic sugar for this:

require 'users_repo'

class CreateUser

  attr_reader :users_repo

  def initialize(users_repo: UsersRepo.new)
    @users_repo = users_repo
  end

  def call(user_attrs)
    users_repo.create(user_attrs)
  end
end

Are there benefits beside the removal of boilerplate?

Tim Riley
@timriley
Aug 31 2018 01:05
Yeah. In your second example it has to be UsersRepo.new
Jonah
@jonahx
Aug 31 2018 01:05
right. fixed.
Tim Riley
@timriley
Aug 31 2018 01:06
And if you did that, it would have to instantiate all of its dependencies in turn (presuming it has any)
Jonah
@jonahx
Aug 31 2018 01:06
correct. they’d all have to be there in ctor
Tim Riley
@timriley
Aug 31 2018 01:06
Auto-inject allows this to be lazy
Jonah
@jonahx
Aug 31 2018 01:07
yeah, i’m doing this currently on a couple projects (the manual, 2nd way). it’s not so bad. boilerplate is never good, but selling the team on new tools is hard too
Tim Riley
@timriley
Aug 31 2018 01:08
You can of course write out the full boilerplate
But it adds a lot of friction
Dry auto inject is nothing but a convenient
Convenience
Jonah
@jonahx
Aug 31 2018 01:10
yeah. it’s def cleaner and more convenient. the cost is that devs unfamilar with it are more easily confused. cool, will read that link now.
Andy Holland
@AMHOL
Aug 31 2018 01:11
Also fine in that instance, but when your dependencies have dependencies it's going to get unwieldy, and if you're injecting the same dependency into multiple classes, you're duplicating the construction logic each time
Like, if you modify the constructor of UserRepo, and use that in 6 places, do you really want make the same change 6 times rather than having a single place to handle construction?
Jonah
@jonahx
Aug 31 2018 01:13
@AMHOL yeah, i suppose that’s the key thing. That hasn’t happened yet on any of these projects. @timriley re: article, i’m already sold on DI — hardcoded constants are basically indefensible imo. I think manual DI vs container is an actual debate though. what they call the hybrid approach in that article.
Andy Holland
@AMHOL
Aug 31 2018 01:17
I think the hybrid/factory method approach is fine personally, it still has the issue of duplicated construction logic but if you don't like containers or aren't worried about that, it's a perfectly good solution, and definitely beats hard-coded dependencies any day of the week
Jonah
@jonahx
Aug 31 2018 01:18
@AMHOL yeah, it’s also an easier sell for most devs. less buyin required.
Jonah
@jonahx
Aug 31 2018 01:26
@timriley your example of the correct CreateArticle is exactly what i’ve been doing for the last few months. i really like that method.
Andy Holland
@AMHOL
Aug 31 2018 01:34
@jonahx re: why does injecting deps manually cause that? the wide interfaces issue was referring to "idiomatic" Ruby, in which DI/composition seems to be largely ignored
i.e. rather than actually using a dependency, just not following SRP
Jonah
@jonahx
Aug 31 2018 01:35
ah, meaning because people make junk drawer objects instead of breaking them down into proper units
Andy Holland
@AMHOL
Aug 31 2018 01:35
Yep
Jonah
@jonahx
Aug 31 2018 01:37
i think it would be more clear if it separated out the benefits of DI (smaller interfaces, better testing, clearer deps) from the benefits of DI with auto inject (less boilerplate). there’s two separate sales being made, as it were. do the projects take PRs on the docs?
bbiab
Andy Holland
@AMHOL
Aug 31 2018 01:39
Yep, they're always welcome: https://github.com/dry-rb/dry-rb.org
Jonah
@jonahx
Aug 31 2018 06:32
So here’s an interesting problem i run into when creating manual command objects in the style of Tim’s article. Sometimes the call method is passed a number of arguments at runtime, and also has a number of different steps, which are just calls to private methods. but it requires explicitly passing the runtime args down into the private methods. this adds a lot of visual noise, and makes the nice declarative list of steps a bit less nice. here’s a solution i’ve come up with. i’d be curious if anyone has anything better.
PodRequest = Struct.new(:a, :b)

class ValidatePodRequest

  # class to avoid passing pod_request inside call ValidatePodRequest#call
  # ie, to avoid:
  #
  # methA(pod_request)
  # methB(pod_request)
  #
  class Call < SimpleDelegator
    def initialize(validate_pr, pod_request)
      super(validate_pr)
      @pod_request = pod_request
    end

    def call
      methA
      methB
      # imagine more methods we don't have to pass pod_request to...
    end

    private

    def methA
      puts dep1
      puts @pod_request.a
    end

    def methB
      puts dep2
      puts @pod_request.b
    end
  end

  attr_reader :dep1, :dep2

  def initialize(
    dep1: "Some Dep 1",
    dep2: "Some Dep 2"
  )
    @dep1 = dep1
    @dep2 = dep2
  end

  def call(pod_request)
    Call.new(self, pod_request).call
  end

end

pr = PodRequest.new('aa', 'bb')
ValidatePodRequest.new.call(pr)
i’d also be curious to know if people prefer just passing the runtimes args down to the method explicitly, over this arguably too complex solution.
Nazar Matus
@FunkyloverOne
Aug 31 2018 11:18
Hey guys, I'm here with a small question. I got issues with testing code related to dry-transaction operations. Sometimes when I run few certain tests (instead of whole test suite) I got some strange infinite loops related to dry/transaction/operation_resolver
Nazar Matus
@FunkyloverOne
Aug 31 2018 11:42
BTW, here's the loop I'm getting in console:
     # ./app/transactions/operations/global_container.rb:29:in `new'
     # ./app/transactions/operations/global_container.rb:29:in `block (4 levels) in <class:GlobalContainer>'
     # /home/funky/.gem/ruby/2.5.1/gems/dry-container-0.6.0/lib/dry/container/item.rb:29:in `call'
     # /home/funky/.gem/ruby/2.5.1/gems/dry-container-0.6.0/lib/dry/container/resolver.rb:25:in `call'
     # /home/funky/.gem/ruby/2.5.1/gems/dry-container-0.6.0/lib/dry/container/mixin.rb:112:in `resolve'
     # /home/funky/.gem/ruby/2.5.1/gems/dry-container-0.6.0/lib/dry/container/mixin.rb:125:in `[]'
     # /home/funky/.gem/ruby/2.5.1/gems/dry-transaction-0.13.0/lib/dry/transaction/operation_resolver.rb:10:in `block (4 levels) in initialize'
     # /home/funky/.gem/ruby/2.5.1/gems/dry-transaction-0.13.0/lib/dry/transaction/operation_resolver.rb:8:in `fetch'
     # /home/funky/.gem/ruby/2.5.1/gems/dry-transaction-0.13.0/lib/dry/transaction/operation_resolver.rb:8:in `block (3 levels) in initialize'
     # /home/funky/.gem/ruby/2.5.1/gems/dry-transaction-0.13.0/lib/dry/transaction/operation_resolver.rb:7:in `map'
     # /home/funky/.gem/ruby/2.5.1/gems/dry-transaction-0.13.0/lib/dry/transaction/operation_resolver.rb:7:in `block (2 levels) in initialize'
Oh, I've fixed it, that's my bad, I have an operation with the same name as transaction, they are in a different namespaces, but it just got confused, because I wasn't clear enough
Nazar Matus
@FunkyloverOne
Aug 31 2018 11:53
BTW, I'm having those operation and transaction with same names, because operation does not validate it's input, and could raise some exceptions. On the other hand Transaction uses this operation inside, and acts like a wrapper for it, which validates input, and also handles some exceptions. And also if I will need to add some additional steps - they will go to transaction, and operation will still be clean and minimalistic (and reusable) as possible.