From: Peter Date: 2013-03-07T08:37:13+09:00 Subject: Re: Coupling vs providing dependencies in Ruby --047d7b5d457660b10a04d7492323 Content-Type: text/plain; charset=ISO-8859-1 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. :) -Peter On Wed, Mar 6, 2013 at 4:45 PM, Tom Kos wrote: > 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. > > -- > Posted via http://www.ruby-forum.com/. > > -- http://www.politifact.com/ http://www.factcheck.org/ http://insureblog.blogspot.com/2012/08/the-myth-of-unbiased-media.html http://www.cracked.com/article_16656_6-brainwashing-techniques-theyre-using-you-right-now.html --047d7b5d457660b10a04d7492323 Content-Type: text/html; charset=ISO-8859-1 Content-Transfer-Encoding: quoted-printable
Tom:

I agree mostly with you. The= re is nothing stopping you from injecting outside dependencies on initializ= ation 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 o= nly first starting to run up against large monolithic codebases with a mass= ive coupling problem which is creating negative side effects around such th= ings as test run time and maintainability, and are (re)discovering these pr= inciples that Java devs may have already known about for some time.

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

Another possible p= attern 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 cla= ss definition, so if you are getting a little too coupled for your own good= , it will be obvious:

class SchedulerJob

=A0 # BEGIN ext= erior dependencies
=A0 def request_schedule_class
=A0 =A0 defined?(super) ? super : RequestSchedule
=A0 end
=A0 def request_to_queue_pusher_class
=A0 =A0 de= fined?(super) ? super : RequestToQueuePusher
=A0 end
<= div style>=A0 # END exterior dependencies

=A0 def run
=A0 =A0 expired_requests =3D request_schedule_class.n= ew.fetch_all_expired
=A0 =A0 request_to_queue_pusher_class.new.en= queue(expired_requests)
=A0 end

<= div style> end

Then in your unit test or what hav= e you, you define a module like so

mod= ule CouplingIsForPornStubs

=A0 def req= uest_schedule_class
=A0 =A0 my_tests_fake_schedule_class # or Class.new, or mock, or= what have you
=A0 end

=A0 def request_to_queue_pusher_class
=A0 =A0 raise "= come on, this is too much coupling, it's like a bukkake"
=A0 end

end

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

=A0 sj_under_test =3D SchedulerJob.ne= w
=A0 sj_under_test.extend CouplingIsForPornStubs
= =A0 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 pa= tterns it out, but I haven't put it anywhere (where "anywhere"= ; =3D=3D "github") yet. :)

-= Peter


On Wed,= Mar 6, 2013 at 4:45 PM, Tom Kos <lists@ruby-forum.com> w= rote:
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 =3D RequestSchedule.new, pusher =3D
RequestToQueuePusher.new)
=A0 =A0 @schedule =3D schedule
=A0 =A0 @pusher =3D pusher
=A0 end

=A0 ...

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 "ne= w"
implicitly. Classes are still coupled but I think it may be bit better
than "new" in methods.



--
=
http://www.po= litifact.com/
http://www.factcheck.org/
http://insureblog.blogspot.com/2012/08/the-m= yth-of-unbiased-media.html
http://www.cracked.com/article_16656_6-brainwashing-techniq= ues-theyre-using-you-right-now.html
--047d7b5d457660b10a04d7492323--