SlideShare uma empresa Scribd logo
1 de 60
Baixar para ler offline
Building Cloud Castles
and Finding Firmer Foundations




Ben Scofield / @bscofield
West End Ruby / 2 Feb 2011
this presentation is a
        WORK IN PROGRESS


flickr: natlockwood
THE CLOUD

flickr: turtlemom_nancy
IS FAR AWAY

flickr: sizemoresr
LIMITED ACCESS
DIAGNOSIS
$ ssh deploy@production.server.com
Linux production.server #1 SMP Sat Dec 5 16:04:55 UTC 2009 i686

To access official Ubuntu documentation, please visit:
http://help.ubuntu.com/
Last login: Fri Jan 28 16:33:49 2011 from local.host
deploy@production:~$ cd /var/log/apache2
deploy@production:/var/log/apache2$ tail error.log
[Sun Jan 23 06:25:02 2011] [notice] Apache/2.2.12 (Ubuntu) Phusion_Passenger/2.2.11...
[Tue Jan 25 15:21:42 2011] [error] [client 118.129.166.97] Invalid URI in request G...
[Fri Jan 28 12:01:50 2011] [error] [client 85.132.70.133] client sent HTTP/1.1 requ...
[Sun Jan 30 06:25:06 2011] [notice] SIGUSR1 received. Doing graceful restart
http://hoptoadapp.com
http://newrelic.com
http://newrelic.com - application dashboard
REPAIR
$ ssh deploy@production.server.com
Linux production.server #1 SMP Sat Dec 5 16:04:55 UTC 2009 i686

To access official Ubuntu documentation, please visit:
http://help.ubuntu.com/
Last login: Fri Jan 28 16:33:49 2011 from local.host
deploy@production:~$ cd /var/www/app/current/
deploy@production:/var/www/app/current$ rails console production
Loading production environment (Rails 3.0.3)
>> Article.count
 => 112
>> Article.where(:problem => true).update_attributes(:problem => false)
require 'test_helper'

class ArticleTest < ActiveSupport::TestCase
  context 'Broken articles' do
    setup do
      5.times.do { Factory :broken_article }
    end

    should 'be fixable' do
      assert_equal 5, Article.where(:problem => true).count
      Article.fix_problem_articles
      assert_equal 0, Article.where(:problem => true).count
    end
  end
end

class Article
  def self.fix_problem_articles
    where(:problem => true).update_attributes(:problem => false)
  end
end
THE CLOUD

flickr: turtlemom_nancy
IS UNRELIABLE

flickr: 93921318@N00
FILESYSTEMS
class Comic < ActiveRecord::Base
  has_attached_file :cover, :styles => {
    :thumb => "80x120>",
    :medium => "300x450>"
  }
end



$ cd public/system
$ ls /covers
10/ 12/ 53/ 81/
$ ls /covers/10/
medium/ original/ thumb/
$ ls /covers/10/medium
batman-450.png
class Comic < ActiveRecord::Base
  has_attached_file :cover,
    :storage => s3,
    :s3_credentials => {
       :access_key_id => ENV['s3_key'],
       :secret_access_key => ENV['s3_secret']
    },
    :bucket => 'comicsapp',
    :url => ":s3_path_url",
    :s3_headers => { 'Expires' => 1.year.from_now.httpdate },
    :styles => {
       :thumb => "80x120>",
       :medium => "300x450>"
    }
end
class Comic < ActiveRecord::Base
  has_attached_file :cover,
    :storage => s3,
    :s3_credentials => {
       :access_key_id => ENV['s3_key'],
       :secret_access_key => ENV['s3_secret']
    },
    :bucket => 'comicsapp',
    :url => ":s3_path_url",
    :s3_headers => { 'Expires' => 1.year.from_now.httpdate },
    :styles => {
       :thumb => "80x120>",
       :medium => "300x450>"
    }
end
module Watchtower
  class Application < Rails::Application
    # ...

    require 'openid/store/filesystem'
    config.middleware.use OmniAuth::Strategies::OpenID,
      OpenID::Store::Filesystem.new('/tmp')
  end
end
module Watchtower
  class Application < Rails::Application
    # ...

    require 'openid/store/filesystem'
    config.middleware.use OmniAuth::Strategies::OpenID,
      OpenID::Store::Filesystem.new('./tmp')
  end
end
module Watchtower
  class Application < Rails::Application
    # ...

    require 'openid/store/filesystem'
    config.middleware.use OmniAuth::Strategies::OpenID,
      OpenID::Store::Filesystem.new('./tmp')
  end
end
THE CLOUD

flickr: turtlemom_nancy
IS HOSTILE

flickr: lensonlife
EXTERNAL SERVICES
class Searcher
  def self.search(term)
    Article.where(['content ILIKE ?', "%#{term}%"])
  end
end
class Searcher
  cattr_accessor :index

  def self.index
    @api ||= IndexTank::Client.new(ENV['INDEXTANK_API_URL'])
    @index = @api.indexes 'articles'
  end

  def self.search(term)
    raw = self.index.search(term, :function => 1)
    results = raw['results'].to_a

    article_ids = results.map {|result| result['docid'] }

    unsorted = Article.published.where(:id => article_ids)
    results.map { |result|
      unsorted.find {|u| u.id.to_i == result['docid'].to_i}
    }.compact
  end
end
class Searcher
  cattr_accessor :index

  def self.index
    @api ||= IndexTank::Client.new(ENV['INDEXTANK_API_URL'])
    @index = @api.indexes 'articles'
  end

  def self.search(term)
    results = begin
      raw = self.index.search(term, :function => 1)
      raw['results'].to_a
    rescue URI::InvalidURIError # An IndexTank error occurred
      search_by_sql(term)['results']
    end

    article_ids = results.map {|result| result['docid'] }

    unsorted = Article.published.where(:id => article_ids)
    results.map { |result|
      unsorted.find {|u| u.id.to_i == result['docid'].to_i}
    }.compact
  end

  def self.search_by_sql(term)
    {'results' => Article.where(['content ILIKE ?', "%#{term}%"]).
      map {|a| {'docid' => a.id}}}
  end
