You are on page 1of 280

Table

of Contents
1. Introduction 1.1
1. Learning objectives 1.1.1
2. Your first website 1.2
3. ATM Challenge - Ruby basics 1.3
1. Step 1 1.3.1
2. Step 2 1.3.2
3. Step 3 1.3.3
4. Step 4 1.3.4
5. Step 5 1.3.5
6. Step 6 1.3.6
7. Step 7 1.3.7
8. Step 8 1.3.8
9. Step 9 1.3.9
10. Step 10 1.3.10
4. Library challenge - Advanced Ruby 1.4
1. Important topics 1.4.1
5. Javascript Introduction 1.5
1. Variables, objects and arrays 1.5.1
2. Comparisons and Manipulations 1.5.2
3. Javascript Sample Problems 1.5.3
4. Defining Functions 1.5.4
5. Prototypes & Classes 1.5.5
6. Miscellaneous 1.5.6
6. BMI Challenge - JavaScript basics 1.6
1. Jasmine - Set up 1.6.1
2. First tests 1.6.2
3. The calculator 1.6.3
4. The Document-Object Model 1.6.4
5. Web interface 1.6.5
6. Acceptance tests 1.6.6
7. Moving on 1.6.7
7. Fizz Buzz in JavaScript 1.7
1. NodeJS 1.7.1
8. Checkout challenge 1.8
9. Open Weather Challenge 1.9
10. SlowFood challenge - OO & TDD 1.10
1. Step 1 - Setting up the project 1.10.1
2. Step 2 - Focus on the user experience 1.10.2
3. Step 3 - Entity Relationship Diagrams 1.10.3
4. Step 4 - Implementing the core features 1.10.4
5. Step 5 - Working with the database 1.10.5
6. Step 6 - Working with BDD 1.10.6
7. Extra - Setting up RSpec & Cucumber 1.10.7
11. Static Website with Middleman 1.11
1. Week lab 1.11.1
2. Setup Middleman 1.11.2
3. HAML - HTML abstraction markup language 1.11.3
4. SASS 1.11.4
5. Accessing data 1.11.5
2

12.
13.
14.

15.
16.

17.

18.

19.
20.

6. Partials 1.11.6
7. Deploy to Github pages 1.11.7
Ruby On Rails introduction 1.12
BDD with Rails 1.13
Rails Messaging 1.14
1. Working with Legacy Code 1.14.1
2. Tips and Tricks 1.14.2
Mid Course Project 1.15
1. Project Schedule 1.15.1
Going mobile with Ionic 1.16
1. Getting started 1.16.1
2. Cleaning up and adding views 1.16.2
3. Adding the calculator tab 1.16.3
4. Adding functionality 1.16.4
5. Extras - Source code 1.16.5
The Cooper test challenge 1.17
1. The logic 1.17.1
2. The Back-end 1.17.2
3. The Client 1.17.3
4. Connecting the dots 1.17.4
5. Saving and retrieving data 1.17.5
6. Display charts 1.17.6
7. Wrapping up 1.17.7
8. Results tables 1.17.8
SlowFood Online Challenge - Hello World! 1.18
1. Main features 1.18.1
2. Design Sprint 1.18.2
3. Pivotal Tracker 1.18.3
SlowFood API - API first or second? 1.19
Extras 1.20
1. Naming Standards 1.20.1
2. Classes vs Modules 1.20.2
3. Code structure 1.20.3
4. Bower 1.20.4
5. Code Review Instructions 1.20.5
6. About README's 1.20.6
7. MVC 1.20.7
8. Three-Tier Architecture 1.20.8
9. AngularJS 1.20.9

Introduction

Introduction
Craft Academy Bootcamp
Coding as a Craft - in 12 weeks

Craft Academy is a 12-week intensive, practice-oriented course for aspiring junior developers. The
course is delivered as a coding bootcamp by Craft Academy in Gothenburg, Sweden. It gives you the
basics you need to create, develop and launch web-based applications.
The concept of Craft Academy Bootcamp is simple, it is a 12 week intensive coding camp, aimed at
teaching you the fundamentals of web development, enabling you to hit the ground running and
keep running. But what is important is that modern engineering practices are considered to be part of
this foundation. As a student, you work from day one with automated testing, continuous
integration and deployment, and other essential skills. Meaning that when you enter the workforce
and are faced with problem, your instinct will be to solve right rather than just hacking a solution
together.

Open Source
This publication contains the course materials we ask all our future students to go through. You are
free to use it for your personal learning needs even if you are not attending our bootcamp, we
encourage that.
It is however not allowed to use this material in a commercial context without our written
consent. It is not okay and it is against the very meaning of open source.
If you have any questions, feel free to contact us at info@craftacademy.se
Thomas Ochman
Gothenburg, April 2016
www.craftacademy.se

Figure: Craft Academy by Pragmatic Sweden AB

Craft Academy Bootcamp by Pragmatic Sweden is licensed under a Creative Commons AttributionNonCommercial 4.0 International License.

Learning objectives

Learning objectives
Learning objectives
The 4 streams of skills

Technology
We are a coding camp - so one of the most tangible results of your participation in Craft Academy
will be an ability to write code. We will cover different set of programming languages, frameworks
and...

Agile methodology
Collaboration
Business orientation

Your first website

Your first website


Your first website
It's all about the web. During this course we'll be building a lot of applications. For starters we'll be
writing small pieces of software that will be run on our own computers. Relatively soon we'll move
on to more advanced applications built to be deployed an run on a web server.
The idea is that you get to master the different techniques before we introduce another layer of
complexity. Before you know it we'll be deploying fully featured, dynamic web apps on virtual
servers. But not today. Today we'll learn how to crawl so that we can run an marathon in the future.
Learning experience
In this exercise we will start scratching the surface of HTML and CSS and build a small static web
page - and deploy it on the internet for everybody to see. We'll also get a chance to practice some gitskills - an essential part of your skill-set as a developer.
Walkthrough
Note: The following walkthrough is inspired by the guide you can find on
https://pages.github.com/
All of us are using GitHub for storage of our code in the cloud and for collaboration. If you still don't
have a GitHub account - then you should definitely head over to their site and set up an account
ASAP.
One of the many features of GitHub offers is the possibility of to create web sites for users and
projects. We'll take advantage of that feature to publish a personal web site.
Head over to GitHub and create a new repository named username.github.io, where username
is your username on GitHub. In my case it would be: tochman.github.io. If the first part of the
repository name doesn't exactly match your username, it won't work, so make sure to get it right.

Your first website

In your terminal, go to the folder where you want to store your project, and clone the new repository:
$ git clone https://github.com/username/username.github.io
$ cd username.github.io
$ echo "<h1>Hello World</h1>" > index.html
$ echo "<p>I'm Thomas, and I attend the Craft Academy Bootcamp</p>"
>> index.html
Use Git to commit, and push your changes to GitHub:
$ git add .
$ git commit -m "initial commit"
$ git push origin master
Open your browser and go to username.github.io.
You have just deployed your first web site.

Your first website

ATM Challenge - Ruby basics

ATM Challenge - Ruby basics


Ruby basics - ATM Challenge
Learning experience
Review fundamentals learned in the prep course
Learn about Ruby classes, modules, methods and attributes
Learn about unit testing with RSpec and the benefits of writing automated tests
Learn about naming standards
Learn about using double, class_double and instance_double
Learn about debugging and common techniques

The challenge
Our client is a financial institution that wants to allow its customers to withdraw funds from their
accounts using an Automatic Teller Machine (ATM). They have turned to us for a prototype of a
system with limited functionality. Our job is to write a simple Ruby program that can be run in the
Interactive Ruby Shell (IRB).
You will be working with your Coach and your peers to get started with using RSpec as a
testing framework an with implementing your code.

Scope
The following objectives must be met:
An ATM machine can hold up to $1000
Withdrawal can be cleared only if
The ATM holds enough funds
The amount is divisible by 5
the person attempting the withdrawal provides a valid ATM card
Valid pin and expire date
Card status must be active (Not report stolen or lost)
the person attempting the withdrawal has sufficient funds on his account
There are only $5, $10 and $20 bills in the ATM. Withdrawals for amounts not divisible by 5
must be rejected.
Upon a successful withdrawal the system should return a receipt with information about the
date, amount and bills that was dispatched. (The receipt should be presented in the form of a
Hash
Example output
# sucessful withdrawal
{ status: true, message: 'success', date: '2016-01-30', amount: 35,
bills: [20,10,5]}
# wrong pin
{ status: false, message: 'wrong pin', date: '2016-01-30'}
9

ATM Challenge - Ruby basics

# expired card
{ status: false, message: 'card expired', date: '2016-01-30'}

Tips and Tricks


Don't overdo it. Keep your implementation as simple as possible.
Test your code - both manually in IRB but, most importantly with automated tests.
You have to examine all tests before you write your production code. See tests as a blueprint for
your implementation.
**Run one test at the time to avoid a massive output in your terminal. Disable it blocks by
adding an x before (as in xit "Should..." do)
Make sure to commit often and switch navigators (the person that writes code)
This is a good time to read the Naming Standards section.

10

Step 1

Step 1
Step 1 - Setting the stage
The first thing we need to do is to set up the necessary tools we'll be using. We know that we'll be
using Ruby as the programming language. That is already set up on our system.
We also know that we'll be trying to write our application using Test Driven Development - or at least
try to do that. For that we'll need a testing framework. Enter RSpec - the most frequently used testing
library for Ruby applications. Even though it has a very rich and powerful DSL (domain-specific
language), at its core it is a simple tool which you can start using rather quickly.
In order to be able to use it we need to install it. There are two ways to install libraries (gems). A
direct install from your terminal (gem install rspec) or by adding a gem as a dependency to
your application using Bundler. It is pretty simple, you just add a gem to a specific file named
Gemfile.
Let's do that.
Create a new Gemfile from your terminal in the folder that you want to use for your application.
$ touch Gemfile
Add the following content to that file.
!FILENAME Gemfile
source 'https://rubygems.org'
gem 'rspec'
Save and head over to your terminal window and run the bundle install command.
If you get an error message and the system complains about not finding Bundler, just run this
command to install it.
$ gem install bundler
And run bundle install again.
That installs RSpec.
The next step is to initialize RSpec and configure it for our needs.
$ rspec --init
Edit the .rspec file and add --format documentation to see a more verbose rspec output. Your
.rspec file needs to look like this.
!FILENAME .rspec
--format documentation
11

Step 1

--color
--require spec_helper
Now, if you go back to your terminal and run the rspec command, you should see something like this.
$ rspec
No examples found.
Finished in 0.00028 seconds (files took 0.40297 seconds to load)
0 examples, 0 failures
Alright, that means we are set and ready to test.

Using Git
Let me put down some ground rules about version control. Commit often, write good commit
messages and push up to your GitHub account. That is the only way for us coaches to see your
progress. It does not matter if the code is working. We still want to see it. Bad code is better then No
code!
At this stage you need to set up a git repository. I suggest that you create a GitHub repository, copy
the address and add it as a remote to your local repository (We are about to create one).
In your terminal, initialize a new git repository with the init command.
$ git init
Initialized empty Git repository in /your/path/atm/.git/
~/your/path/atm [master|2]
Next, you need to create a .gitignore file. That file is used to keep information about files we
want to EXCLUDE from version control.
$ touch .gitignore
Add at this to that file.
!FILENAME .gitignore
.DS_Store
.DS_Store (Desktop Services Store) is a OSX file that stores custom attributes of its containing
folder, such as the position of icons or the choice of a background image. We don't want to track those
files with git.
Now, perform the following steps.
$ git remote add origin <your git repo url>
$ git add .
$ git commit -am "<your message>"
$ git push origin master

12

Step 2

Step 2
Step 2 - The core functionality
My approach to writing new software is to always do the most important thing first and get it done
before I move on to other, less important, functions. What is the core functionality of this application?
I would argue that creating an ATM that has some funds is the first thing that we should focus on. If
there is no ATM you will not be able to do a withdrawal, right? And it the ATM has no funds you
won't be able to get any cash from it either.
So let's start with creating a ATM class and assign some funds to each ATM that we create. You already
know a little bit about classes from the Prep Course material.
Since we are working with TDD, we start with creating a test file first.
As a reminder, It is important that we agree on three things at this point.
Your tests/specs are placed in the spec folder
Your implementation (or production code) are placed in the lib folder
Your settings (like Gemfile, etc.) are placed in the main project folder
Alright?
Okay, moving on... Create a new file named atm_spec.rb in your spec folder.
$ touch spec/atm_spec.rb
Let's add the following test to that file. Note the keywords describe and it. Also, as in all ruby
programs we are creating blocks with the do and end keywords. Make it a habit that you always add
an end if you type do.
!FILENAME spec/atm_spec.rb
require './lib/atm.rb'
describe Atm do
it 'has 1000$ on intitialize' do
expect(subject.funds).to eq 1000
end
end
Make sure that you run that spec from your terminal.
$ rspec spec/atm_spec.rb
What will follow now is a series of steps that aims at showing you how testing can drive your
development. In the future you will probably skip some of this steps but for now, bear with me.
If you examine the terminal output, you'll see a line like this one.
/your/path/atm/spec/atm_spec.rb:1:in `require': cannot load such fil
e -- ./lib/atm.rb (LoadError)
...
13

Step 2

That means that the spec file can not load ./lib/atm.rb (where we are supposed to have our
implementation code).
Of course not, we haven't created that file yet. There's no lib folder yet either. Let's create all that
now.
$ mkdir lib
$ touch lib/atm.rb
Run your spec again.
rspec spec/atm_spec.rb
/your/path/atm/spec/atm_spec.rb:2:in `<top (required)>': uninitializ
ed constant Atm (NameError)
A new error message. But not the same as before. That is good. So what have we here?
uninitialized constant Atm? Yes, there is no Atm class defined. Let's do that.
!FILENAME lib/atm.rb
class Atm
end
Let's have another go at the spec.
$ rspec spec/atm_spec.rb
Atm
has 1000$ on initialize (FAILED - 1)
Failures:
1) Atm has 1000$ on initialize
Failure/Error: expect(subject.funds).to eq 1000
NoMethodError:
undefined method `funds' for #<Atm:0x007f8043fdf2a8>
New error message? Cool!
Yes, there is no method funds for the Atm class. Let's add that by adding a attr_accessor
:funds to the class. What is attr_accessor? You can read about it in this Stack Overflow
answer.
!FILENAME lib/atm.rb
class Atm
attr_accessor :funds
end
Another go at the spec and another error message.
$ rspec spec/atm_spec.rb
14

Step 2

Atm
has 1000$ on intitialize (FAILED - 1)
Failures:
1) Atm has 1000$ on intitialize
Failure/Error: expect(subject.funds).to eq 1000
expected: 1000
got: nil
Okay, so we expected funds to be 1000 but it was nil. Let's make it so that every time an ATM
object is instantiated the balance is automatically set to 1000.
We can do that by setting that value in the initialize method. initialize is a constructor
method that will be run every time an instance of a class is created.
!FILENAME lib/atm.rb
class Atm
attr_accessor :funds
def initialize
@funds = 1000
end
end
And now, when you run RSpec, the test passes.
$ rspec spec/atm_spec.rb
Atm
has 1000$ on initialize
Finished in 0.00195 seconds (files took 0.67858 seconds to load)
1 example, 0 failures
Yay! First success! Green is GOOD!
Lesson learned: Every feature, no matter how small, will lead to a series of failures. Until it doesn't.
This goes for new, inexperienced programmers, as well as for those of us who has been doing this for
a long time. There's nothing wrong with you. Just get used to it and see everything you do as a
learning experience.
Keep your calm, read the error messages that RSpec so kindly throws at you and make small steps
forward. Be thankful that you have a testing framework that helps you to figure out what is wrong
with your code. Imagine if you were coding without it?
Alright, enough of coding philosophy. Let's move on. This is a great time to do a commit and push up
your code (Unless you already did that).

Doing a withdrawal
Let's add another test to the atm_spec. Inside the describe Atmblock, add this spec.
15

Step 2

!FILENAME spec/atm_spec.rb
it 'funds are reduced at withdraw' do
subject.withdraw 50
expect(subject.funds).to eq 950
end
In my spec file that it block starts at line 7. What I can do is to run JUST that particular block,
instead of the entire spec file. It might seem trivial right now, but further down the road we'll have
dozens of specs and trust me, you don't want to keep running them all at once.
$ rspec spec/atm_spec.rb:7
Run options: include {:locations=>{"./spec/atm_spec.rb"=>[7]}}
Atm
funds are reduced at withdraw (FAILED - 1)
Failures:
1) Atm funds are reduced at withdraw
Failure/Error: subject.withdraw 50
NoMethodError:
undefined method `withdraw' for #<Atm:0x007fac30e79378 @balan
ce=1000>
...
Shoots! New error. Yes, yes, yes. That is supposed to happen! ;-)
So, we have an undefined method 'withdraw'. Alright. let's create the withdraw method
and let it take one argument - the amount we want to withdraw from the Atm.
!FILENAME lib/atm.rb
class Atm
attr_accessor :funds
def initialize
@funds = 1000
end
def withdraw(amount)
end
end
Run RSpec just to see another error message.
$ rspec spec/atm_spec.rb:7
Run options: include {:locations=>{"./spec/atm_spec.rb"=>[7]}}
Atm
funds are reduced at withdraw (FAILED - 1)
Failures:
16

Step 2

1) Atm funds are reduced at withdraw


Failure/Error: expect(subject.funds).to eq 950
expected: 950
got: 1000
...
Okay, I think you get the point now. We don't have to follow the error messages in such detail from
now on.
Let's add some functionality to the withdraw method that actually adjust the balance.
!FILENAME lib/atm.rb
class Atm
#...
def withdraw(amount)
@funds -= amount
end
end
And when you run RSpec again, the test passes. Another one bites the dust!

17

Step 3

Step 3
Step 3
Okay, so we have a basic withdraw method for our Atm class. It's a good start. Now, if we have a
look at the requirements we initially got from our client, we see that a successful withdraw should
generate a response in the form of a Hash.
This hash is the equivalent of a receipt that the Atm prints out in the real life. It should look like
this if the transaction was successful:
{ status: true, message: 'success', date: '2016-01-30', amount: 35,
bills: [20,10,5]}
For unsuccessful transactions, it should look like this:
{ status: false, message: '[reason for failure e. e. wrong pin]', da
te: '2016-01-30'}
Let's break this down.
status
Can be true or false depending if the transaction was successful.
message
A message to the user. We can set that to success when the transaction was successful and to
something else if we for some reason can not perform the transaction.
date
The date of the transaction - simply today's date.
amount
Visible only when transaction was successful.
Simply the amount that was withdrawn.
bills
Visible only when transaction was successful.
An array of bills that was dispatched by the ATM. This symbolize the actual cash you would
get in real life.

Testing the happy path


The first test we will write is the so called "Happy Path". We know that a transaction can either be
successful or rejected for some reason. We'll get back to the rejections (that is the lion-share of the
work that lies ahead of us). At this stage, let's focus on a simple successful transaction.
Let's start with preparing our test.
18

Step 3

The ATM needs to interact with another class - we will call it Account. The Account class will
symbolize both the bank account and a card we can use in the ATM (there is no need to create both an
Account class and a Card class for the sake of this prototype).
However, we have not created that class yet, so in out atm_spec we will use a so called
instance_double in order to be able to test the functionality. Doubles are objects that can be
used as stand-ins for instances of other classes (hence the name instance_double). Even if they
still are not defined (as in our case). We will go over doubles more extensively further down the road
in the camp. You can think of doubles as "fake" objects that we use for testing. We don't want to build
the Account class yet, so we'll just make a fake one for now.
Let's define a class_double in our spec and give it a name of account. We'll give our
account a @balance of 100. Then we'll be able to use this in our testing.
!FILENAME spec/atm_spec.rb
describe Atm do
let(:account) { instance_double('Account') }
before do
# Before each test we need to add an attribute of `balance`
# to the `account` object and set the value to `100`
allow(account).to receive(:balance).and_return(100)
# We also need to allow `account` to receive the new balance
# using the setter method `balance=`
allow(account).to receive(:balance=)
end
[...]
end
Okay, we want the withdraw method to have access to the account object in order to know things
about it. Things like a balance for instance, right? The ATM needs to know if there are enough
funds in the account before it clears the transaction.
First we will write a test and then we will modify the implementation code.
Note: Make sure that you read the comments in the it block below but do not include them in your
spec.
!FILENAME spec/atm_spec.rb
describe Atm do
[...]
it 'allow withdraw if account has enough balance.' do
# We need to tell the spec what to look for as the responce
# and store that in a variable called `expected_outcome`.
# Please note that we are omitting the `bills` part at the momen
t,
# We will modify this test and add that later.
expected_output = { status: true, message: 'success', date: Date
.today, amount: 45 }
# We need to pass in two arguments to the `withdraw` method.
19

Step 3

# The amount of money we want to withdraw AND the `account` obje


ct.
# The reason we pass in the `account` object is that the Atm nee
ds
# to be able to access information about the `accounts` balance
# in order to be able to clear the transaction.
expect(subject.withdraw(45, account)).to eq expected_output
end
end
Now, make sure you run this spec and study the error messages from Rspec carefully.
In order to make this pass, we need to modify the withdraw method in our implementation code.
Again, be mindful of the comments in the code below.
!FILENAME spec/atm_spec.rb
def withdraw(amount, account)
# We will be using Ruby's `case`- `when` - `then` flow control sta
tement
# and check if there is enough funds in the account
case
when amount > account.balance
# we exit the method if the amount we want to withdraw is
# bigger than the balance on the account
return
else
# If it's not, we perform the transaction
# We DEDUCT the amount from the Atm's funds
@funds -= amount
# We also DEDUCT the amount from the accounts balance
account.balance = account.balance - amount
# and we return a responce for a successfull withdraw.
{ status: true, message: 'success', date: Date.today, amount: am
ount }
end
end
Run your specs again.
Note that your first spec, the one that was passing just a moment ago is failing now. The error
message tells you that you have passed in 1 argument but the method expects 2. Why is that?
Well, we have modified the withdraw method since we wrote that first spec. Not the method is
expecting the account object as well as the amount we want to withdraw to be passed in as
arguments. So we need to modify that spec and add account as an argument. (See the modified
expect statement below).
!FILENAME spec/atm_spec.rb
[...]
it 'funds are reduced at withdraw' do
subject.withdraw(50, account)
expect(subject.funds).to eq 950
20

Step 3

end
[...]
Now, everything should go green when you run your tests.

21

Step 4

Step 4
Step 4 - Refactoring
Okay, we will stop here for a moment ad do some changes to our code to make it more readable and
to follow the principle that each method should only have one responsibility. In our case, the way we
have written the withdraw method, the method perform several tasks. Since we will be developing
that method further, we want to introduce a better, more readable structure.
In practical terms, we want to extract some of this methods responsibilities to separate, so called
private methods.
1. We want to extract the check of account.balance to a separate method.
2. We want to extract the transaction to a separate method.
Evaluate this code carefully.
!FILENAME lib/atm.rb
class Atm
attr_accessor :funds
def initialize
@funds = 1000
end
def withdraw(amount, account)
# We will be using Ruby's `case`- `when` - `then` flow control s
tatement
# and check if there is enough funds in the account
case
when insufficient_funds_in_account?(amount, account)
# we exit the method if the amount we want to withdraw is bigg
er than
# the balance on the account
return
else
# If it's not, we perform the transaction
perform_transaction(amount, account)
end
end
private
def insufficient_funds_in_account?(amount, account)
amount > account.balance
end
def perform_transaction(amount, account)
# We DEDUCT the amount from the Atm's funds
22

Step 4

@funds -= amount
# We also DEDUCT the amount from the accounts balance
account.balance = account.balance - amount
# and we return a responce for a successfull withdraw.
{ status: true, message: 'success', date: Date.today, amount: am
ount }
end
end
Note that we have NOT made any changes to our test and if you run them now, they should all pass
green.
Refactoring is all about that. You make your code better WITHOUT introducing any new
functionality.

23

Step 5

Step 5
Step 5 - Testing the sad path
Okay, so now we can do a withdrawal IF there is money in the account.
Let's start thinking about everything that can go wrong with a withdrawal.
For starters, what if there is not enough money in the account? At this point we only return from
the method without any feedback for the user.
Let's change that.
!FILENAME spec/atm_spec.rb
[...]
it 'rejects withdraw if account has insufficient funds' do
expected_output = { status: false, message: 'insufficient funds',
date: Date.today }
# We know that the account created for the purpose of this test
# has a balance of 100. So let's try to withdraw
# a larger amount. In this case 105.
expect(subject.withdraw(105, account)).to eq expected_output
end
[...]
This test should fail for you.
The following implementation is needed.
!FILENAME lib/atm.rb
def withdraw(amount, account)
case
when insufficient_funds_in_account?(amount, account)
{ status: false, message: 'insufficient funds', date: Date.today
}
else
perform_transaction(amount, account)
end
end

24

Step 6

Step 6
Step 6 - More checks
Another check we need to do in the withdraw method is to see if there are funds in the ATM, right?
We can not perform a transaction if there are no funds in the machine.
The ATM has a funds attribute. We can perform a check if the amount we try to withdraw is larger
then the funds available.
Let's add a spec for that.
!FILENAME spec/atm_spec.rb
[...]
it 'reject withdraw if ATM has insufficient funds' do
# To prepare the test we want to decrease the funds value
# to a lower value then the original 1000
subject.funds = 50
# Then we set the `expected_output`
expected_output = { status: false, message: 'insufficient funds in
ATM', date: Date.today }
# And prepare our assertion/expectation
expect(subject.withdraw(100, account)).to eq expected_output
end
And implement a new when in the withdraw method.
!FILENAME lib/atm.rb
[...]
def withdraw(amount, account)
case
when insufficient_funds_in_account?(amount, account)
{ status: false, message: 'insufficient funds in account', date:
Date.today }
when insufficient_funds_in_atm?(amount)
{ status: false, message: 'insufficient funds in ATM', date: Date.to
day }
else
[...]
And, we also need to create a new private method, just as we did with the previous example.
!FILENAME lib/atm.rb
[...]
private
def insufficient_funds_in_atm?(amount)
@funds < amount
end
25

Step 6

The PIN code


The next check will be to make sure that the user passes in the right pin code when trying to withdraw
money from his account - just as in normal life.
We will need to modify the withdraw to accept a pin_code at one of the arguments. This will
have an effect on all our tests.
!FILENAME lib/atm.rb
def withdraw(amount, account) -> withdraw(amount, pin_code, account)
So, after that change most of the tests will fail.
Change every call to the withdraw method to include 1234 as the second argument.
!FILENAME spec/atm_spec.rb
[...]
subject.withdraw(50, '1234', account)
[...]
We also need to change our instance_double we are using for account.
!FILENAME spec/atm_spec.rb
[...]
let(:account) { instance_double('Account', pin_code: '1234') }
[...]
Okay, make sure that all the tests you have written up until now are passing before you move on.
Next, introduce this test.
!FILENAME spec/atm_spec.rb
it 'reject withdraw if pin is wrong' do
expected_output = { status: false, message: 'wrong pin', date: Dat
e.today }
expect(subject.withdraw(50, 9999, account)).to eq expected_output
end
And implement a new when in the withdraw method.
!FILENAME lib/atm.rb
[...]
def withdraw(amount, account)
case
[...]
when incorrect_pin?(pin_code, account.pin_code)
{ status: false, message: 'wrong pin', date: Date.today }
else
[...]
And again, we need to create a new private method, just as we did with the previous example.
26

Step 6

!FILENAME lib/atm.rb
[...]
private
def incorrect_pin?(pin_code, actual_pin)
pin_code != actual_pin
end

Expired card
Let's tackle the check for card expiration date.
First, let's modify our double.
!FILENAME spec/atm_spec.rb
[...]
let(:account) { instance_double('Account', pin_code: '1234', exp_dat
e: '04/17') }
[...]
And, as always, we write a test. (I will not include comments. By now you know what we need to do
to build a test)
!FILENAME spec/atm_spec.rb
it 'reject withdraw if card is expired' do
allow(account).to receive(:exp_date).and_return('12/15')
expected_output = { status: false, message: 'card expired', date:
Date.today }
expect(subject.withdraw(6, '1234', account)).to eq expected_output
end
And again, we need to modify the withdraw method.
!FILENAME lib/atm.rb
[...]
def withdraw(amount, account)
case
[...]
when card_expired?(account.exp_date)
{ status: false, message: 'card expired', date: Date.today }
else
[...]
Now, the method card_expired? is a little tricky. We need to make use of Ruby's Date object.
account.exp_date is of String class. We need to transform it to a Date object and compare it to
today's date. Examine the following implementation closely before implementing it.
!FILENAME lib/atm.rb
[...]
27

Step 6

def card_expired?(exp_date)
Date.strptime(exp_date, '%m/%y') < Date.today
end
Can you understand what we are doing here?

More checks
It is time for you to start to write code on your own. There is yet another one check we need to
perform. The account_status attribute will tell us if an account is active or disabled.
Our class_double will be updated with this attribute to look like this.
!FILENAME spec/atm_spec.rb
[...]
let(:account) { instance_double('Account', pin_code: '1234', exp_dat
e: '04/17', account_status: :active) }
[...]
Things to you to consider (in random order)
Note that we are using a Symbol rather than a String to set account_status.
You need to write a test for what happens if an account is :disabled
You need to update the output of every test that assumes that withdrawal was successful.
You are on your own here. If you are unsure on how to proceed make sure to go over the
methods we've already created. This is a highly repetitive process at the moment. All the
answers lies in in front of you. ;-) Good luck!

28

Step 7

Step 7
Step 7 - Cash is King
Yeah, remember when we kind of skipped adding bills to our successful output? We can't ignore that
requirement any longer. After all, the cash is the reason we use ATM's.
A part of the output our program is supposed to return on successful withdrawal, is an array of bills.
We also know that the ATM holds 5, 10 & 20$ bills, right?
Another thing we know is that one of the conditions for a successful transaction is that the amount a
user can withdraw is divisible by 5.
Knowing all this, we can build a method that tells us what bills we will get from the ATM.
Let's try some stuff out in irb. As always, read the comments as carefully as anything else in this
documentation.
18:05 $ irb
# create an array of denominations. We need to start with the bigges
t value.
# want to know why? Try the other way around for yourself.
2.2.1 :001 > denominations = [20, 10, 5]
=> [20, 10, 5]
# create an embty array and store it in a variable called `bills`
# this object will be populated with our bills.
2.2.1 :002 > bills = []
=> []
# choose an arbitrary amount - remember that we need to stick to the
# business objective. The amount needs to be divisible by 5.
2.2.1 :003 > amount = 65
=> 65
# here comes the tricky part with the `while` loop.
# for each value in the `denominations` array, we subtract it
# from `amount` until amount is lower than zero.
# at the same time we `push` the value into the `bills` array.
2.2.1 :004 > denominations.each do |bill|
2.2.1 :005 > while amount - bill >= 0
2.2.1 :006?> amount -= bill
2.2.1 :007?> bills << bill
2.2.1 :008?> end
2.2.1 :009?> bills
2.2.1 :010?> end
=> [20, 10, 5]
# and now, if we check the `bills` array, we can see that there are
4 positions.
29

Step 7

# 3 x 20$ ans 1 x 5$. Cash is king


2.2.1 :011 > bills
=> [20, 20, 20, 5]
2.2.1 :012 >
The same code without my comments and the irb line numbers.
denominations = [20, 10, 5]
bills = []
amount = 65
denominations.each do |bill|
while amount - bill >= 0
amount -= bill
bills << bill
end
bills
end
bills
A few words about this type of loops in general and specifically the while loop. A loop is a flow
control method that will make code run a number of times until some condition is met.
The while loop
while allows you to specify the condition that must be true to keep looping, then the condition is
evaluated to false the loop exits. An example
count = 0
while count < 5
print '- The count is now ', count, '. '
count = count + 1
end
Result:
- The count is now 0.
- The count is now 1.
- The count is now 2.
- The count is now 3.
- The count is now 4.
The until loop
The until loop is a variation on the while loop but reverse. ;-)
count = 0
until count == 5
print 'The count is now ', count, '. '
count = count + 1
end
Result: same as above.
Pay attention to the double equal signs. In Ruby a == 5 means "Compare a to 5", and a = 5
30

Step 7

means "Set the value of a to 5".


Back to our implementation.
As usual, let's start with our test. We don't necessarily need to add any new specs but rather modify
the ones we already have written.
Let's revisit this spec.
!FILENAME spec/atm_spec.rb
it 'allow withdraw if account has enough balance.' do
expected_output = {
status: true,
message: 'success',
date: Date.today,
amount: 45,
bills: [20, 20, 5]}
expect(subject.withdraw(45, '1234', account)).to eq expected_outpu
t
end
Apart from a slightly updated formatting, we've added bills: [20, 20, 5] to the
expected_output.
If you run this spec now, it will go red.
Let's add bills: to the output and create a method that builds upon our experimental code above
and populates the array.
!FILENAME lib/atm.rb
[...]
def perform_transaction(amount, account)
@funds -= amount
account.balance = account.balance - amount
{ status: true, message: 'success', date: Date.today, amount: amou
nt, bills: add_bills(amount) }
end
def add_bills(amount)
denominations = [20, 10, 5]
bills = []
denominations.each do |bill|
while amount - bill >= 0
amount -= bill
bills << bill
end
end
bills
end
[...]
With this we are closing to the end of the line with the Atm class.
31

Step 7

32

Step 8

Step 8
Step 8 - The Account
Now that we are finished (at least for now) with the Atm class, we should move forward and create
the Account class.
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 class_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 the Account 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.
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)

Programming showcase - Pin and Balance

33

Step 8

atm_account_pi_balance

Playback isn't supported on this device.

0:00 / 8:17

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.
!FILENAME 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
34

Step 8

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

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.
!FILENAME 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.
!FILENAME 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
35

Step 8

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.

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.
!FILENAME spec/account_spec.rb
# we create the double in our `describe` block and give him one sigl
e 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.
!FILENAME 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
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.
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).
!FILENAME lib/account.rb
[...]
def initialize(attrs = {})
[...]
set_owner(attrs[:owner])
end
private
def set_owner(obj)
# here we are using a Ternary Operator for the first time
# take a look at this StackOverflow aswer to find out more
36

Step 8

