Hi David, Just my own experience, DI as I understand it isn't something you add in later, as the project grows, it can be as fundamental as injecting a clock, and never calling a method on an object you weren't passed in, seen another way you could take it as far as expecting to be able to simply `call` (e.g `to_proc`) on any object, passing them arround and just calling it, to give an example (somewhat contrived):
Here's some takeaways: - *Sometimes you want to pass in a *class* and let your objects construct it with their own params*. (UserValidator, in this case), it's implemented this way so that we can stick to "do all DI in initialize, call call without params" (which might be a bridge too far, but I like it) - *Even mundane things like the system clock are worth DI'ing.* You might think not, until suddenly someone sneaks TimeCop into your app and now you're thinking "*van Damme, I hate global state.*" - *I make liberal use of BasicObject in these Ruby styles, because a typical Ruby "Object" comes with ~200 methods which have *zero* meaning to your app.* By making a BasicObject you severely restrict what people can call on the things you pass around, and this makes refactoring and safe and sane testing much simpler. It's also a nice toy in Pry/IRB when you call "something.methods" and get a list of precisely two things. - *I hate the implementation of UserStore here.* I normally would split it into UserStore and UserRetriever, but to keep the example small, and to give myself a chance to demonstrate passing in the UserValidatorType (and the UserSignupHandler constructing it's own dependency's constructor) I did a simple "Store" type which violates SRP for me. I've worked in a lot of apps where storing, and retrieving things work very differently (think on retrieval wanting to use views and caching) class UTCClock < BasicObject def now; return Time.now.utc; end end class StaticClock < BasicObject def now; return Time.new(1970); end end # This is a service object, it's included to # demonstrate the ".call() everything" mantra class UserSignupHandler < BasicObject def initialize(user:, clock:, user_store:, user_validator_class:) @user, @clock, @user_store, @user_validator = user, clock, user_store, user_validator_class end def call if r = @user_validator_class.new(@user, @user_store).call; not r.success? return E_USER_CAN_T_BE_CREATED # (actually pass `r` back up the stack, # and have the railsish frontend know how # to deal with "result" types) end return @user_store.store(@user) # expect that this returns the user with it's db pkey filled, for example end end class UserValidator < BasicObject def initialize(user:, user_store:) @user, @user_store = user, user_store end def call # validation here, return some kind of ResultObject with #errors # and #can_continue? res = ResultObject.new if @user.name.length < 3 res.errors.push E_USER_ALREADY_EXISTS end if @user_store.find_by_email(@user.email) res.errors.push E_USER_ALREADY_EXISTS end return res end end # Somewhere in a rails App class ApplicationController < ActionController::API def signup # check METHOD for `POST` # get params, rely on strong params to do preliminary validation, etc user_store = UserStore.new($dbBackend, $cacheBackend) clock = UTCClock.new render json: UserSignupHandler.new(User.new(params), user_store, clock, UserValidator).call end end ​Code also in this gist ( https://gist.github.com/leehambley/8b4e17e95520058a3708f011939e7816), I hope the ML doesn't eat the formatting here, it's nice to have coloured code in an email :) Hope this helps, at least this is my understanding of DI informed by having done a lot of functional, and typesafe coding in other languages (and the functional stuff also in Ruby) , partly including stuff I've learned from Jim Gay's book on "Clean Ruby" but I take some of what he does a little bit further Lee Hambley http://lee.hambley.name/ +49 (0) 170 298 5667 On 12 July 2016 at 14:02, David Craddock <[email protected]> wrote: > We are getting to the stage on the product I'm working on, that it might > be useful to use Dependency Injection. > https://en.wikipedia.org/wiki/Inversion_of_control > > What do you fellow Rubyists think about using DI in Ruby? Are there any > frameworks or gems that can help this? > > -- > You received this message because you are subscribed to the Google Groups > "North West Ruby User Group (NWRUG)" group. > To unsubscribe from this group and stop receiving emails from it, send an > email to [email protected]. > To post to this group, send an email to [email protected]. > Visit this group at https://groups.google.com/group/nwrug-members. > For more options, visit https://groups.google.com/d/optout. > -- You received this message because you are subscribed to the Google Groups "North West Ruby User Group (NWRUG)" group. To unsubscribe from this group and stop receiving emails from it, send an email to [email protected]. To post to this group, send an email to [email protected]. Visit this group at https://groups.google.com/group/nwrug-members. For more options, visit https://groups.google.com/d/optout.
