Upload
gregmcintyre
View
77
Download
0
Tags:
Embed Size (px)
DESCRIPTION
Citation preview
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
Ruby: Expressive and Flexible
exit unless "restaurant".include? "aura"
5.times { print "Odelay!" }
['toast', 'cheese', 'wine'].each {|food| print food.capitalize }
class Blog has_many :postsend
Ruby DSLs
class Blog has_many :postsend
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 active_record create db/migrate/20120316050430_create_posts.rb create app/models/post.rb invoke rspec create spec/models/post_spec.rb route resources :posts invoke inherited_resources_controller create app/controllers/posts_controller.rb invoke erb create app/views/posts create app/views/posts/index.html.erb create app/views/posts/edit.html.erb create app/views/posts/show.html.erb create app/views/posts/new.html.erb create app/views/posts/_form.html.erb invoke rspec create spec/controllers/posts_controller_spec.rb create spec/views/posts/edit.html.erb_spec.rb create spec/views/posts/index.html.erb_spec.rb create spec/views/posts/new.html.erb_spec.rb create spec/views/posts/show.html.erb_spec.rb invoke helper create spec/helpers/posts_helper_spec.rb create spec/routing/posts_routing_spec.rb invoke rspec create spec/requests/posts_spec.rb invoke helper create app/helpers/posts_helper.rb invoke rspec invoke stylesheets create public/stylesheets/scaffold.css
Sensible DefaultsDavidson::Application.routes.draw do resource 'shared_cookie'end
shared_cookie POST /shared_cookie new_shared_cookie GET /shared_cookie/newedit_shared_cookie GET /shared_cookie/edit GET /shared_cookie PUT /shared_cookie DELETE /shared_cookie
<struts>
<package name = "MyPackage">
<interceptors> <!--Some set of common interceptors for a particular action--> <interceptor name = "A_I1" class = "MyA_I1"> <interceptor name = "A_I2" class = "MyA_I2"> <interceptor name = "A_I3" class = "MyA_I3"> <interceptor name = "A_I4" class = "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 = "A"> <interceptor-ref name = "A_I1"> <interceptor-ref name = "A_I2"> <interceptor-ref name = "A_I3"> <interceptor-ref name = "A_I4"> </interceptor-stack> <interceptor-stack name = "B"> <interceptor-ref name = "B_I1"> <interceptor-ref name = "B_I2"> <interceptor-ref name = "B_I3"> <interceptor-ref name = "B_I4"> </interceptor-stack>
<action name = "MyAction1"> <interceptor-ref name = "A"/> </action>
<action name = "MyAction2"> <interceptor-ref name = "B"/> </action>
</package>
</struts>
ERB versus HAML<%= form_for(@post) do |f| %> <% if @post.errors.any? %> <div id="errorExplanation"> <h2><%= pluralize(@post.errors.count, "error") %> prohibited this post from being saved:</h2> <ul> <% @post.errors.full_messages.each do |msg| %> <li><%= msg %></li> <% end %> </ul> </div> <% end %> <div class="field"> <%= f.label :name %> <%= f.text_field :name %> </div> <div class="field"> <%= f.label :title %> <%= f.text_field :title %> </div> <div class="field"> <%= f.label :content %> <%= f.text_area :content %> </div> <div class="actions"> <%= f.submit %> </div><% end %>
= form_for(@post) do |f| - if @post.errors.any? #errorExplanation %h2 = pluralize(@post.errors.count, "error") prohibited this post from being saved: %ul - @post.errors.full_messages.each do |msg| %li= msg .field = f.label :name = f.text_field :name .field = f.label :title = f.text_field :title .field = f.label :content = f.text_area :content .actions = f.submit
CSS versus SASShtml.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 .user-nav .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 +opacity(.5)
.box -ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=50)" filter: alpha(opacity=50) 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 .new { background-position: 0 -64px; }
.actions .edit { background-position: 0 -32px; }
.actions .save { background-position: 0 -96px; }
.actions .delete { background-position: 0 0; }
CoffeeScript
DI.Home = onload: -> @setupScrollable()
setupScrollable: -> $(".scrollable").scrollable( circular: true speed: 800 ).autoscroll( autoplay: true interval: 10000 ).navigator()
$ -> DI.Home.onload()
(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);
Asset Management<%= javascript_include_tag "application" %>
<script src="/assets/application-908e25f4bf641868d8683022a5b62f54.js" type="text/javascript"></script>
<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>
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)
•...
PluginsI used to write functionality
Now I integrate functionality into solutions
RSpec for TDDrequire '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 endend
Cucumber for BDDFeature: 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 BDDrequire '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 => '[email protected]', :password => 'password') end
scenario 'Valid admin login' do visit admin_path fill_in 'Email', :with => '[email protected]' 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 => '[email protected]' 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') endend
Selenium Webdriver
Deployment# config/deploy.rbset :application, 'davidson'set :repository, '[email protected]'set :deploy_to, '/home/davidson/deployment'set :scm, :gitrole :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...)
2013