end
class Searcher
  cattr_accessor :index

  def self.index
    @api ||= IndexTank::Client.new(ENV['INDEXTANK_API_URL'])
    @index = @api.indexes 'articles'
  end

  def self.search(term)
    results = begin
      raw = self.index.search(term, :function => 1)
      raw['results'].to_a
    rescue URI::InvalidURIError # An IndexTank error occurred
      search_by_sql(term)['results']
    end

    article_ids = results.map {|result| result['docid'] }

    unsorted = Article.published.where(:id => article_ids)
    results.map { |result|
      unsorted.find {|u| u.id.to_i == result['docid'].to_i}
    }.compact
  end

  def self.search_by_sql(term)
    {'results' => Article.where(['content ILIKE ?', "%#{term}%"]).
      map {|a| {'docid' => a.id}}}
  end
end
THE CLOUD

flickr: turtlemom_nancy
IS RECYCLED

flickr: 3sth3r
CACHING
class BooksController < ApplicationController
  caches_page :index

  def index
    @books = Book.all
  end
end
module CardCatalog
  class Application < Rails::Application
    # ...

    ActionController::Base.cache_store = :mem_cache_store, "memcache_host"
  end
end


class BooksController < ApplicationController
  caches_action :index

  def index
    @books = Book.all
  end
end
module CardCatalog
  class Application < Rails::Application
    # ...

    ActionController::Base.cache_store = :mem_cache_store, "memcache_host"
  end
end


class BooksController < ApplicationController
  caches_action :index

  def index
    @books = Book.all
  end
end
class BooksController < ApplicationController
  def index
    response.headers['Cache-Control'] = 'public, max-age=300'
    @books = Book.all
  end
end
class BooksController < ApplicationController
  def index
    response.headers['Cache-Control'] = 'public, max-age=300'
    @books = Book.all
  end
end
THE CLOUD

flickr: turtlemom_nancy
IS MADE OF
                  TINY PARTS




flickr: dev07
THINKING SMALL
App
App         App               App         App



      App                                 App

App                     App

                                          App



App   App         App               App



App               App         App         App
$ heroku create
Creating empty-sword-187....... done
http://empty-sword-187.heroku.com/ | git@heroku.com:empty-sword-187.git
Git remote heroku added
$ git push heroku master
Counting objects: 5, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 285 bytes, done.
Total 3 (delta 2), reused 0 (delta 0)

-----> Heroku receiving push
-----> Rails app detected
-----> Detected use of caches_page
       Installing caches_page_via_http plugin... done
-----> Detected Rails is not set to serve static_assets
       Installing rails3_serve_static_assets... done
-----> Gemfile detected, running Bundler version 1.0.7
       Unresolved dependencies detected; Installing...
       Using --without development:test
       Fetching source index for http://rubygems.org/
       ...
App         App               App         App



      App                                 App

App                     App

                                          App



App   App         App               App



App               App         App         App
App         App               App         App



      App                                 App

App                     App

                                          App



App   App         App               App



App               App         App         App
HTTP and REST
PATTERNS and VIRTUES
SINGLE RESPONSIBILITY
PRINCIPLE
HUMILITY
LAZINESS
PARANOIA
http://spkr8.com/t/5491
Thanks!                       http://benscofield.com
                                    http://heroku.com




Ben Scofield / @bscofield
West End Ruby / 2 Feb 2011

Mais conteúdo relacionado

Mais procurados

Keeping it small - Getting to know the Slim PHP micro framework
Keeping it small - Getting to know the Slim PHP micro frameworkKeeping it small - Getting to know the Slim PHP micro framework
Keeping it small - Getting to know the Slim PHP micro frameworkJeremy Kendall
 
The Coolest Symfony Components you’ve never heard of - DrupalCon 2017
The Coolest Symfony Components you’ve never heard of - DrupalCon 2017The Coolest Symfony Components you’ve never heard of - DrupalCon 2017
The Coolest Symfony Components you’ve never heard of - DrupalCon 2017Ryan Weaver
 
Bullet: The Functional PHP Micro-Framework
Bullet: The Functional PHP Micro-FrameworkBullet: The Functional PHP Micro-Framework
Bullet: The Functional PHP Micro-FrameworkVance Lucas
 
Slim RedBeanPHP and Knockout
Slim RedBeanPHP and KnockoutSlim RedBeanPHP and Knockout
Slim RedBeanPHP and KnockoutVic Metcalfe
 
Inside Bokete: Web Application with Mojolicious and others
Inside Bokete:  Web Application with Mojolicious and othersInside Bokete:  Web Application with Mojolicious and others
Inside Bokete: Web Application with Mojolicious and othersYusuke Wada
 
Best Practices in Plugin Development (WordCamp Seattle)
Best Practices in Plugin Development (WordCamp Seattle)Best Practices in Plugin Development (WordCamp Seattle)
Best Practices in Plugin Development (WordCamp Seattle)andrewnacin
 
Rails 3 overview
Rails 3 overviewRails 3 overview
Rails 3 overviewYehuda Katz
 
Using Sinatra to Build REST APIs in Ruby
Using Sinatra to Build REST APIs in RubyUsing Sinatra to Build REST APIs in Ruby
Using Sinatra to Build REST APIs in RubyLaunchAny
 
Perl web frameworks
Perl web frameworksPerl web frameworks
Perl web frameworksdiego_k
 
ISUCONアプリを Pythonで書いてみた
ISUCONアプリを Pythonで書いてみたISUCONアプリを Pythonで書いてみた
ISUCONアプリを Pythonで書いてみたmemememomo
 
Lightweight Webservices with Sinatra and RestClient
Lightweight Webservices with Sinatra and RestClientLightweight Webservices with Sinatra and RestClient
Lightweight Webservices with Sinatra and RestClientAdam Wiggins
 
And the Greatest of These Is ... Rack Support
And the Greatest of These Is ... Rack SupportAnd the Greatest of These Is ... Rack Support
And the Greatest of These Is ... Rack SupportBen Scofield
 
Rails 3: Dashing to the Finish
Rails 3: Dashing to the FinishRails 3: Dashing to the Finish
Rails 3: Dashing to the FinishYehuda Katz
 
Plugin jQuery, Design Patterns
Plugin jQuery, Design PatternsPlugin jQuery, Design Patterns
Plugin jQuery, Design PatternsRobert Casanova
 
Introducing Assetic (NYPHP)
Introducing Assetic (NYPHP)Introducing Assetic (NYPHP)
Introducing Assetic (NYPHP)Kris Wallsmith
 

Mais procurados (20)

Developing apps using Perl
Developing apps using PerlDeveloping apps using Perl
Developing apps using Perl
 
