Inversion of Control in Ruby by passing classes - does it make any sense?

Hello everyone,

I'm quite new to Ruby, with a strong background in PHP.

I had a great time finding my way around in Ruby, because it is a fantastic language. However, right now I'm a bit stuck regarding the issue of loose coupling / inversion of control / writing testable units.

I spent the last days reading a lot of articles, but feel sort of puzzled now.

As far as I can see, there are several approaches. I will start with my own, which probably is the weirdest of all - feel free to dissect it.

Let's say I have 2 classes, SomeClass and OtherClass, and SomeClass depends on OtherClass, because in some cases it needs to create an instance of this class, and in others it doesn't, which is why we can't simply pass an already created instance of OtherClass into SomeClass.

This is how it could be done using no DI at all:

class SomeClass
  def do_something(a, b)
    c = a + b
    if (c > 5) then
      o = OtherClass.new()
      o.log(c)
    end
  end
end

s = SomeClass.new
s.do_something(1, 2)

Which of course doesn't allow me to replace "o" with a mock object for testing etc.

This is how it might be done using the fact that ruby classes are objects (my approach):

class SomeClass
  def do_something(a, b, logObjectClass)
    c = a + b
    if (c > 5) then
      o = logObjectClass.new()
      o.log(c)
    end
  end
end

s = SomeClass.new
s.do_something(1, 2, OtherClass)

This way, I can inject into SomeClass which class to actually use - as long as whatever I pass using logObjectClass supports the expected protocol, all should be fine. I could easily inject a mock log object for my tests.

But, I couldn't find any articles recommending this approach, and I guess there's a reason for that...

Another solution which seems to be more along "the Ruby way" is this:

class SomeClass
  def do_something(a, b)
    c = a + b
    if (c > 5) then
      o = get_logger()
      o.log(c)
    end
  end

  def get_logger()
    OtherClass.new()
  end
end

// Replacing with mock object:
s = SomeClass.new
def s.get_logger()
  MockLoggerClass.new()
end
s.do_something(1, 2)

Could someone help me to understand which approach is better, and why? Or if maybe I'm getting it completely wrong?

Thanks in advance,

···

--
Manuel

Hello everyone,

I'm quite new to Ruby, with a strong background in PHP.

I had a great time finding my way around in Ruby, because it is a fantastic language. However, right now I'm a bit stuck regarding the issue of loose coupling / inversion of control / writing testable units.

I spent the last days reading a lot of articles, but feel sort of puzzled now.

As far as I can see, there are several approaches. I will start with my own, which probably is the weirdest of all - feel free to dissect it.

Let's say I have 2 classes, SomeClass and OtherClass, and SomeClass depends on OtherClass, because in some cases it needs to create an instance of this class, and in others it doesn't, which is why we can't simply pass an already created instance of OtherClass into SomeClass.

This is how it could be done using no DI at all:

class SomeClass
def do_something(a, b)
   c = a + b
   if (c > 5) then
     o = OtherClass.new()
     o.log(c)
   end
end
end

s = SomeClass.new
s.do_something(1, 2)

Which of course doesn't allow me to replace "o" with a mock object for testing etc.

Actually, all of the Ruby TDD or BDD frameworks include mocking frameworks that support this. Here's what it might look like in rspec:

it "should create instance of OtherClass" do
  o = mock('other class')
  OtherClass.should_receive(:new).and_return(o)

  SomeClass.new.do_something(1, 2)
end

It's a valid technique and gets used quite a bit.

This is how it might be done using the fact that ruby classes are objects (my approach):

class SomeClass
def do_something(a, b, logObjectClass)
   c = a + b
   if (c > 5) then
     o = logObjectClass.new()
     o.log(c)
   end
end
end

s = SomeClass.new
s.do_something(1, 2, OtherClass)

This way, I can inject into SomeClass which class to actually use - as long as whatever I pass using logObjectClass supports the expected protocol, all should be fine. I could easily inject a mock log object for my tests.

But, I couldn't find any articles recommending this approach, and I guess there's a reason for that...

It's not a bad idea and will work just fine. I have used this technique a couple of times. What I usually do is make that 3rd argument a default parameter so that by default it uses the class that I hardcode but I am free to override it (during testing) with a mock of my own.

