Agile Rails
Gregory McIntyre, March 2012
My Creds
• University
• Ruby, TDD
• Tilefile Pty Ltd
• Python, ActionScript, Rails
• realestate.com.au
• Agile (ThoughtWorks)
Waterfall
#fail
• Impenetrable requirements documents
• Scary contracts
• Early is when you know the least
• Requirements tend to change
Agile Manifest’O
• Face time > processes and tools
• Working software > documentation
• Customer collaboration > contracts
• Embracing change > sticking to the plan
You Say Scrum, I Say...
• XP
• Scrum
• Crystal
• Lean
• Roll your own, just follow the ’festo
Turn, Turn, Turn

•Strategy
Release
•
Iteration
•
Daily
•
Strategy
Strategy
• Mission statements
• Scope
• Risk
• Team building
• Priorities
• Finance
Project Inception
• Agile “strategy” process (1-5 days)
• Share vision
• Align goals
• Set realistic expectations
• Swap phone numbers
Strategy Constraints
Risk Chart
Release Planning
Build a Story Backlog
• Do some UX
• Write “stories”
• T-shirt sizing
Burn Down Chart
Kanban
•

Toyota invented it to
manage car production
efficiently

•

Not waterfall, not
iterative

•
•

Continuous, like a pipe
Deliver something of
value every day
Lumpy Bad
Smooth Good
Iterations
Feedback Loops
•
•
•
•

2-4 weeks is common
Involve the customer
Revise estimates
Assess and improve
Planning Poker
Card Wall
Stand Ups
Pair Programming
Conversations
Noise
Mess
Paper and Pens
Test Driven Dev
Behaviour Driven Dev
Continuous Integration
Agile is...
• Technically “backward” (ahem, pragmatic)
• “Last minute”
• Noisy, demanding and confronting
• Practice practice practice
• About visibility, not due dates
Agile is...
Deliver something of value every day
Ruby on Rails
exit unless "restaurant".include? "aura"
5.times { print "Odelay!" }
['toast', 'cheese', 'wine'].each
{|food| print food.capitalize }

class Blog
has_many :posts
end

Ruby: Expressive and Flexible
Ruby DSLs
class Blog
has_many :posts
end
def has_many(things)
“LADIES, let’s make web apps with
Ruby”
Rails is a Web Application
Framework
•

Handles an HTTP
request

•
•
•

Common practices
Sensible defaults
Keeps things orderly
Rapid Prototyping
$ rails generate scaffold Post name:string
title:string content:text
invoke
create
create
invoke
create
route
invoke
create
invoke
create
create
create
create
create
create
invoke
create
create
create
create
create
invoke
create
create
invoke
create
invoke
create
invoke
invoke
create

active_record
db/migrate/20120316050430_create_posts.rb
app/models/post.rb
rspec
spec/models/post_spec.rb
resources :posts
inherited_resources_controller
app/controllers/posts_controller.rb
erb
app/views/posts
app/views/posts/index.html.erb
app/views/posts/edit.html.erb
app/views/posts/show.html.erb
app/views/posts/new.html.erb
app/views/posts/_form.html.erb
rspec
spec/controllers/posts_controller_spec.rb
spec/views/posts/edit.html.erb_spec.rb
spec/views/posts/index.html.erb_spec.rb
spec/views/posts/new.html.erb_spec.rb
spec/views/posts/show.html.erb_spec.rb
helper
spec/helpers/posts_helper_spec.rb
spec/routing/posts_routing_spec.rb
rspec
spec/requests/posts_spec.rb
helper
app/helpers/posts_helper.rb
rspec
stylesheets
public/stylesheets/scaffold.css
Sensible Defaults
Davidson::Application.routes.draw do
resource 'shared_cookie'
end

<struts>
<package name = "MyPackage">
<interceptors>
<!--Some set
particular action-->
<interceptor
<interceptor
<interceptor
<interceptor

of common interceptors for a
name
name
name
name

=
=
=
=

"A_I1"
"A_I2"
"A_I3"
"A_I4"

class
class
class
class

=
=
=
=

"MyA_I1">
"MyA_I2">
"MyA_I3">
"MyA_I4">

