I have previously been over the extraction pattern, Extract Method. Today we will explore it’s big brother, Extract Method Object.
The Extract Method Object pattern is used to take a method or groups of methods from coupled, untestable code into code that adheres to the Single Responsibility Principle, or an object/method/construct should have 1 and only 1 reason to change.
Let’s jump right into some very scary, real, untestable (mostly) controller code:
Whew! That is one big ball of mud!
Let’s start by taking the first block of code and using Extract Method Object on it to build a single purpose object who’s sole responsibility is to take a hash, ‘clean it’ and return it. As it will become a pure ruby object (PRO) it will become immensely easier to write tests for. Time to dive in!
Let’s create an empty object to accept this method:
What I have done here is create a module (unless I need state, which I prefer not to use, I use modules) and then I
extend self which is just a way of saying I want this module to be a stand-alone module not a mixin. Now every method on the object is module level, and I don’t have to type
self over and over.
Now let’s add some tests right into the file!
Now back to our object… Since it now will have a single responsibility, let’s adopt the method
call to do the work. This will make our object interchangeable with the interface for rack objects and lambdas/blocks/procs!
I think this test and implementation code are pretty obvious so let’s move onto the next test to write and then implement - what to do when the hash has valid password hash info. To accomplish this I will create a protected level function that returns false if params are nil or have no :user key. Then if the hash passed in has :user and it’s :password && :password_confirmation have content, we just return the hash. Here is our test!
And then the implementation!
So far so good. We now have a PRO (pure ruby object) that is easy to test and verify. A quick note here on style. You will see me do a lot of
to_s casting. I do this as a confident code style (a la Avdi!) to avoid conditional checks on nil and empty string. I prefer NOT to use rails methods when I can help it… remember, PRO FTW. A
nil.to_s simply returns “”.
We are almost there! Let’s keep at it and then we can discuss the result.
The last thing to implement here is the actual “cleaner” method. If the hash passed in has a :user key with :password_confirmation && :password and those keys have NO values, we want to strip the keys out so our controller doesn’t try to save empty or invalid passwords.
As always, let’s start with the tests. One for the :password and one for the :password_confirmation. Notice my tests always adhere to the SRP as well. This keeps setup to a minimum!
That looks good. We will keep the
:admin key, but toss the password fields as they have no values. Time to implement!.
I omitted the code you have already looked at for brevity and focus.
This code shouldn’t be too tough to figure out. If
call hasn’t short circuited on empty or valid params, we use reject the keys we don’t want, then we return a new hash without those values.
Here is the final code:
Yes, we went for a 4 liner to much more code, but that code is now completely testable with no rails setup required! I will also argue that this Single Responsibility Object is very easy to read, and understand. Also, it is now decoupled from the controller, which to me is always a win!
Another thing to note here is my code is much more along the lines of “Tell, don’t ask”. The original 4 liner was all about asking details about the hash, and then an in-place modification of that hash. The original also would choke on quite a few edge cases which I am covering, ie nil params, params without the appropriate keys and structure.
I think highly robust code that is easy to change is what makes beautiful Ruby code beautiful. Properly used Ruby idioms and appropriately applied design patterns makes all the difference between the Ruby most devs write, and the Ruby that Rubyists write.
I have one last thing to point out here. Running these 4 tests on my PRO…
I can run over 3000 tests a second. Run a single rails test from your test suite and compare :)
What do you think? Is my larger code better, more readable, less coupled, and now testable? Tweet your thoughts and tag em #rubyloverefactoring.