Coupling vs providing dependencies in Ruby

I’m java programmer diving into Ruby language (not rails stuff). Looking
at ruby codebases I see a lot of code like this
Coupling - mock,stub · GitHub.

Take a look at the SchedulerJob definition:

class SchedulerJob

def run
expired_requests = RequestSchedule.new.fetch_all_expired
RequestToQueuePusher.new.enqueue(expired_requests)
end

end

It explicitly creates RequestSchedule and RequestToQueuePusher. Those
two objects are plain stateless “services”. This code works fine, but
for me it introduces very high coupling between SchedulerJob and those
classes. In Java we’d normally inject their instances in constructor and
don’t use “new” inside to create them.
I’m not sure why this is not popular technique in Ruby. I was trying to
google for that and all I got can be told as “Ruby doesn’t need
dependencies injection”. Why is it so? I know stubbing RequestSchedule
or RequestToQueuePusher for tests is easy (with rspec e.g.), but
testability is not the only point.
Is this really Ruby way for OOP? For me it breaks SOLID principles I
know. I’d like to code in Ruby the Ruby way but it seems to be
completely different than in Java, and I thought OOP concepts and
practices should not vary between languages.

How do you approach such situations, or you don’t care and just put
“new” every time you want to reach dependent object?

Tom,

I think like many languages communities get segregated on practices. I
wouldn’t agree that Rubyists avoid the SOLID principles. I teach them
pretty frequently in my workshops. As a first pass I would have coded
the
example like this:
https://gist.github.com/halogenandtoast/3c8da80405de5237feb5

Which would have followed dependency injection. Different people have
different principles but I think practices (if at least consistent on a
project) are generally a good thing.

Thanks, this is more or less how I’d do that. I’d maybe just add default
arguments’ values like:

class SchedulerJob

def initialize(schedule = RequestSchedule.new, pusher =
RequestToQueuePusher.new)
@schedule = schedule
@pusher = pusher
end

end

I’m far from stating that rubyists avoid SOLID principles, but it’s hard
to spot codebases with dependencies injected on regular basis.

The only question remaining is that even using dependency injection you
still need to “new” your dependencies somewhere. In Java you got things
like Spring or Guice (frameworks for wiring up all stuff together). But
I guess in ruby it may be easily done with default arguments doing “new”
implicitly. Classes are still coupled but I think it may be bit better
than “new” in methods.

Tom:

I agree mostly with you. There is nothing stopping you from injecting
outside dependencies on initialization and defaulting to the expected
dependencies; in fact, if we had stuck to that in our Rails app codebase
from the beginning, then our unit tests might have actually run quickly
from the get-go.

I think that a lot of Ruby/Rails devs are only first starting to run up
against large monolithic codebases with a massive coupling problem which
is
creating negative side effects around such things as test run time and
maintainability, and are (re)discovering these principles that Java devs
may have already known about for some time.

So please, bring your (well-argued) opinion here! That said…

Another possible pattern I have been toying around with which could
satisfy
your requirement (without requiring a possibly lengthy/ugly/disturbing
argument list to the initialization routine) is to define a few slightly
clever instance methods as proxies. Another advantage of this approach
is
that you can specify all your class’ exterior dependencies right up
front
at the top of the class definition, so if you are getting a little too
coupled for your own good, it will be obvious:

class SchedulerJob

BEGIN exterior dependencies

def request_schedule_class
defined?(super) ? super : RequestSchedule
end
def request_to_queue_pusher_class
defined?(super) ? super : RequestToQueuePusher
end

END exterior dependencies

def run
expired_requests = request_schedule_class.new.fetch_all_expired
request_to_queue_pusher_class.new.enqueue(expired_requests)
end

end

Then in your unit test or what have you, you define a module like so

module CouplingIsForPornStubs

def request_schedule_class
my_tests_fake_schedule_class # or Class.new, or mock, or what have
you
end

def request_to_queue_pusher_class
raise “come on, this is too much coupling, it’s like a bukkake”
end

end

And then later on in the actual test case you just do

sj_under_test = SchedulerJob.new
sj_under_test.extend CouplingIsForPornStubs
assert_raise { sj_under_test.run }

And now your class instance under test will magically defer to your
module’s version of the methods (or classes) first, then to its own.

I have some code that cleans this up and patterns it out, but I haven’t
put
it anywhere (where “anywhere” == “github”) yet. :slight_smile:

-Peter