# http://stackoverflow.com/a/4252945
obj == nil ? missing_owner : @owner = obj
end
def missing_owner
raise "An Account owner is required"
end

37

Step 9

Step 9
Step 9 - The Person
At this step we want to create a Person class and give him 3 attributes: :name, :cash and
:account. We will give you a basic set of specs for that class. Your job will be to implement the
code that will make these specs pass.
Remember, you are free to modify these specs if you find any flaws in it OR if you find another way
of testing the same behavior.
!FILENAME spec/person_spec.rb
require './lib/person'
require './lib/atm'
describe Person do
subject { described_class.new(name: 'Thomas') }
it 'is expected to have a :name on initialize' do
expect(subject.name).not_to be nil
end
it 'is expected to raise error if no name is set' do
expect { described_class.new }.to raise_error 'A name is require
d'
end
it 'is expected to have a :cash attribute with value of 0 on initi
alize' do
expect(subject.cash).to eq 0
end
it 'is expected to have a :account attribute' do
expect(subject.account).to be nil
end
describe 'can create an Account' do
# As a Person,
# in order to be able to use banking services to manage my funds,
# i would like to be able to create a bank account
before { subject.create_account }
it 'of Account class ' do
expect(subject.account).to be_an_instance_of Account
end
it 'with himself as an owner' do
expect(subject.account.owner).to be subject
end
38

Step 9

