Ruby 101 for .NET Developers: Use blocks to help you refactor 5
As I've learned more about blocks in Ruby, I've had more fun refactoring my code. I first learned about refactoring from Martin Fowler's excellent book about six years ago when I was doing C++ programming for a living. Although the examples were in Java, it was very readable, and that was my first nudge down the agile path. I then started to understand what unit tests were all about, because I could refactor if I already had unit tests written... and then it was on to extreme programming... and the rest is history.
But back to refactoring... I loved refactoring in C#. My refactoring mainly consisted of just breaking up long methods into shorter ones. Once in a while I would create a new class out of a bunch of methods if I felt the existing class was getting too many responsibilities (in other words, more than one :-). But mainly, I was just cutting and pasting code into new methods, to keep each method short and as high-level-looking as possible.
In Ruby, I pretty much do the same thing, but utilizing blocks gives me a weapon I didn't really have before. I suppose the best analogy in C#/VB.NET would be the use of a delegate, but in Ruby the syntax is nicer and easier to understand, at least for me.
Here's an example... I've been working on a payment gateway that processes credit card transactions. First I worked on the part that "preauthorizes" the credit card:
class PaymentGateway
def self.preauth!(payment)
begin
if available?
inputs = create_inputs_for_preauth(payment)
result = call_trust_commerce_api(inputs)
else
result = unavailable_result(:preauth)
end
rescue => e
RAILS_DEFAULT_LOGGER.warn "Exception during PaymentGateway.preauth!: #{e.message}"
result = { 'status' => 'error', 'error_type' => e.message }
end
create_authorization_from_result(:preauth, payment, result)
result
end
end
No need to worry about the details here - basically I create the inputs I need to send to our payment processor, call the api with the inputs, and capture the result (the result is a hash); then I create an Authorization row in our database with the results; and finally I return the raw results I got from the payment processor.
Anyway, my tests passed, and I was happy. A few days later I started to work on the part where we "capture" the credit card (our payment processor calls it a "post authorization"):
def self.capture!(payment)
begin
if available?
inputs = create_inputs_for_capture(payment)
result = call_trust_commerce_api(inputs)
else
result = unavailable_result(:postauth)
end
rescue => e
RAILS_DEFAULT_LOGGER.warn "Exception during PaymentGateway.postauth!: #{e.message}"
result = { 'status' => 'error', 'error_type' => e.message }
end
create_authorization_from_result(:postauth, payment, result)
result
end
The alert reader will now be almost asleep, as the capture! code looks almost identical to, and just as boring as, the preauth! method. As good developers, we hate redundancy, right? I wanted to refactor all the common elements of these two methods into a single method that could be called to do the pre- and post-authorization steps, plus any other kind of credit card action I might need later.
As it stands, the methods have only two differences:
- The use of
:preauthor:postauthas a parameter to a couple of functions - The call to create the inputs is either
create_inputs_for_preauthorcreate_inputs_for_capture
The first one was easy - I can just pass that parameter into the new function. It was the second part - creating the input object - that gave me pause. How would the new, genericized method know which method to call? Here's one way:
def self.authorize!(action, payment)
begin
if available?
inputs = action == :preauth ? create_inputs_for_preauth(payment) : create_inputs_for_capture(payment)
result = call_trust_commerce_api(inputs)
else
result = unavailable_result(action)
end
rescue => e
RAILS_DEFAULT_LOGGER.warn "Exception during PaymentGateway.#{action.to_s}!: #{e.message}"
result = { 'status' => 'error', 'error_type' => e.message }
end
create_authorization_from_result(action, payment, result)
result
end
Ugly. What if I add a third action? And I knew I would be adding a third action later on called :credit, which will credit someone's credit card in case they return the product. Should I write a switch statement and update it every time I need to support a new kind of action? No, that didn't sound right. So I decided that the code calling this method would be responsible for creating the input data.
However, I didn't just want to pass the input data as another parameter, because the inputs aren't always needed. I only need the inputs if the payment processing is available at that moment (the available? method controls this behavior, and can be overridden when desired in certain circumstances). So what I needed was a way for the authorize! method to collaborate with the original calling code, and only when needed.
Ruby blocks to the rescue! Now the ternary-operator-turned-ugly-switch-statement code gets replaced with this:
inputs = yield
This means that the authorize! method will call back to the client code at that point. The client code will now responsible for supplying a block of code that can generate the appropriate input data:
self.authorize(:preauth, payment) { create_inputs_for_preauth(payment) }
So now, the final code looks like this:
def self.preauth!(payment)
self.authorize(:preauth, payment) { create_inputs_for_preauth(payment) }
end
def self.postauth!(payment)
self.authorize(:postauth, payment) { create_inputs_for_capture(payment) }
end
private
def self.authorize!(action, payment)
begin
if available?
inputs = yield
result = call_trust_commerce_api(inputs)
else
result = tclink_unavailable_result(action)
end
rescue => e
RAILS_DEFAULT_LOGGER.warn "Exception during PaymentGateway.#{action.to_s}!: #{e.message}"
result = { 'status' => 'error', 'error_type' => e.message }
end
create_authorization_from_result(action, payment, result)
result
end
Now I can easily add more actions without having to change the authorize! method, even though the input data might need to be generated in a different way for each eaction.
Keep in mind, there are many ways I could have refactored this stuff. I chose to use a block to help me come up with a solution this time, but there are certainly other ways you could have removed the redundancies, especially since refactoring techniques is often a matter of taste and style. There's no one right way to do it. But using a block is a technique that those of us new to Ruby might not use too often, so I thought I'd show an example of it here.
And now I get to refactor the authorize! method! :-)



Oh, thanks very much. Thats just tipped me over to learning Ruby and RoR....
;-)
Nice post - nice and easy to read (for a C# person), well explained etc. All good.
/me heads off to find a good Ruby tutorial or book, and install RoR on my Mac.
Great post Jeff not being fluent in Ruby I still had the 'Ah Ha' moment when I read the final code. The idea of being able to pass code into something that you are calling has so much potential sort of a mini implementation of the Strategy Pattern.
Cheers
@Nic: Excellent! :-) I recommend the "Agile Web Development with Rails" by pragprog.com, and/or "Ruby for Rails" by David A. Black (best price is probably at amazon.com, though I'm not positive). For installing Rails on your Mac, try this: http://hivelogic.com/narrative/articles/ruby-rails-mongrel-mysql-osx.
@Blair: Yes, Strategy is a probably a very good way to describe it. Hmmm. That would have saved me about five paragraphs if I had thought of that first :-)
I have the reflection that the cogitatum is given continuously as an objective unity in a multi-form and changeable multiplicity of noematic descriptions, which belong determinately to it.
I now shift the weight of transcendental evidence of the consciousness of internal time from the ego to noetic acts.