Frozen Rails Slides

130
Rails 3: With a Vengeance Friday, May 7, 2010

description

 

Transcript of Frozen Rails Slides

Page 1: Frozen Rails Slides

Rails 3: With a Vengeance

Friday, May 7, 2010

Page 2: Frozen Rails Slides

Friday, May 7, 2010

Page 3: Frozen Rails Slides

Friday, May 7, 2010

Page 4: Frozen Rails Slides

Rails 3: With a Vengeance

Friday, May 7, 2010

Page 5: Frozen Rails Slides

Friday, May 7, 2010

Page 6: Frozen Rails Slides

New talk title:

Friday, May 7, 2010

Page 7: Frozen Rails Slides

Rails 3: Tasty Burgers

DHH introduced Rails 3 at last RailsConf. He likes whoppers. I actually hate burger king entirely. I’m not sure how anybody can eat there. But that’s OK, because I can make my own rails burger the way I like

Friday, May 7, 2010

Page 8: Frozen Rails Slides

Friday, May 7, 2010

Page 9: Frozen Rails Slides

Skill

Friday, May 7, 2010

Page 10: Frozen Rails Slides

A lot like Rails 2.3We tried really hard to not break any 2.x APIs unless it was really deemed necessary or a big win.

Friday, May 7, 2010

Page 11: Frozen Rails Slides

But not really...

Only surface level stuff is the same. A lot under the hood has changed and there are a lot more features. I obviously can’t cover everything in depth in an hour, so I’m going to gloss over a lot and you can all look into details later.

Friday, May 7, 2010

Page 12: Frozen Rails Slides

Quick Refresher

What hasn’t changed

Friday, May 7, 2010

Page 13: Frozen Rails Slides

MVC

Friday, May 7, 2010

Page 14: Frozen Rails Slides

REST

Friday, May 7, 2010

Page 15: Frozen Rails Slides

Resources

The router / controller / ActiveRecord model is still optimized for organizing your application in terms of Resources

Friday, May 7, 2010

Page 16: Frozen Rails Slides

Controllers

Friday, May 7, 2010

Page 17: Frozen Rails Slides

ActiveRecord

Some changes (Arel, query api - Not going to really cover this)General ideas are the sameassociationsActiveRecord pattern

Friday, May 7, 2010

Page 18: Frozen Rails Slides

Migrations

Friday, May 7, 2010

Page 19: Frozen Rails Slides

So, what did change?

Friday, May 7, 2010

Page 20: Frozen Rails Slides

File structure

There were some tweaks to the default File structure, mostly in the config directory.

This is what you get when you generate a new Rails 3 app

One big thing about Rails 3 is that the app file structure is not sacred, it’s just a convention, not obligation

Friday, May 7, 2010

Page 21: Frozen Rails Slides

config.ru

# This file is used by Rack-based servers# to start the application.

require ::File.expand_path( '../config/environment', __FILE__)run AwesomeBlog::Application

Rails 3 is pure Rack. When you start an app, it evaluates config.ru, which is a rack convention. If this file is there, all rack compatible rack servers know what to do.Could put middleware or whatever here (could even put a full Rails app in this one file).

Who doesn’t know what Rack is?

Could put middleware or whatever here (could even put a full Rails app in this one file).

Friday, May 7, 2010

Page 22: Frozen Rails Slides

config.ru

# This file is used by Rack-based servers# to start the application.

require ::File.expand_path( '../config/environment', __FILE__)run AwesomeBlog::Application

That’s specifically the line that rack is looking for.

The constant is a Rack app that encapsulates everything related to the entire rails application that we’re building

Friday, May 7, 2010

Page 23: Frozen Rails Slides

config/application.rb

require File.expand_path('../boot', __FILE__)require 'rails/all'

Bundler.require(:default, Rails.env) if defined?(Bundler)

module AwesomeBlog class Application < Rails::Application config.encoding = "utf-8" config.filter_parameters += [:password] endend

This is the file that defines the application object from the last slide.

Let’s look at some key components

Friday, May 7, 2010

Page 24: Frozen Rails Slides

config/application.rb

require File.expand_path('../boot', __FILE__)require 'rails/all'

Bundler.require(:default, Rails.env) if defined?(Bundler)

module AwesomeBlog class Application < Rails::Application config.encoding = "utf-8" config.filter_parameters += [:password] endend