<!--Another set of common interceptors -->
<interceptor name = "B_I1" class = "MyB_I1">
<interceptor name = "B_I2" class = "MyB_I2">
<interceptor name = "B_I3" class = "MyB_I3">
<interceptor name = "B_I4" class = "MyB_I4">
</interceptors>
<interceptor-stack name =
<interceptor-ref name
<interceptor-ref name
<interceptor-ref name
<interceptor-ref name
</interceptor-stack>

"A">
= "A_I1">
= "A_I2">
= "A_I3">
= "A_I4">

<interceptor-stack name =
<interceptor-ref name
<interceptor-ref name
<interceptor-ref name
<interceptor-ref name
</interceptor-stack>

"B">
= "B_I1">
= "B_I2">
= "B_I3">
= "B_I4">

<action name = "MyAction1">
<interceptor-ref name = "A"/>
</action>

shared_cookie POST
new_shared_cookie GET
edit_shared_cookie GET
GET
PUT
DELETE

/shared_cookie
/shared_cookie/new
/shared_cookie/edit
/shared_cookie
/shared_cookie
/shared_cookie
ERB versus HAML
= form_for(@post) do |f|
<%= form_for(@post) do |f| %>
- if @post.errors.any?
<% if @post.errors.any? %>
#errorExplanation
<div id="errorExplanation">
%h2
<h2><%= pluralize(@post.errors.count, "error") %>
= pluralize(@post.errors.count, "error")
prohibited this post from being saved:</h2>
prohibited this post from being saved:
<ul>
%ul
<% @post.errors.full_messages.each do |msg| %>
- @post.errors.full_messages.each do |msg|
<li><%= msg %></li>
%li= msg
<% end %>
.field
</ul>
= f.label :name
</div>
= f.text_field :name
<% end %>
.field
<div class="field">
= f.label :title
<%= f.label :name %>
= f.text_field :title
<%= f.text_field :name %>
.field
</div>
= f.label :content
<div class="field">
= f.text_area :content
<%= f.label :title %>
.actions
<%= f.text_field :title %>
= f.submit
</div>
<div class="field">
<%= f.label :content %>
<%= f.text_area :content %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
CSS versus SASS
html.rgba header#global nav .user-nav {
background-color: rgba(0, 0, 0, 0.05);
}
html.rgba header#global nav .user-nav .signin:active,
html.rgba header#global nav .user-nav .signin:hover,
html.rgba header#global nav .user-nav .signin:focus,
html.rgba header#global nav .user-nav .signout:active,
html.rgba header#global nav .user-nav .signout:hover,
html.rgba header#global nav .user-nav .signout:focus {
background-color: rgba(255, 255, 255, 0.5);
}
html.no-rgba header#global nav .user-nav {
background: url("/images/design/black-5.png");
}
html.no-rgba header#global nav .user-nav .signin:active,
html.no-rgba header#global nav .user-nav .signin:hover,
html.no-rgba header#global nav .user-nav .signin:focus,
html.no-rgba header#global nav .user-nav
.signout:active, html.no-rgba header#global nav .usernav .signout:hover, html.no-rgba header#global nav
.user-nav .signout:focus {
background-color: #edebe9;
}

