TDD in Ruby Course

Thursday, September 03, 2015

Using Graph of Objects as the Unit of Reuse

Composition


I was refactoring code from an old project. It had used lot of stubs and mocks. The resulting design was not reviewed after taking a break from coding. But the methods that use the same variables were grouped together. This could also be due to the fact that I was coding alone. TDD and Pair-Programming go together. So when I revisited the code after a few months break, I was able to look at it from a fresh new perspective.

By rewriting code from scratch, while developing the gem, you are forced to think about the user of the library when you have to write the README, gem summary and description in the gem spec file. This  forces you to think about the functionality of the gem and how it can be used from a user’s perspective. Coming up with a concise description of the gem also defines how focused it should be, this helps in developing a cohesive gem.

By using structs instead of stubs and mocks, the code became more cohesive. During the rewriting process, I constantly asked myself :

“How likely is this going to change if Rails changes?”. 

I decided to keep dependencies such as product_id, product_name and anything that did not call ActiveRecord methods as safe bets because I can define methods with the same name on those objects.

Making the objects cohesive was a conscious choice, I asked myself :

“Is this data operating on this object all the time?”. 

If not, I split them so that they did operate with the object all the time.

The private methods were moved to small highly cohesive classes that can be composed to accomplish higher level tasks. The Micro Gem packages a group of objects that collaborate to accomplish one task that is done very well. The objective is not to reuse objects in different contexts but to isolate dependencies and provide a specific service to a developer. It encapsulates the knowledge required to implement something and makes it easy for other developers to utilize the service provided by the group of objects collaborating together.

Developing a quality product is challenging. There is no silver bullet. There is no one right way to do it. It’s like a stereo dial, you have to try different approaches and learn what combination works best for you. It could be top-down, bottom-up or combination of top and bottom up approaches with, some upfront design, TDD, PPP etc.

Took related private methods and moved them into a small classes with well defined interface that encapsulates the knowledge about accessing the external data structure, Paypal in this case.

About Not Aiming for Object Reuse


By focusing on a group of objects that provide one service, we avoid the following questions that can lead to confusion about the code base.
  1. Who are the clients of this class?
  2. What is the impact of changing the class?
  3. Which use cases need this class?
Confusion is the enemy of clarity and leads to difficulty in maintenance. Using a graph of objects that has single well defined purpose is a better unit of reuse in the long term for ease of maintenance.

Data Dictionary - Do you really need it?

A data dictionary is a database that describes all the significant data in a project. On large projects, a data dictionary is also useful for keeping track of huge amount of class definitions. On large team projects, it’s useful for avoiding naming clashes. A clash might be a direct, syntactic clash, in which the same name is used twice, or it might be a more subtle clash (or gap) in which different names are used to mean the same thing or the same name is used to mean subtly different things. For each class, the data dictionary contains the item’s name and description. The dictionary might also contain notes about how the item is used.

-- Steve McConnell (Code Complete 2)

How practical is creating and maintaining data dictionary? In a big company, it's difficult to agree upon a common name. So I think this is not practical. Good news is data dictionary is not required if you create Micro Gems. Since it provides a namespace for the classes that collaborate to provide a cohesive unit of functionality.  You could have many gems with the same class name but has different methods to capture different abstractions.

Wednesday, September 02, 2015

Colt Gem Ruby Critic Analysis

You can see all the classes gets a 'A' rating.

Here is the Churn vs Complexity Chart

The only code smell is the long parameter list. This cannot be avoided because all the parameters are needed to make the Stripe API call. We have an option of creating a value object to provide the needed values but it complicates the client code where you need to create an instance of the value object and unpacking it in the implementation. This is a conscious design choice and is not based on ignorance.





Top Down Vertical Slice Risk Oriented Integration

How can you avoid developing a monolithic Rails app?


1. Write a small Rails app for one function.
2. Get it working.
3. Extract the re-usable code from the Rails app into a Micro Gem.
4. Make sure the dependency direction is from the Rails app to the Micro Gem. If you have dependency that goes in the other direction to the Rails framework, you will have headaches during Rails upgrade. It will defeat the purpose, because you will end up with Spagetti code.
5. Refactor the Rails app to use the Micro gem.

Repeat the process for flushing out the details of other features. Only the features with high risk are split into a small Rails app for each of the high risk feature. The main Rails app uses the Micro Gems to delegate the tasks.

In my project, I have extracted dependencies that are due to third-party API such as:

1. Amazon S3
2. Paypal Express Checkout
3. Stripe Subscription API

to mention a few.  These are not just risky parts of the system, they also have their own release schedule. We don't want to rely on a third party release schedule. We can upgrade the Micro Gems as per our schedule and use the newer version of our gems in the project. We also contain any problems that may arise by isolating them into it's own Micro Gem that provides a useful functionality. I also don't need the flexibility of switching one of these third party API with their alternatives.

I have also extracted gems for functionality that is run as the background jobs when some event happens in the Rails app.

Reference


Code Complete 2 by Steve McConnell

Monday, August 31, 2015