Friday, May 7, 2010

Page 25: Frozen Rails Slides

config/application.rb

require File.expand_path('../boot', __FILE__)require 'rails/all'

Bundler.require(:default, Rails.env) if defined?(Bundler)

module AwesomeBlog class Application < Rails::Application config.encoding = "utf-8" config.filter_parameters += [:password] endend

Friday, May 7, 2010

Page 26: Frozen Rails Slides

config/application.rb

require File.expand_path('../boot', __FILE__)require 'rails/all'

Bundler.require(:default, Rails.env) if defined?(Bundler)

module AwesomeBlog class Application < Rails::Application config.encoding = "utf-8" config.filter_parameters += [:password] endend

A Rack App!

Friday, May 7, 2010

Page 27: Frozen Rails Slides

Application ObjectOne day, we’ll have many application objects in one process.

Ruby summer of code project to get this done.

Contains all application specific information. Takes the “in the sky” constants / globals that were needed for a rails 2.3 application and encapsulates them.

Routes, Config , Middleware, Initializers

Friday, May 7, 2010

Page 28: Frozen Rails Slides

config/application.rb

require File.expand_path('../boot', __FILE__)require 'rails/all'

Bundler.require(:default, Rails.env) if defined?(Bundler)

module AwesomeBlog class Application < Rails::Application config.encoding = "utf-8" config.filter_parameters += [:password] endend

Friday, May 7, 2010

Page 29: Frozen Rails Slides

config/boot.rb

require 'rubygems'

# Set up gems listed in the Gemfile.gemfile = File.expand_path('../../Gemfile', __FILE__)begin ENV['BUNDLE_GEMFILE'] = gemfile require 'bundler' Bundler.setuprescue Bundler::GemNotFound => e STDERR.puts e.message STDERR.puts "Try running `bundle install`." exit!end if File.exist?(gemfile)

Friday, May 7, 2010

Page 30: Frozen Rails Slides

config/boot.rb

require 'bundler' Bundler.setup

Friday, May 7, 2010

Page 31: Frozen Rails Slides

Bundler is a tool we built to manage gem dependencies. As it turns out, as rails applications grow, dependencies become painful to deal with.

Many years in the making. Many iterations. It grew out of projects that had many complicate dependencies and it was basically impossible to deal with it all by hand

Friday, May 7, 2010

Page 32: Frozen Rails Slides

Gemfile

source 'http://rubygems.org'

gem 'rails', '3.0.0.beta3'gem 'sqlite3-ruby', :require => 'sqlite3'

It all starts here. This file is located at the root of the application.

All gem dependencies will be listed here. So far our app is small, so we only depend on rails itself (note the version is the one that we are currently working with) and also we have a dependency on sqlite since that’s the database that we’re using.

Friday, May 7, 2010

Page 33: Frozen Rails Slides

$ bundle install

Step two is to run the “bundle install” command.

* Checks gems already on system* Downloads what is missing

Friday, May 7, 2010

Page 34: Frozen Rails Slides

No step 3

Friday, May 7, 2010

Page 35: Frozen Rails Slides

Wins:

What are the major wins that we get from using bundler?

Friday, May 7, 2010

Page 36: Frozen Rails Slides

Easy to update dependencies

Dependencies in applications will evolve a lot throughout the life of an application

Ruby is maturing. There are a LOT of gems out there that you’ll be wanting to use. Bundler makes doing it easy.

Friday, May 7, 2010

Page 37: Frozen Rails Slides

Gemfile

source 'http://rubygems.org'

gem 'rails', '3.0.0.beta3'gem 'sqlite3-ruby', :require => 'sqlite3'

What happens if you want to update your application to the latest version of rails?

Friday, May 7, 2010

Page 38: Frozen Rails Slides

Gemfile

source 'http://rubygems.org'

gem 'rails', '3.0.0'gem 'sqlite3-ruby', :require => 'sqlite3'

You’re done. All child dependencies will be handled for you.

The same goes if you want to roll back to a previous version. Just change the Gemfile

Friday, May 7, 2010

Page 39: Frozen Rails Slides

Isolation

You know all your dependencies are accounted for and you won’t be surprised when you deploy.

Bundler does not let you use a gem if it is not listed in the Gemfile. So, you can’t accidentally forget to install a gem on the server when you deploy.

