Nordic Ruby 2011

149
Saturday, June 18, 2011

Transcript of Nordic Ruby 2011

Page 1: Nordic Ruby 2011

Saturday, June 18, 2011

Page 2: Nordic Ruby 2011

Saturday, June 18, 2011

Page 3: Nordic Ruby 2011

bitcoin==

con-currency?

Saturday, June 18, 2011

Page 4: Nordic Ruby 2011

Saturday, June 18, 2011

Page 5: Nordic Ruby 2011

Me!José! ♥Saturday, June 18, 2011

Page 6: Nordic Ruby 2011

WWFMD?Saturday, June 18, 2011

Page 7: Nordic Ruby 2011

Public Service Announcements

Saturday, June 18, 2011

Page 8: Nordic Ruby 2011

SIGSEGV is bad

Saturday, June 18, 2011

Page 9: Nordic Ruby 2011

neversaydie

Saturday, June 18, 2011

Page 10: Nordic Ruby 2011

begin # something dangerousrescue NeverSayDie # fix memoryend

Saturday, June 18, 2011

Page 11: Nordic Ruby 2011

"Not working in production mode"

Saturday, June 18, 2011

Page 12: Nordic Ruby 2011

DO NOT USE THIS SOFTWARE

Saturday, June 18, 2011

Page 13: Nordic Ruby 2011

ruby -w

Saturday, June 18, 2011

Page 14: Nordic Ruby 2011

def hello x = Object.new # warning 10 + 10end

Saturday, June 18, 2011

Page 15: Nordic Ruby 2011

warning: assigned but unused variable - x

Saturday, June 18, 2011

Page 16: Nordic Ruby 2011

How deprecation notices SHOULD be

written

Saturday, June 18, 2011

Page 17: Nordic Ruby 2011

class Foo def deprecated if $VERBOSE warn "hey bro, don't call this" end 10 endend

Saturday, June 18, 2011

Page 18: Nordic Ruby 2011

RSpec Problem

Saturday, June 18, 2011

Page 19: Nordic Ruby 2011

warning: useless use of == in void context

Saturday, June 18, 2011

Page 20: Nordic Ruby 2011

it "passes" do 10.should == 10 11.should == 11end

Saturday, June 18, 2011

Page 21: Nordic Ruby 2011

def check(thing)end

def deprecated check 10.should == 10 check 11.should == 10end

Saturday, June 18, 2011

Page 22: Nordic Ruby 2011

@tenderlove

Saturday, June 18, 2011

Page 23: Nordic Ruby 2011

AT&T, AT&T logo and all AT&T related marks are trademarks of AT&T Intellectual Property and/or AT&T affiliated companies.

Saturday, June 18, 2011

Page 24: Nordic Ruby 2011

Ruby Core Team

Saturday, June 18, 2011

Page 25: Nordic Ruby 2011

Rails Core Team

Saturday, June 18, 2011

Page 26: Nordic Ruby 2011

Saturday, June 18, 2011

Page 27: Nordic Ruby 2011

White Guy

Saturday, June 18, 2011

Page 28: Nordic Ruby 2011

Story about Geethika and pinochle, java, etc

Saturday, June 18, 2011

Page 29: Nordic Ruby 2011

It's TYRA!

Story about Geethika and pinochle, java, etc

Saturday, June 18, 2011

Page 30: Nordic Ruby 2011

<3 <3 <3

Saturday, June 18, 2011

Page 31: Nordic Ruby 2011

"Congratulations, you just made us not consider Rails

for anything anymore despite our coders liking it."

Saturday, June 18, 2011

Page 32: Nordic Ruby 2011

SWEDEN!

Saturday, June 18, 2011

Page 33: Nordic Ruby 2011

IKEA!Saturday, June 18, 2011

Page 34: Nordic Ruby 2011

MEATBALLS!Saturday, June 18, 2011

Page 35: Nordic Ruby 2011

SWEDISH FISH!Saturday, June 18, 2011