Keeping it small - Getting to know the Slim PHP micro framework
Keeping it small - Getting to know the Slim PHP micro frameworkKeeping it small - Getting to know the Slim PHP micro framework
Keeping it small - Getting to know the Slim PHP micro framework
 
The Coolest Symfony Components you’ve never heard of - DrupalCon 2017
The Coolest Symfony Components you’ve never heard of - DrupalCon 2017The Coolest Symfony Components you’ve never heard of - DrupalCon 2017
The Coolest Symfony Components you’ve never heard of - DrupalCon 2017
 
Elegant APIs
Elegant APIsElegant APIs
Elegant APIs
 
Bullet: The Functional PHP Micro-Framework
Bullet: The Functional PHP Micro-FrameworkBullet: The Functional PHP Micro-Framework
Bullet: The Functional PHP Micro-Framework
 
Slim RedBeanPHP and Knockout
Slim RedBeanPHP and KnockoutSlim RedBeanPHP and Knockout
Slim RedBeanPHP and Knockout
 
Inside Bokete: Web Application with Mojolicious and others
Inside Bokete:  Web Application with Mojolicious and othersInside Bokete:  Web Application with Mojolicious and others
Inside Bokete: Web Application with Mojolicious and others
 
Best Practices in Plugin Development (WordCamp Seattle)
Best Practices in Plugin Development (WordCamp Seattle)Best Practices in Plugin Development (WordCamp Seattle)
Best Practices in Plugin Development (WordCamp Seattle)
 
Rails 3 overview
Rails 3 overviewRails 3 overview
Rails 3 overview
 
Using Sinatra to Build REST APIs in Ruby
Using Sinatra to Build REST APIs in RubyUsing Sinatra to Build REST APIs in Ruby
Using Sinatra to Build REST APIs in Ruby
 
Perl web frameworks
Perl web frameworksPerl web frameworks
Perl web frameworks
 
ISUCONアプリを Pythonで書いてみた
ISUCONアプリを Pythonで書いてみたISUCONアプリを Pythonで書いてみた
ISUCONアプリを Pythonで書いてみた
 
Symfony 2.0 on PHP 5.3
Symfony 2.0 on PHP 5.3Symfony 2.0 on PHP 5.3
Symfony 2.0 on PHP 5.3
 
Lightweight Webservices with Sinatra and RestClient
Lightweight Webservices with Sinatra and RestClientLightweight Webservices with Sinatra and RestClient
Lightweight Webservices with Sinatra and RestClient
 
And the Greatest of These Is ... Rack Support
And the Greatest of These Is ... Rack SupportAnd the Greatest of These Is ... Rack Support
And the Greatest of These Is ... Rack Support
 
Mojolicious
MojoliciousMojolicious
Mojolicious
 
RESTful web services
RESTful web servicesRESTful web services
RESTful web services
 
Rails 3: Dashing to the Finish
Rails 3: Dashing to the FinishRails 3: Dashing to the Finish
Rails 3: Dashing to the Finish
 
Plugin jQuery, Design Patterns
Plugin jQuery, Design PatternsPlugin jQuery, Design Patterns
Plugin jQuery, Design Patterns
 
Introducing Assetic (NYPHP)
Introducing Assetic (NYPHP)Introducing Assetic (NYPHP)
Introducing Assetic (NYPHP)
 

Destaque

Understanding Mastery
Understanding MasteryUnderstanding Mastery
Understanding MasteryBen Scofield
 
D E V E L O P M E N T A L P S Y C H O L O G Y A N D L E A R N I N G I B...
D E V E L O P M E N T A L  P S Y C H O L O G Y  A N D  L E A R N I N G   I  B...D E V E L O P M E N T A L  P S Y C H O L O G Y  A N D  L E A R N I N G   I  B...
D E V E L O P M E N T A L P S Y C H O L O G Y A N D L E A R N I N G I B...Universidad Técnica Particular de Loja
 
Mind Control - DevNation Atlanta
Mind Control - DevNation AtlantaMind Control - DevNation Atlanta
Mind Control - DevNation AtlantaBen Scofield
 
The Future of Data
The Future of DataThe Future of Data
The Future of DataBen Scofield
 
Marketer Bun Sau Extraordinar
Marketer Bun Sau ExtraordinarMarketer Bun Sau Extraordinar
Marketer Bun Sau ExtraordinarMarius Sescu
 
Charlotte.rb - "Comics" Is Hard
Charlotte.rb - "Comics" Is HardCharlotte.rb - "Comics" Is Hard
Charlotte.rb - "Comics" Is HardBen Scofield
 
Open Source: A Call to Arms
Open Source: A Call to ArmsOpen Source: A Call to Arms
Open Source: A Call to ArmsBen Scofield
 
Mastery or Mediocrity
Mastery or MediocrityMastery or Mediocrity
Mastery or MediocrityBen Scofield
 
Social media pentru companii - mituri si explicatii
Social media pentru companii - mituri si explicatiiSocial media pentru companii - mituri si explicatii
Social media pentru companii - mituri si explicatiiMarius Sescu
 
How to Be Awesome in 2.5 Steps
How to Be Awesome in 2.5 StepsHow to Be Awesome in 2.5 Steps
How to Be Awesome in 2.5 StepsBen Scofield
 
Intentionality: Choice and Mastery
Intentionality: Choice and MasteryIntentionality: Choice and Mastery
Intentionality: Choice and MasteryBen Scofield
 

Destaque (13)

Understanding Mastery
Understanding MasteryUnderstanding Mastery
Understanding Mastery
 
D E V E L O P M E N T A L P S Y C H O L O G Y A N D L E A R N I N G I B...
D E V E L O P M E N T A L  P S Y C H O L O G Y  A N D  L E A R N I N G   I  B...D E V E L O P M E N T A L  P S Y C H O L O G Y  A N D  L E A R N I N G   I  B...
D E V E L O P M E N T A L P S Y C H O L O G Y A N D L E A R N I N G I B...
 
Mind Control - DevNation Atlanta
Mind Control - DevNation AtlantaMind Control - DevNation Atlanta
Mind Control - DevNation Atlanta
 
The Future of Data
The Future of DataThe Future of Data
The Future of Data
 
Marketer Bun Sau Extraordinar
Marketer Bun Sau ExtraordinarMarketer Bun Sau Extraordinar
Marketer Bun Sau Extraordinar
 
Charlotte.rb - "Comics" Is Hard
Charlotte.rb - "Comics" Is HardCharlotte.rb - "Comics" Is Hard
Charlotte.rb - "Comics" Is Hard
 