Friday, May 7, 2010

Page 40: Frozen Rails Slides

Sharing

Your Gemfile accounts for all gems. You share your app with other developers. They run `bundle install` and they’re good to go.

Bundler tracks all child dependencies as well as top level app dependencies

Come back to a project 3 months later. What version of UUID was I using? (yeah, that broke deploys) Nokogiri?

Designers

Friday, May 7, 2010

Page 41: Frozen Rails Slides

No conflicts

Anybody ever hit the “can’t activate gem foo, already activated foo at another version error”?

Friday, May 7, 2010

Page 42: Frozen Rails Slides

Without Bundler

Friday, May 7, 2010

Page 43: Frozen Rails Slides

Friday, May 7, 2010

Page 44: Frozen Rails Slides

The Solution!Step 1) VendorRails + Rack

Friday, May 7, 2010

Page 45: Frozen Rails Slides

The Solution!Step 1) Modify the Rails source

Friday, May 7, 2010

Page 46: Frozen Rails Slides

While we’re on the topic of picard....

Friday, May 7, 2010

Page 47: Frozen Rails Slides

Friday, May 7, 2010

Page 48: Frozen Rails Slides

Friday, May 7, 2010

Page 49: Frozen Rails Slides

http://gembundler.com

Friday, May 7, 2010

Page 50: Frozen Rails Slides

Unobtrusive Javascript

Friday, May 7, 2010

Page 51: Frozen Rails Slides

Helpers do not generate JS anymore

In Rails 2.3, all view helpers that required JS would spew out a whole bunch of Prototype specific javascript inline.

This is bad.

Friday, May 7, 2010

Page 52: Frozen Rails Slides

<%= link_to "hello", hello_path, :remote => true %>

<a href="/hello" data-remote="true">hello</a>

Rails 3.0 helpers will now only output HTML. All required information will be added to the HTML tags via data-*

Custom data-* attributes were added in HTML5, but they work in all old and busted (IE 6).

Friday, May 7, 2010

Page 53: Frozen Rails Slides

public/javascripts/rails.js

