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:
>> 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,ll 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)
>  > end
>
> s = SomeClass.new
> s.do_something(1, 2)

Same problem: global vs. local state.

Kind regards

robert

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