Open Source: A Call to Arms
Open Source: A Call to ArmsOpen Source: A Call to Arms
Open Source: A Call to Arms
 
Mastery or Mediocrity
Mastery or MediocrityMastery or Mediocrity
Mastery or Mediocrity
 
Social media pentru companii - mituri si explicatii
Social media pentru companii - mituri si explicatiiSocial media pentru companii - mituri si explicatii
Social media pentru companii - mituri si explicatii
 
Thinking Small
Thinking SmallThinking Small
Thinking Small
 
Ship It
Ship ItShip It
Ship It
 
How to Be Awesome in 2.5 Steps
How to Be Awesome in 2.5 StepsHow to Be Awesome in 2.5 Steps
How to Be Awesome in 2.5 Steps
 
Intentionality: Choice and Mastery
Intentionality: Choice and MasteryIntentionality: Choice and Mastery
Intentionality: Choice and Mastery
 

Semelhante a Building Cloud Castles

Crossing the Bridge: Connecting Rails and your Front-end Framework
Crossing the Bridge: Connecting Rails and your Front-end FrameworkCrossing the Bridge: Connecting Rails and your Front-end Framework
Crossing the Bridge: Connecting Rails and your Front-end FrameworkDaniel Spector
 
Phoenix for Rails Devs
Phoenix for Rails DevsPhoenix for Rails Devs
Phoenix for Rails DevsDiacode
 
Intro to-rails-webperf
Intro to-rails-webperfIntro to-rails-webperf
Intro to-rails-webperfNew Relic
 
Lean Php Presentation
Lean Php PresentationLean Php Presentation
Lean Php PresentationAlan Pinstein
 
Osiąganie mądrej architektury z Symfony2
Osiąganie mądrej architektury z Symfony2 Osiąganie mądrej architektury z Symfony2
Osiąganie mądrej architektury z Symfony2 3camp
 
QConSP 2015 - Dicas de Performance para Aplicações Web
QConSP 2015 - Dicas de Performance para Aplicações WebQConSP 2015 - Dicas de Performance para Aplicações Web
QConSP 2015 - Dicas de Performance para Aplicações WebFabio Akita
 
Migrating Legacy Rails Apps to Rails 3
Migrating Legacy Rails Apps to Rails 3Migrating Legacy Rails Apps to Rails 3
Migrating Legacy Rails Apps to Rails 3Clinton Dreisbach
 
Kicking off with Zend Expressive and Doctrine ORM (PHP UK 2017)
Kicking off with Zend Expressive and Doctrine ORM (PHP UK 2017)Kicking off with Zend Expressive and Doctrine ORM (PHP UK 2017)
Kicking off with Zend Expressive and Doctrine ORM (PHP UK 2017)James Titcumb
 
Puppet atbazaarvoice
Puppet atbazaarvoicePuppet atbazaarvoice
Puppet atbazaarvoiceDave Barcelo
 
Harmonious Development: Via Vagrant and Puppet
Harmonious Development: Via Vagrant and PuppetHarmonious Development: Via Vagrant and Puppet
Harmonious Development: Via Vagrant and PuppetAchieve Internet
 
Heavy Web Optimization: Backend
Heavy Web Optimization: BackendHeavy Web Optimization: Backend
Heavy Web Optimization: BackendVõ Duy Tuấn
 
Kicking off with Zend Expressive and Doctrine ORM (Sunshine PHP 2017)
Kicking off with Zend Expressive and Doctrine ORM (Sunshine PHP 2017)Kicking off with Zend Expressive and Doctrine ORM (Sunshine PHP 2017)
Kicking off with Zend Expressive and Doctrine ORM (Sunshine PHP 2017)James Titcumb
 
Burn down the silos! Helping dev and ops gel on high availability websites
Burn down the silos! Helping dev and ops gel on high availability websitesBurn down the silos! Helping dev and ops gel on high availability websites
Burn down the silos! Helping dev and ops gel on high availability websitesLindsay Holmwood
 
Código Saudável => Programador Feliz - Rs on Rails 2010
Código Saudável => Programador Feliz - Rs on Rails 2010Código Saudável => Programador Feliz - Rs on Rails 2010
Código Saudável => Programador Feliz - Rs on Rails 2010Plataformatec
 
What's New In Laravel 5
What's New In Laravel 5What's New In Laravel 5
What's New In Laravel 5Darren Craig
 
Rails antipatterns
Rails antipatternsRails antipatterns
Rails antipatternsChul Ju Hong
 

Semelhante a Building Cloud Castles (20)

Crossing the Bridge: Connecting Rails and your Front-end Framework
Crossing the Bridge: Connecting Rails and your Front-end FrameworkCrossing the Bridge: Connecting Rails and your Front-end Framework
Crossing the Bridge: Connecting Rails and your Front-end Framework
 
Phoenix for Rails Devs
Phoenix for Rails DevsPhoenix for Rails Devs
Phoenix for Rails Devs
 
Intro to-rails-webperf
Intro to-rails-webperfIntro to-rails-webperf
Intro to-rails-webperf
 
Lean Php Presentation
Lean Php PresentationLean Php Presentation
Lean Php Presentation
 
Osiąganie mądrej architektury z Symfony2
Osiąganie mądrej architektury z Symfony2 Osiąganie mądrej architektury z Symfony2
Osiąganie mądrej architektury z Symfony2
 
Intro to Rack
Intro to RackIntro to Rack
Intro to Rack
 
QConSP 2015 - Dicas de Performance para Aplicações Web
QConSP 2015 - Dicas de Performance para Aplicações WebQConSP 2015 - Dicas de Performance para Aplicações Web
QConSP 2015 - Dicas de Performance para Aplicações Web
 
Migrating Legacy Rails Apps to Rails 3
Migrating Legacy Rails Apps to Rails 3Migrating Legacy Rails Apps to Rails 3
Migrating Legacy Rails Apps to Rails 3
 
Kicking off with Zend Expressive and Doctrine ORM (PHP UK 2017)
Kicking off with Zend Expressive and Doctrine ORM (PHP UK 2017)Kicking off with Zend Expressive and Doctrine ORM (PHP UK 2017)
Kicking off with Zend Expressive and Doctrine ORM (PHP UK 2017)
 
Puppet atbazaarvoice
Puppet atbazaarvoicePuppet atbazaarvoice
Puppet atbazaarvoice
 