e.g.

def do_something(a, b, klass = OtherClass)
  c = a + b
  if c > 5
    o = klass.new
  end
end

SomeClass.new.do_something(1, 2, MockClass) # inject my own
SomeClass.new.do_something(1, 2) # will use OtherClass by default

Another solution which seems to be more along "the Ruby way" is this:

class SomeClass
def do_something(a, b)
   c = a + b
   if (c > 5) then
     o = get_logger()
     o.log(c)
   end
end

def get_logger()
   OtherClass.new()
end
end

// Replacing with mock object:
s = SomeClass.new
def s.get_logger()
MockLoggerClass.new()
end
s.do_something(1, 2)

Could someone help me to understand which approach is better, and why? Or if maybe I'm getting it completely wrong?

I rarely see code structured like this. I wouldn't do it this way particularly since you have two better solutions that you listed above.

Ultimately, Ruby is flexible enough that you can accomplish your goal *cleanly* using multiple different techniques. I would use the ones you listed in the order that you listed them (best is first, etc).

Welcome to Ruby!

cr

···

On Nov 2, 2010, at 3:01 PM, Manuel Kiessling wrote:

What I'm about to suggest will generate warnings from Ruby and is
probably not the "best" way, but you could also replace OtherClass with
your MockLoggerClass with a simple assignment during your test:

OtherClass = MockLoggerClass # This generates a warning...
s = SomeClass.new
s.do_something(1, 2)

Ruby will warn you about replacing an already initialized constant,
namely OtherClass, but it should get the job done with a minimum of fuss
on your part.

One more option would be to override OtherClass.new and make it simply
call MockLoggerClass.new with the same arguments:

def OtherClass.new(*args)
  MockLoggerClass.new(*args)
end

s = SomeClass.new
s.do_something(1, 2)

Among all the solutions, I don't know the best way, but I would probably
go with overriding OtherClass.new. Your second proposed solution isn't
bad either, but I wouldn't want to go that way just to make testing
easier/possible. I would need a good functional reason to break up the
code that way. Of course, division of labor in your implementation
isn't a bad reason. :wink:

-Jeremy

···

On 11/2/2010 3:01 PM, Manuel Kiessling wrote:

Hello everyone,

I'm quite new to Ruby, with a strong background in PHP.

I had a great time finding my way around in Ruby, because it is a fantastic language. However, right now I'm a bit stuck regarding the issue of loose coupling / inversion of control / writing testable units.

I spent the last days reading a lot of articles, but feel sort of puzzled now.

As far as I can see, there are several approaches. I will start with my own, which probably is the weirdest of all - feel free to dissect it.

Let's say I have 2 classes, SomeClass and OtherClass, and SomeClass depends on OtherClass, because in some cases it needs to create an instance of this class, and in others it doesn't, which is why we can't simply pass an already created instance of OtherClass into SomeClass.

This is how it could be done using no DI at all:

class SomeClass
  def do_something(a, b)
    c = a + b
    if (c > 5) then
      o = OtherClass.new()
      o.log(c)
    end
  end
end

s = SomeClass.new
s.do_something(1, 2)

Which of course doesn't allow me to replace "o" with a mock object for testing etc.

This is how it might be done using the fact that ruby classes are objects (my approach):

class SomeClass
  def do_something(a, b, logObjectClass)
    c = a + b
    if (c > 5) then
      o = logObjectClass.new()
      o.log(c)
    end
  end
end

s = SomeClass.new
s.do_something(1, 2, OtherClass)

This way, I can inject into SomeClass which class to actually use - as long as whatever I pass using logObjectClass supports the expected protocol, all should be fine. I could easily inject a mock log object for my tests.

But, I couldn't find any articles recommending this approach, and I guess there's a reason for that...

Another solution which seems to be more along "the Ruby way" is this:

class SomeClass
  def do_something(a, b)
    c = a + b
    if (c > 5) then
      o = get_logger()
      o.log(c)
    end
  end

  def get_logger()
    OtherClass.new()
  end
end