Page 36: Nordic Ruby 2011

Made in Canada!Saturday, June 18, 2011

Page 37: Nordic Ruby 2011

Made in Canada!Saturday, June 18, 2011

Page 38: Nordic Ruby 2011

How do you catch such tiny fish?

Saturday, June 18, 2011

Page 39: Nordic Ruby 2011

Will you share your Swedish FishFishing spot?

Saturday, June 18, 2011

Page 40: Nordic Ruby 2011

Mountain Dewand my

Trail of Tears

Talk about Rails,Talk about work projects

Saturday, June 18, 2011

Page 41: Nordic Ruby 2011

Legacy Code

Saturday, June 18, 2011

Page 42: Nordic Ruby 2011

What isLegacy Code?

Saturday, June 18, 2011

Page 43: Nordic Ruby 2011

Untested

Saturday, June 18, 2011

Page 44: Nordic Ruby 2011

Old

Saturday, June 18, 2011

Page 45: Nordic Ruby 2011

Not Understood

Maintainers are gone

Saturday, June 18, 2011

Page 46: Nordic Ruby 2011

Importance of Legacy Code

Saturday, June 18, 2011

Page 47: Nordic Ruby 2011

Old code containsKnowledge

Saturday, June 18, 2011

Page 48: Nordic Ruby 2011

Solves Today's Problems

Saturday, June 18, 2011

Page 49: Nordic Ruby 2011

All Legacy Code isNot Equal

Saturday, June 18, 2011

Page 50: Nordic Ruby 2011

TenderloveTear Formula

Saturday, June 18, 2011

Page 51: Nordic Ruby 2011

Code BurdenTears

Time

Saturday, June 18, 2011

Page 52: Nordic Ruby 2011

Tears cried at time T

Saturday, June 18, 2011

Page 53: Nordic Ruby 2011

Volume at time T

Saturday, June 18, 2011

Page 54: Nordic Ruby 2011

Last Week

Saturday, June 18, 2011

Page 55: Nordic Ruby 2011

I LOVE hackingold code

Saturday, June 18, 2011

Page 56: Nordic Ruby 2011

Techniques

Saturday, June 18, 2011

Page 57: Nordic Ruby 2011

Universal

Techniques

Extending

Testing

Saturday, June 18, 2011

Page 58: Nordic Ruby 2011

Universal

Saturday, June 18, 2011

Page 59: Nordic Ruby 2011

Liskov substitution principle

Saturday, June 18, 2011

Page 60: Nordic Ruby 2011

A subclass can be used in place of it's

superclass

Saturday, June 18, 2011

Page 61: Nordic Ruby 2011

Object

Animal

Person

Saturday, June 18, 2011

Page 62: Nordic Ruby 2011

class Animalend

class Person < Animalend

Saturday, June 18, 2011

Page 63: Nordic Ruby 2011

def leg_count(animal) animal.legsend

def account_number(person) person.account_numberend

Saturday, June 18, 2011

Page 64: Nordic Ruby 2011

Single Responsibility Principal

Saturday, June 18, 2011

Page 65: Nordic Ruby 2011

Each class has one and only one responsibility

Saturday, June 18, 2011

Page 66: Nordic Ruby 2011

Each class has one reason to change

Saturday, June 18, 2011

Page 67: Nordic Ruby 2011

Method Extraction

Saturday, June 18, 2011

Page 68: Nordic Ruby 2011

class WebClient def get(url) url = URI.parse url Net::HTTP.get(url.host, url.path) endend

Saturday, June 18, 2011

Page 69: Nordic Ruby 2011

class WebClient def get(url) url = URI.parse url http_get end

private def http_get Net::HTTP.get(url.host, url.path) endend

Saturday, June 18, 2011

Page 70: Nordic Ruby 2011

class WebClient def get(url) url = URI.parse url http_get(url.host, url.path) end

private def http_get(host, path) Net::HTTP.get(host, path) endend