Element not found in the cache - perhaps the page has changed since it was looked up

The workaround is to add a sleep call for 3 seconds. Let me know if there is a better solution.

WARNING: VCR::RSpec::Macros is deprecated. Use RSpec metadata options instead `:vcr => vcr_options`

Change this:

RSpec.configure do |config|
  config.extend VCR::RSpec::Macros #deprecated
end

to :

VCR.configure do |config|
  config.configure_rspec_metadata!
end

Saturday, August 29, 2015

Fail Early and Loudly

One of the things I learned the hard way is that a software should fail loudly. If you fail silently, it becomes very difficult to find the cause of the problem and fix it quickly. Raise exception as close as possible to the failure so that we can quickly figure out the issue. The exception should contain all the relevant data, the location and the likely cause of failure.

The concept of failing loudly is discussed in the Code Complete 2 book by Steve McConnell. If you just read the theory, you need to expose yourself to that theory repeatedly so that you will be able to apply it in practice. If you learn by experience it becomes easier to grasp that theory quickly and you will never forget it.


Example 1:


One of the gems that I developed requires Ruby 2.2.2 or above. When I did:

$ gem install merlot
Fetching: posix-spawn-0.3.11.gem (100%)
Building native extensions.  This could take a while...
Successfully installed posix-spawn-0.3.11
Fetching: pdf-core-0.6.0.gem (100%)
Successfully installed pdf-core-0.6.0
Fetching: ttfunk-1.4.0.gem (100%)
Successfully installed ttfunk-1.4.0
Fetching: prawn-2.0.2.gem (100%)
Successfully installed prawn-2.0.2
Fetching: merlot-0.1.0.gem (100%)t.1.0.gem
ERROR:  Error installing merlot:
merlot requires Ruby version >= 2.2.2.

Boom! The command fails at the very last step. It does not fail early. The precondition must be checked before wasting time by installing all the dependent gems.


Example 2 :


I turned off my WiFi and did:

$ gem install bacardi
ERROR:  Could not find a valid gem 'bacardi' (>= 0), here is why:
          Unable to download data from https://rubygems.org/ - no such name (https://rubygems.org/specs.4.8.gz)

The error message I receive has no indication on how the user can recover from this problem. Ideally, we want our software to check for the precondition to be satisfied. In this case, a valid Internet connection. This also happens when I am at Starbucks and I have connected to the WiFi but I have not agreed to the terms and conditions. Wouldn't it be better to catch this particular exception and provide a clear message to the user like:

'We have detected a problem with your Internet connection. Please check your connection and try again.'.

The software we develop following this principle would be user friendly and robust under abnormal conditions.

Saturday, August 22, 2015

Paypal Express Checkout Error

Problem :

Security header is not valid", "ErrorCode"=>"10002"

Solution:

This error is shown when you are not properly logged into the sandbox account on paypal's site.

0. Login to www.paypal.com
1. Go to https://developer.paypal.com/developer/accounts
2. Copy API credentials for a sandox user with Business-Pro account.

1. Login to sandbox.paypal.com
2. Got to your site and checkout
3. When sent to paypal, login as one of your buyer test accounts

Resources

1. Bogus gateway to test PayPal
https://github.com/sideshowcoder/active_merchant/blob/192f49762172dc41450d7a3605158d45eb55b83b/lib/active_merchant/billing/gateways/paypal_bogus.rb
2. Classic API Endpoints
https://developer.paypal.com/webapps/developer/docs/classic/api/endpoints/
3. Merchant SDK Ruby
https://github.com/paypal/merchant-sdk-ruby
4. How to create and process an order using Express Checkout
https://developer.paypal.com/docs/classic/express-checkout/ht_ec-orderAuthPayment-curl-etc/
5. Sample Webapp that generates SDK code to use
https://paypal-sdk-samples.herokuapp.com/merchant/get_express_checkout_details

Thursday, August 20, 2015

missing files in gem after gem build

Bundler works in a weird way. The files that are not checked in to git will not get included. You have to add all the files to the git and then run gem build. You can verify it has included all the files by running gem unpack and opening the unpacked directory to verify the new files.

Thursday, August 13, 2015

Using RSpec with Ruby Gem

You can generate the required files when you create the directory structure for a gem by doing :

bundler gem --test=rspec my_gem

If you have already created the gem skeleton, it uses Minitest by default. You can make the following changes to switch to rspec:

1. Add :

spec.add_development_dependency "rspec"

to .gemspec file. Run bundle install.

2. Create Rakefile in the root of the gem:

require "bundler/gem_tasks"
require "rspec/core/rake_task"

RSpec::Core::RakeTask.new(:spec)

task :default => :spec

3. Create spec/spec_helper.rb:

$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)

require 'your-gem'

4. Create spec/yukon_spec.rb:

require 'spec_helper'

describe 'Your Gem' do
  it 'has a version number' do
    expect(YourGem::VERSION).not_to be nil
  end

  it 'does something useful' do
    expect(false).to eq(true)
  end
end

5. Run it:

rspec spec/yukon_spec.rb --color --format doc