Rails 4.0
Rails 4.0Rails 4.0
Rails 4.0
 
Harmonious Development: Via Vagrant and Puppet
Harmonious Development: Via Vagrant and PuppetHarmonious Development: Via Vagrant and Puppet
Harmonious Development: Via Vagrant and Puppet
 
Heavy Web Optimization: Backend
Heavy Web Optimization: BackendHeavy Web Optimization: Backend
Heavy Web Optimization: Backend
 
Kicking off with Zend Expressive and Doctrine ORM (Sunshine PHP 2017)
Kicking off with Zend Expressive and Doctrine ORM (Sunshine PHP 2017)Kicking off with Zend Expressive and Doctrine ORM (Sunshine PHP 2017)
Kicking off with Zend Expressive and Doctrine ORM (Sunshine PHP 2017)
 
Burn down the silos! Helping dev and ops gel on high availability websites
Burn down the silos! Helping dev and ops gel on high availability websitesBurn down the silos! Helping dev and ops gel on high availability websites
Burn down the silos! Helping dev and ops gel on high availability websites
 
Rack
RackRack
Rack
 
Código Saudável => Programador Feliz - Rs on Rails 2010
Código Saudável => Programador Feliz - Rs on Rails 2010Código Saudável => Programador Feliz - Rs on Rails 2010
Código Saudável => Programador Feliz - Rs on Rails 2010
 
What's New In Laravel 5
What's New In Laravel 5What's New In Laravel 5
What's New In Laravel 5
 
Ruby For Startups
Ruby For StartupsRuby For Startups
Ruby For Startups
 
Rails antipatterns
Rails antipatternsRails antipatterns
Rails antipatterns
 

Mais de Ben Scofield

Mind Control: Psychology for the Web
Mind Control: Psychology for the WebMind Control: Psychology for the Web
Mind Control: Psychology for the WebBen Scofield
 
The State of NoSQL
The State of NoSQLThe State of NoSQL
The State of NoSQLBen Scofield
 
NoSQL @ CodeMash 2010
NoSQL @ CodeMash 2010NoSQL @ CodeMash 2010
NoSQL @ CodeMash 2010Ben Scofield
 
NoSQL: Death to Relational Databases(?)
NoSQL: Death to Relational Databases(?)NoSQL: Death to Relational Databases(?)
NoSQL: Death to Relational Databases(?)Ben Scofield
 
WindyCityRails - "Comics" Is Hard
WindyCityRails - "Comics" Is HardWindyCityRails - "Comics" Is Hard
WindyCityRails - "Comics" Is HardBen Scofield
 
"Comics" Is Hard: Alternative Databases
"Comics" Is Hard: Alternative Databases"Comics" Is Hard: Alternative Databases
"Comics" Is Hard: Alternative DatabasesBen Scofield
 
Mind Control on the Web
Mind Control on the WebMind Control on the Web
Mind Control on the WebBen Scofield
 
How the Geeks Inherited the Earth
How the Geeks Inherited the EarthHow the Geeks Inherited the Earth
How the Geeks Inherited the EarthBen Scofield
 
And the Greatest of These Is ... Space
And the Greatest of These Is ... SpaceAnd the Greatest of These Is ... Space
And the Greatest of These Is ... SpaceBen Scofield
 
"Comics" Is Hard: Domain Modeling Challenges
"Comics" Is Hard: Domain Modeling Challenges"Comics" Is Hard: Domain Modeling Challenges
"Comics" Is Hard: Domain Modeling ChallengesBen Scofield
 
Page Caching Resurrected
Page Caching ResurrectedPage Caching Resurrected
Page Caching ResurrectedBen Scofield
 
Page Caching Resurrected: A Fairy Tale
Page Caching Resurrected: A Fairy TalePage Caching Resurrected: A Fairy Tale
Page Caching Resurrected: A Fairy TaleBen Scofield
 
All I Need to Know I Learned by Writing My Own Web Framework
All I Need to Know I Learned by Writing My Own Web FrameworkAll I Need to Know I Learned by Writing My Own Web Framework
All I Need to Know I Learned by Writing My Own Web FrameworkBen Scofield
 
Advanced Restful Rails - Europe
Advanced Restful Rails - EuropeAdvanced Restful Rails - Europe
Advanced Restful Rails - EuropeBen Scofield
 
Resourceful Plugins
Resourceful PluginsResourceful Plugins
Resourceful PluginsBen Scofield
 
Advanced RESTful Rails
Advanced RESTful RailsAdvanced RESTful Rails
Advanced RESTful RailsBen Scofield
 

Mais de Ben Scofield (16)

Mind Control: Psychology for the Web
Mind Control: Psychology for the WebMind Control: Psychology for the Web
Mind Control: Psychology for the Web
 
The State of NoSQL
The State of NoSQLThe State of NoSQL
The State of NoSQL
 
NoSQL @ CodeMash 2010
NoSQL @ CodeMash 2010NoSQL @ CodeMash 2010
NoSQL @ CodeMash 2010
 
NoSQL: Death to Relational Databases(?)
NoSQL: Death to Relational Databases(?)NoSQL: Death to Relational Databases(?)
NoSQL: Death to Relational Databases(?)
 
WindyCityRails - "Comics" Is Hard
WindyCityRails - "Comics" Is HardWindyCityRails - "Comics" Is Hard
WindyCityRails - "Comics" Is Hard
 
"Comics" Is Hard: Alternative Databases
"Comics" Is Hard: Alternative Databases"Comics" Is Hard: Alternative Databases
"Comics" Is Hard: Alternative Databases
 
Mind Control on the Web
Mind Control on the WebMind Control on the Web
Mind Control on the Web
 
How the Geeks Inherited the Earth
How the Geeks Inherited the EarthHow the Geeks Inherited the Earth
How the Geeks Inherited the Earth
 
And the Greatest of These Is ... Space
And the Greatest of These Is ... SpaceAnd the Greatest of These Is ... Space
And the Greatest of These Is ... Space
 
"Comics" Is Hard: Domain Modeling Challenges
"Comics" Is Hard: Domain Modeling Challenges"Comics" Is Hard: Domain Modeling Challenges
"Comics" Is Hard: Domain Modeling Challenges
 
Page Caching Resurrected
Page Caching ResurrectedPage Caching Resurrected
Page Caching Resurrected
 
