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.