Coding as a Craft 2.0
  • Introduction
  • Week 1 - Programming Basics - Ruby
    • Understanding the problem statement
    • User stories
    • Pair programming
    • The ATM challenge
      • Step 1 - Setting the stage
      • Step 2 - The core functionality
      • Step 3 - Interacting with objects
      • Step 4 - Refactoring
      • Step 5 - Testing the sad path
      • Step 6 - Cash is King
      • Step 7 - The Account
      • Step 8 - The Person
      • Step 9 - Making it all work together
    • Library Challenge
      • Important Topics
    • Extras
  • Week 2 -Programming Basics - JavaScript
  • Week 3 - TypeScript and Angular
  • Week 4 - Ruby on Rails Basics
  • Week 5 - Working With Legacy Code
  • Week 6 - Midcourse Project
  • Week 7 - Going Mobile
  • Week 8 & 9 - Advanced SaaS Applications
  • Week 10 - Expose and Consume API's
  • Configuring RSpec
Powered by GitBook
On this page
  • Tip 1 - pin code
  • Tip 2 - expiry date
  • Tip 3 - the account status
  • Tip 4 - the account owner
  1. Week 1 - Programming Basics - Ruby
  2. The ATM challenge

Step 7 - The Account

Now that we are finished (at least for now) with the Atm class, we should move forward and create theAccount class.

As a Customer
In order to manage my funds
I need to have a personal account

We will go over the same steps as we did when creating the Atm class.

  1. create a test file (account_spec.rb) and

  2. create a implementation file (account.rb)

In the spec file we'll be writing our spec for code that we are planning to implement. As usual there are decisions about what attributes to add to the class and what methods needs to be defined.

Fortunately, we've already have much of the necessary information about that class to make those decisions easier.

Remember the instance_double we used while testing the Atm's functionality? If we examine that object closely we can see that the attributes we used ware :pin_code, :balance,:account_status and :exp_date. At the very least these are the attributes we need to assign to theAccount class definition.

There is one thing that the Account should have, that was never needed in the interaction wit Atm but that should be a part of it. That is an account owner.

We'll let you code on your own but provide an recording that showcase a possible workflow.

Tip 1 - pin code

Some tips for testing.

A Pin number is generally a 4 digit number. One way to check if a number has 4 digits is this.

it 'check length of a number' do
  number = 1234
  number_length = Math.log10(number).to_i + 1
  expect(number_length).to eq 4
end

Tips: Try running parts of this in IRB if you wonder why the Math.log10 method can be used for this and why we need to call .to_i and + 1 on the result.

Also, I would suggest that we randomize the pin code when we initialize a new Account object. A 4 digit number can be randomly generated with:

rand(1000..9999)

Tip 2 - expiry date

The expiry date on atm cards (and other credit cards) is generally stored in the format of month/year - like "04/16" that translates to April 2016.

When we set the :exp_date we need to make the calculation of today's date and add a predefined amount of years that we want the card to be valid for (remember that for the purpose of this exercise, the account symbolizes BOTH a bank account AND an atm card)

Let's write a test to see if the :exp_date is set in initialize.

spec/account_spec.rb

it 'is expected to have an expiry date on initialize' do
    # Here we set the validity of the card to 5 yrs as default
    expected_date = Date.today.next_year(5).strftime("%m/%y")
    expect(subject.exp_date).to eq expected_date
end

So, how can we implement a method to set an expiry date when an account object is created?

First of all, we need to tell the class that the default validity of the card is 5 yrs. To do that, we can set a type of variable called constant with the default value of how many years a new card should be valid for. It can look something like this.

class Account
    STANDARD_VALIDITY_YRS = 5
end

(I would like you to find out why I use capital letters in the variable name and find out what we mean when we say that you need to avoid Magic Numbers in your code.)

When we set the :exp_date value, we can start with today's date and add the value of the constant and finish off by formatting the date the way we want it. It could look something like this.

def set_expire_date
    Date.today.next_year(Account::STANDARD_VALIDITY_YRS).strftime('%m/%Y')
end

(Do you really need Account::STANDARD_VALIDITY_YRS? Perhaps STANDARD_VALIDITY_YRS in enough? Try it out...)

Tip 3 - the account status

Another check that the Atm does on the Account is to check the :account_status. That will be the next thing we implement on the class.

We start with a spec.

spec/account_spec.rb

it 'is expected to have :active status on initialize' do
  expect(subject.account_status).to eq :active
end

Notice that we are using the datatype Symbol to set the :account_status.

And again we need to set that attribute in our initialize method.

lib/account.rb

def
initialize
    [...]
    @account_status = :active
end

Okay, so an account has status of :active when it is instantiated (created) . But how about if we would like to deactivate an account. We could simply set the value of the :account_status attribute to:deactivated. But we can (and should) create a method for that. The question is if we should create a Class method or an Instance method. You need to research the difference between this type of methods but consider this two different approaches.

def self.deactivate(account)
 account.account_status = :deactivated
end

def deactivate
  @account_status = :deactivated
end

Examine the two following ways of using the methods above.

it 'deactivates account using Class method' do
  Account.deactivate(subject)
  expect(subject.account_status).to eq :deactivated
end


it 'deactivates account using Instance method' do
  subject.deactivate
  expect(subject.account_status).to eq :deactivated
end

Use the one that you find best in your implementation but please be ready to make an argument about your choice.

Tip 4 - the account owner

Each account that we create should have an owner. The owner should have a class of his own. For the moment that class is not defined so we will go ahead and pretend again that there is an instance of Person by using an instance_double.

spec/account_spec.rb

# we create the double in our `describe` block and give him one sigle attribute
let(:person) {instance_double('Person', name: 'Thomas')}
# and modify our `subject` subject { described_class.new({owner: person}) }

First let's see if the owner is being set.

it 'is expected to have an owner' do
    expect(subject.owner).to eq person
end

And add a test for a mandatory owner for each instance of Account.

spec/account_spec.rb

it 'is expected to raise error if no owner is set' do
    expect { described_class.new }.to raise_error 'An Account owner is required'
end

Moving on to the implementation to make these two specs to pass (as usual, this is a suggestion, examine the code closely before using it).

lib/account.rb

[...]

def initialize(attrs = {})
  [...]
  set_owner(attrs[:owner])
end


private

defset_owner(obj)
# here we are using a Ternary Operator for the first time
# take a look at this StackOverflow aswer to find out more
# http://stackoverflow.com/a/4252945
  obj == nil ?  missing_owner :@owner = obj
end

def missing_owner
  raise "An Account owner is required"
end
PreviousStep 6 - Cash is KingNextStep 8 - The Person

Last updated 7 years ago

Note that we are using a new matcher (raise_error). There are many more matchers we can use when writing our tests. Take some time to read through the documentation about .

RSpec's built-in matchers