end
describe 'can manage funds if an account been created' do
let(:atm) { Atm.new }
# As a Person with a Bank Account,
# in order to be able to put my funds in the account ,
# i would like to be able to make a deposit
before { subject.create_account }
it 'can deposit funds' do
expect(subject.deposit(100)).to be_truthy
end
describe 'can not manage funds if no account been created' do
# As a Person without a Bank Account,
# in order to prevent me from using the wrong bank account,
# I should NOT be able to to make a deposit.
it 'can\'t deposit funds' do
expect { subject.deposit(100) }.to raise_error(RuntimeError, '
No account present')
end
end
end
Making all of these tests pass will bring you closer to completing this challenge.

39

Step 10

Step 10
Step 10 - Integrating all parts
Alright, at this stage we can create an Atm, we can create a Person that has an Account. The
Personcan have cash in pocket or hold his money in his Account. All pretty straight forward.
Now we want to create a method that allows a person to withdraw funds from a specific atm and
when he does that 3 things should happen:
1. The balance of the account should DECREASE
2. The funds in the ATM should DECREASE
3. The cash in pocket should INCREASE
Consider these specs.
!FILENAME spec/person_spec.rb
describe 'can manage funds if an account been created' do
[...]
it 'funds are added to the accounst balance - deducted from cash'
do
subject.cash = 100
subject.deposit(100)
expect(subject.account.balance).to be 100
expect(subject.cash).to be 0
end
it 'can withdraw funds' do
command = lambda { subject.withdraw(amount: 100, pin: subject.ac
count.pin_code, account: subject.account, atm: atm) }
expect(command.call).to be_truthy
end
it 'withdraw is expected to raise error if no ATM is passed in' do
command = lambda { subject.withdraw(amount: 100, pin: subject.ac
count.pin_code, account: subject.account) }
expect { command.call }.to raise_error 'An ATM is required'
end
it 'funds are added to cash - deducted from account balance' do
subject.cash = 100
subject.deposit(100)
subject.withdraw(amount: 100, pin: subject.account.pin_code, acc
ount: subject.account, atm: atm)
expect(subject.account.balance).to be 0
expect(subject.cash).to be 100
end
end
Note: There are some new commands and techniques in the code above. Google them, talk to your
40

Step 10

peers and figure out WHY we are using them so you get at good understanding of what we are doing.
I will be going over some of them in my talks and break-out sessions, but it is up to you to find the
best way of using them.
Now I will show you how I implemented the Person class - at least in the first development cycle
(there are plenty of room for refactoring and I expect you to improve on this code).
!FILENAME lib/person.rb
require './lib/account'
class Person
attr_accessor :name, :cash, :account
def initialize(attrs = {})
@name = set_name(attrs[:name])
@cash = 0
@account = nil
end
def create_account
@account = Account.new(owner: self)
end
def deposit(amount)
@account == nil ? missing_account : deposit_funds(amount)
end
def withdraw(args = {})
@account == nil ? missing_account : withdraw_funds(args)
end

private
def deposit_funds(amount)
@cash -= amount
@account.balance += amount
end
def withdraw_funds(args)
args[:atm] == nil ? missing_atm : atm = args[:atm]
account = @account
amount = args[:amount]
pin = args[:pin]
response = atm.withdraw(amount, pin, account)
response[:status] == true ? increase_cash(response) : response
end
def increase_cash(response)
@cash += response[:amount]
end

41

Step 10

def set_name(name)
name == nil ? missing_name : name
end
def missing_name
raise ArgumentError, 'A name is required'
end
def missing_account
raise RuntimeError, 'No account present'
end
def missing_atm
raise RuntimeError, 'An ATM is required'
end
end

42

Library challenge - Advanced Ruby

Library challenge - Advanced Ruby


Library Challenge
Week 1 Ruby challenge
Instructions
Read this entire README carefully and follow all instructions. Go to CraftAcademy/librarychallenge and fork that repository to your own GitHub account.
Challenge time: this weekend, until Monday 9am
Feel free to use Google, Stack Overflow, your notes, previously written code, books, etc. but
work on your own
If you refer to or have in whole or partially used the solution of another coach or student, please
put a link to that in your README
If you have a partial solution, still check in a partial solution to GitHub and create a Pull
Request
You must submit a Pull Request to this repository with your code by 9.30am Monday morning before the stand-up

Learning objective
This challenge will provide you with an opporunity to practice a lot of your newly acquired technical
skills.
Practice TDD to drive your development process
Adopt to a new domain
Work with data stored in hashes and perform
Learn about YAML
Learn about storing information in a text file
Practice writing documentation

Requirements
Write a Library program with the following user stories:

As an individual
In order to get my hands on a good book
I would like to see a list of books currently available in the libra
ry
with information about the title and author
As a library
In order to have good books to offer to the public
I would like to be able to have a collection of books stored in a fi
le
As a library
In order to have good books to offer to the public
I would like to be able to allow individuals to check out a book
43

Library challenge - Advanced Ruby

As a library
In order to make the books available to many individuals
I would like to set a return date on every check out
and I would like that date to be 1 month from checkout date
As an individual
In order to avoid awkward moments at the library
I would like to know when my book is supposed to be returned

Tasks
Fork the challenge repo: https://github.com/CraftAcademy/library-challenge
Run the command 'bundle' in the project directory to ensure you have all the gems
Write your specs and implementation
Be smart about using Git: commit and push often. Use feature branches.
Create a Pull Request as soon as possible
Read the comments from Hound and fix any issues that the service points out.

Tips
Some hints:

A Person needs to have a list of books that he currently has in his possession. That list needs to
include the return date.
The return date can be calculated using the Date object. Out of the box, there are methods you
can use to add days to the current date.
Make use of doubles when writing your specs
Follow the naming conventions/standards for methods and variables

What we are looking for


I'm hoping to see that:

You can take a problem set and write a well tested implementation on your own.
You understand how to define Ruby Classes and work with objects.
You understand how classes can interact with each other.
You know how to make use of arrays, hashes, and associated methods to create dynamic lists.
You know how to write specs and use them as a blueprint in your development.
I can track your work by following you commit history - so please commit as soon you are
done with a feature or when you have made a test pass.
In your Pull Request, I'm hoping to see:

That you are testing the right thing in the right spec file.
That all tests passing - green is good!
High test coverage (above 95% is accepted)
The code is easy to follow: every class has a clear responsibility, methods are short, code is
nicely formatted, etc.
The README.md includes information on how to use your solution with command examples
in irb.
44

Library challenge - Advanced Ruby

Happy coding!

45

Important topics

Important topics
Important topics
A person needs to have an attribute where we can store books/items he or she has checked out from
the library (I called it book_shelf).
The flow of checking out an item is
1. Search for the item in library
2. Check out the item
When an item is checked out we need to store information about it in the book_shelfwith a return
date.

What is YAML?
YAML is a file format for storing object trees and data serialization which is both human readable and
computationally powerful.
All the items in the library must be stored in a yml file. The file feeds to be structured this way.
!FILENAME lib/data.yml
--- :item:
:title: Alfons och soldatpappan
:author: Gunilla Bergstrm
:available: true
:return_date:
- :item:
:title: Skratta lagom! Sa pappa berg
:author: Gunilla Bergstrm
:available: false
:return_date: '2016-05-25'
- :item:
:title: Osynligt med Alfons
:author: Gunilla Bergstrm
:available: true
:return_date:
- :item:
:title: Pippi Lngstrump
:author: Astrid Lindgren
:available: true
:return_date:
- :item:
:title: Pippi Lngstrump gr ombord
:author: Astrid Lindgren
:available: true
:return_date:
46

Important topics

Reading a YML file


The YML file can be parsed as an object and stored in a variable. Let's try that and call the variable
collection. Open IRB.
$ irb
2.2.3 :001 > require 'yaml'
=> true
2.2.3 :002 > collection = YAML.load_file('./lib/data.yml')
=> [{:item=>{:title=>"Alfons och soldatpappan", :author=>"Gunilla B
ergstrm"}, :available=>true, :return_date=>nil}, {:item=>{:title=>"
Skratta lagom! Sa pappa berg", :author=>"Gunilla Bergstrm"}, :avai
lable=>false, :return_date=>"2016-05-25"}, {:item=>{:title=>"Osynlig
t med Alfons", :author=>"Gunilla Bergstrm"}, :available=>true, :ret
urn_date=>nil}, {:item=>{:title=>"Pippi Lngstrump", :author=>"Astri
d Lindgren"}, :available=>true, :return_date=>nil}, {:item=>{:title=>
"Pippi Lngstrump gr ombord", :author=>"Astrid Lindgren"}, :availab
le=>true, :return_date=>nil}]
Knowing this you can create a collection of books for your library.

Writing to a YML file


If we introduce any changes to the object (in out collection variable), we would like to make
then persistent. We need to update (write) to the same file where we got the data from. For example, if
we do this in IRB
# To start with, we mogify the first object in the collection
# by setting `:available` to `false`
2.2.3 :019 > collection[0][:available] = false
=> false
# And now, we open the YML file and store the entire `collection` a
gain.
2.2.3 :020 > File.open('./lib/data.yml', 'w') { |f| f.write collecti
on.to_yaml }
=> 567
Knowing this, you will be able to build a method to update the collection of books in the
libarary after th check-out has been performed.

Searching in a Hash
Let's say that you have the above collection of books or whatever. Now you want to search for a
specific item. If we would like to search for "Pippi Lngstrump", we could do something like this
2.2.3 :002 > collection.detect { |obj| obj[:item][:title] == "Pippi
Lngstrump" }
=> {:item=>{:title=>"Pippi Lngstrump", :author=>"Astrid Lindgren"}
, :available=>true, :return_date=>nil}
So collection is of class: Hash. We are calling #detect on it. What does the #detect method
do?
Note that this will return an exact match, so it requires that you know the exact value of the key you
47

Important topics

are searching for.


How about if you just know part of the title of the book you are searching for? In the method above
we use the equal operator (==), but what if there is another way? Well, we can make use of
#include?.
Try this out (Note that we are only passing in "Pippi").
2.2.3 :004 > collection.detect { |obj| obj[:item][:title].include? "
Pippi" }
=> {:item=>{:title=>"Pippi Lngstrump", :author=>"Astrid Lindgren"}
, :available=>true, :return_date=>nil}
This searchies through the collection and returnes the first object it finds that matches the criteria - in
this case the part of the title. But wait, in our collection we have 2 books with the word "Pippi" in the
title.
Perhaps it would be better if we returned both of them with our search method?
Try this and note that I'm using #select rather than #detect on my collection.
2.2.3 :005 > collection.select { |obj| obj[:item][:title].include? "
Pippi" }
=> [{:item=>{:title=>"Pippi Lngstrump", :author=>"Astrid Lindgren"
}, :available=>true, :return_date=>nil}, {:item=>{:title=>"Pippi Ln
gstrump gr ombord", :author=>"Astrid Lindgren"}, :available=>true, :
return_date=>nil}]
And now I get both objects back.
Knowing this you can build a search method that will allow users to find a book the want to
check out.
```

48

Javascript Introduction

Javascript Introduction
Javascript Introduction
This week you'll learn a completely new programming language: Javascript. While many developers
use Javascript for all three layers of an application (data/persistence, logic, and presentation), in this
camp we'll mostly be using Ruby for logic and data and often both Ruby and Javascript elements for
presentation. During this week, we will use Javascript for both logic and presentation.

Learning Objectives
Practice pair programming & collaboration using Git and GitHub
Basic understanding of JavaScript
Introduction to JQuery and DOM manipulation
Jasmine testing framework & comparison to RSpec
Observe differences and similarities between Ruby and Javascript
Understand the difference between running code on the server vs. in the browser
Javascript and Ruby (and most programming languages) have a lot in common. They both have
variables, functions, arrays, and hashes. And thousands of other matching structures. But in many
cases these are used slightly differently amongst languages. For instance, in Ruby a variable is
declared like so:
age = 32
In Javascript:
var age = 32;
Functions look very different. In Ruby:
def foo(a, b)
a + b
end
In Javascript we'll typically want to store a function inside a variable:
var fn = function foo(a,b){
return a + b;
}
The following pages will give you a huge list of Javascript variables to play with. Open up any
browser (we suggest Chrome), click "Inspect" and click "Console". Then you can put in any
Javascript code - just like in IRB in the terminal.

A few notes about Javascript


As we said above, it runs in the browser and not in the terminal. That may not mean much to
you now, but keep it in mind.
Debugging does not take place with pry but with debugger;. It works in a similar way,
pausing the code. You don't have to install anything.
49

Javascript Introduction

Javascript is very unforgiving. In Ruby you can skip spaces, parentheses, etc. If you forget an
end it will probably tell you expected tEND. Javascript isn't like that. You have to be very
careful about your commas, colons, parentheses, curly brackets, and everything else. Every line
in Javascript should end with a ;.

50

Variables, objects and arrays

Variables, objects and arrays


Variables, objects and arrays
Open the console in your browser (Chrome) and try out these commands:
var string = "Hello";
// local variable, when inside a function
string_2 = "Hello World";
// global variable in default context (window.string_2)
string_3 = 'My string can include quotes: "Yeah" ';
// single or double quote
string_4 = "My really really really \
really long string broken into \
multiple lines";

Numbers
num = 19;
// note lack of ""
num = 0xfe + 2.343 + 2.5e3;
// hex, floats, exponents

Objects
var newObject = new Object();
// constructor
newObject = {};
// constructor shorthand
newObject.name = "Thomas"
// dynamic attributes
newObject.name = null
// it's there (null item)
delete newObject.name
// it's gone (undefined)
newObject["real age"] = 33;
// array notation/hash table
var person = {
name: "Thomas",
details: {
51

Variables, objects and arrays

age: 44,
"favorite color": "orange"
}
}
// create object using JSON (Javascript Object Notation)
// note the difference between age: and "favorite color" - one i
s a symbol and one is a string. They are accessed differently.
person.name
// "Thomas"
person.details["favorite color"]
// "orange"
person.details.age
// 44

Arrays
var newArray = [];
// empty array
newArray[3] = "hello";
// grows dynamically
newArray[2] = 13;
// add any datatype
newArray.push(newObject);
// add new item at last index
newArray.pop();
// remove it from last index

52

Comparisons and Manipulations

Comparisons and Manipulations


Comparisons and Manipulations
JavaScript has some 'weird' datatypes and comparisons.

Datatypes
typeof("text") == "string"
// true
typeof(3) == typeof(3.4) && typeof(0x34) == "number"
// true
typeof(myArray) == "object"
// true (arrays are objects)
typeof(true) == "boolean"
// true
typeof(Math.sin) == "function"
// true
typeof(notThere) == "undefined"
// true (can be useful)

Comparisons
123 == "123"
// true (converts type)
123 === "123"
// false (checks type)
typeof(x) == "undefined"
// true (x isn't there)
x == null
// x is not defined

Numbers
parseInt("123")
// base 10 => 123
parseInt("123", 16);
// base 16 => 291
parseFloat("123.43");
// 123.43
53

Comparisons and Manipulations

isNaN(0/0) == true
// illegal number
3/0 == Infinity
// true (Infinity is displayed when a number exceeds the upper l
imit of the floating point numbers, which is 1.797693134862315E+308)
-3/0 == -Infinity
// true (-Infinity is displayed when a number exceeds the lower
limit of the floating point numbers, which is -1.797693134862316E+30
8)
isFinite(3/0) == true
// false (The isFinite() function determines whether a number is
a finite, legal number. This function returns false if the value is
+infinity, -infinity, or NaN (Not-a-Number), otherwise it returns t
rue.)

Regular expression (regex) string comparisons


matches = "hello".match(/h../)
// returns array ["hel"] or null
myRegex = new RegExp("h..", "ig");
// construct regexp -- no slashes
matches = "hello".match(myRegex);
// use the regex
"hello".replace(/h/,"b")
// => "bello"

Conditionals and Loops


if (string == "Hello"){
alert("Hi");
}
else{
alert("something is wrong!");
}
// if-else popup dialog
a = 3, b = 4;
// multi-assigment
c = a > b ? a : b;
// c gets bigger item (b) (this is a ternary operator)
switch (name){
case "Thomas":
alert("Hi Thomas!")
break
case "John":
54

Comparisons and Manipulations

alert("Hi John.")
break
default: alert("Who are you?")
}
// switch statement
while (i <= n){
console.log(i);
i++;
}
// do something until a value (n) is reached
// don't forget to have i++ or you will loop forever
for (var i=0; i<=n; i++){
console.log(i);
}
// another way to loop an n number of times
for (var key in person){
console.log(key)
}
// do something with person[key]

55

Javascript Sample Problems

Javascript Sample Problems


Javascript Exercises #1
1.
2.
3.
4.
5.

56

Create an array and add three numbers to it.


Use your array to return the second number.
What data type is 123/12? "Things in quotes!"? undefined?
Write an if statement that returns true.
Consider these two arrays: myArray = ['Thomas', 'Amber', 'Raoul'] and
emptyArray = []. Use a for loop to add our names to emptyArray. (Hint: n needs to
be the length of the array. Google a helper method for finding the length of an array in
Javascript. Is it the same as Ruby?)

Defining Functions

Defining Functions
Defining Functions
function foo(a,b){
return a + b;
}
// global function
var fn = function(a,b){
return foo(a,b);
}
// save function as a variable
person.fn = function(a,b){
return a + b;
}
// or as part of object
function bar(a,b){
var n = a;
// local var
function helper(x) {
// defining a function inside of another function
return 1/Math.sqrt(x + n);
// can use local vars
}
return helper(b);
// avoid need for global function
}
foo(1,2) == fn(1,2)
// true (3)
bar(1,3);
// 0.5

Javascript Exercises #2
1. Write a function that returns your first name. Call it.
2. Write a new function that takes your name as an input. The function should return your first
name, plus your last name, as one string. (Hint: strings can be combined with a +)

57

Prototypes & Classes

Prototypes & Classes


Prototypes & Classes
JavaScript Prototypes
All JavaScript objects inherit the properties and methods from their prototype (Date, Array,
RegExp, Function, ....).
We use .prototype to group methods together, not unlike a Ruby class.

Javascript "Classes"
Javascript doesnt have formal class notation, but you can create a constructor and add methods to
it. Examples from here.
function Person(first, last) {
// create "constructor"
this.first = first;
// public variables -- reference current object
this.last = last;
var privateFn = function(first, last){
// private function
}
this.setName = function(first, last){
// public function
this.first = first;
this.last = last;
}
}
Person.prototype.fullName = function() {
// extend prototype
return this.first + ' ' + this.last;
// even at runtime!
}
var bob = new Person("Thomas", "Ochman");
// "new" creates an object
bob.fullName();
// "Thomas Ochman"

Javascript Exercise #3
1. What is this? Does it have an equivalent in Ruby?
58

Prototypes & Classes

59

Miscellaneous

Miscellaneous
Miscellaneous
eval("x = 3"); // execute arbitrary code
timer = setTimeout("myfunction()", 1000)
// execute in 1 second (1000ms)
clearTimeout(timer);
// cancel event

60

BMI Challenge - JavaScript basics

BMI Challenge - JavaScript basics


JavaScript basics - BMI challenge
Body mass index (BMI) is a simple tool that is generally used to estimate the total amount of
body fat. In this challenge we will write a JavaScript program that calculates a individuals BMI
index.

How To Calculate BMI


To calculate BMI, you need to know your weight and height in kilograms and centimeters or meters
(Metric Method). The weight of a person is then divided by the height.
If you know your height and weight in inches and pounds the calculation is a little more complex
(Imperial Method).
Metric Method
The metric formula accepts height measurements in meters and weight in kilograms. If you know
your height in centimeters only, simply divide the number of centimeters by 100 convert it to meters.
For example, a person who is 183 cm tall is 1.83 m tall (183 cm / 100 = 1.83 m).
1. Multiply your height by itself.
2. Divide your weight in kilograms by the value calculated in step 1.
3. The resulting number is your BMI. Compare this BMI value with the weight status table below.
Imperial Method
The imperial formula accepts height measurements in inches and weight in pounds. It's popular in the
US where the imperial system is mostly used. Many people know their height in feet and inches, but
not in inches only.
If this applies to you, we need to convert your height into inches so we can use it in the equation.
There are 12 inches in a foot, so multiply your number of feet by 12 and add them to the number of
extra inches.
For example, if your height is 5 feet 10 inches, multiply 5 by 12 (which gives 60") and add them to
the extra 10 inches (which gives 70").
Now we have the right measurements we can use them in the formula.
There are three simple steps for computing BMI with imperial values:
1. Multiply your weight in pounds by 703.
2. Multiply your height in inches by itself
3. Divide the figure from step 1 by the figure in step 2. The resulting number is your BMI.
Compare this BMI value with the weight status table below.
BMI Weight Status Categories
61

BMI Challenge - JavaScript basics

BMI

Weight Status

Below 18.5 Underweight


18.5 - 24.9 Normal
25 - 29.9 Overweight
Over 30.0 Obese
Since you already know some ruby, here's the implementation of the Person class with a module that
calculates BMI using the metric method. We enclose it here for reference only, going over that code
might help you get started with your JavaScript code. We are using a module, which is a helpful way
of organizing code.
class Person
attr_accessor :bmi_value, :bmi_message, :weight, :height
def initialize(options={})
@weight = options[:weight]
@height = options[:height]
end
def calculate_bmi_met
results = BMICalculator.metric_bmi(self)
end
end
module BMICalculator
def self.metric_bmi(object)
weight = object.weight.to_f
height = object.height.to_f
if weight > 0 && height > 0
final_bmi = weight / (height / 100 * height / 100)
object.bmi_value = final_bmi.round(2)
set_bmi_message(object)
end
end
def self.set_bmi_message(object)
if object.bmi_value < 18.5
object.bmi_message = "Underweight"
elsif object.bmi_value > 18.5 && object.bmi_value < 25
object.bmi_message = "Normal"
elsif object.bmi_value > 25 && object.bmi_value < 30
object.bmi_message = "Overweight"
else object.bmi_value > 30
object.bmi_message = "Obese"
end
end
end

62

BMI Challenge - JavaScript basics

63

Jasmine - Set up

Jasmine - Set up
Testing with Jasmine
Jasmine is a Behaviour Driven Development framework for testing JavaScript code.
Download Jasmine runner
Create a project folder for the BMI Challenge and extract the content of the jasminestandalone.zip package to that folder.
The package contains some examples of tests. We will not use those tests but we can have a quick
look on how testing with Jasmine is set up by browsing the examples.
From your terminal, open the SpecRunner.html file:
$ open SpecRunner.html
That will open the test runner in your default browser and you should see something like this.

Figure: Default tests fron the `jasmine_standalone` package

Now, let's have a look at the file structure. Open the content of your project folder in your text editor.
$ atom .
You should see the basic structure of the example project.
64

Jasmine - Set up

You can leave the files as they are if you want, or you can delete them if you want - we will be
deleting them at some point down the road anyway.
The important thing at this moment is to understand how the SpecRunner.html works. In order to
be able to run the tests and have access to the source code, these files needs to be included in the
runner file. Open it up and locate the following section.
<!-- include source files here... -->
<script src="src/Player.js"></script>
<script src="src/Song.js"></script>
<!-- include spec files here... -->
<script src="spec/SpecHelper.js"></script>
<script src="spec/PlayerSpec.js"></script>
As you see, the runner needs to have both the source and the spec files included. Now you can safely
remove those lines or comment them out.
Remember that whatever files you create (both specs and source files) they MUST be included
in the SpecRunner.html!
At this point you are set up with the very basic configuration of Jasmine that will allow you to run
JavaScript tests.

65

First tests

First tests
First tests
Create a new file in your spec folder. Call it person_spec.js.
$ touch spec/person_spec.js
Note that I use the snake case format for my file names. You are free to use any format you
want but be consistent.
We are going to write two test for our Person object. In order to calculate the BMI, we will be needing
a Person to have two attributes, weight and hight.
# spec/person_spec.js
describe("Person", function() {
var person;
beforeEach(function() {
person = new Person({weight: 90, height: 186});
});
it("should have weight of 90", function() {
expect(person.weight).toEqual(90);
});
it("should have height of 186", function() {
expect(person.height).toEqual(186);
});
});
If you reload your SpecRunner.html you'll get two errors.

66

First tests

Let's implement the code that makes this test pass.


In your src folder, create a file called person.js. Add the following code to that file:
# src/person.js
function Person(attr) {
this.weight = attr.weight;
this.height = attr.height;
};
If you run your tests again, by reloading SpecRunner.html you'll see that our two initial tests are
passing. Can you see why?

67

First tests

Figure: Jasmine - passing tests

Okay, the next thing I want you to do is to right-click anywhere on the browser window and choose
Inspect from the pop-up menu. That opens the developer console. Try creating a new instance of a
Person by following the example below.

68

First tests

Figure: Creating a Person in the browsers console

Cool, the browsers console can function as a way to manually tests your units and functions similarly to ruby's irb.
Let's write some more tests.
# spec/person_spec.js
it("should calculate BMI value", function() {
person.calculate_bmi();
expect(person.bmiValue).toEqual(26.01)
});
it("should have a BMI Message", function() {
person.calculate_bmi();
expect(person.bmiMessage).toEqual("Overweight")
});
In those tests, we are calling a calculate_bmi() function on the person object we have
created. This will fail since we have no such function defined.
We extend the Person function by using prototype, similar to class methods in Ruby.
69

First tests

# src/person.js
Person.prototype.calculate_bmi = function() {
this.bmiValue = 26.01;
this.bmiMessage = "Overweight"
};
That implementation will make the test pass, but it is not what we want, is it? We have just passed in
some hard coded values into that function.
Let's instead call the BMICalculator to do the calculation for us by changing the code to:
# src/person.js
Person.prototype.calculate_bmi = function() {
calculator = new BMICalculator();
calculator.metric_bmi(this);
};
Here we are instantiating a new BMICalculator object and calling a function we call
metric_bmi on it. We are also passing in the current instance of Person to that function. At this
point, we need to shift our attention to the other spec file we need and test the behaviour of the
BMICalculator.

Review and reflect


1. Explain what this is in the context of an instance of a Person object.
2. Make sure you run through the whole code manually using the browsers console. What happens
if you reload the page?

70

The calculator

The calculator
The calculator
Create a spec file for the BMICalculator and call it bmi_calculator_spec.js
Add the following code to it
# spec/bmi_calculator_spec.js
describe("BMICalculator", function() {
var bmi_calculator;
var person;
beforeEach(function() {
person = new Person({weight: 90, height: 186});
calculator = new BMICalculator();
});
it("calculates BMI for a person using metric method", function() {
calculator.metric_bmi(person);
expect(person.bmiValue).toEqual(26.01);
});
});
Make sure that you run your spec runner after each addition and read the error messages.
Add the following code to the src/bmi_calculator.js file
# src/bmi_calculator.js
function BMICalculator(){
};
BMICalculator.prototype.metric_bmi = function(obj) {
var weight = obj.weight;
var height = obj.height;
if (weight > 0 && height > 0) {
var finalBmi = weight / (height / 100 * height / 100);
obj.bmiValue = parseFloat(finalBmi.toFixed(2));
}
};
Why do we need to condition the finalBMI? (Why do we need an if statement?)
Run your tests again.
We still have not set the bmiMessage on the Person. In order to do that, we need to create a
function that sets that message depending on what bmiValue the person has.
71

The calculator

Add this function to the bmi_calculator.js file.


# src/bmi_calculator.js
function setBMIMessage (obj){
if (obj.bmiValue < 18.5) {
obj.bmiMessage = "Underweight"
}
if (obj.bmiValue > 18.5 && obj.bmiValue < 25) {
obj.bmiMessage = "Normal"
}
if (obj.bmiValue > 25 && obj.bmiValue < 30) {
obj.bmiMessage = "Overweight"
}
if (obj.bmiValue > 30) {
obj.bmiMessage = "Obese"
}
}
Remember to call it from the metric_bmi function.
# src/bmi_calculator.js
BMICalculator.prototype.metric_bmi = function(obj) {
var weight = obj.weight;
var height = obj.height;
if (weight > 0 && height > 0) {
var finalBmi = weight / (height / 100 * height / 100);
obj.bmiValue = parseFloat(finalBmi.toFixed(2));
setBMIMessage(obj);
}
};

Review and reflect


1. Why does the setBMIMessage(obj); call do?
2. Why do we have a separate function called setBMIMessage?

72

The Document-Object Model

The Document-Object Model


The Document-Object Model
The HTML DOM (Document Object Model) "The W3C Document Object Model (DOM) is a
platform and language-neutral interface that allows programs and scripts to dynamically access and
update the content, structure, and style of a document."*
World Wide Web Consortium
The HTML DOM is a standard object model and programming interface for HTML. It defines:
The HTML elements as objects
The properties of all HTML elements
The methods to access all HTML elements
The events for all HTML elements When a web page is loaded, the browser creates a Document
Object Model of the page.
The HTML DOM model is constructed as a tree of Objects:

With the object model, JavaScript gets all the power it needs to create dynamic HTML:
JavaScript can:
change all the HTML elements in the page
change all the HTML attributes in the page
change all the CSS styles in the page
remove existing HTML elements and attributes
add new HTML elements and attributes
react to all existing HTML events in the page
create new HTML events in the page

Enabling JavaScript
Include javascript inside HTML:
73

The Document-Object Model

<script>
x = 3;
</script>
Reference external file: <script src="http://example.com/script.js"></script>
Not all browsers are JavaScript enabled. You might want to be prepared for that and redirect the user
to a different page if JavaScript is disabled.
<noscript>
<meta http-equiv="refresh" content="0"; URL="http://example.com/
noscript.html"/>
</noscript>

Javascript Exercises #4
1. Open up a website (yes, any website). It might be easier to understand a very simple website perhaps the one you created in the prep course. Select elements in the page by right-clicking on
them and selecting "inspect".
2. Find a div. Give it a border. Change its background color. You can change any of its properties font colors, sizes, and weights, colors, etc.
3. How would you hide an element? Show it again?

74

Web interface

Web interface
Web interface
The next step is to create a web page that will allow for both input of data (create an instance of
Person) and display the calculated results.
For that, we will be using HTML and jQuery. We will also make sure that we have automated tests for
out interface so that we can be sure that everything behaves as we want it to behave.

Testing jQuery
First, we need to make some adjustments to the Jasmine framework by extending Jasmine with
Jasmine-jQuery: a set of jQuery helpers for Jasmine tests.
Download/Copy Jasmine-jQuery from this location and save to lib/jasmine-jquery.js
In your SpecRunner.html locate where you include the Jasmine boot.js. Right underneath,
make sure to include both jasmine-jquery and jquery (we will get that from a remote location
rather than including it in the file structure).
# SpecRunner.html
<script src='https://code.jquery.com/jquery-2.1.4.js'></script>
<!--script src="lib/jquery.min.js"></script-->
<script>
$.holdReady(true);
</script>
<script src="lib/jasmine-jquery.js"></script>
Let's do a manual test to see if you have set everything up and jQuery has been loaded. Reload your
SpecRunner.html, open up the console and type in:
typeof jQuery
If the response you get is "function" then you are all good and jQuery has been loaded. If you get
"undefined" then something is wrong and you need to check your settings.
In regard to the jQuery snippet $ holdReady(true); that we included in the
SpecRunner.html, please see this issue on GitHub for an explanation.
Crete a new test file in the spec folder and call it bmi_ui_spec.js. Add the following code to
that file.
# spec/bmi_ui_spec.js
describe('BMI_UI - index.html', function() {
beforeEach(function() {
jasmine.getFixtures().fixturesPath = '.';
loadFixtures('index.html');
$.holdReady(false);
75

Web interface

});
});
Create the index.html file in the main project folder and add an HTML form that we need to input
the data we need to create a Person and calculate the BMI. We also need to include jQuery (as we
did in the SpecRunner.html) and make sure that the source files for Person and
BMICalculator are loaded.
Go over the code below and make sure that you understand what is going on before you
implement it.
# index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<script src="src/person.js"></script>
<script src="src/bmi_calculator.js"></script>
<script src='https://code.jquery.com/jquery-2.1.4.js'></script>
</head>
<body>
<form>
<input type="text" id="weight" placeholder="Weight">
<input type="text" id="height" placeholder="Height">
<input type="button" id="calculate" value="Calculate">
</form>
<div>
<span id="display_value"></span>
<span id="display_message"></span>
</div>
</body>
</html>
To open the web page go to your terminal and use the open command to fire up the page in the
browser.
$ open index.html
The page is still very static and nothing happens if you fill in values in the form and click
Calculate. We need to add some jQuery code in order to tell the interface what to do. There are
two ways to do that. We can either create a new js file and include it in our code, or we can insert the
script directly in the HTML. Let's do the latter. In order for HTML to understand that it is dealing
with JavaScript, we need to wrap it in <script></script> tags.
Have a look at the code below and once you understand what it does, add it to the index.html file.
# index.html
<script type="text/javascript">
$(document).ready(function () {
76

Web interface

$('#calculate').click(function () {
var w = parseFloat($('#weight').val());
var h = parseFloat($('#height').val());
var person = new Person({weight: w, height: h});
person.calculate_bmi();
$('#display_value').html('Your BMI is ' + person.bmiValue);
$('#display_message').html('and you are '+ person.bmiMessage);
});
});
</script>

Review and reflect


1. What is the $(document).ready... function?
2. Why are we using parseFloat($('#weight').val());?
3. What would happen if we did not include <script src="src/person.js">
</script> in the index.html source?
4. What line in index.html calls the method that calculates the BMI? What is happening on
this line, in terms of the rest of the program?
Now you can play around with some values to see if everything is working properly. The next step
will be to write some acceptance tests using Jasmine. In order to do that properly, we will introduce a
web framework that we will be working with later during this course - Sinatra.

77

Acceptance tests

Acceptance tests
Acceptance tests
In order to be able to load the index.html as a fixture in Jasmine, we need to have a local web
server up and running locally. If we don't we simply can not run tests on the web interface due to
CORS conflicts.
In your main project folder, create a file called Gemfile (note that there is no suffix and that the file
name starts with a capital 'G') and add the following lines.
# Gemfile
gem 'rack'
gem 'sinatra'
Also, create two other files in the main project folder:
config.ru <- Note the suffix!
server.rb
Add the following code to each of the files:
# config.ru
require './server'
run Sinatra::Application
# server.rb
require 'sinatra'
set :public_folder, proc { File.join(root) }
Now, head over to your terminal and start the local web server.
$ rackup
In your browser, type in this URL to access the web page you recently created:
http://localhost:9292/index.html

78

Acceptance tests

Figure: Web page accessed using a local web server

You can go ahead and enter some values in the fields and see if everything is working as it should.
To stop the server in your terminal, just press the ctrl + C keys on your keyboard. To start it
again, just type rackup and press enter.
At this point, we are ready to add some tests to our bmi_ui_spec.js.
What we want Jasmine to do is to:
1. Fill in the fields for Weight and Height with values.
2. Click the Calculate button.
3. Assert that the right content is displayed on the page.
Modify your bmi_ui_spec.js with the following code
# spec/bmi_ui_spec.js
describe('BMI_UI - index.html', function() {
beforeEach(function() {
jasmine.getFixtures().fixturesPath = '.';
loadFixtures('index.html');
$.holdReady(false);
$('#weight').val('90');
$('#height').val('186');
$('#calculate').trigger('click');
});
it("displays BMI Value", function() {
79

Acceptance tests

expect($('#display_value').text()).toBe('Your BMI is 26.01')


;
});
it("displays BMI Message", function() {
expect($('#display_message').text()).toBe('and you are Overw
eight');
});
});
Why do we need the beforeEach block? What are the last three lines of that block doing?
Now, run your tests by pointing your browser to the following URL.
http://localhost:9292/SpecRunner.html

Figure: Jasmine tests passing using the local web server

80

Moving on

Moving on
Moving on
At this point, we will shift gears and allow you to work for yourself. There is plenty of functionality
left to implement.
1. Add a function to calculate BMI using the imperial method.
2. Modify the UI to allow the user to choose between the Metric- and Imperial methods
3. Add some CSS to the web interface to create a better look and feel of the application - why not
use some of the great frameworks available out there, like ZURB Foundation or Twitter
Bootstrap?
4. Create a local repository and push up your code to GitHub
5. Deploy your own version of the calculator with Github Pages.
If you want to find some inspiration you can have a look at a slightly styled version of the BMI
Calculator that we have deployed using gh-pages to github.io.
http://craftacademy.github.io/bmi/index.html

81

Moving on

You can make any Github repository into a web page. On your repository, click Settings, then scroll
down to Github Pages and select a branch (the one with the latest changes is best, probably master).
Click "save", then Github will show you the link to your new website. That's it!

82

Fizz Buzz in JavaScript

Fizz Buzz in JavaScript


FizzBuzz Challenge
Do you remember the Fizz Buzz game we were working on in the Prep-course? First time around, we
wrote the program using Ruby. Let's do it in JavaScript and implement a web interface using HTML
and jQuery.
- Will this Fizz Buzz bulls^^t ever end?, you ask yourself. Well, the Fizz Buzz kata is a very common
programming challenge when learning a new language. It's a kind of "Hello World" program, but
slightly more advanced.
The idea with this challenge is that since you have written this code in Ruby, you'll be able to
spot both similarities and differences the JavaScript implementation compared to Ruby.

Week 2 JavaScript/Jasmine challenge


To successfully complete the challenge, We want you to deploy it using GitHub pages (also
something we've done during the Prep-course, remember?)
We have prepared this repository for you with all the necessary tools in terms of a testing framework,
the basic folder structure, some spec files, etc. What you need to do it to make it all work together.

Instructions
Read this entire README carefully and follow all instructions.
Challenge time: this weekend, until Monday 9am
Feel free to use Google, Stack Overflow, your notes, previously written code, books, etc. but
work on your own
If you refer to or have in whole or partially used the solution of another coach or student, please
put a link to that in your README
If you have a partial solution, push up your code and check in a partial solution under the
assignment submission section
You must submit your assignment submission by 9.30am Monday morning - before the
stand-up.

Learning objective
To write Unit tests with Jasmine
To write Acceptance tests with Jasmine-jQuery
To add HTML elements and get user data
To modify the DOM using jQuery
To deploy your code using GH-Pages
For the web interface there is this User story
As an individual
So that I can check a number when playing FizzBuzz
I would like to be able to enter a number in an field, click a butto
83

Fizz Buzz in JavaScript

n and see
the output according to the FizzBuzz rules.

Tasks

TODO:
Have a look at the karma-jquery-jasmine_boilerplate and follow the set up instructions.
Use the code in the Karma-jQuery-Jasmine-Boilerplate but set the project and repository
name to fizz_buzz_js.
Write your specs and implementation for the Fizz Buzz game as you did in the initial Ruby
challenge - but in JavaScript
Be smart about using Git: commit and push often. Use feature branches.
Submit your work in using the assignment section in this chapter.
Review and reflect
Compare this implementation to the Ruby version we wrote a while back during the prep-course.
1. What are the similarities/differences between the Ruby and js versions?
2. How do you find unit testing with Jasmine in comparison to RSpec?
3. How can you "gamify" Fizz Buzz? Consider the User Interface/Experience. Can the game be
played by multiple players? What about mobile devices?

What we are looking for

I'm hoping to see that:


You can use the Jasmine testing framework for both Unit as well as Acceptance tests.
You understand how to modify DOM elements using jQuery.
I can track your work by following you commit history - so please commit as soon you are
done with a feature or when you have made a test pass.
On your repository, I'm hoping to see:
That you know how to set up and configure 3rd party services for Continuous Integration and
test coverage metrics (TravisCI and Coveralls).
That you are testing the right thing in the right spec file.
That all tests passing - green is good!
High test coverage (above 95% is accepted)
The code is easy to follow: specs are well described, variable names are easy to understand,
functions are short, code is nicely formatted, etc.
The README.md includes relevant information about your solution
Happy coding!

84

NodeJS

NodeJS
npm & NodeJS
npm, short for Node Package Manager, is two things: first and foremost, it is an online repository for
the publishing of open-source Node.js projects; second, it is a command-line utility for interacting
with said repository that aids in package installation, version management, and dependency
management.
npm is pre-installed with Node. We will not be covering NodeJS in this chapter or using it in this
course. It is a popular Javascript framework for back-end development. We are just using it here to
access some of its tools.
If you have Node, you already have npm! If not, install Node - it's as easy as downloading and then
running an installer.
Visit the NodeJS website and download the installer.

85

NodeJS

Once your download is completed, run the installer and follow that instructions.

86

NodeJS

You can check your version by going to your terminal and typing in this command
$ node -v

87

NodeJS

Now you are ready to start using npm. You can find more documentation about packages on the NPM
website.

88

Checkout challenge

Checkout challenge
Checkout Challenge
Our client is an on-line marketplace, here is a sample of some of the products available on our site:
Product code Name Price
001
002
003

Tie
$9.25
Sweater $45.00
Skirt
$19.95

Our marketing team want to offer promotions as an incentive for our customers to purchase these
items.
If you spend over $60, then you get 10% off of your purchase.
If you buy 2 or more ties then the price drops to $8.50.
Our check-out can scan items in any order, and because our promotions will change, it needs to be
flexible regarding our promotional rules.
The interface to our checkout looks like this:
co = Checkout.new(promotional_rules)
co.scan(item)
co.scan(item)
price = co.total
Implement a checkout system that fulfills these requirements.
Test data
--------Basket: 001,002,003
Total price expected: $66.78
Basket: 001,003,001
Total price expected: $36.95
Basket: 001,002,001,003
Total price expected: $73.76

89

Open Weather Challenge

Open Weather Challenge


Open Weather Challenge
You are challenged with building a simple one page application that allows the user to display the
current weather on his location. Use OpenWeatherMap API for current weather information.

Functionality
The functionality we are looking for is pretty simple yet not in any way trivial.
1. When the web page is being accessed, get the users current position using the
navigator.geolocation.getCurrentPosition() method.
2. Get the weather info for the obtained position by querying the OWM API
3. Parse the response
4. Display appropriate information on the web page without reloding the page.

Learning objective
Learn how to get hold of data from an external source.
Learn about JSON objects
Learn about AJAX and DOM manipulation
Learn how to stub network calls in Jasmine

Technology
In order to access the weather service you need to sign up for the FREE tier on their website and get
an API key.

90

Open Weather Challenge

Invalid API call will result in the following response:


{
"cod":401,
"message":"Invalid API key. Please see http://openweathermap.org/
faq#error401 for more info."
}
Valid API call for Gothenburg, Sweden will result in following response:
{
"coord":{
"lon":11.97,
"lat":57.71
},
"weather":[
{
"id":802,
"main":"Clouds",
"description":"scattered clouds",
"icon":"03n"
}
],
"base":"cmc stations",
"main":{
"temp":288.71,
"pressure":1027.36,
"humidity":93,
"temp_min":288.71,
"temp_max":288.71
},
"wind":{
"speed":4.63,
91

Open Weather Challenge

"deg":45,
"gust":7.2
},
"clouds":{
"all":36
},
"dt":1471467557,
"sys":{
"type":3,
"id":9444,
"message":0.01,
"country":"SE",
"sunrise":1471405395,
"sunset":1471459624
},
"id":2689287,
"name":"Nordstaden",
"cod":200
}

Extended functionality
If you are finished with the above implementation and it is fully tested, you can go on and implement
the following:
1. Add a map display to the page
2. Add a possibility to manually set the location and update the results.
One good way of setting the location manually is to use the Google Places API Web Service. There
are libraries you can use to activate autocomplete on an input field in your UI. You might want to
trigger an event on selecting a location in the pulldown and update the map AND query the OWM
API.

92

SlowFood challenge - OO & TDD

SlowFood challenge - OO & TDD


SlowFood (Sinatra)
Welcome to the Week 3 of the Bootcamp and the Slow Food Challenge. Our client is a
Restaurant owner that needs a website where he can list his menu and allow visitors to place
orders. The challenge is to build an Online Food Ordering System - a web based application
that enables customers to order their food online for home delivery or pick up from the
restaurant.
This is the first challenge in the Online Food Ordering domain that we will be working on during the
course. It constitutes a part of a larger theme that we are using throughout our bootcamp. Further
down the road we will challenge you with developing a more extensive application that will have
more of a marketplace functionality. The idea is that by building a fully functional service with
multiple restaurants, delivery system, payments, ranking, etc, you will learn how to build rather
complex systems and learn to use different technologies. Eventually you will be challenged with
building an API to expose the applications data to other applications (like a mobile client).
This week we will start with building a Minimum Viable Product using Sinatra with limited
functionality (see the Requirements section).
This challenge introduces a new set of technologies and skills you'll need as a developer. The main
objective for this challenge is to practice the workflow that we want to use in our projects with focus
on:
Agile methods for software development
Pair Programming
Collaboration using Git and GitHub
Test and Behavior Driven Development
Learning Objectives
Learn about basic Agile principles
self organizing teams
the importance of planning your work
Learn about Sinatra
understand the concept of ORM's vs SQL (DataMapper)
understand the structure of a Sinatra app
controllers
routes
models
relation between models
concept of params (See: https://www.youtube.com/watch?v=y57OnWV6dRE)
Learn about the basics of working with Legacy code
Learn about User Authentication
Learn about the hardships that will occur if we can't read documentation for libraries we use in
our projects (gems).
Basics of Test- and Behavior Driven Development or Acceptance - Unit Test cycle
Embrace and understand the benefits of Pair Programming & collaboration using Git and
GitHub
Understand Rack based applications
93

SlowFood challenge - OO & TDD

learn about deployment to Heroku and the benefits of services like Heroku, DigitalOcean and
AWS
Tools
During this project you will be using a variety of tools. You have used some of them in other
challenges, but many will be new to you.
Sinatra as web framework
ZURB Foundation 6 as CSS framework
PostgreSQL as a database with DataMapper as ORM
Warden for user authentication
Cucumber for Acceptance tests
RSpec for Unit tests
Pony for creating end editing Entity-Relationship diagrams
GitHub to store your code and make it available for the entire team
Waffle.io or ZenHub.io as Project Management tool for tracking features, issues, bugs, etc.
The purpose of this exercise is to simulate a real project and prepare you for the mid course
project that you will be working on in week 6 and/or 7 of the Bootcamp.
Scope
The first version of the application has limited functionality
The owner need to access a protected part of the application to set up information about his
Restaurant and his Menu
A Menu needs to consist of many Dishes
Each Dish has a Category. It is either a Starter, a Main course or an Dessert
Visitors of the site can add Dishes to an Order
In order to finalize an Order (Check out) a Visitor needs to become a Registered User
Order need to calculate a Total price and a Pick up time (30 minutes)
Nothing else should be considered or implemented.

94

Step 1 - Setting up the project

Step 1 - Setting up the project


Step 1 - Setting up the project
We have set up a repository for the project with some basic functionality regarding user
authentication. Your team should make use of that code and pull it in. Fork that repo and go over the
code and the gems already included in the Gemfile. Your very fist assignment is the get that code
working. Make use of all the previous challenges to find answers to obstacles you might encounter.
RVM help
You will find that you need to use a different version of Ruby to run this code. Run:
$ rvm use 2.2.3
This will prompt you to run some more commands. Run them. You'll need to bundle before you can
do anything else. But what happened to your bundle command!? You've just changed to a new
version of Ruby. That means you need to re-install all of your gems, including bundler. Run:
$ gem install bundler
Now you have bundle back and can install the other gems.
Remember, as with every other material, there might be errors in the provided code. It's up to
you to find and correct them if you choose to use the provided snippets. Don't come blaming the
authors of this challenge if you run into trouble
Finally, it's not just about the code. This challenge is about practicing the workflow.

Agile principles
There are different Agile methodologies you can use in your projects and we will be talking a lot
about that and introducing different agile techniques as we move along in the course. For now, you
should always follow these core Agile principles:
focus on user needs
deliver iteratively
keep improving how your team works
fail fast and learn quickly
keep planning

Planning and Communication


Get Organezized

Travis: I should get one of those signs that says "One of these days I'm gonna get organezized".
Betsy: You mean organized?
Travis: Organezized. Organezized. It's a joke. O-R-G-A-N-E-Z-I-Z-E-D...
Betsy: Oh, you mean organezized. Like those little signs they have in offices that says,
95

Step 1 - Setting up the project

"Thimk"?
Taxi Driver (1976)
Your team should plan together, review these plans regularly and change them based on your progress
and any new facts and requirements that arise during the development process.
Both Waffle.io and ZenHub.io are project management tools powered by your GitHub Issues & Pull
Requests. Waffle.io is the tool of choice for many AgileVentures projects and provides a web based
interface for managing Issues and track Pull Requests. ZenHub provides similar functionality (and
more) as a browser extension. It is up to you to decide what tool your team want to use.

Structure
We will make use of 4 columns in Waffle (or ZenHub if you choose to use that tool) to track our
work.

96

Step 1 - Setting up the project

Backlog
Any issue that we see as important for the near future lives in the Backlog. Issues in this column are
generally prioritized from top to bottom with the top items having the highest priority.
Ready
Any issue that we would like to work on soon is moved to Ready. As we move ahead, we pull issues
from Backlog into Ready, so we have an idea of the most important items to work on for at the
moment.
In Progress
Any issue that is currently being worked on is put into the In Progress column and assigned to a
developer. By limiting the number of issues you work on at one time helps you focus on what needs
to be done.
Done
Any issues that have been closed are reflected in the Done column. For pull requests, this means that
the code has been merged and deployed.

Stories
We will use 3 types of stories in our project plan: Features, Chores and Bugs
Features
Features are stories that provide verifiable business value to the team's customer. Examples of
97

Step 1 - Setting up the project

features include "add a 'special instructions' field to the checkout page", "purchase history should load
in half a second", and "add a new method addToInventory to the public API". Features are worth
points and therefore must be estimated.
Chores
Chores are stories that are necessary, but provide no direct, obvious value to the customer. Examples
include "sign up for access to geocoding service" and "Find out why the detailed test suite takes so
long". Chores can represent "code debt", and/or points of dependency on other teams.
Bugs
Bugs represent unintended behavior that can be related to features, for example "login box is wrong
color", and "price should be non-negative".
Introduction to Waffle.io: https://youtu.be/yEbRaA3rYuA
Introduction to ZenHub.io: https://youtu.be/TRu7vKCg920

98

Step 2 - Focus on the user experience

Step 2 - Focus on the user experience


Step 2 - Focus on the user experience
UX design is the process used to determine what the experience will be like when a user interacts
with your application.
You can find a partially developed version of the SlowFood app deployed to Heroku at this URL:
https://mazen-slow-food.herokuapp.com/
If UX is the experience that a user has while interacting with your product, then UX Design is, by
definition, the process by which we determine what that experience will be. BDD asks questions
about the UX of your system before and during development to reduce miscommunication.
Requirements are written down as user stories
Low Fidelity sketches provide a lightweight flow of how a user interacts with the system.
BDD concentrates on UX vs. implementation (TDD)

The Connextra format


User stories are used with agile methodologies as the basis for defining the functions a system must
provide and to manage requirements. They captures the "who", "what" and "why" of a requirement in
a simple, concise way, often limited in detail by what can be hand-written on a small card.
As a <role>
So that/In order to <business value>
I want to <feature>
The above user story format is the one we will be using to describe the application. It is centered
around three important questions:
Who's using the system?
What are they doing?
Why do they care?
A user story can be formulated as an acceptance test before code is written. The same feature can be
described by viewing it from a different stakeholder perspective.
See dishes that are available
As a customer
So that I can select a dish
I want to see a list of available dishes
Display dishes that are available
As a restaurant ovner
So that I can receive orders from the site's visitors
I want to be able to display a list of available dishes
Think about these two User stories, they seem to be almost identical. But are they in fact identical?
99

Step 2 - Focus on the user experience

Lo-Fi's
Low-fidelity prototypes (LoFi's) of the systems views are rough representations of concepts that help
us validate those concepts early on in the design process. Working with transforming user stories to
low-fidelity prototypes is a unique way to radically improve your work and to build a UX in which
user's needs can be truly realized.
Design thinking advocates for "thinking with your hands" as a way to build empathetic
solutions.
Lean startup relies on early validation and the development of a minimum viable product to
iterate on.
User-centered design calls for a collaborative design process where users deliver continual
feedback based on their reactions to a product's prototype.

100

Step 3 - Entity Relationship Diagrams

Step 3 - Entity Relationship Diagrams


Step 3 - Entity Relationship Diagrams
Databases
A database is a collection of information that is organized so that it can easily be accessed, managed,
and updated. A relational database is a collection of data items organized as a set of formallydescribed tables from which data can be accessed or reassembled in many different ways without
having to reorganize the database tables.
Each table contains data in predefined columns. Each row contains a unique instance of data defined
by the columns. For example, a typical business order entry database would include a table that
described a customer with columns for name, address, phone number, and so forth. Another table
would describe an order: product, customer, date, sales price, etc.
SQL (Structured Query Language) is a standard interactive and programming language for getting
information from and updating a database. Queries take the form of a command language that lets you
select, insert, update and delete data.
ORM (Object-relational mapping) is a mechanism that makes it possible to address, access and
manipulate objects without having to consider how those objects relate to their data sources.
Examples of ORM's that we will use during this course are Data Mapper and Active Record.
ERD (Entity Relationship Diagram) is a data modeling technique to graphically represent a
conceptual data model of a information system. They allow you to display your database structure and
show entities and relationships between tables within that database.
Many developers argue that it is essential to have ER-Diagrams if you want to create a good database
design. My personal take is that an ERD is a great thing to spend some time on in the initial phase of
the project (The Design Sprint) but we should also accept that requirements WILL change during the
development process and so will the database structure. Therefore, be prepared to make continuous
updates to your ERD and don't spend too much time on the parts of the database that you are
uncertain about.
The diagrams help focus on how the database actually works. ER-modeling You can say that Entity
Relationship Diagrams illustrate the logical structure of databases.
There are three basic elements in ER-Diagrams:
Entities are the "things" for which we want to store information. An entity is a person, place,
thing or event.
Attributes are the data we want to collect for an entity.
Relationships describe the relations between the entities.

Task
I want you to draw an ERD of the SlowFood system. You can use this ERD Tool:
https://editor.ponyorm.com/

101

Step 3 - Entity Relationship Diagrams

102

Step 4 - Implementing the core features

Step 4 - Implementing the core features


Step 4 - Implementing the core features
I would argue that creating a Restaurant, adding a Menu and Dishes is the first feature you should
work on.

103

Step 5 - Working with the database

Step 5 - Working with the database


Step 5 - Working with the database
It's time to introduce the database connection. As you've already noticed (if you have reviewed the
gems in your Gemfile) we'll be using Postgres as a database.
There are several different ways to install Postgres on your computer and there is a bid risk that you
will run into some issues while doing it. The simplest way is to head over to http://postgresapp.com/
and download and run the installer.
The setup is fairly straight forward. In the documentation, we can read that the setup for Sinatra
requires two gems and a single line of code.
Install and require the datamapper and do_postgres gems, and create a database
connection:
DataMapper.setup(:default, ENV['DATABASE_URL'] || "postgres://localh
ost/[YOUR_DATABASE_NAME]")
In order to access our database using DataMapper - an Object Relational Mapper library that will
allow us to treat our data as Ruby Objects.
Simply put, a class whose instances of we want to store in a database can be defined by adding a
series of attributes to it.
Consider this.
class User
include DataMapper::Resource
property :id, Serial
property :email, Text
end
Now, you'll have a set of methods you can use when creating an instance of User. Most important
are these.
> my_user = User.new
> my_user.name = "Thomas"
> my_user.save
Or, you can do something like this.
> another_user = User.create(name: "Thomas")
In your code, there is already a User class. Locate it and examine the code closely.
In order to be able to open an interactive console that will allow you to access tis class definition and
create new instances, use this command.
$ bundle exec irb -r ./lib/controller.rb
104

Step 5 - Working with the database

That will open the IRB console with all your gems and dependencies loaded. Now you can try to
create a new user using the class definition in the legacy code.
2.2.3 :001 > User.count
=> 1
2.2.3 :002 > my_user = User.new
=> # \<User @id=nil @username=nil @password=nil>
2.2.3 :003 > my_user.username = "Tochman"
=> "Tochman"
2.2.3 :004 > my_user.password = "my_secret_password"
=> "my_secret_password"
2.2.3 :005 > my_user.save
=> true
2.2.3 :006 > my_user
=> # \<User @id=2 @username="Tochman" @password="$2a$10$Lz6uIslKglm
zGiiGyjjfh.EfGA1Bp2h4PCjo7W0A2M4s/PIJSVOb6">
2.2.3 :007 >

ERD to Datamapper classes


In previous steps, you have created an ERD.

Now let's create some classes using this ERD as a blueprint.


We already have a User class defined. Now we need to expand it with a relation to an
Restaurant.
Before we move on, we need to install RSpec and initiate it (see a separate section of the course
documentation on how to set up testing frameworks for this project)
Let's write some tests by following our entities design of the ERD.
!FILENAME spec/user_spec.rb
require './lib/models/model'
105

Step 5 - Working with the database

describe User do
it { is_expected.to have_property :id }
it { is_expected.to have_property :username }
it { is_expected.to have_property :password }
it { is_expected.to have_one :restaurant }
end
And a spec for the Restaurant class
!FILENAME spec/restaurant_spec.rb
require './lib/models/restaurant'
describe Restaurand do
it { is_expected.to have_property :id }
it { is_expected.to have_property :name }
it { is_expected.to belong_to :user }
end
Your job will be to make those tests to pass. Use the DataMapper documentation to find out how to
set up relationships between entities/classes.

106

Step 6 - Working with BDD

Step 6 - Working with BDD


Step 6 - Working with BDD
Working with BDD
The concept of Behaviour Driven Development (BDD) is pretty simple. You describe what you
want the system to do by describing potential user's interactions with different parts of the application.
You work outside-in to implement features using the examples to validate that you're building the
right thing at the right time.
During this week you will see BDD in action and will experience, at least partially, the benefits of this
method.
The tools we will be using to test our application are RSpec for our unit tests and Cucumber for our
acceptance tests. Those are the two so called testing frameworks that will help us to write good code.
Cucumber (cucumber.io) is a testing framework used to describe high level functionality of your
application. We will refer to them as acceptance test or features. One of Cucumber's most compelling
features is that, it allows you to write these descriptions in plain text. Even in your native language.
During the development process, we'll take an approach that combines high level acceptance tests and
low level unit tests to both drive the development process and make sure that we build a robust and
well structured application.

Test first - as we always do


Let's start with writing some high level acceptance tests.
What we want to do at this stage is to get the user stories we have defined together with our LoFi's
and look at them from a user's perspective but also what an actual implementation of features would
look like.
In short, we want to take each user story and break it down to scenarios that each represents a use case
in the application. Sounds confusing? It is, but look at it as a form of a blue print that you will use
when we start to actually build the app.
Cucumber is not installed on your system so we need to do that as a first step (see the Setup section)
A feature is one of your already defined user stories. It consists of one ore many scenarios. A
scenario is a sequence of steps through the feature that exercises one path.
A scenario is made up of 3 sections related to the 3 types of steps:
Given: This sets up preconditions, or context, for the scenario.
When: This is what the feature is talking about, the action, the behavior that we're focused on.
Then: This checks post-conditions. It verifies that the right thing happen in the When stage.
There is yet another type of step you can use in a scenario path, and that is the And keyword.
And can be used in any of the three sections. It serves as a nice shorthand for repeating the
Given, When, or Then. And stands in for whatever the most recent explicitly named step was.
For example, let's say that we want to test our user registration feature. In the newly created features
107

Step 6 - Working with BDD

folder, please create a user_management.feature by returning to your terminal window and


typing in:
$ touch features/user_management.feature
Open that file and add the following code (this is a feature description AND the first of many
scenarios that constitutes acceptance criteria of this feature)
!FILENAME features/user_management.feature
Feature: As a visitor
So that I can log in to the system and place orders
I would like to see a 'log in' or 'register' button on the home page
.
Scenario: Allows a visitor to access a registration page
Given I am on the "home page"
And I click on the "Log In" link
Then I should be on the registration page
Now, run cucumber in your terminal. You will get a lot of errors that you should go over carefully.
$ cucumber
Feature: As a visitor
So that I can log in to the system and place orders
I would like to see a 'log in' or 'register' button on the home page
.
Scenario: Allows a visitor to access a registration page # feature
s/user_management.feature:5
Given I am on the "home page" # feature
s/user_management.feature:6
And I click on the "Log in" link # feature
s/user_management.feature:7
Then I should be on the registration page # features
/user_management.feature:8
1 scenario (1 undefined)
3 steps (3 undefined)
0m0.033s
And you should also see something like this on your terminal:

108

Step 6 - Working with BDD

This is perfectly normal. What Cucumber is doing is helping you in defining so called step
definitions, that are the actual implementation of your tests so that they can be understood by ruby.
Confusing? Let me get you started by implementing the steps above.
Modify the fetures/support/env.rb file to make cucumber actually start your application
and load the necessary dependencies.
!FILENAME fetures/support/env.rb
ENV['RACK_ENV'] = 'test'
require File.join(File.dirname(__FILE__), '..', '..', 'lib/controlle
r.rb')
require 'capybara'
require 'capybara/cucumber'
require 'rspec'
require 'pry'
Capybara.app = SlowFood
class SlowFoodWorld
include Capybara::DSL
include RSpec::Expectations
include RSpec::Matchers
end
World do
SlowFoodWorld.new
109

Step 6 - Working with BDD

end
Create a new file in the features/step_definitions folder.
$ touch features/step_definitions/basic_steps.rb
Add the following code to that file
!FILENAME features/step_definitions/basic_steps.rb
You can implement step definitions for undefined steps with these sn
ippets:
Given(/^I am on the "([^"]*)"$/) do |arg1|
pending # Write code here that turns the phrase above into concret
e actions
end
Given(/^I click on the "([^"]*)" link$/) do |arg1|
pending # Write code here that turns the phrase above into concret
e actions
end
Then(/^I should be on the registration page$/) do |arg1|
pending # Write code here that turns the phrase above into concret
e actions
end
At the moment all the above step definitions are empty, so we need to add some commands for each
of the steps. I will introduce some Capybara commands to the Given steps and an assertion to the
Then step.
!FILENAME features/step_definitions/basic_steps.rb
Given(/^I am on the "([^"]*)"$/) do |page|
visit '/'
end
Given(/^I click on the "([^"]*)" link$/) do |link|
click_link_or_button link
end
Then(/^I should be on the registration page$/) do
expect(current_path).to eq '/auth/login'
end
If you save your work and run cucumber again you should see something like this.

110

Step 6 - Working with BDD

Seems like good progress, right?


This is passing because we are testing views that are already defined in the legacy code. As usual, if
you introduce any changes your tests needs to reflect them. If the don't you'll find yourself in a messy
situation. ;-)
You probably recognize the expect command from your work with RSpec. What is new is the
Capybara commands. So far, we only make use of 2 of quite many commands that are to our disposal.
Here is a summary of the most common commands
=Navigating=
visit('/projects')
visit(post_comments_path(post))
=Clicking links and buttons=
click_link('id-of-link')
click_link('Link Text')
click_button('Save')
click('Link Text') # Click either a link or a button
click('Button Value')
click_link_or_button('Link Text') # Click either a link or a but
ton

=Interacting with forms=


fill_in('First Name', with: 'John')
fill_in('Password', with: 'Seekrit')
fill_in('Description', with: 'Really Long Text')
choose('A Radio Button')
111

Step 6 - Working with BDD

check('A Checkbox')
uncheck('A Checkbox')
attach_file('Image', '/path/to/image.jpg')
select('Option', from: 'Select Box')
=scoping=
within("//li[@id='employee']") do
fill_in 'Name', with: 'Thomas'
end
within(:css, "li#employee") do
fill_in 'Name', with: 'Thomas'
end
within_fieldset('Employee') do
fill_in 'Name', with: 'Thomas'
end
within_table('Employee') do
fill_in 'Name', with: 'Thomas'
end
=Querying=
expect(page).to have_xpath('//table/tr')
expect(page).to have_css('table tr.foo')
expect(page).to have_content('foo')
expect(page).not_to have_content('foo')
find_field('First Name').value
find_link('Hello').visible?
find_button('Send').click
find('//table/tr').click
locate("//*[@id='overlay'").find("//h1").click
all('a').each { |a| a[:href] }
=Scripting=
result = page.evaluate_script('4 + 4');
=Debugging=
save_and_open_page

112

Extra - Setting up RSpec & Cucumber

Extra - Setting up RSpec & Cucumber


Setting up RSpec & Cucumber
RSpec
Add the following gems to your Gemfile, unless they are already included.
!FILENAME Gemfile
# Gemfile
gem 'data_mapper'
gem 'dm-postgres-adapter'
gem 'pg'
Also, we need to start grouping our gems in that Gemfile. Please create the following group:
!FILENAME Gemfile
group :development, :test do
end
Add the following gem to that group:
!FILENAME Gemfile
gem 'dm-rspec'
The dm-rspec gem extends RSpec with a set of matchers for DataMapper objects. Having access
to those matchers will make our testing much easier.
Also, I would like you to move all gems that we use for testing purposes to the :development,
:test group.
Your Gemfile should include all the gems below:
!FILENAME Gemfile
source 'https://rubygems.org'
gem 'sinatra'
gem 'padrino', '~> 0.13.0'
gem 'data_mapper'
gem 'dm-postgres-adapter'
gem 'pg'
group :development, :test do
gem 'cucumber'
gem 'rspec'
gem 'capybara'
113

Extra - Setting up RSpec & Cucumber

gem 'capybara-webkit'
gem 'dm-rspec'
end
Don't forget to do bundle install once you have made all the changes.
In terminal run the following command:
$ rspec --init
You should see the following output:
create .rspec
create spec/spec_helper.rb
Now, open .rspec file (it is located in your main project folder but it is a hidden file, so your text
editor might not show it) and modify it so that the first line is set to:
!FILENAME .rspec
--format documentation
...
Now, in your terminal, type in rspec and hit enter.
The output you see should be something like:
No examples found.
Finished in 0.00023 seconds (files took 0.5029 seconds to load)
0 examples, 0 failures
We are not quite done yet. Sorry. :-(
I want you to open the spec/spec_helper.rb file. You'll see a lot of lines. Most of them are not
needed so I would like you to delete all lines that begin with the #sign. These are comments and we
don't need them - for now, they are just a distraction.
When you are done you should see something like this:
!FILENAME spec/spec_helper.rb
RSpec.configure do |config|
config.expect_with :rspec do |expectations|
expectations.include_chain_clauses_in_custom_matcher_description
s = true
end
config.mock_with :rspec do |mocks|
mocks.verify_partial_doubles = true
end
end
Not so bad, ey?
114

Extra - Setting up RSpec & Cucumber

Alright, we need to add some of the libraries/gems we want to use in our spec_helper. Modify
your file to include the settings below.
!FILENAME spec/spec_helper.rb
ENV['RACK_ENV'] = 'test'
require File.join(File.dirname(__FILE__), '..', 'lib/controller.rb')
require 'capybara'
require 'capybara/rspec'
require 'rspec'
require 'dm-rspec'
...and in the RSpec.configure block, add:
!FILENAME spec/spec_helper.rb
...
config.include Capybara::DSL
config.include DataMapper::Matchers
...
Now, run rspec again and you should receive no errors.
No examples found.
Finished in 0.00032 seconds (files took 0.72891 seconds to load)
0 examples, 0 failures
Okay, this means that you have successfully added and set up RSpec as a testing framework.

Cucumber
Make sure that cucumber is added to your Gemfile's :development, :test group as a
dependency.
!FILENAME Gemfile
group :development, :test do
gem 'cucumber'
end
If it's not, add it and run bundle install. Once that is complete I'd like you to initiate Cucumber
by simply typing in:
$ cucumber --init
That will create some files and folders for you:
create features
create features/step_definitions
create features/support
create features/support/env.rb
Okay, now, in your terminal, you type in:
115

Extra - Setting up RSpec & Cucumber

$ cucumber
The output you will see should look something like this:
0 scenarios
0 steps
0m0.000s
Alright, we have installed and initiated the first testing framework. Big step.

116

Static Website with Middleman

Static Website with Middleman


Static Website with Middleman
Earlier in the course, you had a first run at building a website. Okay, it was a just some a basic HTML
page. But the aim of that assignment was to give you a refresher of some basic HTML and CSS. In
this section, we will take things up a notch a little bit. We will introduce you to Middleman, a tool that
will allow you to build Static Websites. This will allow us to use some software development best
practices to build our sites.

What is Middleman?
Middleman "is a static site generator using all the shortcuts and tools in modern web
development"
Middleman site
Static websites are widely used because they are fast, easy to setup. They are served to the user
exactly as stored and there is no transaction to a database. It's pretty much HTML, CSS and
JavaScript if needed.
You might be wondering now, why go through the trouble of using a tool like Middleman to build a
static website?
Building a static site with many pages can lead to having lots of repeated code across. Updating them
during development will quickly become a nightmare. Middleman will allow you to structure your
project in a modular way using things like layouts, partials, etc. Giving you a DRY and more
manageable project.
Middleman uses the Ruby programming language. But don't worry if you are still new to Ruby as you
will likely not write too much of it while build your site.
Now let's install middleman and build our first site.

117

Week lab

Week lab
Week lab
The weekly challenge is an individual one. You are supposed to build an individual portfolio site that
will showcase the projects you have been working on. We will leave the visual aspects of the site
itself to your artistic ambitions, but we have some requirements as to the functionality we want the
site to deliver:
1.
2.
3.
4.
5.
6.
7.
8.
9.

Use Middleman as the framework


Include a Portfolio section
Get data about your past projects from a YAML file
Include a About me section
Make use of Partials for the header and the footer section (at least)
Use a css framework (preferably SASS) for styling
Deploy your site on GHPages
Use HAML as the templating engine
Set up Google Analytics

Note: You are welcome to work in pairs if you like, but it is not a requirement and if you do, you
need to figure out how to complete your individual sites and still be able to pair program.

Learning Objectives
The Middleman framework
Using ruby to create static websites
HAML
How can HAML improve your workflow
Write less code and avoid the html mess
Deployment
learn about deployment of static sites.

118

Setup Middleman

Setup Middleman
Setup Middleman
If you don't have Ruby installed yet, please refer to our setup instructions for Linux or Mac OS X
Once you're done, head to your terminal and run the following command:
$ gem install middleman
This will install Middleman, its dependencies and command line tools for using Middleman.

Create a new project - middleman init


Let's build a middleman skeleton project. From your terminal, navigate to the folder where you would
like to create your project and run the following command:
$ middleman init my_site
This will create a folder named my_site in the current directory and generate starter files for your
project.
You'll be prompted the following questions to which you need to answer with either (y)es or (n)o:
Do you want to use Compass? n
Do you want to use LiveReload? y
Do you want a Rack-compatible config.ru file? n

Serving the Application - middleman server


Middleman ships with a local server to test your application during development.
$ cd my_site
$ middleman server
This will start up the server and you should now be able to access the site at http://localhost:4567/

119

Setup Middleman

Figure: Middleman welcome screen

Great! We have successfully setup middleman and created our project. Now let's go through the files
that have been generated for us.

Directory Structure
A tipical middleman application will have a directory structure that looks like this:
my_site/
+-- .gitignore
+-- Gemfile
+-- Gemfile.lock
+-- config.rb
+-- source
+-- images
+-- background.png
+-- middleman.png
+-- index.html.erb
+-- javascripts
+-- all.js
+-- layouts
+-- layout.erb
+-- stylesheets
+-- all.css
+-- normalize.css
120

Setup Middleman

The source directory


The source folder is where you'll spend most of your time while building your website. By default
it contains folders for JavaScript, CSS and images, but you can re-organise them at your own
convenience. We'll see how to achieve that later in this chapter.
The config.rb
The config.rb file is were you will find the settings for your Middleman application. It also
contains commented documentation on how to enable complex features such as compile-time
compression, blog-mode, etc.
The Gemfile
Just like any other ruby project you have worked with so far, Middleman makes use of Bundler
Gemfile to manage your gem dependencies.
Other Middleman directories
Middleman makes use of extra directories for specific purposes. Each of these folders are children of
your main application folder.
The build directory

This is where your static website files will be compiled and exported to.
The lib directory

The lib directory will house your external Ruby modules. These modules will contain helpers to use
while building your application.
The data directory

Data files allow you to create .yml, .yaml or .json files in the data folder and makes this
information available in your templates. We will be covering Data files in another section of this
chapter.

121

HAML - HTML abstraction markup language

HAML - HTML abstraction markup language


HAML - HTML abstraction markup language
At its core, Haml is a lightweight markup language. Haml makes writing HTML much easier, and
when we say "much easier" what we really mean is "you won't believe how much time you can
save by using Haml." With Haml you will immediately see the intense time saving benefits that it
brings to the table.
HTML:
<p>This is an HTML paragraph</p>
Notice how even the simplest bit of HTML involves some repetition. The paragraph tag must be
placed at the beginning of the statement, and then a corresponding closing tag must be placed at the
end. What if we could skip all of this nonsense?
Here's the same statement in Haml:
%p This is also an HTML paragraph
In Haml, we prefix HTML tags with a "%" and it takes care of the rest. But wait you implore, how
does one differentiate between two elements without the closing tag? The answer is of course that
Haml is whitespace dependent, meaning it uses the visual structure of your code to determine the
HTML output. Here's another example that uses multiple elements and even nests some elements
inside of others (the list items are nested in the unordered list).
Haml:
%h2 This is a Headline
%a This is a link.
%ul
%li List item one
%li List item two
%li List item three
%p Another paragraph can go here.
This markup will render:
<h2>This is a Headline</h2>
<a>This is a link.</a>
<ul>
<li>List item one</li>
<li>List item two</li>
<li>List item three</li>
</ul>
<p>Another paragraph can go here.</p>

Classes and IDs


Without the traditional tag structure, you might be wondering how to declare extra HTML items like
classes and IDs. Haml is way ahead of you and even uses a syntax that you'll find oddly familiar.
122

HAML - HTML abstraction markup language

Haml:
%ul.someClass
%li#someID
As you can see, the "." and "#" symbols are used to denote classes and IDs respectively. This is great
because you're already familiar with this method in CSS. That familiarity means that once you really
dive in, you'll be able to pick up Haml in no time. Here's how this code compiles.
HTML:
<ul class='someClass'>
<li id='someID'></li>
</ul>

Divs Are Magic


Let's face it, even with all the fancy new HTML5 elements, we still all love our divs. Given that this
particular element is so common, Haml has assigned it a particularly simple syntax. Consider how
you think you might write the following HTML in Haml.
HTML:
<div id="someDiv">
</div>
Given what we've learned so far, you'll no doubt reach the conclusion that some form of %div is
necessary here. However, Haml allows you to skip the div syntax all together. To write the above
HTML in Haml, this is all you need.
Haml:
#someDiv
That's it! This code compiles to a complete div with an ID of someDiv. Even the skeptics among
you have to admit that this is pretty dang concise. Once again we see the clear benefits of being able
to write our markup like we write CSS.

More complex views


Just to make sure you've really gotten the gist of how this works, here's a much more extensive
example. This time we'll use one main div and two inner divs along with plenty of other elements.
Haml:
#container
.box
%h2 Some Headline
%p Lorem ipsum doller your mom...
%ul.mainList
%li One
%li Two
%li Three
.box
123

HAML - HTML abstraction markup language

%h2 Some Headline


%p Lorem ipsum doller your mom...
%ul.mainList
%li One
%li Two
%li Three
HTML:
<div id='container'>
<div class='box'>
<h2>Some Headline</h2>
<p>Lorem ipsum doller your mom...</p>
<ul class='mainList'>
<li>One</li>
<li>Two</li>
<li>Three</li>
</ul>
</div>
<div class='box'>
<h2>Some Headline</h2>
<p>Lorem ipsum doller your mom...</p>
<ul class='mainList'>
<li>One</li>
<li>Two</li>
<li>Three</li>
</ul>
</div>
</div>
This time around you can really start to feel the awesome power of Haml.
The charm here goes beyond the fact that we've saved tons of characters and even a few entire lines,
notice how much easier it is to read the Haml at a glance. All of the nasty clutter that comes with
HTML has bee stripped away and in its place is a clear hierarchy of markup that is instantly
discernible.

HTML5
Just in case you're wondering, Haml works just fine with all of the cool new tags inside of HTML5. In
fact, it doesn't really care what type of tags you try to create. If you type %somecrazytag the output
result will be regardless of the fact that this isn't even valid HTML.
Here's a quick example to illustrate that uses the new header, nav, section and footer HTML5 tags.
Haml:
.wrapper
%header
%nav
%ul
%li one
%li two
%li three
124

HAML - HTML abstraction markup language

%section
.info
%p Lorem ipsum doller set
%section
.info
%p Lorem ipsum doller set
%section
.info
%p Lorem ipsum doller set
%footer
%p Lorem ipsum doller set
HTML:
<div class='wrapper'>
<header>
<nav>
<ul>
<li>one</li>
<li>two</li>
<li>three</li>
</ul>
</nav>
</header>
<section>
<div class='info'></div>
<p>Lorem ipsum doller set</p>
</section>
<section>
<div class='info'></div>
<p>Lorem ipsum doller set</p>
</section>
<section>
<div class='info'></div>
<p>Lorem ipsum doller set</p>
</section>
<footer>
<p>Lorem ipsum doller set</p>
</footer>
</div>

Outputting Ruby in HAML


So how do we embed Ruby in our HAML? In ERB we might have:
<p class='flash'><%= flash[:notice] %></p>
Given what you've seen from the H1, you would imagine the <p></p> becomes %p. But what about
the Ruby injection?
125

HAML - HTML abstraction markup language

Ruby Injections
HAML's approach is to reduce <%= %> to just =. The HAML engine assumes that if the content
starts with an =, that the entire rest of the line is Ruby. For example, the flash paragraph above would
be rewritten like this:
%p= flash[:notice]
Note that the = must be placed against the %p tag.

Adding a CSS Class


But what about the class? There are two options. The verbose syntax uses a hash-like format:
%p{class: 'flash'}= flash[:notice]
But we can also use a CSS-like shorthand syntax:
%p.flash= flash[:notice]
The latter is easier and more commonly used.

Mixing Plain Text and Ruby


Consider a chunk of content that has both plain text and Ruby like this:
<div id="sidebar">
Filter by Tag: <%= tag_links(Tag.all) %>
</div>
Given what we've seen so far, you might imagine it goes like this:
%div#sidebar Filter by Tag: = tag_links(Tag.all)
But HAML won't recognize the Ruby code there. Since the element's content starts with plain text, it
will assume the whole line is plain text.
Breaking Ruby and Text into Separate Lines
One solution in HAML is to put the plain text and the Ruby on their own lines, each indented under
the DIV:
%div#sidebar
Filter by Tag:
= tag_links(Tag.all)
Since version 3, HAML supports an interpolation syntax for mixing plain text and Ruby:
%div#sidebar
Filter by Tag: #{tag_links(Tag.all)}
And it can be pushed back up to one line:
%div#sidebar Filter by Tag: #{tag_links(Tag.all)}
126

HAML - HTML abstraction markup language

Ruby commands
We've seen plain text, HTML elements, and printing Ruby. Now let's focus on non-printing Ruby.
One of the most common uses of non-printing Ruby in a view template is iterating through a
collection. In ERB we might have:
<ul id='articles'>
<% @articles.each do |article| %>
<li><%= article.title %></li>
<% end %>
</ul>
The second and fourth lines are non-printing because they omit the equals sign. HAML's done away
with the <%. So you might be tempted to write this:
%ul#articles
@articles.each do |article|
%li= article.title
Content with no marker is interpreted as plain text, so the @articles line will be output as plain
text and the third line would cause a parse error.

Marking Non-Printing Lines


We need a new symbol to mark non-printing lines. In HAML these lines begin with a hyphen (-):
%ul#articles
- @articles.each do |article|
%li= article.title
No end!
What about the end that we use in our ruby files and in erb? HAML uses that significant whitespace
to reduce the syntax of HTML and Ruby. The end for the do is not only unnecessary, it will raise an
exception if you try to use it.

Conclusion
HAML templates are cleaner, easier to read, easier to maintain, and easier to develop on.
An anonymous developers opinion will close this section:
HAML makes me faster. A lot faster. Not having to track down where a closing tag was omitted,
never accidentally crossing tag scope, and being able to trivially ascertain the selector path of a
given bit of markup makes templating fast. I do a lot of it "the old way" too, and...it's just not as
fun. It's frustrating, and slow, and it's not because I'm bad at it - I did it for years before HAML it's because if you take the time to learn it, HAML is genuinely better.
Happy HAML coding!

127

HAML - HTML abstraction markup language

128

SASS

SASS
SASS
[TODO: Rewrite this section]
<!-- Include jQuery -->
= javascript_include_tag "//ajax.googleapis.com/ajax/libs/jquery/1.1
1.0/jquery.min.js"
<!-- Include Foundation -->
= stylesheet_link_tag "//cdn.jsdelivr.net/foundation/5.5.0/css/found
ation.min.css" %>
= javascript_include_tag "//cdn.jsdelivr.net/foundation/5.5.0/js/fou
ndation.min.js"

129

Accessing data

Accessing data
Accessing data
Middleman allows you to create .yml, .yaml or .json files in a folder called data and makes
this information available in your templates. The data folder should be placed in the root of your
project i.e. in the same folder as your project's source folder.
Let's add a simple test for listing some project information on your index page.
!FILENAME spec/features/index_spec.rb
it 'displays project list' do
expect(page).to have_css '.projects'
within '.projects' do
expect(page).to have_content 'My First Website'
expect(page).to have_content 'FizzBuzz'
end
end
In order to make this test go green, we need to got through some steps:
1.
2.
3.
4.

create the data folder


add a projects.yml in the data folder
add some project information in the YML format to the projects.yml file
display the project information on our index.hml.hamltemplate

!FILENAME data/projects.yml
- name: My First Website
description: Simple HTML5 site I've build during the PrepCourse
- name: FizzBuzz
description: My first Test driven ruby project
!FILENAME source/index.html.haml
.projects
- data.projects.each do |project|
%h4= project[:name]
%p= project[:description]
Note that Middleman knows how to access the data folder and the projects.yml file as long as
you use the right file name in your code.
Knowing this, you can extract information about your projects from the template and store it in
a YAML file and display it on any page you want.

130

Partials

Partials
Partials
Partials are a way of sharing content across pages and helps you to keep your code DRY. They can be
used both in templates and in layouts.
Let's assume that we want to display a footer on our page.
!FILENAME spec/features/index_spec.rb
it 'renders footer partial' do
expect(page).to have_selector 'footer'
within 'footer' do
expect(page).to have_content 'My Portfolio'
expect(page).to have_content 'Built using the awesome Middleman
framework'
end
end
In order to group our partials we want to create a partials folder in our source folder.
In that folder, let's create a file called _footer.html.haml. Note the underscore at the
beginning of the filename!
!FILENAME source/partials/_footer.html.haml
%footer
%h3 My Portfolio
%p Built using the awesome Middleman framework
Under the = yield command in your main layout file, place the call to render the footer partial.
!FILENAME layouts/layout.html.haml
= partial 'partials/footer'
If you run your specs now, the one we just added should go green.
Knowing this, you can add a more complex partials to your application and keep your code
DRY

131

Partials

Figure: Still not much to show to the workd but with some more content...

132

Deploy to Github pages

Deploy to Github pages


Deploy to Github pages
Now that you are done building your portfolio, it's time to publish it and brag about it to your friends
.
First thing you need to do if you haven't already is create a repository for your project on Github. For
the purpose of this walkthrough, we'll assume our repository on Github is my_portfolio.
There are numerous ways of setting up deployment of middleman sites. We'll make use of the gem
middleman-deploy to get this done. A quick search on the internet will show you other ways of
deploying your application if your interested.

Setup the project


Head over to your Gemfile, and add the following line to it then run bundle install
!FILENAME Gemfile
gem 'middleman-deploy', '= 2.0.0.pre.alpha'
Next, in your config.rb file, add the following code bloc.
!FILENAME config.rb
activate :deploy do |deploy|
deploy.build_before = true
deploy.deploy_method = :git
end
There have been some reports of troubles while publishing the site to Github using the gem. Instead
of deploying the content of the build directory to the gh-pages, where the site is served, the
entire source code of the site is pushed. A fix to this issue involves removing the build directory
before each deploy. We'll create an extension to take care of that for us as suggested here.
Create a new file extensions/build_cleaner.rb and add the following content to it:
!FILENAME extensions/build_cleaner.rb
class BuildCleaner < Middleman::Extension
def initialize(app, options_hash = {}, &block)
super
FileUtils.rm_rf app.config[:build_dir]
end
end
::Middleman::Extensions.register(:build_cleaner, BuildCleaner)
Now let's activate the extension in the config.rb file. Add the following code within the :build
block
133

Deploy to Github pages

!FILENAME config.rb
require 'extensions/build_cleaner'
configure :build do
activate :relative_assets
activate :build_cleaner
end
Great now we're all set to deploy the application.

Deploying the site


With everything setup, all that's left to do is to run the following command to publish the site.
middleman deploy
And voila! Your site should now be accessible from the link https://your-githubusername.github.io/my_portfolio

134

Ruby On Rails introduction

Ruby On Rails introduction


Ruby on Rails - Introduction
Rails is a web-application framework that includes everything needed to create database-backed web
applications according to the Model-View-Controller pattern.
This pattern splits the view (also called the presentation) into "dumb" templates that are primarily
responsible for inserting pre-built data in between HTML tags. The model contains the "smart"
domain objects (such as Account, Product, Person, Post) that holds all the business logic and knows
how to persist themselves to a database. The controller handles the incoming requests (such as Save
New Account, Update Product, Show Post) by manipulating the model and directing data to the view.
In Rails, the model is handled by what's called an object-relational mapping layer entitled Active
Record. This layer allows you to present the data from database rows as objects and embellish these
data objects with business logic methods.
The controller and view are handled by the Action Pack, which handles both layers by its two parts:
Action View and Action Controller. These two layers are bundled in a single package due to their
heavy interdependence. This is unlike the relationship between the Active Record and Action Pack
that is much more separate. Each of these packages can be used independently outside of Rails.

Description of Contents
The default directory structure of a generated Ruby on Rails application:
|-- app
| |-- assets
| |-- images
| |-- javascripts
| `-- stylesheets
| |-- controllers
| |-- helpers
| |-- mailers
| |-- models
| `-- views
| `-- layouts
|-- config
| |-- environments
| |-- initializers
| `-- locales
|-- db
|-- doc
|-- lib
| `-- tasks
|-- log
|-- public
|-- script
|-- test (or spec)
| |-- fixtures
135

Ruby On Rails introduction

| |-- functional
| |-- integration
| |-- performance
| `-- unit
|-- tmp
| |-- cache
| |-- pids
| |-- sessions
| `-- sockets
`-- vendor
|-- assets
`-- stylesheets
`-- plugins
app
Holds all the code that's specific to this particular application.
app/assets
Contains subdirectories for images, stylesheets, and JavaScript files.
app/controllers
All controllers should descend from ApplicationController which itself descends from
ActionController::Base.
app/models
Holds models that should be named like post.rb. Models descend from ActiveRecord::Base by
default.
app/views
Holds the template files for the views.
app/views/layouts
Holds the template files for layouts to be used with views. This models the common
header/footer method of wrapping views. In your views, define a layout using the layout
:default and create a file named default.html.erb. Inside default.html.erb,
call <% yield %> to render the view using this layout.
app/helpers
Helpers can be used to wrap functionality for your views into methods.
config
Configuration files for the Rails environment, the routing map, the database, and other
dependencies.
db
Contains the database schema in schema.rb. db/migrate contains all the sequence of Migrations
for your schema.
136

Ruby On Rails introduction

doc
This directory is where your application documentation will be stored when generated using
rake doc:app
lib
Application specific libraries. Basically, any kind of custom code that doesn't belong under
controllers, models, or helpers. This directory is in the load path.
public
The directory available for the web server. Also contains the dispatchers and the default HTML
files. This should be set as the DOCUMENT_ROOT of your web server.
script
Helper scripts for automation and generation.
test or spec
Unit and functional tests along with fixtures. When using the rails generate command,
test files will be generated for you and placed in this folder.
vendor
External libraries that the application depends on. Also includes the plugins subfolder.

137

BDD with Rails

BDD with Rails


Acceptance-Unit Test Cycle
The concept of Acceptance-Unit Test Cycle is pretty simple. You describe what you want the system
to do by describing potential users interactions with the different parts of the application. You work
outside-in to implement features using the examples to validate that you're building the right thing at
the right time. During this workshop, you will see this workflow in action and will experience, at least
partially, its benefits.
For the purpose of this workshop, we will use Ruby on Rails as the framework for our application.
But the workflow can be applied to any stack or framework of your choice.

Testing\/Development Cycle
A good cycle to follow is this outside-in approach:
1.
2.
3.
4.
5.
6.

Write a high-level (outside) business value example (using Cucumber) that goes red
Write a lower-level (inside) RSpec example for the first step of implementation that goes red
Implement the minimum code to pass that lower-level example, see it go green
Write the next lower-level RSpec example pushing towards passing step 1
Repeat steps 3 and 4 until the high-level test (1) goes green
Start over by writing a new high-level test

During the process think of your red-green state as a permission status:


When your low-level tests are green, you have permission to write new examples or refactor
existing implementation. You must not, in the context of that refactoring, add new
functionality\/flexibility.
When your low-level tests are red, you have permission to write or change implementation
code only for the purpose of making the existing tests go green. You must resist the urge to
write the code to pass your next test, which doesn't exist, or implement features youll need
"some day."
The tools we will be using to test our application are RSpec and Cucumber. Those are the two testing
frameworks that will help us to write good code. By now, you've had a chance to use both tools in
your previous projects, so this is not entirely new to you.
Prerequisites
We assume that you have your Development environment setup according to instructions in this book
and that you are working on either a Linux or OSX based computer.

Setting up the application


If you don't have Rails installed beforehand, it is time to install it now. Rails is a ruby gem, so you can
install it as any other gem:
$ gem install rails
138

BDD with Rails

Once you are done it is time to scaffold the Rails application we will be working with.
$ rails new rails_demo --database=postgresql --skip-test --skip-bund
le
The --database=postgresql selects PostgreSQL as the database (The out-of-the-box
setting is MySQL)
The --skip-test option skips configuring for the default testing tool.
The --skip-bundle option prevents the generator from running bundle install
automatically.
$ cd rails_demo

RSpec and shoulda-matchers


Add rspec-rails gem to the development and test groups of your Gemfile.
The RSpec extension library shoulda-matchers allows us to test common Rails functionality,
like validations and associations, with less code.
The gem factory_girl is a library for setting up Ruby objects as test data. It's essentially a
fixtures replacement. It allows you to create objects that are needed in tests without providing a value
for each required attribute. If you don't provide a value for a required attribute factory_girl will use
the default value that you defined in factory's definition.
group :development, :test do
gem 'rspec-rails'
gem 'shoulda-matchers'
gem 'factory_girl_rails'
end
Now, we can install all the new dependencies with bundle install.
$ bundle install
Run the RSpec generator to add the testing framework to your rails application
$ bundle exec rails generate rspec:install
Why bundle exec before a command? bundle exec executes a command in the context of
your bundle. That means it uses the gems specified in your Gemfile. It basically standardizes the
environment under which the program runs. This helps avoid version hell and makes life much easier.
Before you can use shoulda-matchers, you need to configure it by choosing the test framework
and features of shoulda-matchers you want to use.
Open spec/rails_helper.rb and add the following block.
Shoulda::Matchers.configure do |config|
config.integrate do |with|
with.test_framework :rspec
with.library :rails
end
end
139

BDD with Rails

Before we move on we need to add another configuration to the Rails application to avoid the
generators to scaffold too many files. Make the following modification to the
config/application.rb file:
class Application < Rails::Application
# Disable generation of helpers, javascripts, css, and view, helpe
r, routing and controller specs
config.generators do |generate|
generate.helper false
generate.assets false
generate.view_specs false
generate.helper_specs false
generate.routing_specs false
generate.controller_specs false
end
# ...
end
One last thing in this setup process, open the .rspec file (it is located in your main project folder
but it is a hidden file, so your text editor might not show it) and modify it so that the first line is set to:
--format documentation
Now, in your terminal, type in bundle exec rspec and hit enter.The output you see should be
something like:
$ bundle exec rspec
No examples found.
Finished in 0.00023 seconds (files took 0.5029 seconds to load)
0 examples, 0 failures

Cucumber
Add cucumber-rails and database_cleaner gems to the test group of the Gemfile.
group :development, :test do
[...]
gem 'cucumber-rails', require: false
gem 'database_cleaner'
end
The database_cleaner gem is used to ensure a clean database state for testing and will make
your development easier.
Run this command to bootstrap the application with Cucumber:
$ bundle exec rails generate cucumber:install
This will generate Cucumber configuration files and set up the database for Cucumber tests. The
generator will also create all the code you need to integrate DatabaseCleaner into your Rails project.
As a last step, you will need to create the database and run the migrate command (even if we have
not created any tables and columns yet)
140

BDD with Rails

$ rails db:create --all


$ rails db:migrate
Just as a sanity check, we want to run Cucumber to see if everything is working properly.
$ bundle exec cucumber
Using the default profile...
0 scenarios
0 steps
0m0.000s
Run options: --seed 44264
# Running:

Finished in 0.001691s, 0.0000 runs/s, 0.0000 assertions/s.


0 runs, 0 assertions, 0 failures, 0 errors, 0 skips
You should now be fully equipped to drive your Rails application using the Acceptance-Unit Test
Cycle. Next up, we will go through the cycle of developing a feature using this approach.
Note: As in all other projects during the course we will be storing our work using git. Initialize a
git repository inside the application's directory and remember you should commit early and
often

Our first Acceptance-Unit Test Cycle


Let's develop a simple feature. In the scenario, a user will visit the application's landing page and see
a list of news articles displayed.
Let's start by creating a high-level test file.
features/list_articles.feature
Feature: List articles on landing page
As a visitor,
when I visit the application's landing page,
I would like to see a list of articles
Scenario: Viewing list of articles on application's landing page
When I am on the landing page
Then I should see "A breaking news item"
And I should see "Some really breaking action"
Run the scenario and see it fail.
$ bundle exec cucumber features/list_articles.feature
In the terminal output, you will see snippets for implementing steps:
You can implement step definitions for undefined steps with these sn
ippets:
When(/^I am on the landing page$/) do
141

BDD with Rails

pending # Write code here that turns the phrase above into concret
e actions
end
Then(/^I should see "([^"]*)"$/) do |arg1|
pending # Write code here that turns the phrase above into concret
e actions
end
Let's copy those steps into features/step_definitions/landing_page_steps.rb and
edit them:
When(/^I am on the landing page$/) do
visit root_path
end
Then(/^I should see "([^"]*)"$/) do |content|
expect(page).to have_content content
end
If you run the scenario again, you will see that it fails since the route root_path is not defined.
Let's add it to our routes.rb file:
config/routes.rb
Rails.application.routes.draw do
root controller: :landing, action: :index
end
Now implement the controller that will serve the / route. For that, we will use a Rails Generator.
$ rails generate controller landing index
create app/controllers/landing_controller.rb
route get 'landing/index'
invoke erb
create app/views/landing
create app/views/landing/index.html.erb
invoke rspec
Now that we have the root_path setup with the LandingController, let's run our tests again. The
first step of our scenario is now green.
Using the default profile...
[...]
Scenario: Viewing list of articles on application's landing page # f
eatures/list_articles.feature:6
When I am on the landing page # features/step_definitions/landing_
page_steps.rb:1
Then I should see "A breaking news item" # features/step_definitio
ns/landing_page_steps.rb:5
expected to find text "A breaking news item" in "Landing#index F
ind me in app/views/landing/index.html.erb" (RSpec::Expectations::Ex
pectationNotMetError)
142

BDD with Rails

./features/step_definitions/landing_page_steps.rb:6:in `/^I shou


ld see "([^"]*)"$/'
features/list_articles.feature:8:in `Then I should see "A breaki
ng news item"'
And I should see "Some really breaking action" # features/step_def
initions/landing_page_steps.rb:5
Failing Scenarios:
cucumber features/list_articles.feature:6 # Scenario: Viewing list o
f articles on application's landing page
1 scenario (1 failed)
3 steps (1 failed, 1 skipped, 1 passed)
0m0.533s
Let's get the remaining steps to a green state too. Looking at the previous output of cucumber, we note
that it expected to see some text on the page, but didn't find any that was specified. It also points us to
the file that was supposed to render the text in question
app/views/landing/index.html.erb.
All we need to do at this stage to get the remaining tests to pass is add the respective texts to that view
file. Edit app/views/landing/index.html.erb and replace the file's content with:
<p>A breaking news item</p>
<p>Some really breaking action</p>
Run cucumber now and see all the test pass.
Great! all our steps are now green. We are not quite done yet though. If we take a look at our user
story:
As a visitor,
when I visit the application's landing page,
I would like to see a list of articles
We want to list a number of articles on our landing page. All we managed to do so far is display two
static texts on our page. Let's update our cucumber scenario to clearly reflect that:
Feature: List articles on landing page
As a visitor,
when I visit the application's landing page,
I would like to see a list of articles
Background: Given the following articles exists
| title | content |
| A breaking news item | Some really breaking action |
| Learn Rails 5 | Build awesome rails applications |
Scenario: Viewing list of articles on application's landing page
When I am on the landing page
Then I should see "A breaking news item"
And I should see "Some really breaking action"
And I should see "Learn Rails 5"
And I should see "Build awesome rails applications"
143

BDD with Rails

We now have a Background block that will set the stage for our scenario by creating two articles in
our database. We then check to see that both articles are displayed on our landing page. If we run
cucumber now, we get an undefined step with the following code snippet:
Given(/^the following articles exists$/) do |table|
# table is a Cucumber::MultilineArgument::DataTable
pending # Write code here that turns the phrase above into concret
e actions
end
Let's add that to our step definition.
Given(/^the following articles exists$/) do |table|
table.hashes.each do |hash|
Article.create!(hash)
end
end
Now run the feature file and see a new error message:
...
uninitialized constant Article (NameError)
...
Failing Scenarios:
cucumber features/list_articles.feature:12 # Scenario: Viewing list
of articles on application's landing page
1 scenario (1 failed)
6 steps (1 failed, 5 skipped)
0m0.155s
This is a very crucial step in our development process. This is as far as you can go in the high-level
tests - at this point, we need to shift our attention to the process of creating our units (Article) using
tests as a blueprint. It's time to focus on the lower-level (inside) tests using RSpec.
Let's think about how we want our Articles to be structured:
We need to be able to store Articles in our database - meaning we need to create a Model
We want each article to have a title and content - we need to add these attributes to our
Article model
We don't want to store articles that don't have title and content present - meaning we
need to add a validation that these attributes are not empty
We want to make sure that there is a valid Factory to use in our test environment
The first thing we want to do is to create a spec file in the spec/models folder called
article_spec.rb. Let's add some tests (we will use the matchers provided to us by the
shoulda-matchers gem that extends the built-in RSpec matchers).
spec/models/article_spec.rb
require 'rails_helper'

144

BDD with Rails

RSpec.describe Article, type: :model do


describe 'DB table' do
it { is_expected.to have_db_column :id }
it { is_expected.to have_db_column :title }
it { is_expected.to have_db_column :content }
end
describe 'Validations' do
it { is_expected.to validate_presence_of :title }
it { is_expected.to validate_presence_of :content }
end
describe 'Factory' do
it 'should have valid Factory' do
expect(FactoryGirl.create(:article)).to be_valid
end
end
end
If you run RSpec (and you should) now you will see the tests fail - we haven't created the model yet.
It's time to do that. We'll make use of another rails generator to create the Article model. That will
create the class, a migration and a factory (if you are prompted to overwrite the atricle_specjust
type n)
$ rails generate model Article title:string content:text
Following that, you need to run the migrate command to update your database.
$ rails db:migrate
== 20160725233007 CreateArticles: migrating ========================
===========
-- create_table(:articles)
-> 0.0295s
== 20160725233007 CreateArticles: migrated (0.0296s) ===============
===========
Running the spec now you will see that we are moving in the right direction but there are still some
failing tests - we have not set up any validations and those assertions are giving us errors.
Open the model file and add validation for the presence of both title and content.
app/models/article.rb
class Article < ApplicationRecord
validates :title, presence: true
validates :content, presence: true
end
Running your specs now (rspec) will result in all green tests.
At this point, we can go back to our acceptance test and run Cucumber again. We are still not in the
green, but we should have a different error message.
Now, we need to modify the controller as well as the view to actually display (or try to display) our
145

BDD with Rails

articles.
class LandingController < ApplicationController
def index
@articles = Article.all
end
end
And replace the content of the view that will render LandingController#index action and list
all posts.
<ul>
<% @articles.each do |article| %>
<li>
<%= article.title %><br />
<%= article.content %>
</li>
<% end %>
</ul>
At this point, if you run cucumber you should see all tests passing green.
Wrap up
During this walkthrough, we have completed one Acceptance-Unit Cycle (without the refactoring
part) and added a simple feature that allows visitors to view articles on the landing page of the
application.
Using our tests we were able to craft out some functionality and delivered the objective of the feature:
To list articles on the landing page.
We created a route (URL)
We created an Article model with attributes and validations
We created a controller with an index method that fetches all articles from the database and
stores the collection in a variable that is made available to the view template
We created a view that iterates through a collection of articles
In the next cycle, we would certainly add more scenarios to this feature to test other paths. What
should happen when there are no articles in the system? Will multiple articles be displayed correctly?
Etc..
It takes a little practice but with this approach you are constantly in charge of the workflow and
know what the next step of your implementation should be.

146

Rails Messaging

Rails Messaging
Rails Messaging
The scope
We will be building an application that allows users send and receive messages between themselves.
Our application should keep a list of messages organized into folders: sentbox, inbox and trash
and also ability to send notifications via mail.
Let's start out by creating a new rails application and call it rails_messaging. We will use
PostgreSQL and skip the Test Unit gem (We'll be seting up Cucumber for acceptance testing.
$ rails new rails_messaging --database=postgresql --skip-test-unit
We'll be using the Bootstrap css framework under the hood for some quick styling. Next up, we will
need to set up and authentication mechanism, we'll use Devise for that. Let's also throw in the
Mailboxer gem at this point and leave it to simmer for a while while we work on authentication.
gem 'bootstrap-sass'
gem 'devise'
gem 'mailboxer'
We then install our gems using the bundle command
$ bundle install

Authentication
We now need to run Devise's generator to install an initializer that will describe all options available
to configure Devise.
$ rails generate devise:install
Completing the additional steps as described on the post-install messages that appear after running the
above generator, lets create a controller and name it welcome with one action.
$ rails g controller welcome index
Let's make the root path of our application point to the index action of our newly created welcome
controller
!FILENAME config/routes.rb
Rails.application.routes.draw do
root 'welcome#index'
end
We need to tweak the application.html layout file a bit to add the flash messages
!FILENAME app/views/layoits/application.html.erb
147

Rails Messaging

<!DOCTYPE html>
<html>
<head>
<title>Messenger</title>
<%= stylesheet_link_tag 'application', media: 'all', 'data-turboli
nks-track' => true %>
<%= stylesheet_link_tag "//maxcdn.bootstrapcdn.com/font-awesome/4.
3.0/css/font-awesome.min.css" %>
<%= javascript_include_tag 'application', 'data-turbolinks-track'
=> true %>
<%= csrf_meta_tags %>
</head>
<body>
<%= render 'layouts/nav' %>
<% flash.each do |key, value| %>
<div class="text-center <%= flash_class(key) %>">
<%= value %>
</div>
<% end %>
<div class="container">
<%= yield %>
</div>

</body>
</html>
You'll notice the use of the flash_class function that takes the key as an argument, that's actually
a helper method I define in the application_helper.rb in the helpers folder. Used to return the
appropriate bootstrap classes based on the flash key.
!FILENAME app/helpers/application_helper.rb
module ApplicationHelper
def flash_class(level)
case level.to_sym
when :notice then "alert alert-success"
when :info then "alert alert-info"
when :alert then "alert alert-danger"
when :warning then "alert alert-warning"
end
end
end
`
We'll also add _nav partial that holds our applications navigation in the layouts folder.
!FILENAME views/layouts/_nav.html.erb
<!-- Fixed navbar -->
<nav class="navbar navbar-inverse navbar-fixed-top">
148

Rails Messaging

<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-tog
gle="collapse" data-target="#navbar" aria-expanded="false" aria-cont
rols="navbar">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<%= link_to "Messenger", root_path, class: "navbar-brand" %>
</div>
<div id="navbar" class="navbar-collapse collapse">
<ul class="nav navbar-nav pull-right">
<li class="active"><%= link_to "Login", "#" %></li>
<li><%= link_to "Sign up", "#" %></li>
</ul>
</div><!--/.nav-collapse -->
</div>
</nav>
`
We also want to copy over the Devise's views to our project so that we can tweak them
$ rails generate devise:views
Let's create a User model configured with Devise modules, Devise has an awesome generator for
this:
$ rails generate devise User
$ rake db:migrate
The generator also configures your config/routes.rb file to point to the Devise controller. You
can check the User model for any additional configuration options you might want to add but for our
case we will leave it at its defaults. At this point we can update the login and sign up links in our
_nav partial to point to the appropriate routes.
!FILENAME views/layouts/_nav.html.erb
<!-- [.....] -->
<ul class="nav navbar-nav pull-right">
<% if user_signed_in? %>
<li><%= link_to "Hello, #{current_user.name}", "#" %></li>
<li><%= link_to "Logout", destroy_user_session_path, method:
:delete %></li>
<% else %>
<li class="active"><%= link_to "Login", new_user_session_pat
h %></li>
<li><%= link_to "Sign up", new_user_registration_path %></li>
<% end %>
</ul>
<!-- [.....] -->
149

Rails Messaging

We want users to provide their names during registration, lets create a migration to add a name
attribute to our users.
$ rails g migration AddNameToUsers name:string
$ rake db:migrate
We then add the name input field to Devise's views.
!FILENAME app/views/devise/registrations/new.html.erb
<h2>Sign up</h2>
<%= form_for(resource, as: resource_name, url: registration_path(res
ource_name)) do |f| %>
<%= devise_error_messages! %>
<div class="form-group">
<%= f.label :name %>
<%= f.text_field :name, class: "form-control" %>
</div>
<!-- [.....] -->
<%= f.submit "Sign up", class: "btn btn-primary" %>
<% end %>
`
!FILENAME app/views/devise/registrations/edit.html.erb
<h2>Edit <%= resource_name.to_s.humanize %></h2>
<%= form_for(resource, as: resource_name, url: registration_path(res
ource_name), html: { method: :put }) do |f| %>
<%= devise_error_messages! %>
<div class="form-group">
<%= f.label :name %>
<%= f.text_field :name, class: "form-control" %>
</div>
<!-- [....] -->
<%= f.submit "Update", class: "btn btn-primary" %>
<% end %>
<!-- [...] -->
`
Since we are running this app on Rails 4, we will need to whitelist the name parameter. In our
application controller file:
!FILENAME app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
150

Rails Messaging

protect_from_forgery with: :exception


before_action :configure_permitted_parameters, if: :devise_control
ler?
protected
def configure_permitted_parameters
devise_parameter_sanitizer.for(:sign_up) << :name
devise_parameter_sanitizer.for(:account_update) << :name
end
end

Mailboxer
We've already added the Mailboxer gem already in our Gemfile. Now we can proceed to install it and
update our database.
$ rails g mailboxer:install
$ rake db:migrate
The generator creates an initializer file mailboxer.rb which you should use this to edit the default
mailboxer settings. The options are quite straight forward, tweak them to your liking. We will then
change the default name_method since we already have the name attribute in our user model to
prevent conflict.
!FILENAME config/initializers/mailboxer.rb
Mailboxer.setup do |config|
# [...]
#Configures the methods needed by mailboxer
config.email_method = :mailboxer_email
config.name_method = :mailboxer_name
# [...]
end

The Model
For us to equip our User model with Mailboxer functionalities we have to add
acts_as_messageable to it. This will enable us send user-to-user messages. We also need to add
an identity defined by a name and an email in our User model as required by Mailboxer.
Our model must therefore have this specific methods. These methods are: mailboxer_email and
mailboxer_name as stated in our mailboxer initializer file. You should make sure to change this
when you edit their names in the initializer.
!FILENAME app/models/user.rb
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
151

Rails Messaging

acts_as_messageable
def mailboxer_name
self.name
end
def mailboxer_email(object)
self.email
end
end

The Controllers
We will be using two controllers to handle the messaging system.
MailboxController - This controller will hold our top-level folders (inbox, sentbox and trash)
and be responsible for displaying the messages in each of this folders.
ConversationsController - This controller will handle various actions including creating
conversations, replies and deletion of messages etc.
Let's start by creating the mailbox controller
$ rails g controller mailbox
We will then add some custom routes for inbox, sentbox and trash in our routes.rb file.
!FILENAME config/routes.rb
Rails.application.routes.draw do
devise_for :users
root 'welcome#index'
# mailbox folder routes
get "mailbox/inbox" => "mailbox#inbox", as: :mailbox_inbox
get "mailbox/sent" => "mailbox#sent", as: :mailbox_sent
get "mailbox/trash" => "mailbox#trash", as: :mailbox_trash
end
Let's define this methods in our mailbox controller.
!FILENAME app/controllers/mailbox_controller.rb
class MailboxController < ApplicationController
before_action :authenticate_user!
def inbox
@inbox = mailbox.inbox
@active = :inbox
end
def sent
@sent = mailbox.sentbox
@active = :sent
152

Rails Messaging

end
def trash
@trash = mailbox.trash
@active = :trash
end
end
We will use the @active variable to highlight the currently selected folder in our view. We can now
define the mailbox method as a helper method in our application_controller.rb file.
!FILENAME app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
# [...]
helper_method :mailbox
private
def mailbox
@mailbox ||= current_user.mailbox
end
protected
# [...]
end
Let's now create a view file for each of the actions in the mailbox controller. Each of this will share
the same partial to navigate between the mailbox folders.
!FILENAME app/views/mailbox/inbox.html.erb
<%= render partial: 'mailbox/folder_view' %>
!FILENAME app/views/mailbox/sent.html.erb
<%= render partial: 'mailbox/folder_view' %>
!FILENAME app/views/mailbox/trash.html.erb
<%= render partial: 'mailbox/folder_view' %>
Let's create the folder_view partial, in our mailbox folder, create a new file name it
_folder_view.html.erb and paste the following contents.
!FILENAME app/views/mailbox/_folder_view.html.erb
<div class="row">
<div class="spacer"></div>
<div class="col-md-12">
<!-- we'll configure this to compose new conversations later -->
<%= link_to "Compose", "#", class: "btn btn-success" %>
<div class="spacer"></div>
</div>
153

Rails Messaging

<div class="col-md-4">
<div class="panel panel-default">
<div class="panel-body">
<%= render 'mailbox/folders' %>
</div>
</div>
</div>
<div class="col-md-8">
<div class="panel panel-default">
<div class="panel-body">
<!-- individual conversations will show here -->
</div>
</div>
</div>
</div>
Inside here, we also make use of another partial called _folders. Let's also, in the same mailbox
folde, create a new file _folders.html.erb and have the following contents.
!FILENAME app/views/mailbox/_folders.html.erb
<ul class="nav nav-pills nav-stacked">
<li class="<%= active_page(:inbox) %>">
<%= link_to mailbox_inbox_path do %>
<span class="label label-danger pull-right"><%=unread_messag
es_count%></span>
<em class="fa fa-inbox fa-lg"></em>
<span>Inbox</span>
<% end %>
</li>
<li class="<%= active_page(:sent) %>">
<%= link_to mailbox_sent_path do %>
<em class="fa fa-paper-plane-o fa-lg"></em>
<span>Sent</span>
<% end %>
</li>
<li class="<%= active_page(:trash) %>">
<%= link_to mailbox_trash_path do %>
<em class="fa fa-trash-o fa-lg"></em>
<span>Trash</span>
<% end %>
</li>
</ul>
Remember the @active instance variable we instantiated in our mailbox controller? Let's make a
helper method that makes use of that variable to highlight the current page. In our
application_helper.rb file add the following method.
!FILENAME app/helpers/application_helper.rb
154

Rails Messaging

module ApplicationHelper
# [...]
def active_page(active_page)
@active == active_page ? "active" : ""
end
end
For our inbox, it would be a good idea to display the number of unread messages. We will define a
helper method in our mailbox_helper.rb file in the helpers directory.
!FILENAME app/helpers/mailbox_helper.rb
module MailboxHelper
def unread_messages_count
# how to get the number of unread messages for the current user
# using mailboxer
mailbox.inbox(:unread => true).count(:id, :distinct => true)
end
end
We can now add a link in our navigation bar to link to our inbox page.
!FILENAME app/views/layous/_nav.html.erb
<!-- [...] -->
<div id="navbar" class="navbar-collapse collapse">
<ul class="nav navbar-nav pull-right">
<% if user_signed_in? %>
<li><%= link_to "Hello, #{current_user.name}", "#" %></li
>
<li><%= link_to "Inbox", mailbox_inbox_path %></li>
<li><%= link_to "Logout", destroy_user_session_path, met
hod: :delete %></li>
<% else %>
<li class="active"><%= link_to "Login", new_user_session
_path %></li>
<li><%= link_to "Sign up", new_user_registration_path %>
</li>
<% end %>
</ul>
</div><!--/.nav-collapse -->
<!-- [...] -->
Clicking the inbox link should take you to our inbox page with navigation already in place for the
inbox, sent and trash folders.

Conversations
With our mailbox ready to show messages from our inbox, sent and trash folders, it's now time
we create functionality to create and send new messages to other users. Let's go ahead and create our
second controller (conversations).
$ rails g controller conversations
155

Rails Messaging

Let's not forget to add a resources route for our conversations which will also hold other 3 member
routes to handle reply, trash and untrashing of messages.
!FILENAME config/routes.rb
Rails.application.routes.draw do
# [...]
# conversations
resources :conversations do
member do
post :reply
post :trash
post :untrash
end
end
end
With the conversations routes in place, let's edit our compose button link in our folder_view partial to
point to the new action of our conversations controller.
!FILENAME app/views/mailbox/_folder_view.html.erb
<div class="row">
<div class="spacer"></div>
<div class="col-md-12">
<%= link_to "Compose", new_conversation_path, class: "btn btn-su
ccess" %>
<div class="spacer"></div>
</div>
<!-- [...] -->
</div>
We now need to create the new method in our conversations controller and its corresponding view file
!FILENAME app/controllers/conversations_controller.rb
class ConversationsController < ApplicationController
before_action :authenticate_user!
def new
end
end
!FILENAME app/views/conversations/new.html.erb
<%= render partial: 'mailbox/folder_view', locals: { is_conversation:
true } %>
Notice we are still using our folder_view partial which is in our mailbox folder. This partial
will be responsible for displaying different contents based on the current view. To start with, we are
passing in a locale called is_conversation. When this is set to true, we will render the form to
create new conversations and messages, else we will show contents for each mailbox folder.
In our views folder, conversations folder, create a form partial called _form.html.erb, that will
156

Rails Messaging

hold the form to create new conversations.


!FILENAME app/views/conversations/_form.html.erb
<%= form_for :conversation, url: :conversations, html: { class: "" }
do |f| %>
<div class="form-group">
<%= f.label :recipients %>
<%= f.select(:recipients, User.all.collect {|p| [ p.name, p.id
] }, {}, { multiple: true , class: "form-control" })%>
</div>
<div class="form-group">
<%= f.label :subject %>
<%= f.text_field :subject, placeholder: "Subject", class: "form
-control" %>
</div>
<div class="form-group">
<%= f.label :message %>
<%= f.text_area :body, class: 'form-control',placeholder: "Type
your message here", rows: 4 %>
</div>
<%= f.submit "Send Message", class: "btn btn-success" %>
<% end %>
We then need to update our folder_view partial to render our conversation form if the is_conversation
variable is set to true.
!FILENAME app/views/mailbox/_folder_view.html.erb
<div class="row">
<!-- [...] -->
<div class="col-md-8">
<div class="panel panel-default">
<div class="panel-body">
<% if is_conversation %>
<%= render 'conversations/form' %>
<% else %>
<!-- TODO -->
<% end %>
</div>
</div>
</div>
</div>
Let's not forget to update your mailboxes passing the is_conversation locale as false. Update your
mailbox view files (inbox.html.erb,sent.html.erb,trash.html.erb) to read.
!FILENAME inbox.html.erb,sent.html.erb,trash.html.erb (in the
app/views/mailbox/folder )
<%= render partial: 'mailbox/folder_view', locals: { is_conversation:
157

Rails Messaging

false } %>
With this, clicking on the Compose button on either page should take you to the new view of our
conversations controller and you should have a beautiful form ready to send messages
With our form up and running, we need to implement the create action in our conversations controller
to save the messages to the database. We call Mailboxer's send_message method on the current user
and that takes in an array of recipients, the message body and message subject after which we redirect
to the show action of our conversations controller.
!FILENAME app/controllers/conversations_controller.rb
class ConversationsController < ApplicationController
before_action :authenticate_user!
def new
end
def create
recipients = User.where(id: conversation_params[:recipients])
conversation = current_user.send_message(recipients, conversatio
n_params[:body], conversation_params[:subject]).conversation
flash[:success] = "Your message was successfully sent!"
redirect_to conversation_path(conversation)
end
def show
@receipts = conversation.receipts_for(current_user)
# mark conversation as read
conversation.mark_as_read(current_user)
end

private
def conversation_params
params.require(:conversation).permit(:subject, :body,recipients:
[])
end
end
As you can see in the show action, we query all messages in the current conversation for the current
user and store that in the @reciepts variable. Also, the conversation is actually a helper method we
need to define in our application controller to help us get the current conversation.
!FILENAME app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
# [...]
helper_method :mailbox, :conversation
private
# [...]
def conversation
158

Rails Messaging

@conversation ||= mailbox.conversations.find(params[:id])


end
protected
# [...]
end
Creating the show view of the show action:
!FILENAME app/views/conversations/show.html.erb
<div class="row">
<div class="spacer"></div>
<div class="col-md-12">
<%= link_to "Compose", new_conversation_path, class: "btn btn-su
ccess" %>
<div class="spacer"></div>
</div>
<div class="col-md-4">
<div class="panel panel-default">
<div class="panel-body">
<%= render 'mailbox/folders' %>
</div>
</div>
</div>
<div class="col-md-8">
<div class="panel panel-default">
<div class="panel-body">
<%= render partial: 'messages' %>
</div>
</div>
</div>
</div>
Notice we call another partial messages in our new view. Create a new partial _messages in the
conversations folder.
!FILENAME app/views/conversations/_messages.html.erb
<% @receipts.each do |receipt| %>
<% message = receipt.message %>
<div class="media">
<div class="media-left">
<!-- user avators can go here -->
<a href="#">
<img class="media-object" src="http://placehold.it/64x64"
alt="...">
</a>
</div>
<div class="media-body">
<h4 class="media-heading">
159

Rails Messaging

<%= message.sender.name %> <br>


<small><b>Subject: </b><%= message.subject %></small><br>
<small><b>Date: </b><%= message.created_at.strftime("%A,
%b %d, %Y at %I:%M%p") %></small>
</h4>
<%= message.body %>
</div>
</div>
<% end %>
Creating a new conversation should successfully redirect you to that specific conversation and you
should have your beautiful conversation show page like the one below
Now that we can successfully send a message, we need to mechanism to reply, right? We need to add
a reply form on this page and a subsequent reply action in our conversations controller.
!FILENAME app/views/conversations/show.html.erb
<div class="row">
<!--[...]-->
<div class="col-md-8">
<div class="panel panel-default">
<div class="panel-body">
<%= render partial: 'messages' %>
</div>
<div class="panel-footer">
<!-- Reply Form -->
<%= form_for :message, url: reply_conversation_path(conversa
tion) do |f| %>
<div class="form-group">
<%= f.text_area :body, placeholder: "Reply Message", r
ows: 4, class: "form-control" %>
</div>
<%= f.submit "Reply", class: 'btn btn-danger pull-right'
%>
<% end %>
<div class="clearfix"></div>
</div>
</div>
</div>
</div>
Now lets add the reply action to our controller.
!FILENAME app/controllers/conversations_controller.rb
class ConversationsController < ApplicationController
before_action :authenticate_user!
# [...]
def reply
current_user.reply_to_conversation(conversation, message_params[
160

Rails Messaging

:body])
flash[:notice] = "Your reply message was successfully sent!"
redirect_to conversation_path(conversation)
end
private
# [...]
def message_params
params.require(:message).permit(:body, :subject)
end
end
Mailboxer's reply_to_conversation method makes replying to conversations a breeze. It
takes in a conversation and the message body and optionally a subject as arguments. If you want users
to change the subject during reply, then remember to add the subject text field in the reply form and
also in the reply_to_conversation method above as the third argument.

Displaying messages
Our mailbox controller views are lonely and we need to show contents in each of them from which
users can click on a snippet and view the full message. In this folders, we want to show a snippet of
the last message in each unique conversation.
Lets create a new partial conversation inside our conversations folder name it
_conversation.html.erb
!FILENAME app/views/conversations/_conversation.html.erb
<div class="media">
<div class="media-left">
<a href="#">
<img class="media-object" src="http://placehold.it/64x64" alt=
"...">
</a>
</div>
<div class="media-body">
<h4 class="media-heading">
<%= conversation.originator.name %> <br>
<small><b>Subject: </b><%= conversation.subject %></small><br>
<small><b>Date: </b><%= conversation.messages.last.created_at
.strftime("%A, %b %d, %Y at %I:%M%p") %></small>
</h4>
<%= truncate conversation.messages.last.body, length: 145 %>
<%= link_to "View", conversation_path(conversation) %>
</div>
</div>
Updating our inbox, sent and trash views
!FILENAME app/views/inbox.html.erb
<%= render partial: 'mailbox/folder_view', locals: { is_conversation:
false, messages: @inbox } %>
161

Rails Messaging

!FILENAME app/views/sent.html.erb
<%= render partial: 'mailbox/folder_view', locals: { is_conversation:
false, messages: @sent } %>
!FILENAME app/views/trash.html.erb
<%= render partial: 'mailbox/folder_view', locals: { is_conversation:
false, messages: @trash } %>
In all of our three views, we pass in messages as a locale to our folder_view partial which represents
messages from either our inbox, sentbox or trash. Finally lets update our folder_view
partial to use the messages locale and consequently render the conversation partial we just created.
!FILENAME app/views/mailbox/_folder_view.html.erb
<div class="row">
<!--[...]-->
<div class="col-md-8">
<div class="panel panel-default">
<div class="panel-body">
<% if is_conversation %>
<%= render 'conversations/form' %>
<% else %>
<%= render partial: 'conversations/conversation', collec
tion: messages %>
<% end %>
</div>
</div>
</div>
</div>
You should now be able to see your messages, if any, when you click the inbox and sent links in any
view. One last part is remaining, as you can guess, that is ability to delete messages and send them to
the trash and also ability to undelete messages which we might have deleted by mistake.

Deleating and undeleating messages


We will now need to add a "Move to trash" button in each conversation that is not yet deleted. For
those already deleted, we need to show an "Untrash" button.
!FILENAME app/views/conversations/_conversation.html.erb
<div class="row">
<div class="spacer"></div>
<div class="col-md-6">
<%= link_to "Compose", new_conversation_path, class: "btn btn-su
ccess" %>
</div>
<div class="col-md-6 text-right">
<% if conversation.is_trashed?(current_user) %>
<%= link_to 'Untrash', untrash_conversation_path(conversatio
n), class: 'btn btn-info', method: :post %>
<% else %>
162

Rails Messaging

<%= link_to 'Move to trash', trash_conversation_path(convers


ation), class: 'btn btn-danger', method: :post,
data: {confirm: 'Are you sure?'} %>
<% end %>
</div>
</div>
<div class="row">
<div class="spacer"></div>
<div class="col-md-4">
<div class="panel panel-default">
<div class="panel-body">
<%= render 'mailbox/folders' %>
</div>
</div>
</div>
<div class="col-md-8">
<div class="panel panel-default">
<div class="panel-body">
<%= render partial: 'messages' %>
</div>
<div class="panel-footer">
<!-- Reply Form -->
<%= form_for :message, url: reply_conversation_path(conversa
tion) do |f| %>
<div class="form-group">
<%= f.text_area :body, placeholder: "Reply Message", r
ows: 4, class: "form-control" %>
</div>
<%= f.submit "Reply", class: 'btn btn-danger pull-right'
%>
<% end %>
<div class="clearfix"></div>
</div>
</div>
</div>
</div>
We now add the corresponding trash and untrash methods in our controller.
!FILENAME app/controllers/conversations_controller.rb
class ConversationsController < ApplicationController
before_action :authenticate_user!
# [...]
def trash
conversation.move_to_trash(current_user)
redirect_to mailbox_inbox_path
end

163

Rails Messaging

def untrash
conversation.untrash(current_user)
redirect_to mailbox_inbox_path
end
private
# [..]
end
As you can see Mailboxer provides handful methods move_to_trash to send messages to the current
users's trash box and untrash to move back the message back to the users inbox.

Additional features
As you many have noted, the current method of selecting recipients is not very efficient especially
when the number of users increase, finding a user can become way too tedious. We can improve on
this by use of a jQuery plugin called Chosen which makes it simple to select items from a large list
and is more user-friendly. Fortunately for us, there is a chosen-rails gem that makes integrating
this plugin into Rails app very easy.
To Install the gem in our rails app, add chosen-rails to your Gemfile and run bundle install.
!FILENAME GEMFILE
# [...]
gem 'chosen-rails'
We then need to add Chosen to application.js and application.css.scss files:
!FILENAME app/assets/javascripts/application.js
# [....]
//= require jquery
//= require jquery_ujs
//= require chosen-jquery
//= require turbolinks
//= require_tree .
!FILENAME app/assets/stylesheets/application.css
/* [...] */
*
*= require_tree .
*= require chosen
*= require_self
*/
@import 'bootstrap';
@import 'bootstrap/theme';
We then add the chosen-select class to our select dropdown in our conversation's form select field.
!FILENAME app/views/conversations/_form.html.erb
164

Rails Messaging

<%= form_for :conversation, url: :conversations, html: { class: "" }


do |f| %>
<div class="form-group">
<%= f.label :recipients %>
<%= f.select(:recipients, User.all.collect {|p| [ p.name, p.id
] }, {}, { multiple: true , class: "chosen-select form-control" })%>
</div>
<!--[...]-->
<%= f.submit "Send Message", class: "btn btn-success" %>
<% end %>
Lastly, lets equip our multiple-select dropdown with some Chosen magic. As we will be using plain
jquery, rename conversations.js.coffee to conversations.js and paste in the
following code.
!FILENAME app/assets/conversations.js
var ready;
ready = function(){
// enable chosen js
$('.chosen-select').chosen({
no_results_text: 'No results matched'
});
}
$(document).ready(ready);
// if using turbolinks
$(document).on("page:load",ready);
Reload the page with your form and you should see jQuery Chosen in action.

Wrap up
At this stage the application has the core functionality of the mailboxer gem implemented.

165

Working with Legacy Code

Working with Legacy Code


Working with Legacy Code
This week is all about Rails and legacy code! Working with largely untested legacy code base is a
quite common situation you'll might find yourself in as a junior developer - so grasping the skills
needed to get to know an existing code base, write new tests and perform refactoring will make your
life much easier in the future.
From our perspective we feel that throwing you out on deep waters is challenging way to introduce
you to a new framework, but it is a method that will give you a solid foundation to build upon when it
is time to move ahead and build your own applications.
Happy coding!

Your assignment
You are assigned by your Project Manager to work on an application that is totally undocumented and
not actively maintained. Your and your teammates task is to get the code base, deploy it to a staging
server and get it to a state where adding new functionality is possible.
A big part of your work will be to cover the application with tests.
1. Write Acceptance tests for the entire workflow using either RSpec OR Cucumber
2. Write Unit tests for the models using RSpec
3. Set up an automated way of deploying the software.
You are asked to use a service of your choice in order to get metrics on how well the code is tested.
You are free to use any service you want in order to document your work.

Objective
The main objective for this challenge is to practice the workflow that we want to use in our
projects with focus on:
1.
2.
3.
4.

Agile methods for software development


Pair Programming
Collaboration using Git and GitHub
Test- and Behavior Driven Development

Also, setting up a tool that allows you to plan your work (Pivotal Tracker or Waffle.io) in this projects
is not a bad idea.
This is a non-trivial application and even if the gems we will be using will provide most of the
functionality we will need, it is still a hard challenge. The only way you will succeed is if you pay
close attention to details and focus on the right tasks in the right time.
At some point during this week you will be asked to explain your workflow to the coaches. We will
evaluate each and every commit you make to GitHub and we will pay close attention to the sequence
in which you do things.
166

Working with Legacy Code

Documenting all aspects of your application in your README file is, of course, important.
As with every other material, there might be errors in the provided code. It's up to you to find
and correct them.
Finally, remember, Its not just about the code. This challenge is about practicing the workflow.

Resources
Deployed application
https://ca-mailboxer.herokuapp.com/
Code base
https://github.com/CraftAcademy/rails_messaging

167

Tips and Tricks

Tips and Tricks


Tips and Tricks
Routes
When writing your acceptance tests you should use the router helper methods made available by
Rails.
Instead of writing
visit '/'
You can do:
visit root_path
How to find all the different paths, you ask? :wink:. There is a rake task you can use. Just run this in
your terminal.
$ rake routes
Prefix Verb URI Pattern
Controller#Action
new_user_session GET /users/sign_in(.:format)
devise/sessions#new
user_session POST /users/sign_in(.:format)
devise/sessions#create
destroy_user_session DELETE /users/sign_out(.:format)
devise/sessions#destroy
user_password POST /users/password(.:format)
devise/passwords#create
new_user_password GET /users/password/new(.:format)
devise/passwords#new
edit_user_password GET /users/password/edit(.:format)
devise/passwords#edit
PATCH /users/password(.:format)
devise/passwords#update
PUT /users/password(.:format)
devise/passwords#update
cancel_user_registration GET /users/cancel(.:format)
devise/registrations#cancel
user_registration POST /users(.:format)
devise/registrations#create
new_user_registration GET /users/sign_up(.:format)
devise/registrations#new
edit_user_registration GET /users/edit(.:format)
devise/registrations#edit
PATCH /users(.:format)
devise/registrations#update
PUT /users(.:format)
devise/registrations#update
168

Tips and Tricks

DELETE /users(.:format)
devise/registrations#destroy
welcome_index GET /welcome/index(.:format)
welcome#index
root GET /
welcome#index
mailbox_inbox GET /mailbox/inbox(.:format)
mailbox#inbox
mailbox_sent GET /mailbox/sent(.:format)
mailbox#sent
mailbox_trash GET /mailbox/trash(.:format)
mailbox#trash
reply_conversation POST /conversations/:id/reply(.:format)
conversations#reply
trash_conversation POST /conversations/:id/trash(.:format)
conversations#trash
untrash_conversation POST /conversations/:id/untrash(.:format)
conversations#untrash
conversations GET /conversations(.:format)
conversations#index
POST /conversations(.:format)
conversations#create
new_conversation GET /conversations/new(.:format)
conversations#new
edit_conversation GET /conversations/:id/edit(.:format)
conversations#edit
conversation GET /conversations/:id(.:format)
conversations#show
PATCH /conversations/:id(.:format)
conversations#update
PUT /conversations/:id(.:format)
conversations#update
DELETE /conversations/:id(.:format)
conversations#destroy
Now, lets say that you have a user object stored in @user and want to access the edit user view in
your test. In this case you can tell capybara to go to that edit page with:
visit edit_user_registration_path(@user)
Or if you want to go to that users inbox:
visit mailbox_inbox_path
In this case you probably dont have to pass in the @user since we have a method in our
ApplicationController setting mailbox to the current_user.mailbox.
!FILENAME app/controllers/application_controller.rb
def mailbox
@mailbox ||= current_user.mailbox
end
current_user being a Devise method of course.
169

Tips and Tricks

Warden helpers
The gem we are using for user authentication, Devise, is build upon Warden that you might remember
from the Slow Food challenge.
Using Warden/Devise methods to log in a user: Create a new file in the features/support
folder.
!FILENAME features/support/warden.rb
Warden.test_mode!
World Warden::Test::Helpers
After { Warden.test_reset! }
Now you can create a step def that look something like this:
Given(/^I am logged in as "([^"]*)"$/) do |name|
user = User.find_by(name: name)
login_as(user, scope: :user)
end
With this method you are NOT required to hardcode and use the users password. You need to
understand that the controller action is not being hit with this way of logging in the user so in order to
go to a specific page you will need to add a separate step. Like..
And I am on the "inbox page"
...or something.
For logging out the user you can define a step def like this:
Given /^I log out$/ do
logout
end
Much cleaner and DRY.

Testing JavaScript
Add bothe Poltergeist and PhantomJS to your Gemfile.
!FILENAME Gemfile
gem 'poltergeist'
gem 'phantomjs', require: 'phantomjs/poltergeist'
In your features/support/env.rb add poltergeist as a dependancy.
!FILENAME features/support/env.rb
require 'capybara/poltergeist'
...
Capybara.javascript_driver = :poltergeist
With this setup you have access to a full set of Poltergeist commands you can use in your step
170

Tips and Tricks

definitions. To activate the JavaScript driver on a scenario you have to tag your scenario with
@javascript.
@javascript
Scenario: Deleting a message
Given I am logged-in as "Daniel"
And I send a mail to "Jenny"
And I am on the "home page"
...

Mailboxer methods
You can use methods available by the Mailboxer gem in your step definitions to make them run faster.
Let's say you have a scenario like this:
Background:
Given following users exists
| name | email | password |
| Jenny | jenny@ranom.com | password |
| Daniel | daniel@random.com | password |
Scenario: Deleting a message
Given I am logged in as "Daniel"
And I send a mail to "Jenny"
And I am on the "home page"
And I click on the "Logout" link
Given I am logged-in as "Jenny"
And I am on the "home page"
And I click on the "Inbox" link
Then I should have "1" messages
And I click on the "View" link
And I click on the "Move to trash" link
Then I should have "0" messages
You can use some mailboxer methods
Given(/^I am logged in as "([^"]*)"$/) do |name|
@user = User.find_by(name: name)
login_as(@user, scope: :user)
end
And(/^I send a mail to "([^"]*)"$/) do |name|
@receiver = User.find_by(name: name)
@user.send_message(@receiver, 'Lorem ipsum...', 'Subject')
end

Then(/^I should have "([^"]*)" messages$/) do |expected_count|


count = @receiver.mailbox.inbox.count
expect(count).to eq expected_count.to_i
end

171

Tips and Tricks

172

Mid Course Project

Mid Course Project


Mid Course Project
It's Week 6 and that means that you will be putting all your new skills into practice. It's time for the
Mid Course Project.

Objectives
Put all of the skills you have picked up to use in a group project.
1.
2.
3.
4.
5.

Agile methods for software development


Pair Programming
Collaboration using Git and GitHub
Test- and Behavior Driven Development
Third-party services

Requirements
Build and deploy a SAAS (Software as as Service) application.
Implement user authentication and authorization (Devise, CanCanCan)
Implement OAuth authentication
Implement some sort of market place functionality (services or products)
Make use of a payment gateway (PayPal, Klarna, Stripe)
Make use of the geocoding and maps (Google Maps, OpenStreet Map) for vendors or service
providers.
Implement a rating system (for products, vendors or something else)
Set up a third party API integration (TraficLab, Facebook, etc)

Execution
The MidCourse Project is about execution: planning your work, collaboration as a team, doing the
right thing at the right time and always have your priorities straight.
1. How do we start our project?
2. What is the domain?
3. Do we start with creating a high level description or filling the backlog with user stories and
chores. Or both?
4. Shall we make use of LoFi's?
5. What framework shall we use? Rails or Sinatra? Or something else?
6. Who does what and when?
7. Should we have a main repository?
Remember that this is a simulation of a real enterprise project and even if there is a lot of uncertainties
in terms of client requirements you can treat this as a project for a start-up. You have the power to
make the decisions. The above mentioned requirements are suggestions to make the project an
interesting learning experience.
This project will become part of your personal portfolio so don't aim too high but do make sure that
there is enough complexity for an reviewer to find the application interesting.
173

Mid Course Project

174

Project Schedule

Project Schedule
Project Schedule
The MidCourse Project is a 7-day task. During this week you need to work as a regular project team
and plan and execute daily stand-ups and project planning meetings on your own.

Suggested schedule
Start each day with a stand-up to access each team members progress and to plan your daily activities.
Assign a team member to be responsible for deployment. That person can have multiple heroku
sources on his local repo or, even better, set up Travis (or what have you) to deploy to different
servers depending on what branch is being used (you should work on development branch.
End each day with a deploy to a development server and a short demo in a stand up format. Plan the
evening's or the next day's activities.

Tips and Tricks


1. Create pairs to work on features or assign individual team members to a feature.
2. Make sure to flag your Waffle issues as a feature, chore or bug
3. Write as many tests as you can. You will be tempted to skip some tests but that will lead to a lot
of manual testing that is really inefficient use of your time.
4. Ask for help. We are here to help you. If there is a feature that you would like to implement but
lack knowledge on how, don't hesitate to ask us for help. DO your own research first but don't
stay stuck on anything for more than 30 minutes.
5. Remember that this is a simulation, so play pretend. This week can mean a lot of fun if you
make a bit of a theatrical game out of it. ;-)
6. Remember that there is NO project that does not include some level of chaos. If you think that
all professional projects runs smoothly, forget about that. Remember everything we have
thought you and make use of it this week and you'll be fine!

Feature descriptions - User stories


Use the following guidance when describing features.
Field

Description

For user stories, provide enough detail for estimating how much work will be required to
implement the story. Focus on who the feature is for, what users want to accomplish, and
Description
why. Don't describe how the feature should be developed. Do provide sufficient details
so that your team can write tasks and test cases to implement the item.
Provide the criteria to be met before the bug or user story can be closed.
Before work begins, describe the customer acceptance criteria as clearly as possible.
Acceptance Conversations between the team and customers to define the acceptance criteria will
Criteria
help ensure that your team understands your customer's expectations. The acceptance
criteria can be used as the basis for acceptance tests so that you can more effectively
evaluate whether an item has been satisfactorily completed.
The area of customer value addressed by the epic, feature, requirement, or backlog item.
Values include:
175

Project Schedule

Value area Architectural: Technical services to implement business features that deliver solution
Business: Services that fulfill customers or stakeholder needs that directly deliver
customer value to support the business (Default)
Story
Estimate the amount of work required to complete a user story using any unit of
Points
measurement your team prefers, such as t-shirt size, story points, or time.
A subjective rating of the user story, feature, or requirement as it relates to the business.
Allowed values are
1. Product cannot ship without the feature.
Priority
2: Product cannot ship without the feature, but it doesn't have to be addressed
immediately.
3: Implementation of the feature is optional based on resources, time, and risk.
A subjective rating of the relative uncertainty around the successful completion of a user
story. Allowed values are:
Risk
1 - High
2 - Medium
3 - Low

176

Going mobile with Ionic

Going mobile with Ionic


Going mobile with Ionic
We are spending more time online than with any other media and much of that digital time is
spent on mobile devices. Most of that time is spent in apps rather than in the mobile browser.
But hold on! My website is responsive, so why do I need a mobile App?
Responsive web design is just one part of a complete mobile strategy. Responsive designed sites
ensure that no matter which device is being used, visitors are met with a usable experience. Native
apps, on the other hand, take the mobile experience to the next level. At their best, they do more than
just repackage existing content and functionality; they also present navigation, content, and
functionality in a way specifically optimized for the measurably different ways in which people
actually use their mobile devices.

Hybrid app development with Ionic


Ionic is an HTML5 mobile app development framework targeted at building hybrid mobile apps.
Hybrid apps are essentially small websites running in a browser shell in an app that have access to the
native platform layer. Hybrid apps have many benefits over pure native apps, specifically in terms of
platform support, speed of development, and access to 3rd party code.

177

Going mobile with Ionic

Think of Ionic as the front-end UI framework that handles all of the look and feel and UI interactions
your app needs in order to be compelling. Kind of like Bootstrap for Native, but with support for a
broad range of common native mobile components, slick animations, and beautiful design.
Unlike a responsive framework, Ionic comes with very native-styled mobile UI elements and layouts
that you would get with a native SDK on iOS or Android but didnt really exist before on the web.
Ionic also gives you some opinionated but powerful ways to build mobile applications that eclipse
existing HTML5 development frameworks.

178

Getting started

Getting started
Getting started
Prerequisites
We will build a simple application that allows the user to enter his weight and height and calculate his
personal BMI value. This requires you to have completed the previously described BMI Challenge. If
you haven't completed that challenge, please go back and do it before you proceed or grab the source
code to get the necessary files from the Extras chapter of this walkthrough. You will need to get your
hands on person.js and bmi_calculator.js.

Install and set up


You need to install Node.js. Make sure that Homebrew is up to date.
$ brew update
$ brew doctor
Next, install Node (this will also install npm):
$ brew install node
To check your version of node run
$ node -v
v4.3.1 # Might be a different version on your system
Then, install the latest Cordova and Ionic command-line tools.
$ npm install -g cordova ionic
iOS development requires iOS simulator which you can install with
$ npm -g install ios-sim
Better yet, you should make sure that you have Xcode installed on your system.
Good stuff. You are ready to start your project.

Initiate your app


We will make use of the Ionic Tabs template. Navigate to the folder where you want to create your
project and run the following command to start your application.
$ ionic start bmi_calculator tabs
Once that is complete, run the following commands to add support for iOS and Android.
$ cd bmi_calculator
$ ionic platform add ios
$ ionic platform add android
179

Getting started

At this point, inside the project folder, you should initialize a git repository.
$ git init
$ git add .
$ git commit -am "scaffold new Ionic tabs application"
You should also create a new repository on GitHub and add it as a remote. Git flow is outside the
scope of this walkthrough but do make use of version control when working on this project.

Run the application


In your terminal run the following command to start a local server and run the application
$ ionic serve -c --lab
A browser window will open and you'll be presented with a view of both the iOS and the Android
version of the application. Pretty cool, ey?

180

Getting started

Figure: Initial view

181

Cleaning up and adding views

Cleaning up and adding views


Cleaning up and adding views
As you could see in the preview, the scaffolded app comes with some initial views, tabs, services and
controllers. We will not be using them so we might as well delete those files and clean up the code.
Open up the application code in your editor. We are using Atom so we will run this command from
our project folder:
$ atom .

182

Cleaning up and adding views

Figure: BMI Calculator source code in Atom

The folder I want you to focus on is the www folder. That is where we will do most of our work. But
let's start with adding the following two lines to your .gitignore file. It is located in the project
root.
!FILENAME .gitignore
.idea/
.DS_Store
This ensures that no unnecessary files tracked under version control.
Find app.js in the www/js folder and locate the following parts:
!FILENAME www/js/app.js
.state('tab.dash', {
url: '/dash',
views: {
'tab-dash': {
templateUrl: 'templates/tab-dash.html',
controller: 'DashCtrl'
}
}
})
183

Cleaning up and adding views

.state('tab.chats', {
url: '/chats',
views: {
'tab-chats': {
templateUrl: 'templates/tab-chats.html',
controller: 'ChatsCtrl'
}
}
})
.state('tab.chat-detail', {
url: '/chats/:chatId',
views: {
'tab-chats': {
templateUrl: 'templates/chat-detail.html',
controller: 'ChatDetailCtrl'
}
}
})
.state('tab.account', {
url: '/account',
views: {
'tab-account': {
templateUrl: 'templates/tab-account.html',
controller: 'AccountCtrl'
}
}
});
Delete that entire block of code. Now head over to the controllers.js file and delete the
following code:
!FILENAME www/js/controllers.js
.controller('DashCtrl', function($scope) {})
.controller('ChatsCtrl', function($scope, Chats) {
// With the new view caching in Ionic, Controllers are only called
// when they are recreated or on app start, instead of every page
change.
// To listen for when this page is active (for example, to refresh
data),
// listen for the $ionicView.enter event:
//
//$scope.$on('$ionicView.enter', function(e) {
//});
$scope.chats = Chats.all();
$scope.remove = function(chat) {
Chats.remove(chat);
};
})

184

Cleaning up and adding views

.controller('ChatDetailCtrl', function($scope, $stateParams, Chats)


{
$scope.chat = Chats.get($stateParams.chatId);
})
.controller('AccountCtrl', function($scope) {
$scope.settings = {
enableFriends: true
};
});
In the js folder, you'll also find the services.js file. Open it up and delete the Chats factory.
!FILENAME www/js/services.js
.factory('Chats', function() {
// Might use a resource here that returns a JSON array
// Some fake testing data
var chats = [{
id: 0,
name: 'Ben Sparrow',
lastText: 'You on your way?',
face: 'img/ben.png'
[...]
}
return null;
}
};
});
There is also a tabs.html file in the templates folder. Open it up and delete the following code:
!FILENAME www/templates/tabs.html
<!-- Dashboard Tab -->
<ion-tab title="Status" icon-off="ion-ios-pulse" icon-on="ion-iospulse-strong" href="#/tab/dash">
<ion-nav-view name="tab-dash"></ion-nav-view>
</ion-tab>
<!-- Chats Tab -->
<ion-tab title="Chats" icon-off="ion-ios-chatboxes-outline" icon-on
="ion-ios-chatboxes" href="#/tab/chats">
<ion-nav-view name="tab-chats"></ion-nav-view>
</ion-tab>
<!-- Account Tab -->
<ion-tab title="Account" icon-off="ion-ios-gear-outline" icon-on="
ion-ios-gear" href="#/tab/account">
<ion-nav-view name="tab-account"></ion-nav-view>
</ion-tab>
Also, make sure to delete the following files:
185

Cleaning up and adding views

www/templates/chat-detail.html
www/templates/tab-account.html
www/templates/tab-chats.html
www/templates/tab-dash.html
The only file we want to keep is the templates/tabs.html
Make sure you commit your changes with:
$ git add .
$ git commit -am "clean up scaffolded code"

Adding views
Now that we have cleaned up the code base we are ready to add our own stuff. We'll be making use of
two tabs. One About tab that will route us to an "About page", and one "BMI Calculator" tab that will
be displaying the view where we will be doing the calculations.
Let's start with creating the About page.
Open up the templates/tabs.html file again and add the markup for the About tab
!FILENAME www/templates/tabs.html
<!-- AboutTab -->
<ion-tab title="About" icon-off="ion-ios-compose-outline" icon-on="i
on-ios-compose" href="#/tab/about">
<ion-nav-view name="tab-about"></ion-nav-view>
</ion-tab>
Create a route by adding this code to the js/app.js where we define $stateProvider
!FILENAME www/js/app.js
[...]
.state('tab.about', {
url: '/about',
views: {
'tab-about': {
templateUrl: 'templates/about/about.html',
controller: 'AboutController'
}
}
})
Also, in the same file, at the bottom of the $stateProvider block, change the following code to
point to the About page as default.
!FILENAME www/js/app.js
[...]
// if none of the above states are matched, use this as the fallback
$urlRouterProvider.otherwise('/tab/about');
Okay, time to add a HTML template we want to display then that tab is called upon.
186

Cleaning up and adding views

In your templates folder create a new folder named about and add a new template inside that
folder. Call it about.html.
(Note, I prefer to keep my templates in separate folders for better readability)
$ mkdir www/templates/about
$ touch www/templates/about/about.html
Head over to the controllers.js file in your www/js folder and create an
AboutController
.controller('AboutController', function () {
});
And finally, open up the about.html template and add some content.
!FILENAME www/templates/about/about.html
<ion-view title="BMI Mobile">
<ion-content>
<div class="row">
<div class="col">
<p>Craft Academy Demo application porting the BMI calculator
to Ionic.</p>
</div>
</div>
</ion-content>
</ion-view>
At this stage you can start the server again (you can keep it running in the background on a separate
Terminal tab. It is reloading your code as you make the changes) and head over to the browser. You
should see your new About page as the default view.

187

Cleaning up and adding views

Figure: BMI Calculator About view

188

Adding the calculator tab

Adding the calculator tab


Adding the calculator tab
In order to add the view where we will be displaying our calculator, we need to go through same steps
that we did when adding the About view.
1.
2.
3.
4.

Create a tab that will display the view


Add a route and point it to a template and a controller.
Create a controller (we will call it BmiContoller)
Add a HTML template for the view

Let's open the www/templates/tabs.html file and add the markup for the tab:
!FILENAME www/templates/tabs.html
<!-- BmiTab -->
<ion-tab title="BMI Calculator" icon-off="ion-ios-compose-outline" i
con-on="ion-ios-compose" href="#/tab/bmi">
<ion-nav-view name="tab-bmi"></ion-nav-view>
</ion-tab>
Create the route in www/js/app.js by adding the following ABOVE the About route (the reason
for that is that you might mess up some semicolon settings at the end of the block)
!FILENAME www/js/app.js
[...]
.state('tab.bmi', {
url: '/bmi',
views: {
'tab-bmi': {
templateUrl: 'templates/calculator/calculator.html',
controller: 'BmiController'
}
}
})
.state('tab.about', {
url: '/about',
views: {
'tab-about': {
templateUrl: 'templates/about/about.html',
controller: 'AboutController'
}
}
});
Now, let's create the controller in www/js/controllers.js. (Again, watch the semicolon!)
!FILENAME www/js/controllers.js
189

Adding the calculator tab

.controller('BmiController', function() {
});
And finally, let's create the template.
$ mkdir www/templates/calculator
$ touch www/templates/calculator/calculator.html
Open the file and add some basic content:
!FILENAME www/templates/calculator/calculator.html
<ion-view title="BMI Calculator">
<ion-content>
<div class="row">
<div class="col">
</div>
</div>
</ion-content>
</ion-view>
At this point you should be able to navigate between the two tabs we've set up if you visit your app in
the browser.

190

Adding the calculator tab

Figure: BMI View

191

Adding functionality

Adding functionality
Adding functionality
Okay, let's move on to implementing the BMI Calculator.
Copy the person.js and bmi_calculator.js files into your www/js folder. If you don't
have the source files, you can find them in the Extras chapter of this walkthrough.
Reference those files in your main template file (www/index.html) in the block where you are
including your other js files.
!FILENAME www/index.html
<!-- your app's js -->
<script src="js/app.js"></script>
<script src="js/controllers.js"></script>
<script src="js/services.js"></script>
<script src="js/person.js"></script>
<script src="js/bmi_calculator.js"></script>
Let's create a form on the calculator.html template that will allow the user to input his/her
weight and height and return the calculated values.
!FILENAME www/templates/calculator/calculator.html
[...]
<div class="list">
<label class="item item-input">
<input type="number" class="item item-input" placeholder="Weight"
ng-model="data.weight">
</label>
<label class="item item-input">
<input type="number" class="item item-input" placeholder="Height"
ng-model="data.height">
</label>
<button class="button button-full button-calm" ng-click="calculate
BMI()">Calculate</button>
</div>
When submitted, the form will send the values from the input fields and store it in the controllers
$scope as data and execute the calculateBMI() function. Given that there is a function
defined. There isn't at the moment, so let's define it.
Head over to the BmiController in your www/js/controllers.js file.
We need to add $scope to the controller and define the calculateBMI() function.
!FILENAME www/js/controllers.js
.controller('BmiController', function($scope) {
$scope.data = {};
192

Adding functionality

$scope.calculateBMI = function() {
var person = new Person({
weight: $scope.data.weight,
height: $scope.data.height
});
person.calculate_bmi_met();
$scope.person = person;
};
});
This will make the object person accessible in the HTML template for us to show. Lets add the
following code to calculator.html
!FILENAME www/templates/calculator/calculator.html
[...]
<div class="card" ng-if="person">
<div class="item item-divider">
BMI Calculation
</div>
<div class="item item-text-wrap">
<p>Person: Weight {{person.weight}}, Height {{person.height}}</p>
<p>BMI: {{person.bmiValue}}</p>
<p><strong>You are {{person.bmiMessage}}</strong></p>
</div>
</div>
Some explanation
1. The ng-if="person" makes sure that the div element is only displayed IF the
$scope.person object defined.
2. The {{}} brackets are a way to display variables/objects passed in to the template from the
controller.
You can head over to the browser and try it out. Remember that we have set up the metric method of
calculation the users BMI. There is a method to calculate BMI using the imperial method in the
bmi_calculator.js code. You can easily make the switch or add functionality that allows the
user to switch between the two methods.
That is however outside the scope of this walkthrough. ;-) The final app should look something like
this.

193

Adding functionality

Figure: BMI Calculator

Wrap up
There are plenty of other little things you can do in order to update the UI and add functionality to this
little application.
One final thing before we call it quits is to publish your app to Ionic View.

194

Adding functionality

Head over to the Ionic platform signup page to get signed up and to get your Ionic ID.

195

Adding functionality

In your project folder, run the following command:


$ ionic upload
You will be prompted for your username and password (the one you received when you signed
up). Once this is done, your app will be uploaded to the Ionic platform.
Once this is finished, you will receive output like the following:
Uploading App...
App Uploaded (xxxxxxx) #that will be your application code
Once this message is shown, your app has been uploaded to the Ionic platform and ready to go.
If you want to view your Ionic App on an iOS or Android device you need to install the Ionic View
app from the App Store/Google Play. Log in with your credentials. Once logged in, you should see
your uploaded app
In order to share your Ionic app with your friends share the app number with them and ask them to
download the Ionic View app. If they input that number in the Ionic View app they will be given
access to your app.

196

Extras - Source code

Extras - Source code


Extras - Source code
If you haven't completed the BMI Challenge, you can get the code for the person.js and the
bmi_calculator.js right here. Make sure you place them in the right folder.
If you HAVE your own code, use that code. Also, if you plan to implement an extended UI, including
the Imperial method, the code below will not work.
person.js
function Person(attr) {
this.weight = attr.weight;
this.height = attr.height;
};
Person.prototype.calculate_bmi_met = function() {
calculator = new BMICalculator();
calculator.metric_bmi(this);
};
Person.prototype.calculate_bmi_imp = function() {
calculator = new BMICalculator();
calculator.imperial_bmi(this);
};
bmi_calculator.js
function BMICalculator(){
};
BMICalculator.prototype.metric_bmi = function(obj) {
var weight = obj.weight;
var height = obj.height;
if (weight > 0 && height > 0) {
var finalBmi = weight / (height / 100 * height / 100);
obj.bmiValue = parseFloat(finalBmi.toFixed(2));
setBMIMessage(obj);
}
};
BMICalculator.prototype.imperial_bmi = function(obj) {
var weight = obj.weight;
var height = obj.height;
if (weight > 0 && height > 0) {
var finalBmi = (weight * 703 ) / (height * height);
obj.bmiValue = parseFloat(finalBmi.toFixed(2));
setBMIMessage(obj);
}
197

Extras - Source code

};
function setBMIMessage (obj, value){
if (obj.bmiValue < 18.5) {
obj.bmiMessage = "Underweight"
}
if (obj.bmiValue > 18.5 && obj.bmiValue < 25) {
obj.bmiMessage = "Normal"
}
if (obj.bmiValue > 25 && obj.bmiValue < 30) {
obj.bmiMessage = "Overweight"
}
if (obj.bmiValue > 30) {
obj.bmiMessage = "Obese"
}
}

198

The Cooper test challenge

The Cooper test challenge


The Cooper test challenge
We have a client request to build a mobile fitness tracking application. The idea is that the app will
allow users to track their condition using a specific test called The Cooper Test.
The Cooper Test is used to monitor the development of a persons aerobic endurance and to obtain an
estimate of their VO2 max.
The challenge is to build a software solution that will make it possible not to only make the
calculation but to record the data over time. Another requirement is that the app should have
functionality that presents historical data of tests if a user has saved any historical data.
This is a pretty advanced challenge that will require you to use both your Ruby and your
JavaScript knowledge. We will be using Ruby on Rails (back-end/API) and Ionic (mobile client)
as frameworks.

Learning objectives
Learn how to build an API using Ruby on Rails
Learn about testing API endpoints with RSpec using so called request specs
Learn about CORS
Learn how to authenticate users from an Ionic application
Learn how to set consume an API from a mobile client
Learn about Factories and Controllers in AngularJS
Make use of knowledge of JavaScript and Jasmine to build application logic
Extra challenge
Learn about acceptance testing using Protractor

199

The logic

The logic
The logic - Step 1
Let's start at the unit level.
As an athlete,
In order to get to know my results from the Cooper test
I want to be able to input my distance and get a result from the nor
mative data table
We will be doing all calculations on the client side and only save the results to our centralized
database (back-end). This means that we will be working defining our logic in JavaScript. This means
that we will be using Jasmine for Unit testing.
As a first step we want to make sure that our units are working.
Here are the steps you need to complete at this stage:
Create a new project folder
Set up Jasmine (see this section).
Write a JavaScript program that returns an assessment given the users gender, age and
completed distance. Store that program in cooper.js
Make the software accessible in your browsers console
You DON'T need to create a UI at this point.
You need to create a Person object with a minimum of 2 attributes in order to be able to make the
calculation - gender and age are required to be present.
function Person(attr) {
this.gender = attr.gender;
this.age = attr.age;
};
Then, you need to add a function that takes the distance of the 12-minute run and makes the
calculation and fetches the results depending on gender.
There are several ways to achieve that. If you need some inspiration you can review this repo where
you'll find two solutions. One using a case method and the second one that uses array index and
ranges to set the message (written by Raoul).
Make sure you review the specs in order to understand how those method differ and how they can be
implemented.
Remember, try to write your own implementation rather than just copying the provided code. It's all
about learning. When the course is over, you'll be on your own solving problems.
Once you are done, put that code aside. Even though it is the core of the application, we will not
be using it for the next step - the back end. That part does not care about HOW we come up
with the results, just THAT we do. The calculation will be used in our mobile app - not in the
back-end.
200

The logic

201

The Back-end

The Back-end
The Back-end - Step 2
We will be using Ruby on Rails as the stack for our back-end.
The challenge is to set up an API-only application that will make it possible to store information
about users and their historical data.
We will be using RSpec as out testing framework and PostgreSQL as our database.
Note that we WILL NOT be using Cucumber, so you don't have to install that framework. We
are also using Rails 5 in this walkthrough.
Let's go ahead and scaffold our application
rails new cooper_api --api --database=postgresql --skip-test --skipbundle
This will do a couple of things for you:
Configure your application to start with a limited set of middleware than normal.
Make ApplicationController inherit from ActionController::API instead of
ActionController::Base. As with middleware, this will leave out any Action Controller modules
that provide functionalities primarily used by browser applications.
Configure the generators to skip generating views, helpers and assets when you generate a new
resource.
--database=postgresql selects PostgreSQL as the database
--skip-test option skips configuring for the default testing tool.
--skip-bundle option prevents the generator from running bundle install automatically.
Next, update your Gemfile to have the following:
source 'https://rubygems.org'
ruby '2.3.1'
gem 'rails', '~> 5.0.0', '>= 5.0.0.1'
gem 'pg', '~> 0.18'
gem 'puma', '~> 3.0'
gem 'jbuilder', '~> 2.5'
group :development, :test do
gem 'pry'
gem 'pry-byebug'
end
group :development do
gem 'listen', '~> 3.0.5'
gem 'spring'
gem 'spring-watcher-listen', '~> 2.0.0'
end
202

The Back-end

We also want to add the rack-cors gem to allow external clients to access our application. Add the
dependency to your Gemfile.
!FILENAME Gemfile
# Use Rack CORS for handling Cross-Origin Resource Sharing (CORS),
# making cross-origin AJAX possible
gem 'rack-cors', require: 'rack/cors'
Put something like the code below in config/application.rb of your Rails application. This
will allow GET, POST, PUT and DELETE requests from any origin on any resource.
!FILENAME config/application.rb
module CooperApi
class Application < Rails::Application
# [...]
config.middleware.insert_before 0, Rack::Cors do
allow do
origins '*'
resource '*', headers: :any, methods: [:get, :post, :put, :d
elete]
end
end
end
end
There are plenty of settings you can add to enhance security of your application. Read about it in the
rack-cors and devise_token_auth gem documentation.
Next, we are now going to setup our testing framework.
Update your Gemfile with the following gems,
group :development, :test do
gem 'rspec-rails'
gem 'shoulda-matchers'
gem 'factory_girl_rails'
# [...]
end
Remember to run bundle install everytime you update your Gemfile.
Run rails generate rspec:install to install rspec for your rails project. Update the
following file with respective code provided below:
spec/rails_helper.rb
ENV['RAILS_ENV'] ||= 'test'
require File.expand_path('../../config/environment', __FILE__)
abort('The Rails environment is running in production mode!') if Rai
ls.env.production?
require 'spec_helper'
require 'rspec/rails'
203

The Back-end

ActiveRecord::Migration.maintain_test_schema!
Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }
RSpec.configure do |config|
config.fixture_path = "#{::Rails.root}/spec/fixtures"
config.use_transactional_fixtures = true
config.infer_spec_type_from_file_location!
config.filter_rails_from_backtrace!
end
spec/spec_helper.rb
RSpec.configure do |config|
config.expect_with :rspec do |expectations|
expectations.include_chain_clauses_in_custom_matcher_description
s = true
end
config.mock_with :rspec do |mocks|
mocks.verify_partial_doubles = true
end
config.shared_context_metadata_behavior = :apply_to_host_groups
end
.rspec
--color
--require rails_helper
Create the following files:
spec/support/factory_girl.rb
RSpec.configure do |config|
config.include FactoryGirl::Syntax::Methods
end
spec/support/shoulda_matcher.rb
Shoulda::Matchers.configure do |config|
config.integrate do |with|
with.test_framework :rspec
with.library :rails
endend
RSpec.configure do |config|
config.include(Shoulda::Matchers::ActiveRecord, type: :model)
end
That's all for the setup. Next up, we will test-drive the creation of a test endpoint.
204

The Back-end

Testing APIs with RSpec


We will be using request specs to test our api endpoints.
Let's create a dummy endpoint just to make sure everything is okay in terms of security settings.
Let's write our first spec to see if we can get a response from our endpoint.
In your spec folder create a folder named requests. Within that folder we need to add a folder
structure that corresponds to the one we have in our app/controllers folder.
$ mkdir spec/requests
$ mkdir spec/requests/api
$ mkdir spec/requests/api/v0
# or use the -p flag (stand for 'parents')
$ mkdir -p spec/requests/api/v0
Create a ping_spec.rb file and add the following code.
$ touch spec/requests/api/v0/ping_spec.rb
!FILENAME spec\/requests\/api\/v0\/ping_spec.rb
require 'rails_helper'
RSpec.describe Api::V0::PingController, type: :request do
describe 'GET /v0/ping' do
it 'should return Pong' do
get '/api/v0/ping'
json_response = JSON.parse(response.body)
expect(response.status).to eq 200
expect(json_response['message']).to eq 'Pong'
end
end
end
At this stage if we run this test, it will eventually fail. Let's work on getting that to pass.
Note that if this is the first time you're running the test or the application, rails will throw you an
error about non-existant database. Now would be a good time to set them up. Run rails
db:setup --all then rails db:migrate to take care of this.
What we need to do next is create our PingController.
In the app/controllers folder, create the following folder structure.
$ mkdir app/controllers/api
$ mkdir app/controllers/api/v0
Note: The actual API routes will be placed in another namespace that we will call V1.
Inside that folder, we want to create our dummy controller.
205

The Back-end

$ touch app/controllers/api/v0/ping_controller.rb
We will let it inherit from our modified ApplicationController
!FILENAME app\/controllers\/api\/v0\/ping_controller.rb
class Api::V0::PingController < ApplicationController
end
If we run the test now, the error about undefined constant Api::V0::PingController is gone
and RSpec should now throw you a No route matches [GET] "/api/v0/ping" error.
In your routes.rb create an API namespace and add V0 within it. Nested in that namespace we
want to add a :ping resource with one single :index action.
!FILENAME config\/routes.rb
Rails.application.routes.draw do
namespace :api do
namespace :v0 do
resources :ping, only: [:index], constraints: { format: 'json'
}
end
end
end
Run rake routes in your terminal to see if the route has been added properly.
With our route in place, the test now throws the following error
AbstractController::ActionNotFound:
The action 'index' could not be found for Api::V0::PingController
In order to make this spec to pass, we need to add an index method to the
Api::V0::PingController. When called, that method will respond with Json object with a
single entry: {message: 'Pong'}.
!FILENAME app\/controllers\/api\/v0\/ping_controller.rb
class Api::V0::PingController < ApplicationController
def index
render json: { message: 'Pong' }
end
end
Does it work? Fire up your server with rails s and visit
http://localhost:3000/api/v0/ping

Adding a User class


We know that we will be accessing our Rails app from an external client and that we will require
authentication. At this point, you are familiar with Devise - one of the most popular authentication
libraries for Rails applications. We will be using devise_token_auth a token-based
authentication gem for Rails JSON APIs. It is designed to work well with ng-token-auth the
206

The Back-end

token based authentication module for AngularJS.


As usual, we will be testing our units with RSpec and in order to make writing our specs a breeze, we
will use shoulda-matchers, but this is probably old news for you at this stage. Again, if you
need some pointers please go back in this documentation and revisit the BDD with Rails chapter.
Make sure you install the devise_token_auth gem by adding it to your Gemfile and run
bundle install. Note that you don't need to add gem 'devise' since
devise_token_auth requires it automatically.
!FILENAME Gemfile
gem 'devise_token_auth'
Using a generator that the gem provides, we can create a user model, define routes, etc.
Run the following command for an easy one-step installation.
$ rails g devise_token_auth:install User auth
Remember to migrate your database in order to create the users table (the Devise generator created
a migration for you). But before you do that, make sure to open up the migration file and change the
data type for tokens to text.
!FILENAME db/migrate/XXX_devise_token_auth_create_users.rb
## Tokens
t.text :tokens
In our User model (app/models/user.rb) we want to make sure that Devise is set up for our
needs. We will remove the OAuth and Confirmation methods.
!FILENAME app/models/user.rb
class User < ActiveRecord::Base
# Include default devise modules.
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
include DeviseTokenAuth::Concerns::User
end
$ rails db:migrate --all
Another generator we need to run is a Factory generator for User. Generally, Rails generator invokes
the Factory generators once that gem is installed, but not in the case of Devise Token Auth.
Run the generator from your terminal.
$ rails g factory_girl:model User email password password_confirmati
on
Make sure that the factory is properly configured with a valid email and password (if you don't you
will get into trouble when validating the objects it creates).
!FILENAME spec/factories/users.rb
207

The Back-end

FactoryGirl.define do
factory :user do
email 'user@random.com'
password 'password'
password_confirmation 'password'
end
end
You can add more attributes to the User factory if you like, we added just the minimal required
attributes at the moment.
Let's add a spec for the User factory we just created.
!FILENAME spec/models/user_spec.rb
require 'rails_helper'
RSpec.describe User, type: :model do
it 'should have valid Factory' do
expect(FactoryGirl.create(:user)).to be_valid
end
end
Now, we can add some basic model specs for User that will test the Devise setup.
!FILENAME spec\/models\/user_spec.rb
RSpec.describe User, type: :model do
# [...]
describe 'Database table' do
it { is_expected.to have_db_column :id }
it { is_expected.to have_db_column :provider }
it { is_expected.to have_db_column :uid }
it { is_expected.to have_db_column :encrypted_password }
it { is_expected.to have_db_column :reset_password_token }
it { is_expected.to have_db_column :reset_password_sent_at }
it { is_expected.to have_db_column :remember_created_at }
it { is_expected.to have_db_column :sign_in_count }
it { is_expected.to have_db_column :current_sign_in_at }
it { is_expected.to have_db_column :last_sign_in_at }
it { is_expected.to have_db_column :current_sign_in_ip }
it { is_expected.to have_db_column :last_sign_in_ip }
it { is_expected.to have_db_column :confirmation_token }
it { is_expected.to have_db_column :confirmed_at }
it { is_expected.to have_db_column :confirmation_sent_at }
it { is_expected.to have_db_column :unconfirmed_email }
it { is_expected.to have_db_column :nickname }
it { is_expected.to have_db_column :image }
it { is_expected.to have_db_column :email }
it { is_expected.to have_db_column :tokens }
it { is_expected.to have_db_column :created_at }
it { is_expected.to have_db_column :updated_at }
end
end
208

The Back-end

We can also test some basic validations added by Devise.


!FILENAME spec/models/user_spec.rb
RSpec.describe User, type: :model do
#[...]
describe 'Validations' do
it { is_expected.to validate_presence_of(:email) }
it { is_expected.to validate_confirmation_of(:password) }
context 'should not have an invalid email address' do
emails = ['asdf@ ds.com', '@example.com', 'test me @yahoo.com'
,
'asdf@example', 'ddd@.d. .d', 'ddd@.d']
emails.each do |email|
it { is_expected.not_to allow_value(email).for(:email) }
end
end
context 'should have a valid email address' do
emails = ['asdf@ds.com', 'hello@example.uk', 'test1234@yahoo.s
i',
'asdf@example.eu']
emails.each do |email|
it { is_expected.to allow_value(email).for(:email) }
end
end
end
end
Okay, there will be plenty of opportunities to write more specs for the User model. But let's focus on
adding some request specs to test our endpoints.
Next up, we need to add a new namespace to our routes.rb and move the generated Devise route
into that namespace. We also want to tell Devise to skip omniauth_callbacks.
!FILENAME config/routes.rb
Rails.application.routes.draw do
namespace :api do
# [...]
namespace :v1 do
mount_devise_token_auth_for 'User', at: 'auth', skip: [:omniau
th_callbacks]
end
end
end
Now, we can run the rails routes command in the terminal to make sure we are set up correctly.
$ rails routes
209

The Back-end

Prefix Verb URI Pattern


Controller#Action
api_v0_ping_index GET /api/v0/ping(.:format)
api/v0/ping#index {:format=>/(json)/}
new_api_v1_user_session GET /api/v1/auth/sign_in(.:format
) devise_token_auth/sessions#new
api_v1_user_session POST /api/v1/auth/sign_in(.:format
) devise_token_auth/sessions#create
destroy_api_v1_user_session DELETE /api/v1/auth/sign_out(.:forma
t) devise_token_auth/sessions#destroy
api_v1_user_password POST /api/v1/auth/password(.:forma
t) devise_token_auth/passwords#create
new_api_v1_user_password GET /api/v1/auth/password/new(.:f
ormat) devise_token_auth/passwords#new
edit_api_v1_user_password GET /api/v1/auth/password/edit(.:
format) devise_token_auth/passwords#edit
PATCH /api/v1/auth/password(.:forma
t) devise_token_auth/passwords#update
PUT /api/v1/auth/password(.:forma
t) devise_token_auth/passwords#update
cancel_api_v1_user_registration GET /api/v1/auth/cancel(.:format)
devise_token_auth/registrations#cancel
api_v1_user_registration POST /api/v1/auth(.:format)
devise_token_auth/registrations#create
new_api_v1_user_registration GET /api/v1/auth/sign_up(.:format
) devise_token_auth/registrations#new
edit_api_v1_user_registration GET /api/v1/auth/edit(.:format)
devise_token_auth/registrations#edit
PATCH /api/v1/auth(.:format)
devise_token_auth/registrations#update
PUT /api/v1/auth(.:format)
devise_token_auth/registrations#update
DELETE /api/v1/auth(.:format)
devise_token_auth/registrations#destroy
api_v1_user_confirmation POST /api/v1/auth/confirmation(.:f
ormat) devise_token_auth/confirmations#create
new_api_v1_user_confirmation GET /api/v1/auth/confirmation/new
(.:format) devise_token_auth/confirmations#new
GET /api/v1/auth/confirmation(.:f
ormat) devise_token_auth/confirmations#show
api_v1_auth_validate_token GET /api/v1/auth/validate_token(.
:format) devise_token_auth/token_validations#validate_token

Testing the endpoints - request specs


First we need to add a helper method that will parse the server response body to JSON. Create a
support folder in the spec folder. Add a new file named response_json.rb. In that file we
will create a module that will parse the response.body to JSON and allow us to DRY out our
request specs.
!FILENAME spec/support/response_json.rb
module ResponseJSON
210

The Back-end

def response_json
JSON.parse(response.body)
end
end
RSpec.configure do |config|
config.include ResponseJSON
end

User registration
Okay, let's write some specs for user registration.
$ mkdir -p spec/requests/api/v1
$ touch spec/requests/api/v1/registrations_spec.rb
!FILENAME spec/requests/api/v1/registrations_spec.rb
RSpec.describe 'User Registration', type: :request do
let(:headers) { { HTTP_ACCEPT: 'application/json' } }
context 'with valid credentials' do
it 'returns a user and token' do
post '/api/v1/auth', params: {
email: 'example@craftacademy.se', password: 'password',
password_confirmation: 'password'
}, headers: headers
expect(response_json['status']).to eq 'success'
expect(response.status).to eq 200
end
end
context 'returns an error message when user submits' do
it 'non-matching password confirmation' do
post '/api/v1/auth', params: {
email: 'example@craftacademy.se', password: 'password',
password_confirmation: 'wrong_password'
}, headers: headers
expect(response_json['errors']['password_confirmation'])
.to eq ["doesn't match Password"]
expect(response.status).to eq 422
end
it 'an invalid email address' do
post '/api/v1/auth', params: {
email: 'example@craft', password: 'password',
password_confirmation: 'password'
}, headers: headers
expect(response_json['errors']['email']).to eq ['is not an ema
il']
211

The Back-end

expect(response.status).to eq 422 end


it 'an already registered email' do
FactoryGirl.create(:user, email: 'example@craftacademy.se',
password: 'password',
password_confirmation: 'password')
post '/api/v1/auth', params: {
email: 'example@craftacademy.se', password: 'password',
password_confirmation: 'password'
}, headers: headers
expect(response_json['errors']['email']).to eq ['already in us
e']
expect(response.status).to eq 422
end
end
end
The first spec is the happy path testing that user registration with the minimum of required fields
works. The next specs are exposing the error messages we'll get if something goes wrong.
What other possible scenarios in the context of user registration should we test for?

User authentication
Let's write some specs for logging in.
$ touch spec/requests/api/v1/sessions_spec.rb
!FILENAME spec/requests/api/v1/sessions_spec.rb
RSpec.describe 'Sessions', type: :request do
let(:user) { FactoryGirl.create(:user) }
let(:headers) { { HTTP_ACCEPT: 'application/json' } }
describe 'POST /api/v1/auth/sign_in' do
it 'valid credentials returns user' do
post '/api/v1/auth/sign_in', params: {
email: user.email, password: user.password
}, headers: headers
expected_response = {
'data' => {
'id' => user.id, 'uid' => user.email, 'email' => user.emai
l,
'provider' => 'email', 'name' => nil, 'nickname' => nil,
'image' => nil
} }
expect(response_json).to eq expected_response
end
it 'invalid password returns error message' do
212

The Back-end

post '/api/v1/auth/sign_in', params: {


email: user.email, password: 'wrong_password'
}, headers: headers
expect(response_json['errors'])
.to eq ['Invalid login credentials. Please try again.']
expect(response.status).to eq 401
end
it 'invalid email returns error message' do
post '/api/v1/auth/sign_in', params: {
email: 'wrong@email.com', password: user.password
}, headers: headers
expect(response_json['errors'])
.to eq ['Invalid login credentials. Please try again.']
expect(response.status).to eq 401
end
end
end

Add PerformanceData model


Let's add a way to store historical data for each user.
Run the following generator.
rails g model PerformanceData user:references data:hstore --force-pl
ural
Open up the migration and add a line that adds hstore as a datatype and enables the database to
store hashes.
!FILENAME db/migrate/XXXX_create_performance_data.rb
class CreatePerformanceData < ActiveRecord::Migration[5.0]
def change
execute 'CREATE EXTENSION IF NOT EXISTS hstore'
create_table :performance_data do |t|
t.references :user, index: true, foreign_key: true
t.hstore :data
t.timestamps null: false
end
end
end
In your User model, add the following relationship.
!FILENAME app/models/user.rb
class User < ActiveRecord::Base
# [...]
has_many :performance_data, class_name: 'PerformanceData'
213

The Back-end

end
Run the new migration.
$ rails db:migrate --all
Add the following specs.
!FILENAME spec/models/user_spec.rb
RSpec.describe User, type: :model do
# [...]
describe 'Relations' do
it { is_expected.to have_many :performance_data }
end
end
And to the newly created spec/models/performance_data_spec.rb.
!FILENAME spec/models/performance_data_spec.rb
require 'rails_helper'
RSpec.describe PerformanceData, type: :model do
describe 'Database table' do
it { is_expected.to have_db_column :id }
it { is_expected.to have_db_column :data }
end
describe 'Relations' do
it { is_expected.to belong_to :user }
end
end

The controller
Let's create the controller we will use to create, update and retrieve users historical data.
This is where we will be performing our CRUD actions.
The first thing we want to be able to do is to save data.
Let's create a request spec and start adding functionality to our controller.
In the spec/requests/api/v1/ folder we want to create a new test file. Let's call it
performance_data_spec.rb. We can start with a simple test to see if our entry will be saved to
the database (it WILL fail at first, but that is the way we do it, right?)
!FILENAME spec/requests/api/v1/performance_data_spec.rb
RSpec.describe Api::V1::PerformanceDataController, type: :request do
let(:headers) { { HTTP_ACCEPT: 'application/json' } }
describe 'POST /api/v1/performance_data' do
214

The Back-end

it 'creates a data entry' do


post '/api/v1/performance_data', params: {
performance_data: { data: { message: 'Average' } }
}, headers: headers
entry = PerformanceData.last
expect(entry.data).to eq 'message' => 'Average'
end
end
end
Create a new file in the following path: app/controllers/api/v1/ Call it
performance_data_controller.rb and add the class definition to it.
!FILENAME app/controllers/api/v1/performance_data_controller.rb
class Api::V1::PerformanceDataController < ApplicationController
end
We also need to create a route for that so let's modify our routes.rb by adding a post route to it.
Update your config/routes.rb with the following code. Note the addition of defaults: {
format: :json } to our v1 namespace. This will constraint the application to respond to json
requests only.
!FILENAME config\/routes.rb
namespace :v1, defaults: { format: :json } do
# [...]
resources :performance_data, only: [:create]
end
In your terminal, run $ rails routes to make sure everything is working. You should have a
new route pointing to the performance_data_controller.rb's create method.
api_v1_performance_data POST /api/v1/performance_data(.:format) api/
v1/performance_data#create {:format=>:json}
So far so good...
Next, lets add a create method with the following code to our new controller in order to get the test
to pass.
!FILENAME app/controllers/api/v1/performance_data_controller.rb
class Api::V1::PerformanceDataController < ApplicationController
def create
@data = PerformanceData.new(params[:performance_data])
if @data.save
render json: { message: 'all good' }
end
end
end
When you run the test now you will get an error with Rails complaining about
215

The Back-end

ForbiddenAttributesError
Performance Data
POST /api/v1/performance_data/
creates a data entry (FAILED - 1)
Failures:
1) Performance Data POST /api/v1/performance_data/ creates a dat
a entry
Failure/Error: @data = PerformanceData.new(params[:performanc
e_data])
ActiveModel::ForbiddenAttributesError:
ActiveModel::ForbiddenAttributesError
# ./app/controllers/api/v1/performance_data_controller.rb:4:i
n `create'
....
That is due to our params not being whitelisted. Some explanation is in place. Also do through this
resource about Action Controller to fully understand what is going on in the controllers we create and
use in our application.
Action Controller parameters are forbidden to be used in Active Model mass assignments until they
have been whitelisted. Strong Parameters provides an interface for protecting attributes from end-user
assignment.
So we need to whitelist our params. There are many different approaches for doing that. For now we
will whitelist all params contained on the :performance_data key and update the
createmethod to use them. Modify the performance_data_controller.rb with this code.
!FILENAME app\/controllers\/api\/v1\/performance_data_controller.rb
class Api::V1::PerformanceDataController < ApplicationController
def create
@data = PerformanceData.new(performance_data_params)
if @data.save
render json: { message: 'all good' }
else
render json: { error: @data.errors.full_messages }
end
end
private
def performance_data_params
params.require(:performance_data).permit!
end
end
Note: We can use the permit! method for now, but there might be some security issues involved
with that. Can you figure out a better way?
216

The Back-end

Okay, at this stage if you run your tests again you'll get a different error
undefined method `data' for nil:NilClass
The problem is that we are not assigning a user to the created entry, thus resulting in the entry not
being created in the DB. The application does not know what user it should reference to the new
object.
We need to update out controller to retrieve that information. We can do that with the built in Devise
method authenticate_user!. We also need to add the user information to the
performance_data_params. This can be done with a merge! command. Review the
following code before you implement it in your performance_data_controller.rb.
!FILENAME app\/controllers\/api\/v1\/performance_data_controller.rb
class Api::V1::PerformanceDataController < ApplicationController
before_action :authenticate_api_v1_user!
def create
@data = PerformanceData.new(performance_data_params.merge(user:
current_api_v1_user))
if @data.save
render json: { message: 'all good' }
else
render json: { error: @data.errors.full_messages }
end
end
private
def performance_data_params
params.require(:performance_data).permit!
end
end
We also need to update our spec and create a user (using Factory Girl) and sent that users credentials
with the request (in headers). We will use the DeviseTokenAuth method
create_new_auth_token to generate the necessary credentials. Make sure to use your debugger
and break the test at some point to run this command on the user object manually to see it in action.
Anyway, your spec should look something like this.
!FILENAME spec/requests/api/v1/performance_data_spec.rb
RSpec.describe Api::V1::PerformanceDataController, type: :request do
let(:user) { FactoryGirl.create(:user) }
let(:credentials) { user.create_new_auth_token }
let(:headers) { { HTTP_ACCEPT: 'application/json' }.merge!(credent
ials) }
describe 'POST /api/v1/performance_data' do
it 'creates a data entry' do
post '/api/v1/performance_data', params: {
217

The Back-end

performance_data: { data: { message: 'Average' } }


}, headers: headers
entry = PerformanceData.last
expect(entry.data).to eq 'message' => 'Average'
end
end
end
If you run your specs now, you should not be getting any errors as the PerformanceData entry is
connected to a User and no validation errors should be present.
You need, however, add some tests that hits the sad path and makes sure you have full control of what
kind of error messages are being returned if the object you are trying to create fails validation.
But before we continue, let's update our cors configuration to allow more headers keys from user
authentication.
!FILENAME config/application.rb
module CooperApi
class Application < Rails::Application
# [...]
config.middleware.insert_before 0, Rack::Cors do
allow do
origins '*'
resource '*', headers: :any, methods: [:get, :post, :put, :d
elete],
expose: %w(access-token expiry token-type uid
client),
max_age: 0
end
end
end
end
We are almost done now with our basic CRUD actions. Let us move on and create a method that will
retrieve a collection of PerformanceData objects, but only for the user that makes the request. Let's
start by setting the stage for a request spec and test if we get the right response.
!FILENAME spec/requests/api/v1/performance_data_spec.rb
# [...]
describe 'GET /api/v1/performance_data' do
before do
5.times { user.performance_data.create(data: { message: 'Average'
}) }
end
it 'returns a collection of performance data' do
get '/api/v1/performance_data', headers: headers
expect(response_json['entries'].count).to eq 5
end
end
218

The Back-end

Go ahead and run this spec just to get a friendly reminder that there is no such route as GET
/api/v1/performance_data/. Let's add that to our routes.rb
!FILENAME config/routes.rb
namespace :v1 do
#[...]
resources :performance_data, only: [:create, :index]
end
The next error you will see if you run the spec now, tells you that there is no index method defined
in the controller. Let's create that.
!FILENAME app/controllers/api/v1/performance_data_controller.rb
class Api::V1::PerformanceDataController < ApplicationController
# [...]
def index
@collection = current_api_v1_user.performance_data
render json: { entries: @collection }
end
# [...]
end
We would need to add a few more specs to make sure that the index method works the way it is
intended - to only return PerformanceData that belongs to the current user. But I'll leave that for
you to add.
Note that we are NOT building any views or templates to display data. We MIGHT need more control
of how the json response looks like and for that we will use the template capabilities of JBuilder.
But for now, returning a rather uncomplicated json object will do.
That's it! We don't need to create the update and delete actions. There could be a use-case for
them in the future, but at this stage they are not needed.

219

The Client

The Client
The Client - Step 3
We will be using Ionic to build our mobile client. If you are not familiar with Ionic yet, please go
back in the documentation and complete the Going mobile with Ionic chapter.
The objective of this step is to allow the user to calculate his results. We will NOT be accessing the
API yet. The login functionality will not work once we are finished with this step. That will be the
objective of the next chapter.

Set up
For this app we will be using the sidemenu template.
Run this command it your terminal to create the application.
$ ionic start cooper_client sidemenu
cd into the new folder (cooper_client) and install the npm and bower dependencies.
$ npm install
$ bower install
Note: If you don't have bower installed on your computer you can install it with npm
install -g bower
Once that is ready, you might want to start the Ionic server to see if everything is set up correctly.
$ ionic serve -c --lab
You should see something like this.

220

The Client

Figure: Ionic Sidemenu template

Clean up the code


The template comes with a lot of scaffold functions and views that we will not be using.
Open the code in your editor and delete the PlaylistsCtrl and PlaylistCtrl from the
www/js/controllers.js file.
You also want to remove all files in the www/templates folder except the login.html and
menu.html.
Edit the menu.html and delete unused menu items. Keep the Login item
!FILENAME www/templates/menu.html
<ion-content>
<ion-list>
<ion-item menu-close ng-click="login()">
Login
</ion-item>
221

The Client

<!-- Detlete this block below: -->


<ion-item menu-close href="#/app/search">
Search
</ion-item>
<ion-item menu-close href="#/app/browse">
Browse
</ion-item>
<ion-item menu-close href="#/app/playlists">
Playlists
</ion-item>
<!-- end of deleted block -->
</ion-list>
</ion-content>
Finally, in the app.js file, delete unused routes. We will also ADD a route to a new About view.
Make sure to modify the default route to the new /app/about route. Your .config block should
look like this.
!FILENAME www/js/app,js
.config(function($stateProvider, $urlRouterProvider) {
$stateProvider
.state('app', {
url: '/app',
abstract: true,
templateUrl: 'templates/menu.html',
controller: 'AppCtrl'
})
.state('app.about', {
url: '/about',
views: {
'menuContent': {
templateUrl: 'templates/about/about.html'
}
}
});
// if none of the above states are matched, use this as the fallba
ck
$urlRouterProvider.otherwise('/app/about');
});
Create a new folder named about in the www/templates folder. We will place a new template
called about.html in that folder.
!FILENAME www/templates/about/about.html
<ion-view title="About">
<ion-content>
<div class="row">
<div class="col">
Craft Academy Cooper Test Challenge - Mobile Client.
</div>
222

The Client

</div>
</ion-content>
</ion-view>
If you head over to your browser you should see something like this.

223

The Client

This will do for now. We will add the Login functionality further down the road. Right now, we want
to focus on the main functionality of this application - to calculate the test results and give the user
some feedback.

Adding the logic


The next step is to add the cooper.js code to your application.
1. Import the code and place it in the www/js folder.
2. Include it in the main application view (www/index.html)

The test view


Create a template for the test view in the www/templates/test/ folder (you need to
create it first). Call ittest.html`and add the following markup to it.
!FILENAME www/templates/test/test.html
<ion-view title="Cooper Test">
<ion-content>
<div class="row">
<div class="col">
<div class="list">
224

The Client

<label class="item item-input">


<input type="text" class="item item-input" placeholder="
Gender" ng-model="data.gender">
</label>
<label class="item item-input">
<input type="number" class="item item-input" placeholder=
"Age" ng-model="data.age">
</label>
<label class="item item-input">
<input type="number" class="item item-input" placeholder=
"Distance" ng-model="data.distance">
</label>
<button class="button button-full button-calm" ng-click=
"calculateCooper()">Send</button>
</div>

<div class="card" ng-if="person">


<div class="item item-divider">
Cooper Test results
</div>
<div class="item item-text-wrap">
<p>Person: Age {{person.age}}, Gender {{person.gender}}</
p>
<p>Result: {{person.cooperMessage}}</p>
</div>
</div>
</div>
</div>
</ion-content>
</ion-view>
Create a new route in the www/js/app.js to point to the template and a new controller we will be
creating in a minute.
!FILENAME www/js/app.js
.state('app.test', {
url: '/test',
views: {
'menuContent': {
templateUrl: 'templates/test/test.html',
controller: 'TestController'
}
},
})
In the www/templates/menu.html we need to add a new menu item so we can navigate to that
view.
!FILENAME www/templates/menu.html
225

The Client

#[...]
<ion-item menu-close href="#/app/test">
Cooper Test
</ion-item>
#[...]
Let's focus on the controller. We need to create a new controller in www/js/controllers.js
!FILENAME www/js/controllers.js
#[...]
.controller('TestController', function($scope) {
$scope.data = {};
$scope.calculateCooper = function() {
var person = new Person({
gender: $scope.data.gender,
age: $scope.data.age
});
person.assessCooper($scope.data.distance);
$scope.person = person;
console.log($scope.person)
};
Run the app and input some test data and click Send. You should see the results displayed on the
page.

The user interface


Entering values using the keyboard is always hard on a mobile device. We will do some refactoring to
make the user experience a little better, although there is much more you can do on your own to make
this application more appealing to the user (we are mainly focusing on the basic functionality at the
moment).
Let's change the input fields to a select field for gender and sliders for age and distance. We will also
do a small refactoring of the initial values and move those settings to our controller. It feels better to
have them stored there.
Replace the input fields in the test.html template with this form.
!FILENAME
#[...]
<form name="testdata">
<label class="item item-input item-select">
<div class="input-label">
Gender
</div>
<select
ng-model="data.gender"
ng-options="option as option for option in gender"
required></select>
</label>
<div class="item item-divider">Age
226

The Client

<output name="agevalue" for="age"></output>


</div>
<div class="item range range-positive">
<input
name="age"
type="range"
min="{{ageValues.min}}"
max="{{ageValues.max}}"
value="{{ageValues.value}}"
ng-model="data.age"
oninput="agevalue.value = age.value"
required>{{ageValues.min}}/{{ageValues.max}} yrs
</div>
<div class="item item-divider">Distance
<output name="distvalue" for="distance"></output>
</div>
<div class="item range range-positive">
<input
name="distance"
type="range"
min="{{distanceValues.min}}"
max="{{distanceValues.max}}"
value="{{distanceValues.value}}"
ng-model="data.distance"
oninput="distvalue.value = distance.value"
required>{{distanceValues.min}} - {{distanceValues.max}} m
</div>
<button
class="button button-full button-calm"
ng-click="calculateCooper()"
ng-disabled="testdata.$invalid">Get results</button>
</form>
#[...]
And modify your controller with predefined values for gender, age and distance
!FILENAME www/js/controllers.js
#[...]
.controller('TestController', function($scope) {
$scope.gender = ['Male', 'Female']
$scope.ageValues = {
min: 20,
max: 60,
value: 20
};
$scope.distanceValues = {
min: 1000,
max: 3500,
value: 1000
};
227

The Client

$scope.data = {};
#[...]
};
If you run the application you should see this interface.

228

The Client

Figure: Cooper Test Viev UI

229

Connecting the dots

Connecting the dots


Connecting the dots - step 4
It's time to connect the dots and get the two applications to talk to each other.
First we need to deploy the Rails application to a production server. As usual, we'll be using the
simplest solution available.
Let's create a Heroku application
$ heroku create ca-cooper-api
# where 'ca' are your initials
To enable features such as static asset serving and logging on Heroku we need to add the
rails_12factor gem to our Gemfile.
!FILENAME Gemfile
# [...]
group :production do
gem 'rails_12factor'
end
Make sure to run bundle and commit all your changes (You have been making commits during this
walk-through, right?).
Now let's create a database and push the app to Heroku.
$ heroku addons:create heroku-postgresql
$ git push heroku master
$ heroku run rake db:migrate
You can manually test the API using Postman by doing a POST request to the registration endpoint.
As a matter of fact, you need to create at least one user this way in order to move forward.

230

Connecting the dots

And if you try to send the request again, that should fail.

231

Connecting the dots

Alright, if that works we should shift our focus to the Ionic application.
We will be using ng_token_auth - a token based authentication module for AngularJS that works
really well with devise_token_auth.
We'll start by installing the library using Bower. Run the install command from your Terminal.
$ bower install ng-token-auth --save
Make sure that angular-cookie, and ng-token-auth are included in your index.html.
!FILENAME www/index.html
<!-- ionic/angularjs js -->
<script src="lib/ionic/js/ionic.bundle.js"></script>
<script src="lib/angular-cookie/angular-cookie.js"></script>
<script src="lib/ng-token-auth/dist/ng-token-auth.js"></script>
Include ng-token-auth in your module's dependencies and add a basic configuration.
!FILENAME www/js/app.js
angular.module('starter', ['ionic', 'starter.controllers', 'ng-token
-auth'])
.constant('API_URL', 'https://ca-cooper-api.herokuapp.com/api/v1'
)
.config(function ($authProvider, API_URL) {
232

Connecting the dots

$authProvider.configure({
apiUrl: API_URL
});
})
In controllers.js AppCtrl locate the doLogin() method.
!FILENAME www/js/controllers.js
//...
// Perform the login action when the user submits the login form
$scope.doLogin = function () {
$auth.submitLogin($scope.loginData)
.then(function (resp) {
// handle success response
$scope.closeLogin();
})
.catch(function (error) {
// handle error response
$scope.errorMessage = error;
});
};
//...
Now, we need to make some small changes and additions to our view templates.
Change the input field Username to Email and the ng-model from loginData.username to
loginData.email.
!FILENAME www/templates/login.html
//...
<label class="item item-input">
<span class="input-label">Email</span>
<input type="text" ng-model="loginData.email">
</label>
//...
And add a placeholder for display of error messages.
!FILENAME www/templates/login.html
//...
<ion-content>
<div class="row" ng-if="errorMessage">
<p ng-repeat="error in errorMessage.errors">
{{error}}
</p>
</div>
//...
We also want to create a currentUser object. In theAppCtrl add this method to create the
currentUser on successful authentication.
!FILENAME www/js/controllers.js
233

Connecting the dots

//...
$rootScope.$on('auth:login-success', function(ev, user) {
$scope.currentUser = user;
});
//...
And make use of this object on the about.html template.
!FILENAME www/templates/about/about.html
//...
<div class="col">
Craft Academy Cooper Test Challenge - Mobile Client.
<p ng-if="currentUser">Logged in as {{currentUser.email}}</p>
</div>
//...
One final touch to enhance the user experience. Sometimes the API endpoint will take some time to
respond and the user might be left wondering if his request is actually being processed or not. There is
a very simple way to show the user that we are really processing his request by displaying an overlay
with some sort of a message. In our case "Logging is..." could do, right?
Ionic provides us with $ionicLoading as a way to display such overlays. Let's imlement it.
Add $ionicLoading to the AppCtrl.
!FILENAME www/js/controllers.js
//...
.controller('AppCtrl', function ($rootScope,
$scope,
$ionicModal,
$timeout,
$auth,
$ionicLoading) {
//...
And update the doLogin() function with the following code.
!FILENAME www/js/controllers.js
//...
// Perform the login action when the user submits the login form
$scope.doLogin = function () {
$ionicLoading.show({
template: 'Logging in...'
});
$auth.submitLogin($scope.loginData)
.then(function (resp) {
// handle success response
$ionicLoading.hide();
$scope.closeLogin();
})
.catch(function (error) {
234

Connecting the dots

$ionicLoading.hide();
$scope.errorMessage = error;
});
};
//...
With this in place an overlay will be displayed while the app is making the request and hidden when
the promise is resolved.
At this stage we have a method to login the user. The next step will be to add an interface to create
and update users. That is, however, something that we leave up to you. Just a friendly reminder, make
sure that you read the ng_token_auth documentation. Everything you need to know is well
documented in the README file of the project.

235

Saving and retrieving data

Saving and retrieving data


Saving and retrieving data - step 5
At this stage we have a possibility to interact with the back and login to the mobile application. It is
time to set up a mechanism to both save and retrieve our test results.
We have an API endpoint in our beck-end that can take a POST and a GET request, right? Let's build
a service that will give us a way to access it.
We will use ngResource to access out API and perform our requests.
$ bower install angular-resource --save
Create a new file in the www/js folder and call it services.js and make sure to reference it in
the index.html together with all the other dependencies.
$ touch www/js/services.js
www/index.html
<!-- ionic/angularjs js -->
/...
<script src="lib/angular-resource/angular-resource.js"></script>
/...
<!-- your app's js -->
<script src="js/cooper.js"></script>
<script src="js/app.js"></script>
<script src="js/controllers.js"></script>
<script src="js/services.js"></script>
Since we have not used any services of factories before in this application, we need to add our
services to our main module in app.js. We also need to make sure that ngResource is included.
www/js/app.js
angular.module('starter', ['ionic', 'starter.controllers', 'starter.
services', 'ng-token-auth', 'ngResource'])
// ...
Okay, so now we need to create our Factory.
www/js/services.js
angular.module('starter.services', [])
.factory('performanceData', function ($resource, API_URL) {
return $resource(API_URL + '/data', {}, {
query: {method: 'GET', isArray: false}
});
});
236

Saving and retrieving data

With this factory we will be able to both write to and read from our back-end database.
Let's create a new controller for doing that. Remember that we need to include that factory in our
controller in order to make it accessible. We'll also add two methods, one to save the data and a
second one to retrieve it.
www/js/controllers.js
.controller('PerformanceCtrl', function($scope, performaceData){
$scope.saveData = function(){
};
$scope.retrieveData = function(){
};
})
Let's start with saving the data. On our view where we do the calculations, we want to display a
button that calls the saveData function. But we only want to display that button IF there is a user
logged in and the calculation has been performed.
Modify your test.html template with this code.
www/templates/test/test.html
<div ng-if="person">
<div class="card">
<div class="item item-divider">
Cooper Test results
</div>
<div class="item item-text-wrap">
<p>Person: Age {{person.age}}, Gender {{person.gender}}</p>
<p>Result: {{person.cooperMessage}}</p>
</div>
</div>
<button
ng-if="currentUser"
ng-controller="PerformanceCtrl"
class="button button-full button-calm"
ng-click="saveData(person)">Save results
</button>
</div>
Let's build our saveData function with a success and an error fallback. It can look something like
this for the moment.
www/js/controllers.js
$scope.saveData = function(person){
data = {performace_data: {data: {message: person.cooperMessage}}}
performaceData.save(data, function(response){
console.log(response);
}, function(error){
console.log(error);
237

Saving and retrieving data

})
};
If you try this out in the browser while having the console open, you'll see that you'll get an
Unauthorized error.

Well, that's a bit of a problem but we'll solve it. At least we know that the API endpoint is
within our reach. That IS good progress!
So, let's make this work.
The reason we are getting a 401 on the request is because we are not sending any credentials with the
request and thus we can not get authorized.
Let's make sure that we get the necessary info stored in the currentUser object.
At this stage you need to go back to your Rails application for a moment. We need to make an
addition to config/application.rb in order to make the API include authorization credentials
in the response headers.
config/application.rb
# ...
config.middleware.insert_before 0, 'Rack::Cors' do
allow do
origins '*'
#resource '*', headers: :any, methods: [:get, :put, :delete, :po
st, :options]
resource '*',
headers: :any,
methods: [:get, :post, :delete, :put, :options, :head],
expose: %w(access-token expiry token-type uid client),
max_age: 0
end
end
#...
Now we'll be getting the right response from the back-end application. We need to modify the way we
store that information.
Localize the 'auth:login-success' function in our AppCtrl. We will make a change to store
access-token, uid, etc by grabbing that info from the response headers.
www/js/controllers.js
$rootScope.$on('auth:login-success', function (ev, user) {
238

Saving and retrieving data

$scope.currentUser = angular.extend(user, $auth.retrieveData('auth


_headers'));
});
With this setup we should be able to make the POST request and save our data.
We also want to add $ionicLoading, $ionicPopup to our PerformanceCtrl. We will
use them those methods to give our user feedback on the requests progress.
We will also add an showAlert() function and refactor our saveData() function. Examine the
code below to fully understand what it does before you implement it.
www/js/controllers.js
//...
.controller('PerformanceCtrl', function($scope, $state, performaceDa
ta, $ionicLoading, $ionicPopup, $state){
$scope.saveData = function(person){
var data = {performance_data: {data: {message: person.cooperMess
age}}};
$ionicLoading.show({
template: 'Saving...'
});
performaceData.save(data, function(response){
$ionicLoading.hide();
$scope.showAlert('Sucess', response.message);
}, function(error){
$ionicLoading.hide();
$scope.showAlert('Failure', error.statusText);
})
};
$scope.retrieveData = function(){
//Still not implemented...
};
$scope.showAlert = function(message, content) {
var alertPopup = $ionicPopup.alert({
title: message,
template: content
});
alertPopup.then(function(res) {
// Place some action here if needed...
});
};
})
//...

Display data
Before we start retrieving any historical data, let's create a route and a view template for showcasing
it.
239

Saving and retrieving data

First, we start with defining a route in our www/js/app.js file.


www/js/app.js
.state('app.data', {
url: '/data',
params: {
savedDataCollection: {}
},
views: {
'menuContent': {
templateUrl: 'templates/test/data.html',
controller: 'DataCtrl'
}
}
})
Next step is to create a new template.
$ touch www/templates/test/data.html
For starters, let's just add the basic markup before we start adding any content.
www/templates/test/data.html
<ion-view title="Historical Data">
<ion-content>
</ion-content>
</ion-view>
We also want to add an item to the side menu but condition it's display to a state where there is a
currentUser signed in. We also want to get rid of the Login item IF there is a signed in user,
right?
www/templates/menu.html
//...
<ion-item ng-if="!currentUser" menu-close ng-click="login()">
Login
</ion-item>
//...
<ion-item ng-if="currentUser" menu-close ng-controller="PerformanceC
trl" ng-click="retrieveData()">
Saved results
</ion-item>
//...
Next we want to update the retrieveData() function in PerformanceCtrl. What we want
this function to do, is to get the data using the performaceData factory and open the
data.html while passing in the data to the view (as savedDataCollection).
www/js/controllers.js
//...
240

Saving and retrieving data

$scope.retrieveData = function(){
$ionicLoading.show({
template: 'Retrieving data...'
});
performaceData.query({}, function(response){
$state.go('app.data', {savedDataCollection: response.entries});
$ionicLoading.hide();
}, function(error){
$ionicLoading.hide();
$scope.showAlert('Failure', error.statusText);
})
};
//...
We also need to create a new controller to handle the view. When the view is entered, we want to
retrieve the data sent in params and save it in the current $scope.
www/js/controllers.js
//...
.controller('DataCtrl', function($scope, $stateParams){
$scope.$on('$ionicView.enter', function () {
$scope.savedDataCollection = $stateParams.savedDataCollection;
});
})
//...
Finally, we can update our template and display the data. The way we do that is to iterate through the
array of entries and condition the display IF there is a message key in the data attribute (remember
the way we store the results in our database?).
We also need to format the date - please read the docs for date in AngularJS.
www/templates/test/data.html
<ion-view title="Historical Data">
<ion-content>
<div ng-if="savedDataCollection" ng-repeat="entry in savedDataCo
llection ">
<p ng-if="entry.data.message">{{entry.data.message}} - {{entry
.created_at | date:'mediumDate'}}</p>
</div>
</ion-content>
</ion-view>
That will do it for now. The next challenge is to present the data as charts. That can be
interesting...

241

Display charts

Display charts
Display charts - step 6
We will use Angular Chart to display users historical data.
$ bower install chart.js#2.3.0-rc.1 --save
$ bower install angular-chart.js --save
As always, make sure to include the library in your intex.html.
!FILENAME www/index.html
<!-- ionic/angularjs js -->
//...
<script src="lib/chart.js/dist/Chart.min.js"></script>
<script src="lib/angular-chart.js/dist/angular-chart.min.js"></script
>
And also, make chart.js available to your app by adding it to the main module.
!FILENAME www/js/app.js
angular.module('starter', ['ionic', 'starter.controllers', 'starter.
services', 'ng-token-auth', 'ngResource', 'chart.js'])
Okay, here comes the tricky part. We want to display two charts on our view. One Doughnut Chart
and one Radar Chart. The tricky part is that we only want to display labels for values that are actually
stored in the collection of historical data. Meaning for instance that if a user has stored several
"Average" and "Above Average" entries, then we should only show those two labels with a value.
Nothing else. Same thing goes for both chart types.
So what we need to do is to go through the savedDataCollection and get unique values from
data.message and store them in an array. This is the responsibility of the following function.
function getLabels(collection) {
var uniqueLabels = [];
for (i = 0; i < collection.length; i++) {
if (collection[i].data.message && uniqueLabels.indexOf(collectio
n[i].data.message) === -1) {
uniqueLabels.push(collection[i].data.message);
}
}
return uniqueLabels;
}
We are going to store that array in $scope.labels.
The second thing we need to do is to get the value of how many times each message is present in the
collection. For that we add another function that we use when iterating over $scope.labels and
store the results in $scope.data
242

Display charts

angular.forEach($scope.labels, function(label){
$scope.data.push(getCount($scope.savedDataCollection, label));
});
function getCount(arr, value){
var count = 0;
angular.forEach(arr, function(entry){
count += entry.data.message == value ? 1 : 0;
});
return count;
}
All in all, the DataCtrl should look something like this.
!FILENAME
.controller('DataCtrl', function ($scope, $stateParams) {
$scope.$on('$ionicView.enter', function () {
$scope.savedDataCollection = $stateParams.savedDataCollection;
$scope.labels = getLabels($scope.savedDataCollection);
$scope.data = [];
angular.forEach($scope.labels, function(label){
$scope.data.push(getCount($scope.savedDataCollection, label));
});
$scope.radardata = [$scope.data];
});

function getLabels(collection) {
var uniqueLabels = [];
for (i = 0; i < collection.length; i++) {
if (collection[i].data.message && uniqueLabels.indexOf(collect
ion[i].data.message) === -1) {
uniqueLabels.push(collection[i].data.message);
}
}
return uniqueLabels;
}
function getCount(arr, value){
var count = 0;
angular.forEach(arr, function(entry){
count += entry.data.message == value ? 1 : 0;
});
return count;
}
})
Finally, let's turn out attention to the view template.
We want to add markup for the two charts and clear up the data display.
!FILENAME
243

Display charts

<ion-view title="Historical Data">


<ion-content>
<div class="row">
<div class="col">
<h5>Saved data for {{currentUser.name || currentUser.email}}
</h5>
<canvas ng-if="savedDataCollection" id="doughnut" class="cha
rt chart-doughnut"
chart-data="data" chart-labels="labels" legend="true"
>
</canvas>
<canvas ng-if="savedDataCollection" id="radar" class="chart
chart-radar"
chart-data="radardata" chart-labels="labels">
</canvas>
<ion-list>
<ion-item ng-repeat="entry in savedDataCollection " ng-if=
"savedDataCollection && entry.data.message">
<span>{{entry.data.message}} - {{entry.created_at | date
:'mediumDate'}}</span>
</ion-item>
</ion-list>
</div>
</div>
</ion-content>
</ion-view>
If you run the application now it should look something like this.

244

Display charts

That looks pretty cool, right? ;-)


There is of course many more charts that you can display on this view if you like. Especially if you
make the choice of storing not only the cooperMessage but also the distance for each entry.

245

Wrapping up

Wrapping up
Wrapping up
What we have focused on is the most basic functions and connecting the client to the back-end api. As
with all walk-throughs in this book, there's plenty of functionality you can add to this app no make the
user experience much better.
Some suggestions as to what to do next:
More user management functions
Sign out
Register
Update account, etc
OAuth login (Facebook, Twitter)
UI enhancements
Colors
Backgrounds
Display current user profile name in side menu
Add mote chart types
Functionality
Add more the the Cooper Test
Calculate VO2-max
Add the BMI calculator
Add local storage for better performance (ni internet connection)
Resources
Generate icons and a splash screen.
There's plenty of possibilities!

246

Results tables

Results tables
Results tables
Required Resources
To undertake this test you will require:
400 meter track
Stopwatch
Whistle
Assistant
How to conduct the test
This test requires the athlete to run as far as possible in 12 minutes.
The athlete warms up for 10 minutes
The assistant gives the command GO, starts the stopwatch and the athlete commences the test
The assistant keeps the athlete informed of the remaining time at the end of each lap (400m)
The assistant blows the whistle when the 12 minutes has elapsed and records the distance the
athlete covered to the nearest 10 meters

Assessment
The following normative data is available for this test:
Males
Age Excellent Above Average
13-14 >2700m
15-16 >2800m
17-19 >3000m
20-29 >2800m
30-39 >2700m
40-49 >2500m
50+ >2400m

2400-2699m
2500-2799m
2700-2999m
2400-2799m
2300-2699m
2100-2499m
2000-2399m

Average

Below Average Poor

2200-2399m 2100-2199m
2300-2499m 2200-2299m
2500-2699m 2300-2499m
2200-2399m 1600-2199m
1900-2299m 1500-1999m
1700-2099m 1400-1699m
1600-1999m 1300-1599m

<2100m
<2200m
<2300m
<1600m
<1500m
<1400m
<1300m

Females
Age Excellent Above Average
13-14 >2000m
15-16 >2100m
17-19 >2300m
20-29 >2700m
30-39 >2500m
40-49 >2300m
50+ >2200m
247

1900-1999m
2000-2099m
2100-2299m
2200-2699m
2000-2499m
1900-2299m
1700-2199m

Average

Below Average Poor

1600-1899m 1500-1599m
1700-1999m 1600-1699m
1800-2099m 1700-1799m
1800-2199m 1500-1799m
1700-1999m 1400-1699m
1500-1899m 1200-1499m
1400-1699m 1100-1399m

<1500m
<1600m
<1700m
<1500m
<1400m
<1200m
<1100m

Results tables

248

SlowFood Online Challenge - Hello World!

SlowFood Online Challenge - Hello World!


SlowFood Online
In this Week Lab, we will be expanding on the SlowFood challenge we've already worked on and
expand it with more functionality. This challenge will differ a bit from the previous ones in the
curriculum. We will simulate a real project and go over the entire process of a Design Sprint first,
before we start writing our implementation.

Learning Objectives
Learn about Design Sprint

249

Main features

Main features
Main features
Core functionality
Allow local restaurant operators to publish their menus and accept orders.
Allow the general public to browse local restaurants and place orders for home delivery
Location
We want the system to be used in several geographical locations. All Restaurants in the system should
be geo-coded by street address. All users should be geo-coded using their browser location or/and
their IP address
Categories (restaurant)
Each restaurant in the system should belong to a category (i. e. Thai, Chinese, French, Italian, etc).
Restaurants
We want to be able to host several Restaurants in each geographical location.
A restaurant should specify what radius it will deliver to (5, 10, 20, 30 km).
Rating
Every Restaurant should be rated using rating system (1 - 5) with a possibility to add an optional
comment.
Delivery
All orders from restaurants within a 3 kilometer radius should be able to be delivered together. Orders
from restaurant in a wider area will add an extra delivery fee. The system should be able to handle
free delivery as fell as charged.
Menus
Each restaurant should have one or more menus (lunch, la carte, etc.). Each menu contains several
dishes grouped in configurable categories.
Categories (dish)
The system should have some default categories set up (Starter, Main course, Dessert) but also allow
System users and Administrators to define their own.
Dishes
250

Main features

Each dish should be described and have some optional fields containing allergy information,
ingredients, calories, etc. If ingredients are active for a dish then the user should be able to choose to
if he want to remove a specific ingredient.
Order
An order should take into account all dishes from several restaurants in the area.
User roles
Administrator - Can perform all CRUD actions on all objects in the system
System user - Can create restaurants with associated objects (see below)
User - Can place orders and add rating to Restaurants
UI
Index/Landing page

The index page of the system should list restaurants in the proximity of the user that visits the system.
The page should display a map and ask the user a question "What do you feel like tonight?" The user
should be given a set of restaurant categories to choose from. Once a choice has been made, the
restaurants should pop up as markers on a map. A separate list (with links) of restaurants should be
displayed underneath the map. The list should include the user rating average of the restaurant.
The restaurant show page

Clicking on a marker or a restaurant name/description in the list should take the user to the restaurants
show page. Apart from the restaurant name, description and address, the page should also include a
list of popular/selected dishes with ability to order ("add to order" button).
A link to the full menu should be placed on the page. (i.e. "see full menu")
The menu page

The menu page should show all dishes with pictures and a brief description. A link to full dish show
page should be visible on the list.
The dish show page

The dish show page should show a detailed view of the dish with full description and optional
information (activated by the System user that owns the Restaurant object) about the dish.

251

Design Sprint

Design Sprint
Design Sprint
As a first step your team needs to gather for a working session called the Design Sprint. In real
projects this is done over several days (2 - 5 days approximately). In this simulation we will set aside
one day to this task.
Normally, this sprint includes a lot of input from the Client (Product Owner) and relies on his
participation. We will rely in the information provided in the previous chapter and any questions we
have will have to be answered by the team or, in the uncertainty is to high, just ignored.
The main objective of this workshop/session is create a Backlog of feature requests that are described
in a structured way, discuss each feature, define the definition of done, and assess the complexity of
the implementation.

Epics
The first step is to create a series of user stories for the main functions outlined in the system
requirements. Initially these will be on a relatively high level and be easily mapped to the overall
business objectives of the system - we will refer to them as Epics.

Features and Chores


Each epic will in turn consist of several features each one of them described in the form of a user
story. Apart from features, that by definition need to deliver some business value and a visible result,
we will also define and describe several chores. Chores are development work not resulting in
tangible product changes but still needs to be performed by the team.

Acceptance Criteria
Once we have defined a set of features and chores for each epic, it is time to start thinking about the
definition of done. We create a list of Acceptance Criteria for each entry in our backlog. This list is
crucial for setting a scope for the feature and defining when the feature should be considered done and
delivered. (In real projects this criteria are agreed upon with the client. In our simulation, we will be
responsible for setting them within the team.)

Complexity
Once all features and chores has been grouped into epics and the acceptance criteria for each one of
them has been defined, it's time to review all of them and talk about how complex and resource
consuming the implementation of each story will be.
We will use a fairly straight forward way of assessing complexity - making use of a three point scale.
A story that is deemed pretty simple and will only take a few hours to both implement and test should
be given 1 complexity point.
A story that will require more than a day to develop, test and ship, or a feature that relies on a third
party API or service, should be approach with caution, deemed to be of intermediate complexity and
given 2 complexity points.
252

Design Sprint

A story that will require implementation through the entire stack (new models with relation,
controllers and views) and features that relies on many conditions that needs to be met, should be
considered complex and given 3 points.
Note that during this process you might find yourselves in a situation where the complexity of a
specific feature or a chore won't fit in the three point scale. In this case you need to split the story into
two or more separate features or chores and assess them individually.

253

Pivotal Tracker

Pivotal Tracker
Pivotal Tracker
In this project, we will be using Pivotal Tracker as a tool for planning and collaboration.

Pivotal Tracker is a straightforward project planning tool that allows teams to collaborate and react
instantly to real-world changes. Its based on agile software development methods, but it can be used
on a variety of projects. Tracker frees you up to focus on getting things done, without getting bogged
down, keeping your plans in sync with reality.

Get started with PT


Visit www.pivotaltracker.com

254

Pivotal Tracker

1. Sign up for an unlimited 30-Day Free Trial (no credit card required) or click the link in a
project invitation email to set your password and sign in.
2. Click Create Project on the Dashboard. Enter a name to create a project.

The Basics
The core unit of projects is called a story. Stories will usually be one-line descriptions of features that
should be implemented in the project. The description can be further expanded in the story's details.
Stories are arranged by their priority the most important ones will be on the top of the list. They
are also divided into a few panels based on their status. The Backlog contains all of the project's
stories.
From left to right there are three panels: Current, Backlog and Icebox.
Current
The Current panel is the list of stories the team is working on this week.
Backlog
The Backlog contains all the stories that the Product Owner has prioritized for the team to do next.
Ideally, the stories at the top of the backlog have already been estimated as well. This is where the
team looks for additional stories to work on once they've finished what's in the current pile.
Based on the velocity of the project (how quickly features are implemented and approved), the most
255

Pivotal Tracker

important ones will be moved to the Current section so you have easy access to them.
Icebox
The Icebox is where you collect all your stories. Whether your starting a project or are adding new
stories to an ongoing project this is where all stories that has not been prioritized live. In real projects,
it is up to the customer to maintain the icebox and feed stories from it to the backlog. In our
simulation

Project view
We have used Pivotal Tracker for many projects within Agile Ventures. Here is an example of a
dashboard for Project Unify. It is a public project and you can visit that dashboard on
https://www.pivotaltracker.com/n/projects/1525675

256

Pivotal Tracker

Feature view
Pivotal Tracker allows you to describe each feature, chore or bug in a very detailed way and fully
supports the agile methods we use in our project simulations. Here is an example of a feature with a
user story, acceptance criteria, complexity points, etc.

257

Pivotal Tracker

Remember, the tool you use can support your workflow but it won't do the job for you. You
need to add the content and work with maintaining it actively.

258

SlowFood API - API first or second?

SlowFood API - API first or second?


Extending the SlowFood application
Opening up the SlowFood application to the outside world as well as adding some cloud based
services.
This weeks challenge is about adding more functionality to the Slow Food application. The client has
reached out to us and gave us some new requirements. First he wants to store all images that are used
on the system on Amazon Web Services and second, he also wants us to expose the application data
using a RESTful API.

Cloud Storage
All image assets needs to be stored on an AWS S3 bucket.
Action points
Create stories in PT regarding Cloud Storage functionality
Create an account on AWS
Create an S3 bucket
Research an appropriate gem to use
Set up necessary permissions to upload assets to the S3 bucket from your application

RESTful API
All application data should be able to be retrieved through a separate set of uri's. Authorized users
should be able to place orders using the API.
Action points
Create stories in PT regarding API functionality desribing all endpoints you deem necessary
Create a new namespace in your application
Set up tests (request specs) for the API endpoints
Set up controllers for various API endpoints
If necessary, add custom views using JBuilder
The API should be accessible using Postman.

259

Extras

Extras
Extras

260

Naming Standards

Naming Standards
Naming standards
You are free to set your own class and methods names.

Classes and Modules


Class and Module names starts with an uppercase letter, by convention they are named using
MixedCase, e.g. module Withdraval, class AccountHolder.

Methods
When naming methods the goal is to be descriptive but short. Name based on what it will return or
what the major intended side effect will be. You shouldn't be missing any parts from the name
because the method should only do one thing anyway. If you can't tell what the method will return
based on the name, you probably need a better name. If your method name seems insanely long, your
method may be trying to do more than one thing. End with a question mark ? if it will return
true/false.
Method names should begin with a lowercase letter (or an underscore). Apart from letters, only ?, !
and = characters are allowed as method name suffixes. This is a list of suggestions on naming
standards for different kind of methods:
Methods that do something should be verbs:
obj.calculate
obj.set_name
obj.get_date
Methods that are accessors (or behave like them) should be nouns:
foo = obj.name
obj.date
obj.order_total
Interrogative methods (if the method returns true/false) get phrased as questions:
obj.date_today?
obj.name?
obj.calculation_done?
Methods that modify the object itself, should be exclamations:
obj.truncate_body!
obj.remove_name!

Variables
Variables in Ruby can contain data of any type. You can use variables in your Ruby programs without
any declarations. Variable name itself denotes its scope (local, global, instance, class.).
A local variable (declared within an object) name consists of a lowercase letter (or an
underscore) followed by name characters (balance, cyrrent_collection).
An instance variable name starts with an @ sign followed by a name (@sign, @name, etc).
A class variable name start with a double @ sign (@@) and may be followed by digits,
underscores, and letters, e.g. @@colour
261

Naming Standards

Global variables starts with a dollar ($) sign followed by other characters, e.g. $global

262

Classes vs Modules

Classes vs Modules
Classes and Modules
Classes are about objects; Modules are about functions.
Modules are about providing methods that you can use across multiple classes - think about them as
"libraries". A Module is just a collection of methods and constants and comes in handy at times when
you want to group things together that don't naturally form a Class. Classes are also a collection of
methods and constants, but with the added functionality of being able to be instantiated. A Module
cannot be instantiated.
Class
Instantiation can be instantiated
Usage
object creation
Superclass Module
class methods and
Methods
instance methods
can inherit behaviour and
Inheritance can be base for
inheritance
Inclusion

Extension

Module
can not be instantiated
mixin facility, provide a namespace.
Class
module methods and instance methods
no inheritance

can be included in classes and modules by using the


cannot be included
include command (includes all instance methods as
instance methods in a class/module)
can not extend with
module can extend instance by using extend command
extend command (only (extends given instance with singleton methods from
with inheritance)
module)

Class - methods
Class methods are methods that are called on a class and instance methods are methods that are called
on an instance of a class.
class FooClass
def self.bar
'class method'
end
def baz
'instance method'
end
end
FooClass.bar # => "class method"
FooClass.baz # => NoMethodError: undefined method baz for Foo:Class

FooClass.new.baz # => instance method


FooClass.new.bar # => NoMethodError: undefined method bar for #<Fo
263

Classes vs Modules

o:0x1e820>

Module - methods
The methods in a module may be instance methods or module methods. You can include methods in
your module that you can be both functions or included by one or several Classes and used as instance
methods. Instance methods appear as methods in a class when the module is included, module
methods do not. Conversely, module methods may be called without creating an encapsulating object,
while instance methods may not.
There are several ways you can create methods in a Module. Let's say you want to be able to do
FooModule.foo(some_value)
You can define the foo method like this
module FooModule
def self.foo(bar)
bar
end
end
Or like this...
module FooModule
extend self
def foo(bar)
bar
end
end
...or like this.
module FooModule
module_function
def foo(bar)
bar
end
end

264

Code structure

Code structure
Code structure
Great code means great class and method names. Great code means also a great structure.

265

Bower

Bower
Bower
Bower is a front-end package manager built by Twitter. As a package manager, Bower simplifies
installing and updating project dependencies, which are libraries you use in your project.
Browsing all the library websites, downloading and unpacking the archives, copying files into the
project folder all of this can be replaced with a few Bower commands in the terminal.

266

Bower

Installing Bower
Bower can be installed using npm, the Node package manager (If you don't already have npm
installed, head over to the Node.js website and follow the install instructions). The npm program is
included in Node.js.
Once you have npm installed, open up your terminal and run the following command:
$ npm install -g bower
This will install Bower globally on your system.
Now that you have Bower installed, we can start looking at the commands that are used to manage
packages.

Finding Packages
There are two different ways that you can find Bower packages. Either using the online component
directory, or using the command line utility.
To search for packages on the command line you use the search command. This should be followed
267

Bower

by your search query.


$ bower search [query]
For example to search for packages that contain the word jquery you could do the following:
$ bower search jquery
This command would return a whole bunch of results, some of which are displayed in the snippet
below.
Search results:
jQuery https://github.com/jquery/jquery.git
jquery https://github.com/jquery/jquery-dist.git
jquery.x https://github.com/jljLabs/jquery.x.git
jquery.Q https://github.com/jsbuzz/jQuery_Q.git
jt_jquery https://github.com/vicanso/jt_jquery.git
jquery-m https://github.com/meetup/jquery.git
jquery.j2d https://github.com/fsggs/jquery.j2d.git
jquery.ua https://github.com/cloudcome/jquery.ua.git
jquery-ol https://github.com/jordanmarkov/jquery-ol.git
jquery-tm https://github.com/trymore/jquery-tm.git
jquery.ui https://github.com/jquery/jquery-ui.git
hn.jquery https://github.com/loi-chuanoi/jquery.git
...
Each result displays the name of the package and a Git endpoint. You will need either the name or Git
endpoint to install a package.

Installing packages
To add a new Bower package to your project you use the install command. This should be passed the
name of the package you wish to install.
$ bower install [package]
As well as using the package name, you can also install a package by specifying one of the following:
A Git endpoint such as git://github.com/components/jquery.git
A path to a local Git repository.
A shorthand endpoint like components/jquery. Bower will assume that GitHub is being used, in
which case, the endpoint is the part after github.com in the repository URL.
A URL to an archive (a zip or tar file). The files in the archive will be extracted
automatically.
You can install a specific version of the package by adding a pound-sign (#) after the package name,
followed by the version number.
$ bower install [package]#[version]
Installed packages will be placed in a bower_components directory. This is created in the folder
which the bower program was executed. You can change this destination using the configuration
options in the .bowerrc file.
268

Bower

Once installed, you can use a package by simply adding a <script> or <link> tag to your HTML
markup. Although Bower packages most commonly contain JavaScript files, they can also contain
CSS or even images.
<script src="path/to/bower_components/jquery/jquery.min.js"></script
>

Installing packages using a bower.json file


If you are using multiple packages within your project, it's often a good idea to list these packages in a
bower.json file. This will allow you to install and update multiple packages with a single
command.
bower.json
{
"name": "app-name",
"version": "0.0.1",
"dependencies": {
"sass-bootstrap": "~3.0.0",
"modernizr": "~2.6.2",
"jquery": "~1.10.2"
},
"private": true
}
The example above shows a bower.json file which defines some information about the projects as
well as a list of dependencies. The bower.json file is actually used to define a Bower package, so
in effect you're creating your own package that contains all of the dependencies for your application.
The properties used in this example are explained below.
name The name of your application/package.
version A version number for your application/package.
dependencies The packages that are required by your application.
private Setting this property to true means that you want the package to remain private and
do not wish to add it to the registry in the future.
Once you've got your bower.json file set up you can simply run the bower install
command to install all of the packages you have specified.
Bower includes a handy utility that will help you to create a bower.json file for your project.
Running the bower init command from the root folder of your project will launch an interactive
program that will create the file for you. However, you may still need to add some packages to the file
yourself.

Listing Installed Packages


You can easily find out which packages are installed using the list command.
$ bower list
The snippet below shows the output for a simple project that uses jQuery, Modernizr and Sass. Notice
that Bower also does a check to see if a newer version of each of the packages is available.
269

Bower

bower check-new Checking for new versions of the project depende


ncies...
HelloIonic /Users/thomas/CraftAcademy/solutions/cooper/cooper_client
Chart.js#1.1.1 (latest is 2.1.5)
angular-animate#1.4.3 extraneous (latest is 1.5.7-build.4875+sha
.9686f3a)
angular#1.5.6 incompatible with 1.4.3 (1.4.3 available, latest
is 1.5.7-build.4875+sha.9686f3a)
angular-chart.js#0.9.0 (latest is 1.0.0-alpha6)
Chart.js#1.1.1 incompatible with ~1.0.1 (1.0.2 available, late
st is 2.1.5)
angular#1.5.6 incompatible with 1.4.x (1.4.11 available, lates
t is 1.5.7-build.4875+sha.9686f3a)
angular-fusioncharts#0.0.2
angular-resource#1.5.6 (1.5.7-build.4875+sha.9686f3a available)
angular#1.5.6 (latest is 1.5.7-build.4875+sha.9686f3a)
angular-sanitize#1.4.3 extraneous (latest is 1.5.7-build.4875+sh
a.9686f3a)
angular#1.5.6
angular-ui-router#0.2.13 extraneous (latest is 1.0.0-alpha.5)
angular#1.5.6 (1.5.7-build.4875+sha.9686f3a available)
ionic not installed
ng-token-auth#0.0.28 (latest is 0.0.29)
angular#1.5.6 incompatible with ~1.4.4 (1.4.11 available, late
st is 1.5.7-build.4875+sha.9686f3a)
angular-cookie#4.0.10 (latest is 4.1.0)
angular#1.5.6 (1.5.7-build.4875+sha.9686f3a available)

Updating Packages
Updating a package is pretty simple. If you've used a bower.json file you can run a simple update
command to update all of the packages at once. However, the update tool will abide by the version
restrictions you've specified in the bower.json file.
$ bower update
To update an individual package you again use the update command, this time specifying the name of
the package you wish to update.
$ bower update [package]

Uninstalling Packages
To remove a package you can use the uninstall command followed by the name of the package
you wish to remove.
$ bower uninstall [package]
It's possible to remove multiple packages at once by listing the package names.
bower uninstall Chart.js ng-token-auth angular-resource

Bower and Rails


270

Bower

After reading terms like CSS, Javascript and HTML, you are already having nightmares thinking at
the idea of bringing a 3rd party tool into the dreaded asset pipeline but fear not as Bower and the asset
pipeline actually play very well together. Without further ado, here's how to set the whole thing up.
Install Bower
Make sure you have bower installed (see above)
Configuring Bower
By default Bower install the components (what it calls JS/CSS libraries) in bower_components
folder, which doesn't really play with the standard Rails folder hierarchy. To change that, simply
create a .bowerrc (bower's config file) file at the root of your Rails app and add this:
{ "directory": "vendor/assets/bower_components" } This will tell Bower to save all of the component
files in that directory which follow Rails' convention on storing assets.

package.json
From the root of your Rails app, run bower init to create your bower.json file.
Depending on your answers, your bower.json should look something like this:
{
name: 'MyApp',
version: '0.0.1',
authors: [ ],
description: 'Your description',
license: 'MIT',
homepage: 'http://whatever.com',
ignore: [
'**/.*',
'node_modules',
'bower_components',
'test',
'tests'
],
"dependencies": {
"angular": "~1.2.16"
}
}
Now if I run bower installin your terminal, it will pull Angular 1.2.16 from the Github repo into
vendor/assets/bower_components.
Configuring Rails
Now that Bower is installed and working, you need to make sure that Rails play nice with it.
To do so, head to your config/application.rb file to let the asset pipeline about it. Simple
add the following line to your file and save it.
config.assets.paths << Rails.root.join('vendor', 'assets', 'componen
271

Bower

ts')
Including assets
To require the components downloaded using Bower, open up
app/assets/javascripts/application.js and require the necessary JS file like you
would usually do. In the example with Angular, you do so by adding this one line to it.
//= require angular/angular
For CSS frameworks and libraries like Bootstrap or Foundation, you can pop in the following line to
app/assets/css/application.css.
*= require bootstrap/dist/css/bootstrap
That's it! You can now use Bower to manage all of the 3rd party CSS & Javascript libraries you want
to use in your project and Rails' asset pipeline will take care of the rest for you.

272

Code Review Instructions

Code Review Instructions


Code Review Instructions
Performing a Peer Review is a good way to enhance your own learning.
We have put together a set of common issues that you might find useful in the process of doing a code
review (You might also want to use this checklist on your own code at times).

The README
It is important to ensure that the projects README includes correct and relevant information that will
allow a new developer to get the necessary insights about your project in order to use and understand
the application.
In our code reviews we want to pay attention to a well written and structured README. Please
follow this guide for how to structure a good README while performing your code review.

The Setup
Checkout the code follow the setup instructions. What you want to be looking for is:
Are the setup instructions in the README correct?
Are there any pitfalls in the set up process?

The Tests
Once you are set up with the application, run all automated tests.
What you want to be looking for is:
Are the tests passing after a vanilla install?
Is there any test coverage metrics? How much of the code is covered in tests?

The Functionality
Read the code and try and use the app through the web interface if there is one, or load the code in the
console (IRB or Rails console) and experiment with it.
What you want to be looking for is:
Is the application behaving the way it is intended?
Are there any outstanding functions that are still not been implemented?

The Code
Now, go over the code and ...
https://github.com/eliotsykes/rails-code-review
https://github.com/CraftAcademy/airport_challenge/blob/master/docs/review.md

273

Code Review Instructions

274

About README's

About README's
About README's
It is important to ensure that the projects README includes correct and relevant information that will
allow a new developer to get the necessary insights about your project in order to use and understand
the application.
With a comprehensive, well-written README any developer should be able to hop on to your project
and begin writing code within 10 minutes. If you consider that over the course of developing an app
you'll likely see multiple developers set up the app multiple times, you'll cumulatively save hours of
developer time with just minutes of work.
General Information
The General Information section should give a new developer an idea of what the project is about
and who is involved with it.
Information you might want to include is:
The name of the project
The name and contact details of the client and any 3rd party vendors as well as names of the
developers on the project
A brief description of the project that provides the answer to the most important question of
them all: What problem is this project solving?
An outline of the technologies in the project.programming language, frameworks, important
gems, database, ORM, etc.
Links to any related projects
Links to online tools related to the application (e.g.: Links to the CI server, Pivotal Tracker or
Waffle board, development and staging servers, the production site, etc.
Getting Started
The Getting Started section outlines the process of getting the app installed for a new developer.
Information you might want to include is:
A detailed spin-up process. This should include:
Instructions on installing any software and/or gems the application is dependent on.
Detailed instructions on running the app (with step-by-step commands needed to get up
and running on a local machine)
A list of settings that need to be configured (for third party API's)
A list of seeded credentials that can be used to log in with each user type in the system
Any information about subdomains in the app (e.g.: api.myapp.dev/)
A good tip is to When writing instructions pretend youre writing them for someone who
knows next to nothing about developing in the framework/language your application
uses.
Testing
275

About README's

The Testing section need to include the commands to run any of the test suites you have (RSpec,
Jasmine, Cucumber) and any setup you need to do before-hand
Deployment
The Deployment section should include any information needed to deploy the application to a remote
server.
Note that README files are most commonly written in Markdown - a lightweight and easy-touse syntax to style text on the web. Mastering Markdown is easy but do give this Guide a read.

276

MVC

MVC
Model View Controller
Model View Controller (MVC) is a 3-tiered architectural software design pattern for
implementing user interfaces. It divides a given software application into three interconnected parts,
to separate internal representations of information from the ways that information is presented to or
accepted from the user.
In addition to dividing the application into three kinds of components, the MVC design pattern
defines the interactions between them.
A Model stores data that is retrieved according to commands from the Controller and
displayed in the View.
A View generates new output to the user based on changes in the Model.
A Controller can send commands to the model to update the Model's state. It can also send
commands to its associated View to change the it's presentation of the Model.

Figure: Model View Controller

The flow of request & response


277

MVC

Alright, so you already know the basics of the Request response pattern - the MVC pattern deals with
what is happening with the request by your web- and application server and the application itself. At
the and, an appropotiate responce is sent back to the client (the browser or whatever has made the
request) and forgotten about.
Instead of a sterile 3-tiered architecture walkthrough, it's more fun to imagine a story with fat
model, skinny controller, dumb view .
Models do the grunt work, Views are the happy face, and Controllers are the masterminds behind it
all.
Think about this flow:
The browser makes a request (such as http://localhost:9292/dish/15)
The web server (WEBrick, etc.) receives the request. It uses routes to find out which
controller to use. The web server then uses the dispatcher to create a new controller, call the
action and pass the parameters.
The Controller do the work of parsing user requests (data submissions, cookies, sessions and
other browser stuff). They're the pointy-haired manager that orders employees around. The
best controller is Dilbertesque: It gives orders without knowing (or caring) how it gets done.
Models are Ruby classes extended by ORM methods. They talk to the database, store and
validate data, perform the business logic and otherwise do the heavy lifting. They're the chubby
guy in the back room crunching the numbers.
Views are what the user sees: HTML, CSS, JavaScript, JSON. They're the sales rep putting up
flyers and collecting surveys, at the managers direction. Views are merely puppets reading what
the controller gives them. They don't know what happens in the back room.
Finally, the Controller returns the response body (HTML, XML, etc.) and metadata (caching
headers, redirects) to the server. The server combines the raw data into a proper HTTP
response and sends it to the client to be presented to the user.
The web server is the invisible gateway, shuttling data back and forth (users never interact with
the controller directly).

278

Three-Tier Architecture

Three-Tier Architecture
Three-Tier Architecture
Three-tier architecture is an architectural deployment style that describe the separation of
functionality into layers with each segment being a tier that can be located on a physically separate
computer.
Three-tier application architecture is characterized by the functional decomposition of applications,
service components, and their distributed deployment, providing improved scalability, availability,
manageability, and resource utilization.

Structure
Using this architecture the software is divided into 3 different tiers: Presentation, Logic (also
refereed to as "business logic", "data access tier", or "middle tier"), and Data. Each tier is developed
and maintained as an independent tier.
Presentation tier
This is the topmost level of the application. The presentation layer provides the application's user
interface (UI). This communicates with other tiers by outputting results to the browser/client tier and
all other tiers in the network.
Logic tier
The Logic tier controls an application's functionality by performing detailed processing. Logic tier is
where mission-critical business problems are solved.
Data tier
Here information is stored and retrieved. This tier keeps data neutral and independent from
application servers or business logic. Giving data its own tier also improves scaleability and
performance.

Benefits
Maintainability - Since each tier is independent of the other tiers, updates or changes can be
carried out without affecting the application as a whole.
Scalability - Because tiers are based on the deployment of layers, scaling out an application is
reasonably straightforward.
Flexibility - Because each tier can be managed or scaled independently, flexibility is increased.
Availability - Applications can exploit the modular architecture of enabling systems using
easily scalable components, which increases availability.

279

AngularJS

AngularJS
AngularJS
A basic application
$ bower init
Install Angular
$ bower install angularjs --save
Create index.html
<!DOCTYPE html>
<html lang="en-US">
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/an
gular.min.js"></script>
<body ng-app="">
<div >
<p>Name : <input type="text" ng-model="name"></p>
<h1>Hello {{name}}</h1>
</div>
</body>
</html>

280

You might also like