I don't like it, but it helps us to reason

Saturday, June 18, 2011

Page 71: Nordic Ruby 2011

Object#extend

Saturday, June 18, 2011

Page 72: Nordic Ruby 2011

class Foo def metaclass class << self; self; end endend

x = Foo.newp x.metaclass.ancestors

x.extend(Module.new { })p x.metaclass.ancestors

Saturday, June 18, 2011

Page 73: Nordic Ruby 2011

[Foo, Object, Kernel, BasicObject]

[#<Module:0x81c98>, Foo, Object, Kernel, BasicObject]

Saturday, June 18, 2011

Page 74: Nordic Ruby 2011

Code Seams

Saturday, June 18, 2011

Page 75: Nordic Ruby 2011

Testing

Saturday, June 18, 2011

Page 76: Nordic Ruby 2011

Load Path Hacking

Saturday, June 18, 2011

Page 77: Nordic Ruby 2011

$LOAD_PATH

Saturday, June 18, 2011

Page 78: Nordic Ruby 2011

ruby -Ifoo script.rb

Saturday, June 18, 2011

Page 79: Nordic Ruby 2011

require 'net/http'

class WebClient def get(url) url = URI.parse url Net::HTTP.get(url.host, url.path) endend

Saturday, June 18, 2011

Page 80: Nordic Ruby 2011

[aaron@higgins project (master)]$ find .../test./test/lib./test/lib/net./test/lib/net/http.rb./web.rb

Saturday, June 18, 2011

Page 81: Nordic Ruby 2011

module Net class HTTP def self.get(host, path) "hello world" end endend

test/lib/net/http.rb

Saturday, June 18, 2011

Page 82: Nordic Ruby 2011

ruby -I test/lib web.rb

Saturday, June 18, 2011

Page 83: Nordic Ruby 2011

require 'web'require 'minitest/autorun'

class WebTest < MiniTest::Unit::TestCase def test_get client = WebClient.new response = client.get 'http://www.reddit.com/r/ruby' assert_equal 'hello world', response endend

Saturday, June 18, 2011

Page 84: Nordic Ruby 2011

require File.expand_path( File.join('..', 'foo'))

People don't know about -IRSpec and test/unit will set your -I for you

Saturday, June 18, 2011

Page 85: Nordic Ruby 2011

Heavy Handed

Saturday, June 18, 2011

Page 86: Nordic Ruby 2011

Good for Small APIs

Saturday, June 18, 2011

Page 87: Nordic Ruby 2011

Constant Hacking

Saturday, June 18, 2011

Page 88: Nordic Ruby 2011

require 'net/http'

class WebClient def get(url) url = URI.parse url Net::HTTP.get(url.host, url.path) endend

Saturday, June 18, 2011

Page 89: Nordic Ruby 2011

class MyHTTP def self.get(host, path) "hello world" endend

WebClient.const_set(:Net, Module.new)WebClient::Net.const_set(:HTTP, MyHTTP)

Saturday, June 18, 2011

Page 90: Nordic Ruby 2011

class WebTest < MiniTest::Unit::TestCase def setup WebClient.const_set(:Net, Module.new) WebClient::Net.const_set(:HTTP, MyHTTP) end

def teardown WebClient.send(:remove_const, :Net) end

def test_get client = WebClient.new response = client.get 'http://www.reddit.com/r/ruby' assert_equal 'hello world', response endend

Saturday, June 18, 2011

Page 91: Nordic Ruby 2011

class WebClient def get(url) url = URI.parse url ::Net::HTTP.get(url.host, url.path) endend

Saturday, June 18, 2011

Page 92: Nordic Ruby 2011

Heavy Handed

Localized changes to one class

Still must mock entire API

Saturday, June 18, 2011

Page 93: Nordic Ruby 2011

Subclass Testing

Saturday, June 18, 2011

Page 94: Nordic Ruby 2011

class WebClient def get(url) url = URI.parse url Net::HTTP.get(url.host, url.path) endend

Saturday, June 18, 2011

Page 95: Nordic Ruby 2011

require 'net/http'

class WebClient def get(url) url = URI.parse url http_get(url.host, url.path) end

private def http_get(host, path) Net::HTTP.get(host, path) endend

Saturday, June 18, 2011

Page 96: Nordic Ruby 2011

Class.new

Saturday, June 18, 2011

Page 97: Nordic Ruby 2011

class WebTest < MiniTest::Unit::TestCase def test_get client = Class.new(WebClient) { def http_get(host, path) 'hello world' end }.new

response = client.get 'http://www.reddit.com/r/ruby' assert_equal 'hello world', response endend

Saturday, June 18, 2011

Page 98: Nordic Ruby 2011

Annoying Constructors

Saturday, June 18, 2011

Page 99: Nordic Ruby 2011

class Column def initialize(name, default, sql_type = nil, null = true) @name = name @sql_type = sql_type @null = null @limit = extract_limit(sql_type) @precision = extract_precision(sql_type) @scale = extract_scale(sql_type) @type = simplified_type(sql_type) @default = extract_default(default) @primary = nil @coder = nil end ....end

Many parameters,

don't care about some of them

Saturday, June 18, 2011

Page 100: Nordic Ruby 2011

Just Pass nil

Saturday, June 18, 2011

Page 101: Nordic Ruby 2011

def test_type_cast_true c = Column.new(nil, 1, 'int')

assert_equal 't', @conn.type_cast(true, nil) assert_equal 1, @conn.type_cast(true, c)end

Saturday, June 18, 2011

Page 102: Nordic Ruby 2011

Not Constructible

Can't construct, but want to break dependencies

Saturday, June 18, 2011

Page 103: Nordic Ruby 2011

pool = ActiveRecord::Base.connection_pool

Too hard to construct

Saturday, June 18, 2011

Page 104: Nordic Ruby 2011

pool = ActiveRecord::Base.connection_pool.dup

pool.extend(Module.new { def checkin conn @checkins << conn conn.object_id end})

Saturday, June 18, 2011

Page 105: Nordic Ruby 2011

Detecting Changes

Saturday, June 18, 2011

Page 106: Nordic Ruby 2011

Measure http_getclass WebClient def get(url) url = URI.parse url http_get(url.host, url.path) end

private def http_get(host, path) Net::HTTP.get(host, path) endend

Saturday, June 18, 2011

Page 107: Nordic Ruby 2011

via Class.new

Saturday, June 18, 2011

Page 108: Nordic Ruby 2011

def test_http_get_count call_count = 0 client = Class.new(WebClient) { define_method(:http_get) { |host, path| call_count += 1 "hello world" } }.new

assert_equal 0, call_count client.get 'http://www.reddit.com/r/ruby' assert_equal 1, call_countend

Saturday, June 18, 2011

Page 109: Nordic Ruby 2011

def test_http_get_count call_count = 0 client = Class.new(WebClient) { define_method(:http_get) { |host, path| call_count += 1 "hello world" } }.new

assert_equal 0, call_count client.get 'http://www.reddit.com/r/ruby' assert_equal 1, call_countend

Saturday, June 18, 2011

Page 110: Nordic Ruby 2011

def test_http_get_count call_count = 0 client = Class.new(WebClient) { define_method(:http_get) { |host, path| call_count += 1 "hello world" } }.new

assert_equal 0, call_count client.get 'http://www.reddit.com/r/ruby' assert_equal 1, call_countend

Decide to call super or not

Saturday, June 18, 2011

Page 111: Nordic Ruby 2011

via Module.new

Saturday, June 18, 2011

Page 112: Nordic Ruby 2011

def test_http_get_count client = WebClient.new

call_count = 0 client.extend(Module.new { define_method(:http_get) { |host, path| call_count += 1 "hello world" } })

assert_equal 0, call_count client.get 'http://www.reddit.com/r/ruby' assert_equal 1, call_countend

Saturday, June 18, 2011

Page 113: Nordic Ruby 2011

def test_http_get_count client = WebClient.new

call_count = 0 client.extend(Module.new { define_method(:http_get) { |host, path| call_count += 1 "hello world" } })

assert_equal 0, call_count client.get 'http://www.reddit.com/r/ruby' assert_equal 1, call_countend

Saturday, June 18, 2011

Page 114: Nordic Ruby 2011

def test_http_get_count client = WebClient.new

call_count = 0 client.extend(Module.new { define_method(:http_get) { |host, path| call_count += 1 "hello world" } })

assert_equal 0, call_count client.get 'http://www.reddit.com/r/ruby' assert_equal 1, call_countend

Saturday, June 18, 2011

Page 115: Nordic Ruby 2011

Extending

Saturday, June 18, 2011

Page 116: Nordic Ruby 2011

Huge Methods!

Saturday, June 18, 2011

Page 117: Nordic Ruby 2011

def table_rows rows[table_name] = fixtures.map do |label, fixture| if model_class && model_class < ActiveRecord::Base reflection_class.reflect_on_all_associations.each do |association| case association.macro when :belongs_to # Do not replace association name with association foreign key if they are named the same fk_name = (association.options[:foreign_key] || "#{association.name}_id").to_s

if association.name.to_s != fk_name && value = row.delete(association.name.to_s) if association.options[:polymorphic] && value.sub!(/\s*\(([^\)]*)\)\s*$/, "") # support polymorphic belongs_to as "label (Type)" row[association.foreign_type] = $1 end

