Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

"The Ruby way" (mixins and class reopening) vs. dependency injection

In studying mixins vs. dependency injection, I often hear the phrase "the Ruby way." Often developers say something along the lines of

Ruby lets you reopen classes and redefine methods means that you can easily "inject" new references into your code at test-time.

(see #6 at http://weblog.jamisbuck.org/2007/7/29/net-ssh-revisited)

But testing is not my main concern; my concern is class reuse. I want classes I can reuse in multiple enterprise-scale Rails applications.

So what happened to REUSING classes? Using mixins and reopening classes does not seem to provide a way to write classes in such a way that they are decoupled from application-specific details without a bunch of extra work. But Perhaps I am wrong. If I am, can someone provide a link to an article containing sample code that clearly explains how to accomplish this properly using mixins and reopening of classes?

As an example, the class Foo here is coupled to the class Logger:

class Foo
  def initialize
    @logger = new_logger
  end

  def new_logger
    Logger.new
  end
end

Yes, I can reopen Foo and redefine new_logger, but I just cannot believe this is considered a realistic, standard approach to writing reusable classes usable by multiple Rails applications.

like image 312
Chad Johnson Avatar asked Aug 15 '11 13:08

Chad Johnson


5 Answers

Actually when I came from Java world to ruby world the first thing that I was interested in is how do they manage dependencies. At that time I was using Google Guice in all java projects and I was really inspired by its clever design and ease of use. The first thing that I've done in ruby was my very own DI container that had approximately the same set of features as Google Guice - (it is still here on github but it is very outdated).

But now after 2 years of working with Rails/Ruby I think that DI is not needed here. The good article on DI in ruby is http://weblog.jamisbuck.org/2008/11/9/legos-play-doh-and-programming it is actually an article about why DI is not needed by author of one of the first DI containers for ruby. It is definitely worth reading.

like image 86
iafonov Avatar answered Oct 17 '22 04:10

iafonov


Well just because we can reopen classes in Ruby doesn't mean we always have to, you can think of reopening classes as the method of last resort. You have a library which does everything you need except for one method, rather than forking the whole library patching it and using your fork, you can simply reopen the class, redefine the method and you're in business again. This is not something you would do willy-nilly, but having the ability to do this is extremely useful.

Having said all of that, in Ruby we have a concept that can almost always be a good substitute for dependency injection - duck typing. Since there is no type checking you can pass any object into a function and as long as the object has the methods that the function would expect, everything will work fine.

Let us look at your example - it is not really class that is conducive to dependency injection, you would not write it like this in Java, for example, if you wanted to inject some dependencies. You can only inject through the constructor or through getters and setters. So let's rewrite this class in that way:

class Foo
  def initialize(logger)
    @logger = logger
  end
end

Much better we can now inject/pass in a logger into our Foo class. Let's add a method that would use this logger to demonstrate:

class Foo
  def initialize(logger)
    @logger = logger
  end

  def do_stuff
    @logger.info("Stuff")
  end
end

In java if you wanted to create Foo objects with different types of loggers, all those loggers would have to implement the same interface in very literal sense (e.g. public class logger implements Loggable), or at least be child classes. But in Ruby as long as the object has an info method that accepts a string, you can pass it into the constructor and Ruby keep chugging along merrily. Let's demonstrate:

class Logger
  def info(some_info)
  end
end

class Widget
  def info(some_widget_info)
  end
end

class Lolcat
  def info(lol_string)
  end
end

Foo.new(Logger.new).do_stuff
Foo.new(Widget.new).do_stuff
Foo.new(Lolcat.new).do_stuff

With all 3 of the above instances of the Foo class calling the do_stuff method, everything will work fine.

As you can see from this example adhering to the principles of OO design is still important, but Ruby is somewhat less restrictive about what it will accept, as long as the right methods are there everything will be fine.

Depending on how you look at it duck typing either makes dependency injection totally irrelevant or makes it more powerful than ever.

like image 32
skorks Avatar answered Oct 17 '22 04:10

skorks


The simple answer is that nothing in the Ruby language prevents you from writing re-usable classes. The common approach to using mix-ins and class reopening doesn't necessarily promote it, but the language doesn't actually prevent other approaches. Think of "The Ruby Way" as a subset of "The Things Ruby Can Do."

That said, I know that it's usually preferable to enforce a design decision with language constructs, and to my knowledge (which I will warn you is far from complete on the subject) DI isn't currently a core Ruby-ism. A bit of Googling found me some articles on the subject, though, leading me to believe that there are libraries to be found to add DI to Ruby, if you so desire (even though they seem to receive criticism from many Rubyists). Informative articles included these two, and this SO question. Hope that helps.

like image 2
asfallows Avatar answered Oct 17 '22 05:10

asfallows


Article about how to use IoC in Ruby You underestimate the power of IoC

With working samples.

UPDATE

Link is dead by now, here's the source https://github.com/alexeypetrushin/rubylang/blob/master/draft/you-underestimate-the-power-of-ioc.md

I used that IoC as a part of my web framework, I kinda re-created Ruby on Rails and it worked, but, it won't give significant advantage over RoR, had similar characteristics. So, it became a burden and I abandoned it, some details http://petrush.in/blog/2011/rad-web-framework .

like image 2
Alex Craft Avatar answered Oct 17 '22 05:10

Alex Craft


I totally agree. Dynamic languages are no substitutes for dependency injection. And nothing stops you from writing one for a dynamic language. Here's a dependency injection framework for Smalltalk: http://www.squeaksource.com/Seuss.html

like image 1
nes1983 Avatar answered Oct 17 '22 04:10

nes1983