$(document.body).observe("click", function(event) { // .... var element = event.findElement( "a[data-remote]"); if (element) { handleRemote(element); event.stop(); return true; } // ....});

The javascript that handles all the data-* attributes is in rails.js

Friday, May 7, 2010

Page 54: Frozen Rails Slides

Using jQuery?http://github.com/rails/jquery-ujs

Rails will ship with the Prototype driver, but you can just drop in the jquery driver at public/javascripts/rails.js

We maintain it and it’s on github at the URL.

You can finally easily completely rid your Rails app of all Prototype

Who uses jQuery?

Friday, May 7, 2010

Page 55: Frozen Rails Slides

public/javascripts/rails.js

$('a[data-remote],input[data-remote]').live('click', function (e) { $(this).callRemote(); e.preventDefault();});

This is the code that is in the jQuery rails.js driver

Friday, May 7, 2010

Page 56: Frozen Rails Slides

Pick your JS framework when you write JS, not when you generate

your Rails app.

Other wins too.

Friday, May 7, 2010

Page 57: Frozen Rails Slides

Customizable

Just JS events being triggered. You can easily replace specific default behavior.

There are events that are triggered that you can bind to. ajax:before, ajax:after, etc...

Friday, May 7, 2010

Page 58: Frozen Rails Slides

Everything is a “plugin”

Rails is like a toolkit to build rails. Think about it.

Friday, May 7, 2010

Page 59: Frozen Rails Slides

ActiveRecord?

Friday, May 7, 2010

Page 60: Frozen Rails Slides

Plugin!

Friday, May 7, 2010

Page 61: Frozen Rails Slides

ActionController?

Friday, May 7, 2010

Page 62: Frozen Rails Slides

Plugin!

Friday, May 7, 2010

Page 63: Frozen Rails Slides

TestUnit

Friday, May 7, 2010

Page 64: Frozen Rails Slides

Plugin!

Friday, May 7, 2010

Page 65: Frozen Rails Slides

ActiveResource?

Friday, May 7, 2010

Page 66: Frozen Rails Slides

...

Friday, May 7, 2010

Page 67: Frozen Rails Slides

You can build plugins too.

Friday, May 7, 2010

Page 68: Frozen Rails Slides

Railtie

Railtie is a class that integrates a library with rails.

Look at how ActiveRecord does it. Look at how everything else does it.

When we built Rails 3, we didn’t add hook points for all these other libraries after the fact. We made hooks for ourselves to use, which has the nice side effect of other libraries being able to use them.

RSpec, DataMapper, HAML, etc... all use the same hooks that Rails uses

Friday, May 7, 2010

Page 69: Frozen Rails Slides

Configuration

Rake Tasks

Generator Overrides

Initializers

You actually rarely need a Railtie to integrate with Rails. Railties are for a few specific things.

Friday, May 7, 2010

Page 70: Frozen Rails Slides

Configuration

Rake Tasks

Generator Overrides

Initializers

Provide configuration opportunities to Rails applications

config.active_record in config/application.rb

If you want to provide default config values, you need a Railtie

Friday, May 7, 2010

Page 71: Frozen Rails Slides

Configuration

Rake Tasks

Generator Overrides

Initializers

All ActiveRecord’s rake tasks are in it’s Railtie, which means that when AR isn’t required, the rake tasks go away.

If you want to easily provide Rake tasks to a Rails app, you need a Railtie

Friday, May 7, 2010

Page 72: Frozen Rails Slides

Configuration

Rake Tasks

Generator Overrides

Initializers

If you want to hook in to:* rails generate controller Foo* rails generate model Foo

you need a Railtie. RSpec uses this to provide the same level of integration that TestUnit gets

Friday, May 7, 2010

Page 73: Frozen Rails Slides

Configuration

Rake Tasks

Generator Overrides

Initializers

If you want to hook into the initialization cycle of a Rails application, you’ll need a Railtie

Friday, May 7, 2010

Page 74: Frozen Rails Slides

HTML escaping

Friday, May 7, 2010

Page 75: Frozen Rails Slides

<p> <%= h @comment.title %> (by <%= h @comment.author.username %>)</p>

<%= simple_format h(@comment.body) %>

Rails 2.3

This is what a view might have looked in Rails 2.3

Friday, May 7, 2010

Page 76: Frozen Rails Slides

Rails 2.3

If you forget one spot, you’re site gets hacked

... I’m sure everybody here has missed at least 1 spot to escape

<p> <%= h @comment.title %> (by <%= h @comment.author.username %>)</p>

<%= simple_format h(@comment.body) %>

Friday, May 7, 2010

Page 77: Frozen Rails Slides

<p> <%= @comment.title %> (by <%= @comment.author.username %>)</p>

<%= simple_format(@comment.body) %>

Rails 3.0

New! No more pesky h()

Now, you don’t have to worry about escaping anything anymore. Rails does it for you.

Friday, May 7, 2010

Page 78: Frozen Rails Slides

XSS Protection by default

Friday, May 7, 2010

Page 79: Frozen Rails Slides

def escaped(str) strend

def not_escaped(str) str.html_safeend

app/helpers/application_helper.rb

Simply returning a string a string will be escaped.

If you don’t want a string to be escaped, you just mark it as html_safe and it won’t be modified by Rails.

Friday, May 7, 2010

Page 80: Frozen Rails Slides

Block helpers

Friday, May 7, 2010

Page 81: Frozen Rails Slides

<% form_for @post do |f| %> <%= f.text_field :title %><% end %>

<% box do %> <p>Hello World!</p><% end %>

Rails 2.3

This is what block helpers might look like in 2.3

Friday, May 7, 2010

Page 82: Frozen Rails Slides

def box(&block) content = "<div class='box'>" << capture(&block) << "</div>"

if block_called_from_erb? concat(content) else content endend

Rails 2.3

This is the implementation.

I’m not even going to really talk about this, Just notice that it’s crazy

Most of this is needed to make the helper work in and outside of ERB.

Friday, May 7, 2010

Page 83: Frozen Rails Slides

<% form_for @post do |f| %> <%= f.text_field :title %><% end %>

<% box do %> <p>Hello World!</p><% end %>

Rails 2.3

Outputs method’s return value

Ignores method’s return value

The reason why you need the craziness is because of how ERB works.

Friday, May 7, 2010

Page 84: Frozen Rails Slides

Rails 3.0

<%= form_for @post do |f| %> <%= f.text_field :title %><% end %>

<%= box do %> <p>Hello World!</p><% end %>

In Rails 3.0 block helpers have become consistent with ERB.

form_for will actually output content to the view, aka, the form tags. So, now you use %=

The same goes for custom block helpers

Friday, May 7, 2010

Page 85: Frozen Rails Slides

def box(&block) "<div class='box'>" \ "#{capture(&block)}" \ "</div>"end

Rails 3.0

All you do now for block helpers is return the string that you want outputted to the view.

Block helpers aren’t special anymore. They work the same inside and outside ERB

Friday, May 7, 2010

Page 86: Frozen Rails Slides

def box(&block) "<div class='box'>" \ "#{capture(&block)}" \ "</div>".html_safeend

Rails 3.0

Friday, May 7, 2010

Page 87: Frozen Rails Slides

Router

Massive API overhaul.

Friday, May 7, 2010

Page 88: Frozen Rails Slides

Old DSL still works, just deprecated.

You don’t have to rush to update your route file to get on Rails 3.0

The old DSL will probably stop working in Rails 3.1 or 3.2, but you have time before that happens.

Friday, May 7, 2010

Page 89: Frozen Rails Slides

Matching

map.connect "/posts", :controller => :posts, :action => :index

Friday, May 7, 2010

Page 90: Frozen Rails Slides

Matching

map.connect "/posts", :controller => :posts, :action => :index

match "/posts" => "posts#index"

posts#index is short hand for specifying the controller and action. It’s just much easier to write.

Friday, May 7, 2010

Page 91: Frozen Rails Slides

Optional Segments

match "/posts(/:page)" => "posts#index"

Will match a request to /posts, and a request to /posts/5

if page is not there, params[:page] will be nil, if it is present in the request, the param will be set.

Friday, May 7, 2010

Page 92: Frozen Rails Slides

Defaults

match "/posts(/:page)" => "posts#index",:defaults => { :page => 1 }

You can specify default parameters. If the page segment is not in the Request, params[:page] will be set to 1

Friday, May 7, 2010

Page 93: Frozen Rails Slides

Named Routes

match "/posts(/:page)" => "posts#index",:defaults => { :page => 1 }, :as => "posts"

The same helpers are available. * posts_path * posts_url

Friday, May 7, 2010

Page 94: Frozen Rails Slides

ScopesLet’s you set the same options to a group of routes.

Almost anything can be scoped.

Friday, May 7, 2010

Page 95: Frozen Rails Slides

Path Scopes

scope :path => "/admin" do match "/posts" => "posts#index" match "/users" => "users#index"end

/admin/posts

Friday, May 7, 2010

Page 96: Frozen Rails Slides

Path Scopes

scope "/admin" do match "/posts" => "posts#index" match "/users" => "users#index"end

Paths are probably the most common item to scope on, so it’s the default.

Friday, May 7, 2010

Page 97: Frozen Rails Slides

Module Scope

scope :module => "admin" do match "/posts" => "posts#index" match "/users" => "users#index"end

Routes to Admin::PostsController / Admin::UsersController

Friday, May 7, 2010

Page 98: Frozen Rails Slides

Both

namespace "admin" do match "/posts" => "posts#index" match "/users" => "users#index"end

Friday, May 7, 2010

Page 99: Frozen Rails Slides

HTTP Methods

Routing by specific HTTP methods

Friday, May 7, 2010

Page 100: Frozen Rails Slides

POST Request

match "/posts" => "posts#index",:via => "delete"

Any HTTP method can be used here

Friday, May 7, 2010

Page 101: Frozen Rails Slides

Scoping

scope "/posts" do controller :posts do get "/" => :index endend

Friday, May 7, 2010

Page 102: Frozen Rails Slides

Scoping

scope "/posts" do controller :posts do get "/" => :index endend

Yet another shorthand

Friday, May 7, 2010

Page 103: Frozen Rails Slides

Scoping

scope "/posts" do controller :posts do get "/" => :index endend

get URL

Friday, May 7, 2010

Page 104: Frozen Rails Slides

Scoping

get "/posts" => "posts#index"

get / post / post / delete methods are shorthands for :via => “get”

This method can be used anywhere in the routes file

Friday, May 7, 2010

Page 105: Frozen Rails Slides

Constraints

Friday, May 7, 2010

Page 106: Frozen Rails Slides

Regexp Constraint

get "/:id" => "posts#index", :id => /\d+/

Friday, May 7, 2010

Page 107: Frozen Rails Slides

Regexp Constraint

get "/posts" => "posts#mobile", :user_agent => /iPhone/

Constraints on arbitrary methods of Request object.

Will use the “user_agent” method on the Request object

Friday, May 7, 2010

Page 108: Frozen Rails Slides

Defaults + Constraints

Friday, May 7, 2010

Page 109: Frozen Rails Slides

Constraint + Default

get "/posts" => "posts#mobile", :user_agent => /iPhone/, :mobile => true

How does it not get confused?

If you use a Regex on as the value of the hash, it is a constraint, otherwise, it is a default

Friday, May 7, 2010

Page 110: Frozen Rails Slides

Object Constraints

class DubDubConstraint def self.matches?(request) request.host =~ /^(www.)/ # true / false endend

get "/" => "posts#index", :constraints => DubDubConstraint

Any object that responds to matches? can be a router constraint

Friday, May 7, 2010

Page 111: Frozen Rails Slides

Rack EverywhereCan be used without AC and only Rack. This is the minimum requirement for “Rails”

Everything that used to be in ActionController that made sense without ActionController. There is a lot, all rack middleware.* Session* Cookies* Router* Flash* etc..

Friday, May 7, 2010

Page 112: Frozen Rails Slides

Rack

PostsController.action(:index)

You can get a rack app for any controller action

Friday, May 7, 2010

Page 113: Frozen Rails Slides

Routing + Rack

get "/posts" => PostsController.action(:index)

This is what happens behind the scenes in my previous examples

Friday, May 7, 2010

Page 114: Frozen Rails Slides

Routing + Rack

get "/posts" => PostsController.action(:index)

Just a Rack app

Friday, May 7, 2010

Page 115: Frozen Rails Slides

Routing + Rack

get "/posts" => PostsController.action(:index)

Any Rack app

Friday, May 7, 2010

Page 116: Frozen Rails Slides

Routing + Rack

get "/posts" => MySinatraPostsApp

Sinatra app

Friday, May 7, 2010

Page 117: Frozen Rails Slides

Routing + Rack

get "/posts" => MyCampingPostsApp

Camping app

Friday, May 7, 2010

Page 118: Frozen Rails Slides

Routing + Rack

get "/posts" => MyRamazePostsApp

Ramaze app

Friday, May 7, 2010

Page 119: Frozen Rails Slides

Routing + Rack

get "/posts" => MyConstantThatRespondsToCall

Any Rack app

The possibilities are endless

Friday, May 7, 2010

Page 120: Frozen Rails Slides

ActionMailer

Friday, May 7, 2010

Page 121: Frozen Rails Slides

It’s newMassive API overhaul

I’m out of time

Friday, May 7, 2010

Page 122: Frozen Rails Slides

A lot of detailed stuff about what’s new with Rails 3.

* Articles* Blog posts* Screencasts* QAs* Aggregation of other Rails 3 info

Friday, May 7, 2010

Page 123: Frozen Rails Slides

http://www.railsdispatch.com/posts/actionmailer

Friday, May 7, 2010

Page 124: Frozen Rails Slides

UpgradingI want to mention upgrading really quick

Rails 1.x apps -> 3.0 is really easy (15 minutes)

Rails 2.x is a little bit harder

Friday, May 7, 2010

Page 125: Frozen Rails Slides

Step 1) Generate a new appLook at the generated file structure

Friday, May 7, 2010

Page 126: Frozen Rails Slides

config.ru

Friday, May 7, 2010

Page 127: Frozen Rails Slides

config/*

Friday, May 7, 2010

Page 128: Frozen Rails Slides

http://github.com/rails/rails_upgrade* Mail API* Known broken plugins* Helpers

* But all this stuff still will work in rails 3, just will spew out deprecation notices.

It will try to find all deprecated but still working methods as well* Router* AR API* config/** Deprecated constants

Friday, May 7, 2010

Page 129: Frozen Rails Slides

Questions?We have 2 other Rails core people here, they can answer

questions too.

Friday, May 7, 2010

Page 130: Frozen Rails Slides

Questions?

Email: [email protected] (I accept fan mail)

Twitter : @carllerche (I’m interesting. Really!)

I will tweet the slides. So make sure you are following me.

Friday, May 7, 2010