row[fk_name] = ActiveRecord::Fixtures.identify(value) end when :has_and_belongs_to_many if (targets = row.delete(association.name.to_s)) targets = targets.is_a?(Array) ? targets : targets.split(/\s*,\s*/) table_name = association.options[:join_table] rows[table_name].concat targets.map { |target| { association.foreign_key => row[primary_key_name], association.association_foreign_key => ActiveRecord::Fixtures.identify(target) } } end end end end row end rowsend

Saturday, June 18, 2011

Page 118: Nordic Ruby 2011

Extract Methods

Saturday, June 18, 2011

Page 119: Nordic Ruby 2011

def belongs_to_row(association, row) # Do not replace association name with association foreign key if they are named the same fk_name = (association.options[:foreign_key] || "#{association.name}_id").to_s

if association.name.to_s != fk_name && value = row.delete(association.name.to_s) if association.options[:polymorphic] && value.sub!(/\s*\(([^\)]*)\)\s*$/, "") # support polymorphic belongs_to as "label (Type)" row[association.foreign_type] = $1 end

row[fk_name] = ActiveRecord::Fixtures.identify(value)end

Saturday, June 18, 2011

Page 120: Nordic Ruby 2011

def habtm_row(association, row) if (targets = row.delete(association.name.to_s)) targets = targets.is_a?(Array) ? targets : targets.split(/\s*,\s*/) table_name = association.options[:join_table] rows[table_name].concat targets.map { |target| { association.foreign_key => row[primary_key_name], association.association_foreign_key => ActiveRecord::Fixtures.identify(target) } } endend