// Replacing with mock object:
s = SomeClass.new
def s.get_logger()
  MockLoggerClass.new()
end
s.do_something(1, 2)

One way to do it (assuming you want to reuse the OtherClass instance):

class SomeClass
   def do_something(a, b)
     c = a + b
     if c > 5
       otherclass.log(c)
     end
   end

   def otherclass
     @otherclass ||= OtherClass.new
   end
end

mock = SomeClass.new
mock.otherclass = MockOtherClass.new # or whatever
   # note, you will have to define a writer
   # (or use instance_variable_set)

···

On 11/02/2010 01:01 PM, Manuel Kiessling wrote:

Hello everyone,

I'm quite new to Ruby, with a strong background in PHP.

I had a great time finding my way around in Ruby, because it is a fantastic language. However, right now I'm a bit stuck regarding the issue of loose coupling / inversion of control / writing testable units.

I spent the last days reading a lot of articles, but feel sort of puzzled now.

As far as I can see, there are several approaches. I will start with my own, which probably is the weirdest of all - feel free to dissect it.

Let's say I have 2 classes, SomeClass and OtherClass, and SomeClass depends on OtherClass, because in some cases it needs to create an instance of this class, and in others it doesn't, which is why we can't simply pass an already created instance of OtherClass into SomeClass.

This is how it could be done using no DI at all:

class SomeClass
   def do_something(a, b)
     c = a + b
     if (c> 5) then
       o = OtherClass.new()
       o.log(c)
     end
   end
end

Hello everyone,

I'm quite new to Ruby, with a strong background in PHP.

I had a great time finding my way around in Ruby, because it is a fantastic language. However, right now I'm a bit stuck regarding the issue of loose coupling / inversion of control / writing testable units.

I spent the last days reading a lot of articles, but feel sort of puzzled now.

As far as I can see, there are several approaches. I will start with my own, which probably is the weirdest of all - feel free to dissect it.

Let's say I have 2 classes, SomeClass and OtherClass, and SomeClass depends on OtherClass, because in some cases it needs to create an instance of this class, and in others it doesn't, which is why we can't simply pass an already created instance of OtherClass into SomeClass.

This is how it could be done using no DI at all:

class SomeClass
def do_something(a, b)
c = a + b
if (c > 5) then
o = OtherClass.new()
o.log(c)
end
end
end

s = SomeClass.new
s.do_something(1, 2)

Which of course doesn't allow me to replace "o" with a mock object for testing etc.

This is how it might be done using the fact that ruby classes are objects (my approach):

class SomeClass
def do_something(a, b, logObjectClass)
c = a + b
if (c > 5) then
o = logObjectClass.new()
o.log(c)
end
end
end

s = SomeClass.new
s.do_something(1, 2, OtherClass)

This way, I can inject into SomeClass which class to actually use - as long as whatever I pass using logObjectClass supports the expected protocol, all should be fine. I could easily inject a mock log object for my tests.

But, I couldn't find any articles recommending this approach, and I guess there's a reason for that...

Certainly. But whether this reason also means that your approach is
bad is another question. Personally I find it totally valid and
natural to use a class object as /factory/ for instances. The other
approach, which might be a bit more Ruby like, is to use a block:

class SomeClass
  # block as factory
  def do_something_1(a, b, &log)
    c = a + b
    if (c > 5) then
      o = log.call
      o.log(c)
    end
  end

  # block as callback
  def do_something_2(a, b, &log)
    c = a + b
    if (c > 5) then
      log.call(c)
    end
  end
end

sc.do_something_1(1,2) { OtherClass.new }
sc.do_something_2(1,2) {|item| OtherClass.new.log(item) }

Btw, if OtherClass is some kind of logger I would probably make the
factory a member of SomeClass, so you do not have to pass it into
every call.

What I'm about to suggest will generate warnings from Ruby and is
probably not the "best" way, but you could also replace OtherClass with
your MockLoggerClass with a simple assignment during your test:

OtherClass = MockLoggerClass # This generates a warning...
s = SomeClass.new
s.do_something(1, 2)

This is not exactly the same as it does not allow control over the
created instance per instance. You basically change global state vs.
Manuel's approach with local state.

