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.
18.225.57.126