Saturday, June 18, 2011

Page 121: Nordic Ruby 2011

def table_rows rows[table_name] = fixtures.map do |label, fixture| row = fixture.to_hash

if model_class && model_class < ActiveRecord::Base

# If STI is used, find the correct subclass for association reflection reflection_class = if row.include?(inheritance_column_name) row[inheritance_column_name].constantize rescue model_class else model_class end

reflection_class.reflect_on_all_associations.each do |association| case association.macro when :belongs_to belongs_to_row(association, row) when :has_and_belongs_to_many habtm_row(association, row) end end end

row end rowsend

Saturday, June 18, 2011

Page 122: Nordic Ruby 2011

Extract Object

Saturday, June 18, 2011

Page 123: Nordic Ruby 2011

class RowFilter def rows fixtures.map do |label, fixture| row = fixture.to_hash if model_class && model_class < ActiveRecord::Base # If STI is used, find the correct subclass for association reflection reflection_class = if row.include?(inheritance_column_name) row[inheritance_column_name].constantize rescue model_class else model_class end

reflection_class.reflect_on_all_associations.each do |association| case association.macro when :belongs_to belongs_to_row(association, row) when :has_and_belongs_to_many habtm_row(association, row) end end end row end endend

Saturday, June 18, 2011

Page 124: Nordic Ruby 2011

