Refactoring to Use a Database

In our current system, the balance of the single account is stored in a file and read and written by the BalanceStore class. In our new design, we will make the Account responsible for reading the balance straight out of a database instead. It’s refactoring time again!

We’ll start by moving the Account class into its own lib/account.rb file and make it an ActiveRecord class:

databases/00/lib/account.rb
 
require ​'active_record'
 
ActiveRecord::Base.establish_connection(:adapter => ​'sqlite3'​,
 
:database => ​'db/bank.db'​)
 
ActiveRecord::Migrator.migrate(​"db/migrate/"​)
 
class​ Account < ActiveRecord::Base
 
validates_uniqueness_of :number
 
def​ queue
 
@queue ||= TransactionQueue.new
 
end
 
def​ credit(amount)
 
queue.write(​"+​#{amount}​,​#{number}​"​)
 
end
 
def​ debit(amount)
 
queue.write(​"-​#{amount}​,​#{number}​"​)
 
end
 
end

At the top of the file we set up ActiveRecord to use a SQLite database in db/bank.db. We are also telling ActiveRecord to run migrations in the db/migrate/ directory. This will force it to update our database schema, if necessary, as soon as we load this file. Then we create our Account, inheriting from ActiveRecord::Base so that it will act on the accounts table we’re about to create.

We’ll create an accounts table shortly with three columns:

id

The primary key, which is generated automatically by ActiveRecord.

number

The account number, which serves as a secondary key. This has to be unique. We’ve used ActiveRecord’s validates_uniqueness_of method within our Account class to enforce this.

balance

The current balance of the account.

ActiveRecord will make all three fields available as attributes of each Account instance. This is why we’ve removed the :balance field that we had before we refactored the class. We’ve also changed the credit and debit methods to write out the account number as well as the amount so that the TransactionProcessor can know what account a message refers to.

Let’s set up a database migration that will create the accounts table if it doesn’t exist. We’ll store the migration script in db/migrate/01_create_accounts.rb:

databases/00/db/migrate/01_create_accounts.rb
 
class​ CreateAccounts < ActiveRecord::Migration
 
def​ change
 
create_table :accounts ​do​ |t|
 
t.string :number
 
t.integer :balance
 
end
 
end
 
end

Now that we have an Account that knows how to persist itself along with the balance it’s time to get rid of the BalanceStore class, so we will simply delete the lib/balance_store.rb file. We also need to modify features/support/hooks.rb, which no longer needs to reference it:

databases/00/features/support/hooks.rb
 
Before ​do
 
TransactionQueue.clear
 
end

The next step is to update our require_relative statements. The top of our lib/nice_bank.rb now looks like this:

databases/00/lib/nice_bank.rb
 
require_relative ​'transaction_queue'
 
require_relative ​'account'
 
 
class​ Teller

And here is lib/transaction_processor.rb:

databases/00/lib/transaction_processor.rb
 
require_relative ​'transaction_queue'
 
require_relative ​'account'
 
 
transaction_queue = TransactionQueue.new
 
 
puts ​"transaction processor ready"

Before we run Cucumber again, we’ll add the activerecord and sqlite3 gems to our Gemfile:

databases/00/Gemfile
 
source :rubygems
 
 
gem 'sinatra', '1.3.2'
 
gem 'service_manager', '0.6.4'
*
gem 'activerecord', '3.2.7'
*
gem 'sqlite3', '1.3.6'
 
 
group :development do
 
gem 'rspec', '2.11.0'
 
gem 'cucumber', '1.2.1'
 
gem 'capybara', '2.0.0.beta2'
 
gem 'launchy', '2.1.0'
 
end

Now let’s run bundle install followed by cucumber:

 
== CreateAccounts: migrating =================================================
 
-- create_table(:accounts)
 
-> 0.0008s
 
== CreateAccounts: migrated (0.0009s) ========================================
 
 
Starting transaction_processor in ~/message_queues/01 with
 
'ruby lib/transaction_processor.rb'
 
transaction processor ready
 
Server transaction_processor (94557) is up.
 
Feature: Cash Withdrawal
 
 
Scenario: Successful withdrawal from an account in credit
 
Given my account has been credited with $100
 
When I withdraw $20
 
Then $20 should be dispensed
 
lib/transaction_processor.rb:13:in `block (2 levels) in <main>':
 
undefined local variable or method `balance_store' for main:Object (NameError)
 
from ~/databases/00/lib/transaction_queue.rb:21:in `read'
 
from lib/transaction_processor.rb:10:in `block in <main>'
 
from lib/transaction_processor.rb:9:in `loop'
 
from lib/transaction_processor.rb:9:in `<main>'
 
And the balance of my account should be $80
 
 
expected: 80
 
got: nil
 
 
(compared using ==)
 
(RSpec::Expectations::ExpectationNotMetError)
 
./features/step_definitions/account_steps.rb:7
 
./features/support/async_support.rb:8
 
./features/support/async_support.rb:6
 
./features/support/async_support.rb:6
 
./features/step_definitions/account_steps.rb:7
 
features/cash_withdrawal.feature:6
 
 
Failing Scenarios:
 
cucumber features/cash_withdrawal.feature:2
 
 
1 scenario (1 failed)
 
4 steps (1 failed, 3 passed)
 
0m2.264s
 
Shutting down transaction_processor (94557)
 
Server transaction_processor (94557) is shut down

The first thing we see is that the database is automatically migrated—the accounts table is created. This technique of letting the application automatically migrate the database when it starts is a handy trick. But then we get a failure further down.

It looks like we have some more work to do. Not to worry—Cucumber tells us exactly what we need to fix. We no longer have a balance_store, so we need to load the balance in a different way.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.225.57.126