Ruby will warn you about replacing an already initialized constant,
namely OtherClass, but it should get the job done with a minimum of fuss
on your part.

One more option would be to override OtherClass.new and make it simply
call MockLoggerClass.new with the same arguments:

def OtherClass.new(*args)
MockLoggerClass.new(*args)
end

s = SomeClass.new
s.do_something(1, 2)

Same problem: global vs. local state.

Kind regards

robert

···

On Tue, Nov 2, 2010 at 9:29 PM, Jeremy Bopp <jeremy@bopp.net> wrote:

On 11/2/2010 3:01 PM, Manuel Kiessling wrote:

--
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/

I agree, but I was approaching this from a minimal unit test standpoint
rather than a generally applicable part of Manuel's design. Would you
say that changing the global state in this way or in the other way I
suggested is still a problem in that case?

-Jeremy

···

On 11/3/2010 5:20 AM, Robert Klemme wrote:

On Tue, Nov 2, 2010 at 9:29 PM, Jeremy Bopp <jeremy@bopp.net> wrote:

What I'm about to suggest will generate warnings from Ruby and is
probably not the "best" way, but you could also replace OtherClass with
your MockLoggerClass with a simple assignment during your test:

OtherClass = MockLoggerClass # This generates a warning...
s = SomeClass.new
s.do_something(1, 2)

This is not exactly the same as it does not allow control over the
created instance per instance. You basically change global state vs.
Manuel's approach with local state.

I cannot really tell. Maybe, if you need different loggers in
different unit tests and mix in multi threading to make your test
suite finish faster. :slight_smile:

But I do know that global state is problematic. I guess you can find
plenty of evidence on the web (or reason about it for yourself /
according to your own code). It should be used with extra care. I
try to avoid it whenever possible and that approach has served me
well.

Kind regards

robert

···

On Wed, Nov 3, 2010 at 3:00 PM, Jeremy Bopp <jeremy@bopp.net> wrote:

On 11/3/2010 5:20 AM, Robert Klemme wrote:

On Tue, Nov 2, 2010 at 9:29 PM, Jeremy Bopp <jeremy@bopp.net> wrote:

What I'm about to suggest will generate warnings from Ruby and is
probably not the "best" way, but you could also replace OtherClass with
your MockLoggerClass with a simple assignment during your test:

OtherClass = MockLoggerClass # This generates a warning...
s = SomeClass.new
s.do_something(1, 2)

This is not exactly the same as it does not allow control over the
created instance per instance. You basically change global state vs.
Manuel's approach with local state.

I agree, but I was approaching this from a minimal unit test standpoint
rather than a generally applicable part of Manuel's design. Would you
say that changing the global state in this way or in the other way I
suggested is still a problem in that case?

--
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/

I'll keep that in mind. Thanks for the tips. :wink:

-Jeremy

···

On 11/3/2010 9:06 AM, Robert Klemme wrote:

On Wed, Nov 3, 2010 at 3:00 PM, Jeremy Bopp <jeremy@bopp.net> wrote:

On 11/3/2010 5:20 AM, Robert Klemme wrote:

On Tue, Nov 2, 2010 at 9:29 PM, Jeremy Bopp <jeremy@bopp.net> wrote:

What I'm about to suggest will generate warnings from Ruby and is
probably not the "best" way, but you could also replace OtherClass with
your MockLoggerClass with a simple assignment during your test:

OtherClass = MockLoggerClass # This generates a warning...
s = SomeClass.new
s.do_something(1, 2)

This is not exactly the same as it does not allow control over the
created instance per instance. You basically change global state vs.
Manuel's approach with local state.

I agree, but I was approaching this from a minimal unit test standpoint
rather than a generally applicable part of Manuel's design. Would you
say that changing the global state in this way or in the other way I
suggested is still a problem in that case?

I cannot really tell. Maybe, if you need different loggers in
different unit tests and mix in multi threading to make your test
suite finish faster. :slight_smile:

But I do know that global state is problematic. I guess you can find
plenty of evidence on the web (or reason about it for yourself /
according to your own code). It should be used with extra care. I
try to avoid it whenever possible and that approach has served me
well.