class RowFilter attr_reader :fixtures, :model_class

def initialize(fixtures, model_class) @fixtures = fixtures @model_class = model_class endend

Saturday, June 18, 2011

Page 125: Nordic Ruby 2011

def table_rows filter = RowFilter.new(fixtures, model_class) rows[table_name] = filter.rows rowsend

Saturday, June 18, 2011

Page 126: Nordic Ruby 2011

Huge Classes!

Saturday, June 18, 2011

Page 127: Nordic Ruby 2011

Look at Method Names

Saturday, June 18, 2011

Page 128: Nordic Ruby 2011

Look at shared instance variables

Saturday, June 18, 2011

Page 129: Nordic Ruby 2011

Group Similar Methods

Saturday, June 18, 2011

Page 130: Nordic Ruby 2011

Use SRP to create a new class

Saturday, June 18, 2011

Page 131: Nordic Ruby 2011

Then Delegate.

Saturday, June 18, 2011

Page 132: Nordic Ruby 2011

All API calls

Saturday, June 18, 2011

Page 133: Nordic Ruby 2011

class WebClient def get(url) url = URI.parse url http_get(url.host, url.path) end

private def http_get(host, path) Net::HTTP.get(host, path) endend

Once we had extracted the HTTP methods to their own functions, could reason about HTTP api.

Saturday, June 18, 2011

Page 134: Nordic Ruby 2011

class WebClient def initialize(client = Net::HTTP) @client = client end

def get(url) url = URI.parse url http_get(url.host, url.path) end

private def http_get(host, path) @client.get(host, path) endend

Saturday, June 18, 2011

Page 135: Nordic Ruby 2011

class TestHTTP < Struct.new(:data) def get(host, path) data[[host, path]] endend

Saturday, June 18, 2011

Page 136: Nordic Ruby 2011

class WebTest < Test::Unit::TestCase def test_get_home mockhttp = TestHTTP.new({ ['localhost', '/~aaron/'] => 'hello' }) wc = WebClient.new mockhttp assert_equal 'hello', wc.get('http://localhost/~aaron/') endend

Saturday, June 18, 2011

Page 137: Nordic Ruby 2011

We define expectations

Saturday, June 18, 2011

Page 138: Nordic Ruby 2011

Not Bound to HTTP

Saturday, June 18, 2011

Page 139: Nordic Ruby 2011

Legacy code can make you cry

Saturday, June 18, 2011

Page 140: Nordic Ruby 2011

But it doesn't have to

Saturday, June 18, 2011

Page 141: Nordic Ruby 2011

Don't be afraid!

Saturday, June 18, 2011

Page 142: Nordic Ruby 2011

Ruby is your mocking framework

Saturday, June 18, 2011

Page 143: Nordic Ruby 2011

Credits & More Info

Saturday, June 18, 2011

Page 144: Nordic Ruby 2011

Open Questions?

Saturday, June 18, 2011

Page 145: Nordic Ruby 2011

Do you like mocking / stubbing?

Saturday, June 18, 2011

Page 146: Nordic Ruby 2011

How do these testing tools impact your API?

Saturday, June 18, 2011

Page 147: Nordic Ruby 2011

Do you like hearts?

Saturday, June 18, 2011

Page 148: Nordic Ruby 2011

How about kittens?

Saturday, June 18, 2011

Page 149: Nordic Ruby 2011

<3 <3 <3

Saturday, June 18, 2011