html
&.rgba header#global nav .user-nav
background-color: rgba(0, 0, 0, 0.05)
.signin, .signout
&:active, &:hover, &:focus
background-color: rgba(255, 255, 255, 0.5)
&.no-rgba header#global nav .user-nav
background: url("/images/design/black-5.png"
.signin, .signout
&:active, &:hover, &:focus
background-color: #edebe9
SASS + Compass <3
.box
-ms-filter:
"progid:DXImageTransform.Microsoft.A
lpha(Opacity=50)"
filter: alpha(opacity=50)
opacity: .5
.box
+opacity(.5)
Advanced Compass
@import "my-icons/*.png"
.actions
.new
@include my-icons-sprite(new)
.edit
@include my-icons-sprite(edit)
.save
@include my-icons-sprite(save)
.delete
@include my-icons-sprite(delete)

.my-icons-sprite,
.actions .new,
.actions .edit,
.actions .save,
.actions .delete { background: url('/images/my-icons-s34fe0604ab.png') no-repeat; }
.actions
.actions
.actions
.actions

.new
.edit
.save
.delete

{
{
{
{

background-position:
background-position:
background-position:
background-position:

0
0
0
0

-64px;
-32px;
-96px;
0;

}
}
}
}
CoffeeScript
(function() {
DI.Home = {
onload: function() {
return this.setupScrollable();
},
setupScrollable: function() {
return $('.scrollable').scrollable({
circular: true,
speed: 800
}).autoscroll({
autoplay: true,
interval: 10000
}).navigator();
}
};
$(function() {
return DI.Home.onload();
});
}).call(this);

DI.Home =
onload: ->
@setupScrollable()
setupScrollable: ->
$(".scrollable").scrollable(
circular: true
speed: 800
).autoscroll(
autoplay: true
interval: 10000
).navigator()
$ ->
DI.Home.onload()
Asset Management
<%= javascript_include_tag "application" %>

<script src="/assets/core.js?body=1" type="text/javascript"></script>
<script src="/assets/projects.js?body=1" type="text/javascript"></script>
<script src="/assets/tickets.js?body=1" type="text/javascript"></script>

<script src="/assets/application908e25f4bf641868d8683022a5b62f54.js"
type="text/javascript"></script>
Rails Plugins
Rails Plugins
• Authentication (Devise, OmniAuth, Facebook, Twitter)
• Storage (MySQL, PostgreSQL, MongoDB)
• Search (ElasticSearch, Sphinx)
• HTML (HAML, SASS, Less, Compass, Slim)
• Deployment (Capistrano, Heroku)
• Monitoring (Airbrake, NewRelic)
• Testing (RSpec, Cucumber, Capybara)
• JavaScript (jQuery, MooTools)
• ...
Plugins
I used to write functionality
Now I integrate functionality into solutions
RSpec for TDD
require 'spec_helper'
describe Region do
context 'with regions Sydney, North Sydney and Melbourne' do
before do
@sydney = Region.make(:sydney)
@north_sydney = Region.make(:north_sydney)
@melbourne = Region.make(:melbourne)
end
describe '.find_by_postcode' do
it 'should be Sydney for 2000' do
gpo = Region.find_by_postcode('2000')
gpo.should == @sydney
end
end
end
end
Cucumber for BDD
Feature: Manage Articles
In order to make a blog
As an author
I want to create and manage articles
Scenario: List articles
Given I have articles titled Pizza, Breadsticks
When I go to the list of article
Then I should see "Pizza"
And I should see "Breadsticks"
Scenario: Create an article
Given I have no articles
And I am on the list of articles
When I follow "New Article"
And I fill in "Title" with "Spuds"
And I fill in "Content" with "Delicious potato wedges!"
And I press "Create"
Then I should see "New article created."
And I should see "Spuds"
And I should see "Delicious potato wedges!"
And I should have 1 article
Steak for BDD
require 'spec_helper'
feature 'Admin Sign In and Out', %q{
As an admin user
I want to sign in and out
So that I can access the admin section
} do
background do
AdminUser.make(:email => 'jdoe@protein-one.com', :password => 'password')
end
scenario 'Valid admin login' do
visit admin_path
fill_in 'Email', :with => 'jdoe@protein-one.com'
fill_in 'Password', :with => 'password'
click_link 'Sign in'
page.should have_content('Signed in successfully.')
page.should have_css('a', :text => 'Sign out')
end
scenario 'Invalid admin login' do
visit admin_path
fill_in 'Email', :with => 'jdoe@protein-one.com'
fill_in 'Password', :with => 'wrong password'
click_link 'Sign in'
page.should have_content('Invalid email or password.')
page.should_not have_content('Sign out')
end
end
Selenium Webdriver
Deployment
# config/deploy.rb
set :application, 'davidson'
set :repository, 'git@git.protein-one.com'
set :deploy_to, '/home/davidson/deployment'
set :scm, :git
role :app, '192.168.1.1', '192.168.1.2'
role :web, '192.168.1.101', '192.168.1.102'
role :db, '192.168.1.229', :primary => true

$ cap staging deploy
...
$ cap production deploy
...
Can I Learn All This?
• Come pair program with me
• Ask me for my collection of Rails ebooks
• Pick an app to write
• Ask (shyness is niiiice but...)
• Nettuts+, Lynda.com, Treehouse
Rails at Protein One

• Let’s rapidly prototype our ideas
• Let’s focus on solutions, not code
• Let’s test and monitor so we can be
suave and confident
Designers and Me
• If you give me Photoshop files
• I will use Compass (and spriting)
• If you give me HTML5
• I will convert it to HAML, SASS and
CoffeeScript

• (so if you wanna save me time...)
Ta
greg@gregorymcintyre.com
2013

Agile and rails

  • 1.
  • 2.
    My Creds • University •Ruby, TDD • Tilefile Pty Ltd • Python, ActionScript, Rails • realestate.com.au • Agile (ThoughtWorks)
  • 3.
  • 4.
    #fail • Impenetrable requirementsdocuments • Scary contracts • Early is when you know the least • Requirements tend to change
  • 5.
    Agile Manifest’O • Facetime > processes and tools • Working software > documentation • Customer collaboration > contracts • Embracing change > sticking to the plan
  • 6.
    You Say Scrum,I Say... • XP • Scrum • Crystal • Lean • Roll your own, just follow the ’festo
  • 8.
  • 9.
  • 10.
    Strategy • Mission statements •Scope • Risk • Team building • Priorities • Finance
  • 11.
    Project Inception • Agile“strategy” process (1-5 days) • Share vision • Align goals • Set realistic expectations • Swap phone numbers
  • 12.
  • 13.
  • 14.
  • 15.
    Build a StoryBacklog • Do some UX • Write “stories” • T-shirt sizing
  • 16.
  • 17.
    Kanban • Toyota invented itto manage car production efficiently • Not waterfall, not iterative • • Continuous, like a pipe Deliver something of value every day
  • 18.
  • 19.
  • 20.
  • 21.
    Feedback Loops • • • • 2-4 weeksis common Involve the customer Revise estimates Assess and improve
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
    Agile is... • Technically“backward” (ahem, pragmatic) • “Last minute” • Noisy, demanding and confronting • Practice practice practice • About visibility, not due dates
  • 34.
  • 35.
  • 36.
    exit unless "restaurant".include?"aura" 5.times { print "Odelay!" } ['toast', 'cheese', 'wine'].each {|food| print food.capitalize } class Blog has_many :posts end Ruby: Expressive and Flexible
  • 37.
    Ruby DSLs class Blog has_many:posts end def has_many(things)
  • 38.
    “LADIES, let’s makeweb apps with Ruby”
  • 39.
    Rails is aWeb Application Framework • Handles an HTTP request • • • Common practices Sensible defaults Keeps things orderly
  • 40.
    Rapid Prototyping $ railsgenerate scaffold Post name:string title:string content:text invoke create create invoke create route invoke create invoke create create create create create create invoke create create create create create invoke create create invoke create invoke create invoke invoke create active_record db/migrate/20120316050430_create_posts.rb app/models/post.rb rspec spec/models/post_spec.rb resources :posts inherited_resources_controller app/controllers/posts_controller.rb erb app/views/posts app/views/posts/index.html.erb app/views/posts/edit.html.erb app/views/posts/show.html.erb app/views/posts/new.html.erb app/views/posts/_form.html.erb rspec spec/controllers/posts_controller_spec.rb spec/views/posts/edit.html.erb_spec.rb spec/views/posts/index.html.erb_spec.rb spec/views/posts/new.html.erb_spec.rb spec/views/posts/show.html.erb_spec.rb helper spec/helpers/posts_helper_spec.rb spec/routing/posts_routing_spec.rb rspec spec/requests/posts_spec.rb helper app/helpers/posts_helper.rb rspec stylesheets public/stylesheets/scaffold.css
  • 41.
    Sensible Defaults Davidson::Application.routes.draw do resource'shared_cookie' end <struts> <package name = "MyPackage"> <interceptors> <!--Some set particular action--> <interceptor <interceptor <interceptor <interceptor of common interceptors for a name name name name = = = = "A_I1" "A_I2" "A_I3" "A_I4" class class class class = = = = "MyA_I1"> "MyA_I2"> "MyA_I3"> "MyA_I4"> <!--Another set of common interceptors --> <interceptor name = "B_I1" class = "MyB_I1"> <interceptor name = "B_I2" class = "MyB_I2"> <interceptor name = "B_I3" class = "MyB_I3"> <interceptor name = "B_I4" class = "MyB_I4"> </interceptors> <interceptor-stack name = <interceptor-ref name <interceptor-ref name <interceptor-ref name <interceptor-ref name </interceptor-stack> "A"> = "A_I1"> = "A_I2"> = "A_I3"> = "A_I4"> <interceptor-stack name = <interceptor-ref name <interceptor-ref name <interceptor-ref name <interceptor-ref name </interceptor-stack> "B"> = "B_I1"> = "B_I2"> = "B_I3"> = "B_I4"> <action name = "MyAction1"> <interceptor-ref name = "A"/> </action> shared_cookie POST new_shared_cookie GET edit_shared_cookie GET GET PUT DELETE /shared_cookie /shared_cookie/new /shared_cookie/edit /shared_cookie /shared_cookie /shared_cookie
  • 42.
    ERB versus HAML =form_for(@post) do |f| <%= form_for(@post) do |f| %> - if @post.errors.any? <% if @post.errors.any? %> #errorExplanation <div id="errorExplanation"> %h2 <h2><%= pluralize(@post.errors.count, "error") %> = pluralize(@post.errors.count, "error") prohibited this post from being saved:</h2> prohibited this post from being saved: <ul> %ul <% @post.errors.full_messages.each do |msg| %> - @post.errors.full_messages.each do |msg| <li><%= msg %></li> %li= msg <% end %> .field </ul> = f.label :name </div> = f.text_field :name <% end %> .field <div class="field"> = f.label :title <%= f.label :name %> = f.text_field :title <%= f.text_field :name %> .field </div> = f.label :content <div class="field"> = f.text_area :content <%= f.label :title %> .actions <%= f.text_field :title %> = f.submit </div> <div class="field"> <%= f.label :content %> <%= f.text_area :content %> </div> <div class="actions"> <%= f.submit %> </div> <% end %>
  • 43.
    CSS versus SASS html.rgbaheader#global nav .user-nav { background-color: rgba(0, 0, 0, 0.05); } html.rgba header#global nav .user-nav .signin:active, html.rgba header#global nav .user-nav .signin:hover, html.rgba header#global nav .user-nav .signin:focus, html.rgba header#global nav .user-nav .signout:active, html.rgba header#global nav .user-nav .signout:hover, html.rgba header#global nav .user-nav .signout:focus { background-color: rgba(255, 255, 255, 0.5); } html.no-rgba header#global nav .user-nav { background: url("/images/design/black-5.png"); } html.no-rgba header#global nav .user-nav .signin:active, html.no-rgba header#global nav .user-nav .signin:hover, html.no-rgba header#global nav .user-nav .signin:focus, html.no-rgba header#global nav .user-nav .signout:active, html.no-rgba header#global nav .usernav .signout:hover, html.no-rgba header#global nav .user-nav .signout:focus { background-color: #edebe9; } html &.rgba header#global nav .user-nav background-color: rgba(0, 0, 0, 0.05) .signin, .signout &:active, &:hover, &:focus background-color: rgba(255, 255, 255, 0.5) &.no-rgba header#global nav .user-nav background: url("/images/design/black-5.png" .signin, .signout &:active, &:hover, &:focus background-color: #edebe9
  • 44.
    SASS + Compass<3 .box -ms-filter: "progid:DXImageTransform.Microsoft.A lpha(Opacity=50)" filter: alpha(opacity=50) opacity: .5 .box +opacity(.5)
  • 45.
    Advanced Compass @import "my-icons/*.png" .actions .new @includemy-icons-sprite(new) .edit @include my-icons-sprite(edit) .save @include my-icons-sprite(save) .delete @include my-icons-sprite(delete) .my-icons-sprite, .actions .new, .actions .edit, .actions .save, .actions .delete { background: url('/images/my-icons-s34fe0604ab.png') no-repeat; } .actions .actions .actions .actions .new .edit .save .delete { { { { background-position: background-position: background-position: background-position: 0 0 0 0 -64px; -32px; -96px; 0; } } } }
  • 46.
    CoffeeScript (function() { DI.Home ={ onload: function() { return this.setupScrollable(); }, setupScrollable: function() { return $('.scrollable').scrollable({ circular: true, speed: 800 }).autoscroll({ autoplay: true, interval: 10000 }).navigator(); } }; $(function() { return DI.Home.onload(); }); }).call(this); DI.Home = onload: -> @setupScrollable() setupScrollable: -> $(".scrollable").scrollable( circular: true speed: 800 ).autoscroll( autoplay: true interval: 10000 ).navigator() $ -> DI.Home.onload()
  • 47.
    Asset Management <%= javascript_include_tag"application" %> <script src="/assets/core.js?body=1" type="text/javascript"></script> <script src="/assets/projects.js?body=1" type="text/javascript"></script> <script src="/assets/tickets.js?body=1" type="text/javascript"></script> <script src="/assets/application908e25f4bf641868d8683022a5b62f54.js" type="text/javascript"></script>
  • 48.
  • 49.
    Rails Plugins • Authentication(Devise, OmniAuth, Facebook, Twitter) • Storage (MySQL, PostgreSQL, MongoDB) • Search (ElasticSearch, Sphinx) • HTML (HAML, SASS, Less, Compass, Slim) • Deployment (Capistrano, Heroku) • Monitoring (Airbrake, NewRelic) • Testing (RSpec, Cucumber, Capybara) • JavaScript (jQuery, MooTools) • ...
  • 50.
    Plugins I used towrite functionality Now I integrate functionality into solutions
  • 51.
    RSpec for TDD require'spec_helper' describe Region do context 'with regions Sydney, North Sydney and Melbourne' do before do @sydney = Region.make(:sydney) @north_sydney = Region.make(:north_sydney) @melbourne = Region.make(:melbourne) end describe '.find_by_postcode' do it 'should be Sydney for 2000' do gpo = Region.find_by_postcode('2000') gpo.should == @sydney end end end end
  • 52.
    Cucumber for BDD Feature:Manage Articles In order to make a blog As an author I want to create and manage articles Scenario: List articles Given I have articles titled Pizza, Breadsticks When I go to the list of article Then I should see "Pizza" And I should see "Breadsticks" Scenario: Create an article Given I have no articles And I am on the list of articles When I follow "New Article" And I fill in "Title" with "Spuds" And I fill in "Content" with "Delicious potato wedges!" And I press "Create" Then I should see "New article created." And I should see "Spuds" And I should see "Delicious potato wedges!" And I should have 1 article
  • 53.
    Steak for BDD require'spec_helper' feature 'Admin Sign In and Out', %q{ As an admin user I want to sign in and out So that I can access the admin section } do background do AdminUser.make(:email => 'jdoe@protein-one.com', :password => 'password') end scenario 'Valid admin login' do visit admin_path fill_in 'Email', :with => 'jdoe@protein-one.com' fill_in 'Password', :with => 'password' click_link 'Sign in' page.should have_content('Signed in successfully.') page.should have_css('a', :text => 'Sign out') end scenario 'Invalid admin login' do visit admin_path fill_in 'Email', :with => 'jdoe@protein-one.com' fill_in 'Password', :with => 'wrong password' click_link 'Sign in' page.should have_content('Invalid email or password.') page.should_not have_content('Sign out') end end
  • 54.
  • 55.
    Deployment # config/deploy.rb set :application,'davidson' set :repository, 'git@git.protein-one.com' set :deploy_to, '/home/davidson/deployment' set :scm, :git role :app, '192.168.1.1', '192.168.1.2' role :web, '192.168.1.101', '192.168.1.102' role :db, '192.168.1.229', :primary => true $ cap staging deploy ... $ cap production deploy ...
  • 56.
    Can I LearnAll This? • Come pair program with me • Ask me for my collection of Rails ebooks • Pick an app to write • Ask (shyness is niiiice but...) • Nettuts+, Lynda.com, Treehouse
  • 57.
    Rails at ProteinOne • Let’s rapidly prototype our ideas • Let’s focus on solutions, not code • Let’s test and monitor so we can be suave and confident
  • 58.
    Designers and Me •If you give me Photoshop files • I will use Compass (and spriting) • If you give me HTML5 • I will convert it to HAML, SASS and CoffeeScript • (so if you wanna save me time...)
  • 59.