Page Caching Resurrected: A Fairy Tale
Page Caching Resurrected: A Fairy TalePage Caching Resurrected: A Fairy Tale
Page Caching Resurrected: A Fairy Tale
 
All I Need to Know I Learned by Writing My Own Web Framework
All I Need to Know I Learned by Writing My Own Web FrameworkAll I Need to Know I Learned by Writing My Own Web Framework
All I Need to Know I Learned by Writing My Own Web Framework
 
Advanced Restful Rails - Europe
Advanced Restful Rails - EuropeAdvanced Restful Rails - Europe
Advanced Restful Rails - Europe
 
Resourceful Plugins
Resourceful PluginsResourceful Plugins
Resourceful Plugins
 
Advanced RESTful Rails
Advanced RESTful RailsAdvanced RESTful Rails
Advanced RESTful Rails
 

Último

Generative Artificial Intelligence: How generative AI works.pdf
Generative Artificial Intelligence: How generative AI works.pdfGenerative Artificial Intelligence: How generative AI works.pdf
Generative Artificial Intelligence: How generative AI works.pdfIngrid Airi González
 
Why device, WIFI, and ISP insights are crucial to supporting remote Microsoft...
Why device, WIFI, and ISP insights are crucial to supporting remote Microsoft...Why device, WIFI, and ISP insights are crucial to supporting remote Microsoft...
Why device, WIFI, and ISP insights are crucial to supporting remote Microsoft...panagenda
 
Long journey of Ruby standard library at RubyConf AU 2024
Long journey of Ruby standard library at RubyConf AU 2024Long journey of Ruby standard library at RubyConf AU 2024
Long journey of Ruby standard library at RubyConf AU 2024Hiroshi SHIBATA
 
Moving Beyond Passwords: FIDO Paris Seminar.pdf
Moving Beyond Passwords: FIDO Paris Seminar.pdfMoving Beyond Passwords: FIDO Paris Seminar.pdf
Moving Beyond Passwords: FIDO Paris Seminar.pdfLoriGlavin3
 
Passkey Providers and Enabling Portability: FIDO Paris Seminar.pptx
Passkey Providers and Enabling Portability: FIDO Paris Seminar.pptxPasskey Providers and Enabling Portability: FIDO Paris Seminar.pptx
Passkey Providers and Enabling Portability: FIDO Paris Seminar.pptxLoriGlavin3
 
How to write a Business Continuity Plan
How to write a Business Continuity PlanHow to write a Business Continuity Plan
How to write a Business Continuity PlanDatabarracks
 
Digital Identity is Under Attack: FIDO Paris Seminar.pptx
Digital Identity is Under Attack: FIDO Paris Seminar.pptxDigital Identity is Under Attack: FIDO Paris Seminar.pptx
Digital Identity is Under Attack: FIDO Paris Seminar.pptxLoriGlavin3
 
[Webinar] SpiraTest - Setting New Standards in Quality Assurance
[Webinar] SpiraTest - Setting New Standards in Quality Assurance[Webinar] SpiraTest - Setting New Standards in Quality Assurance
[Webinar] SpiraTest - Setting New Standards in Quality AssuranceInflectra
 
Data governance with Unity Catalog Presentation
Data governance with Unity Catalog PresentationData governance with Unity Catalog Presentation
Data governance with Unity Catalog PresentationKnoldus Inc.
 
TrustArc Webinar - How to Build Consumer Trust Through Data Privacy
TrustArc Webinar - How to Build Consumer Trust Through Data PrivacyTrustArc Webinar - How to Build Consumer Trust Through Data Privacy
TrustArc Webinar - How to Build Consumer Trust Through Data PrivacyTrustArc
 
Sample pptx for embedding into website for demo
Sample pptx for embedding into website for demoSample pptx for embedding into website for demo
Sample pptx for embedding into website for demoHarshalMandlekar2
 
Connecting the Dots for Information Discovery.pdf
Connecting the Dots for Information Discovery.pdfConnecting the Dots for Information Discovery.pdf
Connecting the Dots for Information Discovery.pdfNeo4j
 
A Journey Into the Emotions of Software Developers
A Journey Into the Emotions of Software DevelopersA Journey Into the Emotions of Software Developers
A Journey Into the Emotions of Software DevelopersNicole Novielli
 
Testing tools and AI - ideas what to try with some tool examples
Testing tools and AI - ideas what to try with some tool examplesTesting tools and AI - ideas what to try with some tool examples
Testing tools and AI - ideas what to try with some tool examplesKari Kakkonen
 
(How to Program) Paul Deitel, Harvey Deitel-Java How to Program, Early Object...
(How to Program) Paul Deitel, Harvey Deitel-Java How to Program, Early Object...(How to Program) Paul Deitel, Harvey Deitel-Java How to Program, Early Object...
(How to Program) Paul Deitel, Harvey Deitel-Java How to Program, Early Object...AliaaTarek5
 
The State of Passkeys with FIDO Alliance.pptx
The State of Passkeys with FIDO Alliance.pptxThe State of Passkeys with FIDO Alliance.pptx
The State of Passkeys with FIDO Alliance.pptxLoriGlavin3
 
Genislab builds better products and faster go-to-market with Lean project man...
Genislab builds better products and faster go-to-market with Lean project man...Genislab builds better products and faster go-to-market with Lean project man...
Genislab builds better products and faster go-to-market with Lean project man...Farhan Tariq
 
How to Effectively Monitor SD-WAN and SASE Environments with ThousandEyes
How to Effectively Monitor SD-WAN and SASE Environments with ThousandEyesHow to Effectively Monitor SD-WAN and SASE Environments with ThousandEyes
How to Effectively Monitor SD-WAN and SASE Environments with ThousandEyesThousandEyes
 
From Family Reminiscence to Scholarly Archive .
From Family Reminiscence to Scholarly Archive .From Family Reminiscence to Scholarly Archive .
From Family Reminiscence to Scholarly Archive .Alan Dix
 
A Deep Dive on Passkeys: FIDO Paris Seminar.pptx
A Deep Dive on Passkeys: FIDO Paris Seminar.pptxA Deep Dive on Passkeys: FIDO Paris Seminar.pptx
A Deep Dive on Passkeys: FIDO Paris Seminar.pptxLoriGlavin3
 

Último (20)

Generative Artificial Intelligence: How generative AI works.pdf
Generative Artificial Intelligence: How generative AI works.pdfGenerative Artificial Intelligence: How generative AI works.pdf
Generative Artificial Intelligence: How generative AI works.pdf
 
Why device, WIFI, and ISP insights are crucial to supporting remote Microsoft...
Why device, WIFI, and ISP insights are crucial to supporting remote Microsoft...Why device, WIFI, and ISP insights are crucial to supporting remote Microsoft...
Why device, WIFI, and ISP insights are crucial to supporting remote Microsoft...
 
Long journey of Ruby standard library at RubyConf AU 2024
Long journey of Ruby standard library at RubyConf AU 2024Long journey of Ruby standard library at RubyConf AU 2024
Long journey of Ruby standard library at RubyConf AU 2024
 
Moving Beyond Passwords: FIDO Paris Seminar.pdf
Moving Beyond Passwords: FIDO Paris Seminar.pdfMoving Beyond Passwords: FIDO Paris Seminar.pdf
Moving Beyond Passwords: FIDO Paris Seminar.pdf
 
Passkey Providers and Enabling Portability: FIDO Paris Seminar.pptx
Passkey Providers and Enabling Portability: FIDO Paris Seminar.pptxPasskey Providers and Enabling Portability: FIDO Paris Seminar.pptx
Passkey Providers and Enabling Portability: FIDO Paris Seminar.pptx
 
How to write a Business Continuity Plan
How to write a Business Continuity PlanHow to write a Business Continuity Plan
How to write a Business Continuity Plan
 
Digital Identity is Under Attack: FIDO Paris Seminar.pptx
Digital Identity is Under Attack: FIDO Paris Seminar.pptxDigital Identity is Under Attack: FIDO Paris Seminar.pptx
Digital Identity is Under Attack: FIDO Paris Seminar.pptx
 
[Webinar] SpiraTest - Setting New Standards in Quality Assurance
[Webinar] SpiraTest - Setting New Standards in Quality Assurance[Webinar] SpiraTest - Setting New Standards in Quality Assurance
[Webinar] SpiraTest - Setting New Standards in Quality Assurance
 
Data governance with Unity Catalog Presentation
Data governance with Unity Catalog PresentationData governance with Unity Catalog Presentation
Data governance with Unity Catalog Presentation
 
TrustArc Webinar - How to Build Consumer Trust Through Data Privacy
TrustArc Webinar - How to Build Consumer Trust Through Data PrivacyTrustArc Webinar - How to Build Consumer Trust Through Data Privacy
TrustArc Webinar - How to Build Consumer Trust Through Data Privacy
 
Sample pptx for embedding into website for demo
Sample pptx for embedding into website for demoSample pptx for embedding into website for demo
Sample pptx for embedding into website for demo
 
Connecting the Dots for Information Discovery.pdf
Connecting the Dots for Information Discovery.pdfConnecting the Dots for Information Discovery.pdf
Connecting the Dots for Information Discovery.pdf
 
A Journey Into the Emotions of Software Developers
A Journey Into the Emotions of Software DevelopersA Journey Into the Emotions of Software Developers
A Journey Into the Emotions of Software Developers
 
Testing tools and AI - ideas what to try with some tool examples
Testing tools and AI - ideas what to try with some tool examplesTesting tools and AI - ideas what to try with some tool examples
Testing tools and AI - ideas what to try with some tool examples
 
(How to Program) Paul Deitel, Harvey Deitel-Java How to Program, Early Object...
(How to Program) Paul Deitel, Harvey Deitel-Java How to Program, Early Object...(How to Program) Paul Deitel, Harvey Deitel-Java How to Program, Early Object...
(How to Program) Paul Deitel, Harvey Deitel-Java How to Program, Early Object...
 
The State of Passkeys with FIDO Alliance.pptx
The State of Passkeys with FIDO Alliance.pptxThe State of Passkeys with FIDO Alliance.pptx
The State of Passkeys with FIDO Alliance.pptx
 
Genislab builds better products and faster go-to-market with Lean project man...
Genislab builds better products and faster go-to-market with Lean project man...Genislab builds better products and faster go-to-market with Lean project man...
Genislab builds better products and faster go-to-market with Lean project man...
 
How to Effectively Monitor SD-WAN and SASE Environments with ThousandEyes
How to Effectively Monitor SD-WAN and SASE Environments with ThousandEyesHow to Effectively Monitor SD-WAN and SASE Environments with ThousandEyes
How to Effectively Monitor SD-WAN and SASE Environments with ThousandEyes
 
From Family Reminiscence to Scholarly Archive .
From Family Reminiscence to Scholarly Archive .From Family Reminiscence to Scholarly Archive .
From Family Reminiscence to Scholarly Archive .
 
A Deep Dive on Passkeys: FIDO Paris Seminar.pptx
A Deep Dive on Passkeys: FIDO Paris Seminar.pptxA Deep Dive on Passkeys: FIDO Paris Seminar.pptx
A Deep Dive on Passkeys: FIDO Paris Seminar.pptx
 

Building Cloud Castles

  • 1. Building Cloud Castles and Finding Firmer Foundations Ben Scofield / @bscofield West End Ruby / 2 Feb 2011
  • 2. this presentation is a WORK IN PROGRESS flickr: natlockwood
  • 3.
  • 4.
  • 6. IS FAR AWAY flickr: sizemoresr
  • 9. $ ssh deploy@production.server.com Linux production.server #1 SMP Sat Dec 5 16:04:55 UTC 2009 i686 To access official Ubuntu documentation, please visit: http://help.ubuntu.com/ Last login: Fri Jan 28 16:33:49 2011 from local.host deploy@production:~$ cd /var/log/apache2 deploy@production:/var/log/apache2$ tail error.log [Sun Jan 23 06:25:02 2011] [notice] Apache/2.2.12 (Ubuntu) Phusion_Passenger/2.2.11... [Tue Jan 25 15:21:42 2011] [error] [client 118.129.166.97] Invalid URI in request G... [Fri Jan 28 12:01:50 2011] [error] [client 85.132.70.133] client sent HTTP/1.1 requ... [Sun Jan 30 06:25:06 2011] [notice] SIGUSR1 received. Doing graceful restart
  • 14. $ ssh deploy@production.server.com Linux production.server #1 SMP Sat Dec 5 16:04:55 UTC 2009 i686 To access official Ubuntu documentation, please visit: http://help.ubuntu.com/ Last login: Fri Jan 28 16:33:49 2011 from local.host deploy@production:~$ cd /var/www/app/current/ deploy@production:/var/www/app/current$ rails console production Loading production environment (Rails 3.0.3) >> Article.count => 112 >> Article.where(:problem => true).update_attributes(:problem => false)
  • 15. require 'test_helper' class ArticleTest < ActiveSupport::TestCase context 'Broken articles' do setup do 5.times.do { Factory :broken_article } end should 'be fixable' do assert_equal 5, Article.where(:problem => true).count Article.fix_problem_articles assert_equal 0, Article.where(:problem => true).count end end end class Article def self.fix_problem_articles where(:problem => true).update_attributes(:problem => false) end end
  • 16.
  • 20. class Comic < ActiveRecord::Base has_attached_file :cover, :styles => { :thumb => "80x120>", :medium => "300x450>" } end $ cd public/system $ ls /covers 10/ 12/ 53/ 81/ $ ls /covers/10/ medium/ original/ thumb/ $ ls /covers/10/medium batman-450.png
  • 21. class Comic < ActiveRecord::Base has_attached_file :cover, :storage => s3, :s3_credentials => { :access_key_id => ENV['s3_key'], :secret_access_key => ENV['s3_secret'] }, :bucket => 'comicsapp', :url => ":s3_path_url", :s3_headers => { 'Expires' => 1.year.from_now.httpdate }, :styles => { :thumb => "80x120>", :medium => "300x450>" } end
  • 22. class Comic < ActiveRecord::Base has_attached_file :cover, :storage => s3, :s3_credentials => { :access_key_id => ENV['s3_key'], :secret_access_key => ENV['s3_secret'] }, :bucket => 'comicsapp', :url => ":s3_path_url", :s3_headers => { 'Expires' => 1.year.from_now.httpdate }, :styles => { :thumb => "80x120>", :medium => "300x450>" } end
  • 23. module Watchtower class Application < Rails::Application # ... require 'openid/store/filesystem' config.middleware.use OmniAuth::Strategies::OpenID, OpenID::Store::Filesystem.new('/tmp') end end
  • 24. module Watchtower class Application < Rails::Application # ... require 'openid/store/filesystem' config.middleware.use OmniAuth::Strategies::OpenID, OpenID::Store::Filesystem.new('./tmp') end end
  • 25. module Watchtower class Application < Rails::Application # ... require 'openid/store/filesystem' config.middleware.use OmniAuth::Strategies::OpenID, OpenID::Store::Filesystem.new('./tmp') end end
  • 26.
  • 30. class Searcher def self.search(term) Article.where(['content ILIKE ?', "%#{term}%"]) end end
  • 31. class Searcher cattr_accessor :index def self.index @api ||= IndexTank::Client.new(ENV['INDEXTANK_API_URL']) @index = @api.indexes 'articles' end def self.search(term) raw = self.index.search(term, :function => 1) results = raw['results'].to_a article_ids = results.map {|result| result['docid'] } unsorted = Article.published.where(:id => article_ids) results.map { |result| unsorted.find {|u| u.id.to_i == result['docid'].to_i} }.compact end end
  • 32. class Searcher cattr_accessor :index def self.index @api ||= IndexTank::Client.new(ENV['INDEXTANK_API_URL']) @index = @api.indexes 'articles' end def self.search(term) results = begin raw = self.index.search(term, :function => 1) raw['results'].to_a rescue URI::InvalidURIError # An IndexTank error occurred search_by_sql(term)['results'] end article_ids = results.map {|result| result['docid'] } unsorted = Article.published.where(:id => article_ids) results.map { |result| unsorted.find {|u| u.id.to_i == result['docid'].to_i} }.compact end def self.search_by_sql(term) {'results' => Article.where(['content ILIKE ?', "%#{term}%"]). map {|a| {'docid' => a.id}}} end end
  • 33. class Searcher cattr_accessor :index def self.index @api ||= IndexTank::Client.new(ENV['INDEXTANK_API_URL']) @index = @api.indexes 'articles' end def self.search(term) results = begin raw = self.index.search(term, :function => 1) raw['results'].to_a rescue URI::InvalidURIError # An IndexTank error occurred search_by_sql(term)['results'] end article_ids = results.map {|result| result['docid'] } unsorted = Article.published.where(:id => article_ids) results.map { |result| unsorted.find {|u| u.id.to_i == result['docid'].to_i} }.compact end def self.search_by_sql(term) {'results' => Article.where(['content ILIKE ?', "%#{term}%"]). map {|a| {'docid' => a.id}}} end end
  • 34.
  • 38. class BooksController < ApplicationController caches_page :index def index @books = Book.all end end
  • 39. module CardCatalog class Application < Rails::Application # ... ActionController::Base.cache_store = :mem_cache_store, "memcache_host" end end class BooksController < ApplicationController caches_action :index def index @books = Book.all end end
  • 40. module CardCatalog class Application < Rails::Application # ... ActionController::Base.cache_store = :mem_cache_store, "memcache_host" end end class BooksController < ApplicationController caches_action :index def index @books = Book.all end end
  • 41. class BooksController < ApplicationController def index response.headers['Cache-Control'] = 'public, max-age=300' @books = Book.all end end
  • 42. class BooksController < ApplicationController def index response.headers['Cache-Control'] = 'public, max-age=300' @books = Book.all end end
  • 43.
  • 45. IS MADE OF TINY PARTS flickr: dev07
  • 47. App
  • 48. App App App App App App App App App App App App App App App App App
  • 49. $ heroku create Creating empty-sword-187....... done http://empty-sword-187.heroku.com/ | git@heroku.com:empty-sword-187.git Git remote heroku added $ git push heroku master Counting objects: 5, done. Delta compression using up to 4 threads. Compressing objects: 100% (3/3), done. Writing objects: 100% (3/3), 285 bytes, done. Total 3 (delta 2), reused 0 (delta 0) -----> Heroku receiving push -----> Rails app detected -----> Detected use of caches_page Installing caches_page_via_http plugin... done -----> Detected Rails is not set to serve static_assets Installing rails3_serve_static_assets... done -----> Gemfile detected, running Bundler version 1.0.7 Unresolved dependencies detected; Installing... Using --without development:test Fetching source index for http://rubygems.org/ ...
  • 50. App App App App App App App App App App App App App App App App App
  • 51. App App App App App App App App App App App App App App App App App
  • 53.
  • 54.
  • 60. http://spkr8.com/t/5491 Thanks! http://benscofield.com http://heroku.com Ben Scofield / @bscofield West End Ruby / 2 Feb 2011