SlideShare a Scribd company logo
1 of 138
Download to read offline
Chris Morris
Sr. Engineer
@the_chrismo
4,000-plus-person company
Almost $400 million in revenue
About $1 billion in sales last year
Hundreds of millions in the bank
— TIM O'SHAUGHNESSY, CEO

CNNMONEY - JAN 2014
4 PACK OF LIGHTNING TALKS
COBBLER’S PRODUCTION
CONSOLE HAS NO SHOES
LIVINGSOCIAL PAYMENTS

•

Credit Card, PayPal, V.me,
MasterPass, International
(Adyen, iPay88)

•

1.5M API calls a day

•

70k-150k credit card
transactions a day
LIVINGSOCIAL PAYMENTS

•

Whole Foods - 1,000,000

•

Starbucks - 1,500,000
irb(main):004:0> Transaction.find_all_by_person_id 2
=> [#<SaleTransaction id: 5, credit_card_id: 47523892,
reference_transaction_id: nil, amount: #<BigDecimal:
7fb60bc0ad78,'0.2E2',9(18)>, response: nil, transaction_type:
"sale", aasm_state: "processed", created_at: "2014-02-04 19:42:57",
updated_at: "2014-02-04 19:43:05", type: "SaleTransaction",
callback_queue_name: "medium", callback_queue_application: "pry",
options: "{"country":"US","profile":null,"requires_cvv
":null,...", processor_authorization_code: "illoearumi",
gateway_transaction_id: "excepturiq", success: true, failure_reason:
nil, avs_response_code: "M", cvv_response_code: "M",
scope_identifier: nil, person_id: 2>, #<SaleTransaction id: 6,
credit_card_id: 47523892, reference_transaction_id: nil, amount:
#<BigDecimal:7fb60bc0a828,'0.2E2',9(18)>, response: nil,
transaction_type: "sale", aasm_state: "processed", created_at:
"2014-02-04 19:43:11", updated_at: "2014-02-04 19:43:20", type:
"SaleTransaction", callback_queue_name: "medium",
callback_queue_application: "pry", options: "{"country":"US",
"profile":null,"requires_cvv":null,...",
processor_authorization_code: "errorconse", gateway_transaction_id:
"eumauttene", success: true, failure_reason: nil, avs_response_code:
"M", cvv_response_code: "M", scope_identifier: nil, person_id: 2>]
[159] pry(main)> b=PB.from_people_ids 2
=> +----+-------------------------+------------+
| id |
created_at
| aasm_state |
+----+-------------------------+------------+
| 5 | 2014-02-04 19:42:57 UTC | processed |
| 6 | 2014-02-04 19:43:11 UTC | processed |
+----+-------------------------+------------+
irb(main):080:0> b.options
.=> [{"country"=>"US", "profile"=>nil,
"requires_cvv"=>nil, "order"=>nil,
"priority"=>nil, "use_deal_bucks"=>nil,
"requires_avs"=>nil,
"charge_distribution_strategy"=>nil,
"device_data"=>nil}, {"country"=>"US",
"profile"=>nil, "requires_cvv"=>nil,
"order"=>nil, "priority"=>nil,
"use_deal_bucks"=>nil, "requires_avs"=>nil,
"charge_distribution_strategy"=>nil,
"device_data"=>nil}]
[33] pry(main)> b.options
=> [{"country"=>"US",
"profile"=>nil,
"requires_cvv"=>nil,
"order"=>nil,
"priority"=>nil,
"use_deal_bucks"=>nil,
"requires_avs"=>nil,
"charge_distribution_strategy"=>nil,
"device_data"=>nil},
{"country"=>"US",
"profile"=>nil,
"requires_cvv"=>nil,
"order"=>nil,
"priority"=>nil,
"use_deal_bucks"=>nil,
"requires_avs"=>nil,
"charge_distribution_strategy"=>nil,
"device_data"=>nil}]
class Batch < Array
def initialize(ary)
self << ary
flatten!
end
!
!

def method_missing(meth_id, *args)
self.map do |t|
t.send(meth_id, *args)
end
end
end
[14] pry(main)> b.map { |t| t.aasm_state }
=> ["processed", "processed"]
[15] pry(main)> b.map(&:type)
=> ["SaleTransaction", "RefundTransaction"]
[11] pry(main)> b.aasm_state
=> ["processed", "processed"]
[12] pry(main)> b.type
=> ["SaleTransaction", "RefundTransaction"]
class PaymentsBatch < Batch
def self.from_people_ids(ary)
transactions = [ary].flatten.collect do |person_id|
Transaction.find_all_by_person_id(person_id)
end
PaymentsBatch.new(transactions)
end
end
!

PB = PaymentsBatch
!
!

[50] pry(main)> b=PB.from_people_ids 2
=> #<PaymentsBatch:0x3fd8eab40048>
class PaymentsBatch < Batch
def report
puts self.map do |t|
[t.id.to_s.ljust(5),
t.created_at.to_s.ljust(25),
t.aasm_state.to_s.ljust(15)].join
end.join("n")
end
end
!
!

[75] pry(main)> b.report
5
2014-02-04 19:42:57 UTC
6
2014-02-04 19:43:11 UTC
=> nil

processed
processed
class PaymentsBatch < Batch
def report
self.map do |t|
[t.id, t.created_at, t.aasm_state]
end.to_text_table
end
end
!

[22] pry(main)> b.report
=> +---+-------------------------+-----------+
| 5 | 2014-02-04 19:42:57 UTC | processed |
| 6 | 2014-02-04 19:43:11 UTC | processed |
| 7 | 2014-02-04 19:43:42 UTC | processed |
+---+-------------------------+-----------+
class PaymentsBatch < Batch
def report
cols = [:id, :created_at, :aasm_state]
h = serializable_hash(only: cols)
rows = ([h.first.keys] + h.map(&:values))
rows.to_table(:first_row_is_head => true)
end
end
!
!

[87] pry(main)> b.report
=> +------------+-------------------------+----+
| aasm_state |
created_at
| id |
+------------+-------------------------+----+
| processed | 2014-02-04 19:42:57 UTC | 5 |
| processed | 2014-02-04 19:43:11 UTC | 6 |
| processed | 2014-02-04 19:43:42 UTC | 7 |
+------------+-------------------------+----+
[155] pry(main)> b=PB.from_people_ids 2
=> [#<SaleTransaction id: 5, credit_card_id: 47523892,
reference_transaction_id: nil, amount: #<BigDecimal:7ff71ecc9b48,'0.2E2',
9(18)>, response: nil, transaction_type: "sale", aasm_state: "processed",
created_at: "2014-02-04 19:42:57", updated_at: "2014-02-04 19:43:05",
type: "SaleTransaction", callback_queue_name: "medium",
callback_queue_application: "pry", options: "{"country":"US","profile
":null,"requires_cvv":null,...", processor_authorization_code:
"illoearumi", gateway_transaction_id: "excepturiq", success: true,
failure_reason: nil, avs_response_code: "M", cvv_response_code: "M",
scope_identifier: nil, person_id: 2>,
#<SaleTransaction id: 6, credit_card_id: 47523892,
reference_transaction_id: nil, amount: #<BigDecimal:7ff71ecc9620,'0.2E2',
9(18)>, response: nil, transaction_type: "sale", aasm_state: "processed",
created_at: "2014-02-04 19:43:11", updated_at: "2014-02-04 19:43:20",
type: "SaleTransaction", callback_queue_name: "medium",
callback_queue_application: "pry", options: "{"country":"US","profile
":null,"requires_cvv":null,...", processor_authorization_code:
"errorconse", gateway_transaction_id: "eumauttene", success: true,
failure_reason: nil, avs_response_code: "M", cvv_response_code: "M",
scope_identifier: nil, person_id: 2>]
[156] pry(main)> b.report
.=> +----+-------------------------+------------+
| id |
created_at
| aasm_state |
+----+-------------------------+------------+
| 5 | 2014-02-04 19:42:57 UTC | processed |
| 6 | 2014-02-04 19:43:11 UTC | processed |
+----+-------------------------+------------+

LOL
class Batch < Array
def pretty_inspect
report.to_s
end
end
[155] pry(main)> b=PB.from_people_ids 2
=> +----+-------------------------+------------+
| id |
created_at
| aasm_state |
+----+-------------------------+------------+
| 5 | 2014-02-04 19:42:57 UTC | processed |
| 6 | 2014-02-04 19:43:11 UTC | processed |
+----+-------------------------+------------+
class PaymentsBatch
def email
addr = 'you@re.awesome.com'
Mail::Message.new(to: addr,
from: addr,
subject: 'PaymentsBatch',
body: report.to_s).deliver
end
end
LOG SEARCH
chris.morris@support01:$ RAILS_ENV=production bundle exec thor grep:payments -p '(PCI|SPT)
environment' -d 1
********** support01 **********
ssh support01 'grep -P "(PCI|SPT) environment" log/production.log-20140218'
14-02-18T02:48:20 - [INFO] (#21648) SPT environment forwarding #create to PCI environment
14-02-18T02:49:22 - [INFO] (#21651) SPT environment forwarding #create to PCI environment
14-02-18T03:00:04 - [INFO] (#21648) SPT environment forwarding #create to PCI environment
********** support02 **********
ssh support02 'grep -P "(PCI|SPT) environment" log/production.log-20140218'
14-02-17T15:00:50 - [INFO] (#3844) SPT environment forwarding #create to PCI environment
14-02-17T19:18:37 - [INFO] (#3841) SPT environment forwarding #create to PCI environment
14-02-17T22:12:38 - [INFO] (#3844) SPT environment forwarding #create to PCI environment
14-02-18T01:40:56 - [INFO] (#3844) SPT environment forwarding #create to PCI environment
********** boxen01 **********
ssh boxen01 'grep -P "(PCI|SPT) environment" log/production.log-20140218'
14-02-17T19:18:37 - [INFO] (#24618) PCI environment handling #create
14-02-18T01:40:56 - [INFO] (#24618) PCI environment handling #create
14-02-18T02:49:22 - [INFO] (#24615) PCI environment handling #create
********** boxen02 **********
ssh boxen02 'grep -P "(PCI|SPT) environment" log/production.log-20140218'
14-02-17T15:00:50 - [INFO] (#8119) PCI environment handling #create
14-02-17T22:12:38 - [INFO] (#8129) PCI environment handling #create
14-02-18T02:48:20 - [INFO] (#8125) PCI environment handling #create
14-02-18T03:00:04 - [INFO] (#8122) PCI environment handling #create
chris.morris@support01:$ RAILS_ENV=production bundle exec thor grep:payments -p '(PCI|SPT)
environment' -d 1 -c --pry
********** support01 **********
ssh support01 'grep -P -C 15 "(PCI|SPT) environment" log/production.log-20140218'
********** support02 **********
ssh support02 'grep -P -C 15 "(PCI|SPT) environment" log/production.log-20140218'
********** boxen01 **********
ssh boxen01 'grep -P -C 15 "(PCI|SPT) environment" log/production.log-20140218'
********** boxen02 **********
ssh boxen02 'grep -P -C 15 "(PCI|SPT) environment" log/production.log-20140218'
+-------+---------------------+--------+-------------+---------------+
| pid |
timestamp
| action | status_code |
ip_addr
|
+-------+---------------------+--------+-------------+---------------+
| 3844 | 2014-02-17T15:00:50 | POST
| 201
| x.x.x.x
|
| 8119 | 2014-02-17T15:00:50 | POST
| 201
| x.x.x.x
|
| 3841 | 2014-02-17T19:18:36 | POST
| 500
| x.x.x.x
|
| 24618 | 2014-02-17T19:18:37 | POST
| 500
| x.x.x.x
|
| 8129 | 2014-02-17T22:12:38 | POST
| 201
| x.x.x.x
|
| 3844 | 2014-02-17T22:12:38 | POST
| 201
| x.x.x.x
|
| 24618 | 2014-02-18T01:40:56 | POST
| 201
| x.x.x.x
|
| 21648 | 2014-02-18T02:48:20 | POST
| 201
| x.x.x.x
|
| 8125 | 2014-02-18T02:48:20 | POST
| 201
| x.x.x.x
|
| 24615 | 2014-02-18T02:49:22 | POST
| 400
| x.x.x.x
|
| 21651 | 2014-02-18T02:49:22 | POST
| 500
| x.x.x.x
|
| 21648 | 2014-02-18T03:00:04 | POST
| 201
| x.x.x.x
|
| 8122 | 2014-02-18T03:00:04 | POST
| 201
| x.x.x.x
|
+-------+---------------------+--------+-------------+---------------+
From: script/grep.payments.thor @ line 81 Thor::Sandbox::Grep#payments:

!

!

76:
77:
78:
79:
80:
=> 81:
82:
83:
84:
85:
86:

wheelhouse = Wheelhouse.new(sio)
chunks = wheelhouse.chunk_it(:grep => options[:pattern],
:filename => options[:save_chunks],
:range_threshold_in_seconds =>
options[:timestamp_range_in_seconds])
puts wheelhouse.to_text_table(chunks)
binding.pry if options[:pry]
end
end
private

[1] pry(#<Thor::Sandbox::Grep>)>
[1] pry(#<Thor::Sandbox::Grep>)> chunks
=> [#<RequestChunk:0x00000004f39540
@action="POST",
@completed=true,
@controller="Api::V1::CreditCardsController#create",
@ip_addr="x.x.x.x",
@lines=
["14-02-18T02:48:20 - [INFO] (#21648) Started POST ... for x.x.x.x with ...",
"14-02-18T02:48:20 - [INFO] (#21648) Processing by Api::V1::CreditCards...",
"14-02-18T02:48:20 - [INFO] (#21648)
Parameters: {"..."",
"14-02-18T02:48:20 - [INFO] (#21648) SPT environment forwarding #create...",
"14-02-18T02:48:22 - [INFO] (#21648) Completed 201 Created in 1279.5ms ..."]
@parameters=
{"foo"=>
{"token"=>"e4facf2d83cf58bd78b05485e21d0923",
"checkout_resource_url"=>"https://foobar.com",
"return_url"=>"https://www.livingsocial.com",
"country_code"=>"AU"},
"person_id"=>"12345678"},
@pid="21648",
@range_threshold_in_seconds=2,
@referrer="",
@route="http://...",
@status_code="201",
@timestamp="2014-02-18T02:48:20",
@user_agent="Ruby">,
THE END
DIY MOCKS AND FIXTURES
RAVE REVIEWS

"You either get an F for
logic or an A+ for balls.”
!
!

— J. B. Rainsberger
author of JUnit Recipes
[~]$ curl -s http://www.linkedin.com/in/chrismo | grep Relevance
[~]$
NOT GLENN VANDERBURG =>
NOT GLENN VANDERBURG =>

!

CODMONGER
!
FactoryGirl.define do
factory :customer do
external_record_type 'person'
sequence(:external_id)
email Faker::Internet.email
end

!

!
!

!

factory :address do
name Faker::Name.name
customer
end
sequence :purchase_external_id
sequence :purchase_deal_id
factory :purchase do
currency 'USD'
external_record_type 'purchase'
external_id { FactoryGirl.generate(:purchase_external_id) }
deal_id { FactoryGirl.generate(:purchase_deal_id) }
price { rand(50) + 1 }
customer
association :address, :method => :build
quantity { rand(4) + 1 }
title Faker::Company.catch_phrase
end

factory :collection do
purchase
end
end
describe Collection do
it "should not return old exported collection with end status" do
[:collected, :canceled, :refused].each do |status|
collection = Factory.build(:collection, :exported_at => 4.days.ago,
:status => status.to_s)
assert !Collection.problem_old_exports.include?(collection)
end
end
describe Collection do
it "should not return old exported collection with end status" do
[:collected, :canceled, :refused].each do |status|
collection = Factory.build(:collection, :exported_at => 4.days.ago,
:status => status.to_s)
assert !Collection.problem_old_exports.include?(collection)
end
end
end

NoMethodError: undefined method `customer' for nil:NilClass
./app/models/collection.rb:90:in `customer'
./app/models/collection.rb:14:in `block in <class:Collection>'
./spec/models/collection_spec.rb:43:in `block (3 levels) in <top (required)>'
./spec/models/collection_spec.rb:42:in `each'
./spec/models/collection_spec.rb:42:in `block (2 levels) in <top (required)>'
class Collection < ActiveRecord::Base
def customer
purchase.customer
end
end

NoMethodError: undefined method `customer' for nil:NilClass
./app/models/collection.rb:90:in `customer'
./app/models/collection.rb:14:in `block in <class:Collection>'
./spec/models/collection_spec.rb:43:in `block (3 levels) in <top (required)>'
./spec/models/collection_spec.rb:42:in `each'
./spec/models/collection_spec.rb:42:in `block (2 levels) in <top (required)>'
FactoryGirl.define do
...
# YOU HAD ONE JOB
factory :collection do
purchase
end
end
require 'machinist/active_record'
require 'faker'

!

Sham.sham_id { |i| i }

!

Customer.blueprint do
external_record_type { 'person' }
external_id { Sham.sham_id }
email { Faker::Internet.email }
end

!

Address.blueprint do
name Faker::Name.name
customer
purchase
end

!

Purchase.blueprint do
currency {'USD'}
external_record_type {'purchase'}
external_id { Sham.sham_id }
deal_id { Sham.sham_id }
price { rand(50) + 1 }
customer
quantity { rand(4) + 1 }
title { Faker::Company.catch_phrase }
end

!

Collection.blueprint do
purchase
end
describe Collection, 'scope :problem_old_exports (machinist)' do
it "should not return old exported collection with end status" do
[:collected, :canceled, :refused].each do |status|
collection = Collection.make_unsaved(:exported_at => 4.days.ago,
:status => status.to_s)
assert !Collection.problem_old_exports.include?(collection)
end
end
end
describe Collection, 'scope :problem_old_exports (machinist)' do
it "should not return old exported collection with end status" do
[:collected, :canceled, :refused].each do |status|
collection = Collection.make_unsaved(:exported_at => 4.days.ago,
:status => status.to_s)
assert !Collection.problem_old_exports.include?(collection)
end
end
end

!
NoMethodError: undefined method `customer' for nil:NilClass
./app/models/collection.rb:90:in `customer'
./app/models/collection.rb:14:in `block in <class:Collection>'
./spec/models/collection_spec.rb:61:in `block (3 levels) in <top (required)>'
./spec/models/collection_spec.rb:60:in `each'
./spec/models/collection_spec.rb:60:in `block (2 levels) in <top (required)>'
!

# ONE. JOB.
Collection.blueprint do
purchase
end
require 'faker'

!

class CodmongerFixture
def self.create(opts={})
record = self.new(opts); record.save!; record
end
end

!

class AddressFixture < CodmongerFixture
def self.new(opts={})
Address.new({:name => Faker::Name.name}.merge(opts))
end
end

!

class CustomerFixture < CodmongerFixture
@@customer_id = Customer.last.external_id + 1 rescue 1
def self.new(opts={})
Customer.new({:external_record_type => 'person',
:external_id => @@customer_id += 1,
:email => Faker::Internet.email}.merge(opts))
end
end

!

class PurchaseFixture < CodmongerFixture
@@purchase_id = Purchase.last.external_id + 1 rescue 1
def self.new(opts={})
customer = CustomerFixture.new
Purchase.new({:currency => 'USD', :deal_id => 1,
:external_record_type => 'purchase',
:external_id => @@purchase_id += 1,
:price => rand(50) + 1,
:quantity => rand(4) + 1,
:title => Faker::Company.catch_phrase,
:address => AddressFixture.new(:customer => customer),
:customer => customer}.merge(opts))
end
end

!

class CollectionFixture < CodmongerFixture
def self.new(opts={})
Collection.new({:purchase => PurchaseFixture.new}.merge(opts))
end
end
describe Collection do
it "should not return old exported collection with end status" do
[:collected, :canceled, :refused].each do |status|
collection = CollectionFixture.new(:exported_at => 4.days.ago,
:status => status.to_s)
assert !Collection.problem_old_exports.include?(collection)
end
end
end

1 example, 0 failures, 1 passed
FactoryGirl::Proxy::ObjectWrapper

def object
@object ||= @klass.new
assign_object_attributes
@object
end
class Collection < ActiveRecord::Base
state_machine :status, :initial => lambda{ |collection|
collection.customer.new? ? :received : :customer_verified} do
...
end
end
ACTIVE RECORD
IS
HARD ENOUGH
!
!

I DON’T NEED YOUR
FREEKING
DSL ON TOP OF IT
require 'faker'

!

class CodmongerFixture
def self.create(opts={})
record = self.new(opts); record.save!; record
end
end

!

class AddressFixture < CodmongerFixture
def self.new(opts={})
Address.new({:name => Faker::Name.name}.merge(opts))
end
end

!

class CustomerFixture < CodmongerFixture
@@customer_id = Customer.last.external_id + 1 rescue 1
def self.new(opts={})
Customer.new({:external_record_type => 'person',
:external_id => @@customer_id += 1,
:email => Faker::Internet.email}.merge(opts))
end
end

!

class PurchaseFixture < CodmongerFixture
@@purchase_id = Purchase.last.external_id + 1 rescue 1
def self.new(opts={})
customer = CustomerFixture.new
Purchase.new({:currency => 'USD', :deal_id => 1,
:external_record_type => 'purchase',
:external_id => @@purchase_id += 1,
:price => rand(50) + 1,
:quantity => rand(4) + 1,
:title => Faker::Company.catch_phrase,
:address => AddressFixture.new(:customer => customer),
:customer => customer}.merge(opts))
end
end

!

class CollectionFixture < CodmongerFixture
def self.new(opts={})
Collection.new({:purchase => PurchaseFixture.new}.merge(opts))
end
end
FactoryGirl.define do
factory :customer do
external_record_type 'person'
sequence(:external_id)
email Faker::Internet.email
end

!

!
!

!

factory :address do
name Faker::Name.name
customer
end
sequence :purchase_external_id
sequence :purchase_deal_id
factory :purchase do
currency 'USD'
external_record_type 'purchase'
external_id { FactoryGirl.generate(:purchase_external_id) }
deal_id { FactoryGirl.generate(:purchase_deal_id) }
price { rand(50) + 1 }
customer
association :address, :method => :build
quantity { rand(4) + 1 }
title Faker::Company.catch_phrase
end

factory :collection do
purchase
end
end
require 'faker'
!
class CodmongerFixture
def self.create(opts={})
record = self.new(opts); record.save!; record
end
end
!
class AddressFixture < CodmongerFixture
def self.new(opts={})
Address.new({:name => Faker::Name.name}.merge(opts))
end
end
!
class CustomerFixture < CodmongerFixture
@@customer_id = Customer.last.external_id + 1 rescue 1
def self.new(opts={})
Customer.new({:external_record_type => 'person',
:external_id => @@customer_id += 1,
:email => Faker::Internet.email}.merge(opts))
end
end
class PurchaseFixture < CodmongerFixture
@@purchase_id = Purchase.last.external_id + 1 rescue 1
def self.new(opts={})
customer = CustomerFixture.new
Purchase.new({:currency => 'USD', :deal_id => 1,
:external_record_type => 'purchase',
:external_id => @@purchase_id += 1,
:price => rand(50) + 1,
:quantity => rand(4) + 1,
:title => Faker::Company.catch_phrase,
:address => AddressFixture.new(:customer => customer),
:customer => customer}.merge(opts))
end
end

!

class CollectionFixture < CodmongerFixture
def self.new(opts={})
Collection.new({:purchase => PurchaseFixture.new}.merge(opts))
end
end
WHEN A DSL POOPS
OUT ON YOU
!

YOU CAN'T LOOK
AT ITS SOURCE CODE
!

BECAUSE
IT'S ALL META AND STUFF.
MOCK FRAMEWORKS
NOT ALL EXPECTATIONS WERE SATISFIED
not all expectations were satisfied
unsatisfied expectations:
- expected exactly once, not yet invoked: Purchase(id: integer,
person_id: integer, deal_id: integer, created_at: datetime,
updated_at: datetime, credit_card_id: integer, ref_code: string,
charged_at: datetime, coupons_count: integer, aasm_state: string,
approval_code: string, referring_purchase_id: integer,
referring_purchases_count: integer, discount: decimal,
referring_user_id: integer, last_transaction_id: string, collapsed:
boolean, blasted_to_feed_at: datetime, fb_fan_incentive: decimal,
deleted_at: datetime, latitude: float, longitude: float, ip_address:
string, visa_discount: decimal, purchaser_id: integer,
cheerios_discount: decimal, city_id: integer).find('1234567')
unexpected invocation: #<Mock:0x1022aff50>.reload()
satisfied expectations:
- allowed any number of times, not yet invoked: #<Mock:0x1022b6df0>.available_credit(any_parameters)
- allowed any number of times, not yet invoked: #<Mock:0x1022b6df0>.available_credit(any_parameters)
- allowed any number of times, not yet invoked: #<Mock:0x1022b6df0>.Person(any_parameters)
- allowed any number of times, not yet invoked: #<Mock:0x1022b57e8>.LocalDeal(any_parameters)
- allowed any number of times, not yet invoked: #<Mock:0x1022aff50>.Purchase(any_parameters)
- allowed any number of times, not yet invoked: #<Mock:0x1022aff50>.total_price(any_parameters)
- allowed any number of times, not yet invoked: #<Mock:0x1022aff50>.alternate_payment(any_parameters)
- allowed any number of times, invoked once: #<Mock:0x1022aff50>.person(any_parameters)
- allowed any number of times, invoked once: #<Mock:0x1022aff50>.deal(any_parameters)
- allowed any number of times, not yet invoked: #<Mock:0x1022aff50>.aasm_state(any_parameters)
- allowed any number of times, not yet invoked: #<Mock:0x1022aff50>.id(any_parameters)
- allowed any number of times, invoked once: #<Mock:0x1022aff50>.referred_purchases(any_parameters)
purchase = stub()
purchase.stubs(:id).returns(123)
purchase.stubs(:quantity).returns(2)
!
deal = stub()
deal.stubs(:title).returns("Mr. Bones's Deal'")
deal.stubs(:price).returns(12)
deal.stubs(:currency_unit).returns('$')
!
person = stub()
person.stubs(:email).returns('chris.morris@livingsocial.com')
person.stubs(:id).returns(8675309)
!
purchase.stubs(:person).returns(person)
purchase.stubs(:deal).returns(deal)
PurchaseStub = Struct.new(:id, :quantity, :deal, :person)
DealStub = Struct.new(:title, :price, :currency_unit)
PersonStub = Struct.new(:id, :email)
!

person = PersonStub.new(8675309, 'chrismo@ls.com')
deal = DealStub.new("Mr. Bones's Deal'", 12, '$')
purchase = PurchaseStub.new(123, 2, deal, person)
person = OpenStruct.new(id: 8675309,
email: 'chrismo@ls.com')
!

deal = OpenStruct.new(title: "Mr. Bones's Deal'",
price: 12,
currency_unit: '$')
!

purchase = OpenStruct.new(id: 123,
quantity: 2,
deal: deal,
person: person)
require 'ostruct'
!
person = OpenStruct.new(id: 8675309, email: 'chrismo@clabs.org',
available_credit: {'USD'=>5,'MYR'=>0})
!
def person.available_credit_by_currency(currency)
self.available_credit[currency]
end
module PaymentProviders
module DoubleFake
class Payment
@@db = {}

!

!

!

!

def self.load_payment(id, params)
@@db[id]
end
def initialize(purchase, hash, all_params={})
@hash = hash
end
def address
@hash[:address]
end

def save
@@db[@hash[:id]] = self
end
end
end
end
DIY
—
Gem
BUILD
—
BUY
BUY
trades in

FAMILIARITY
TRANSPARENCY
THE END
TRACK YER BIG STUFF
No tengo ni idea de lo
que estoy haciendo
•

2500 translation keys

•

1/2 in use
dozens of translate calls / request
module Humperdink
class DirtySet
attr_reader :dirty, :clean, :config

!

!

!
!

!

!

def initialize(initial_contents=[], config={})
@clean = Set.new(initial_contents)
@dirty = Set.new
@config = config
end
def <<(value)
@dirty << value if !@clean.include?(value)
clean! if getting_messy?
end
def getting_messy?
...
end
def clean!
@clean.merge(@dirty)
cleaned = @dirty.to_a.dup
@dirty.clear
cleaned
end
def length
# wonky -> doesn't include @dirty
@clean.length
end

def clear
@clean.clear
@dirty.clear
end
end
end
describe Humperdink::DirtySet do
let(:set) { Humperdink::DirtySet.new }
!

it 'should only append to dirty' do
set << ‘foo'
set.dirty.should == ['foo'].to_set
set.clean.should == [].to_set
end
!

it 'should clean! dirty things to clean' do
set << 'foo'
wuz_dirty = set.clean!
wuz_dirty.should == [‘foo']
set.dirty.should == [].to_set
set.clean.should == ['foo'].to_set
end
end
it 'should only append to dirty if not in clean' do
set << ‘foo'
set.dirty.should == ['foo'].to_set
set.clean.should == [].to_set
!

set.clean!
set.dirty.should == [].to_set
set.clean.should == ['foo'].to_set
!

end

set << ‘foo'
set.dirty.should == [].to_set
set.clean.should == ['foo'].to_set
it 'should initialize clean with initialize' do
set = Humperdink::DirtySet.new(['a', ‘b'])
set.dirty.should == [].to_set
set.clean.should == ['a', 'b'].to_set
end
!

it 'should only append to dirty if not in clean' do
set = Humperdink::DirtySet.new(['foo'])
set << ‘foo'
set.dirty.should == [].to_set
set.clean.should == ['foo'].to_set
end
module Humperdink
class RedisDirtySet < DirtySet
def initialize(initial_content=[], config={})
super(initial_content, config)
end
!

def clean!
to_save = super
save(to_save)
end
!

def load
@clean.merge(redis.smembers(@config[:key]))
end
!

def save(to_save)
redis.sadd(@config[:key], to_save.to_a)
end
end
end
module Humperdink
class Tracker
attr_reader :set, :config

!

!

!

!

def initialize(set=Set.new, config={})
@set = set
@config = config
end
def track(data)
@set << data
end
def tracker_enabled
@config[:enabled]
end

# Anytime an exception happens, we want to skedaddle out of the way
# and let life roll on without any tracking in the loop.
def shutdown(exception)
begin
on_event(:shutdown, "#{exception.message}")
rescue => e
$stderr.puts([e.message, e.backtrace].join("n")) rescue nil
end
@config[:enabled] = false
end
end
end
class KeyTracker
def initialize(redis, key)
redis_set = Humperdink::RedisDirtySet.new(:redis => redis,
:key => key,
:max_dirty_items => 9)
@tracker = Humperdink::Tracker.new(redis_set, :enabled => true)
end

!

def on_translate(locale, key, options = {})
begin
if @tracker.tracker_enabled
requested_key = normalize_requested_key(key, options)
@tracker.track(requested_key)
end
rescue => e
@tracker.shutdown(e)
end
end
end
module KeyTrackerBackend
def key_tracker
@key_tracker
end

!

!

def key_tracker=(value)
@key_tracker = value
end

def translate(locale, key, options = {})
@key_tracker.on_translate(locale, key, options) if @key_tracker
super
end
end

!

def setup
@redis = Redis.connect(:url => 'redis://127.0.0.1:6379/8')
@redis_key = 'humperdink:example:i18n'

!

tracker = KeyTracker.new(@redis, @redis_key)
I18n.backend.class.class_eval { include KeyTrackerBackend }
I18n.backend.key_tracker = tracker
end
CONFIGURATION
module Humperdink
class DirtySet
!

def <<(value)
@dirty << value if !@clean.include?(value)
clean! if getting_messy?
end
!

end
end
module Humperdink
class DirtySet
!

def getting_messy?
if @config[:max_dirty_items] &&
@dirty.length > @config[:max_dirty_items]
return true
end
!

if @config[:clean_timeout] &&
Time.now > @time_to_clean
return true
end
end
!

end
end
module Humperdink
class DirtySet
def initialize(initial_contents=[], config={})
@clean = Set.new(initial_contents)
@dirty = Set.new
@config = config
...
at_exit { clean! if @config[:clean_at_exit] }
end
end
end
CONFIGURATION
RESQUE
module Resque
class Worker
def work(interval = 5.0, &block)
loop do
break if shutdown?
!
if not paused? and job = reserve
if @child = fork(job)
...
else
perform(job, &block)
exit!(true) if will_fork?
end
!
done_working
@child = nil
else
sleep interval
end
end
end
end
end
module Humperdink::ForkPiping
class KeyTracker
def initialize(redis, key)
redis_set = Humperdink::RedisDirtySet.new(:redis => redis,
:key => key,
:max_dirty_items => 9)
@tracker = Humperdink::Tracker.new(redis_set, :enabled => true)
@tracker.include Humperdink::ForkPiping
end
end
def on_translate(locale, key, options = {})
begin
if @tracker.tracker_enabled
requested_key = normalize_requested_key(key, options)
@tracker.track(requested_key)
end
rescue => e
@tracker.shutdown(e)
end
end
def on_translate(locale, key, options = {})
begin
if @tracker.not_forked_or_parent_process
if @tracker.tracker_enabled
requested_key = normalize_requested_key(key, options)
@tracker.track(requested_key)
end
else
if @tracker.config[:enabled]
requested_key = normalize_requested_key(key, options)
@tracker.write_to_child_pipe(requested_key)
end
end
rescue => e
@tracker.shutdown(e)
end
end
COVERBAND
HTTPS://GITHUB.COM/DANMAYER/COVERBAND
use Coverband::Middleware
module Coverband
class Middleware
!

def call(env)
configure_sampling
record_coverage
results = @app.call(env)
report_coverage
results
end
!

end
end
def configure_sampling
if (rand * 100.0) > @sample_percentage
@enabled = false
else
@enabled = true
end
end
def record_coverage
if @enabled
unless @function_set
set_trace_func proc { |event, file, line, id, binding, classname|
add_file(file, line)
}
@function_set = true
end
else
if @function_set
set_trace_func(nil)
@function_set = false
end
end
end
module Coverband
class Middleware
!

def call(env)
configure_sampling
record_coverage
results = @app.call(env)
report_coverage
results
end
!

end
end
<= NOT DAN MAYER
THE END
ALL THE ANALOGIES
META
THE END
Chris Morris
Sr. Engineer
@the_chrismo
CREDITS
http://www.flickr.com/photos/tracy_olson/61056391/
http://imgs.xkcd.com/comics/the_general_problem.png
http://upload.wikimedia.org/wikipedia/commons/a/a3/PikePlaceMarket2006.jpg
http://pixabay.com/en/why-question-gorilla-monkey-234596/
http://www.flickr.com/photos/liquidator/3450997601/in/photostream/
http://upload.wikimedia.org/wikipedia/commons/c/c2/EWM_shop_2007.jpg
http://upload.wikimedia.org/wikipedia/commons/5/52/Vader_2011_in_Rostock.jpg
http://upload.wikimedia.org/wikipedia/commons/4/4e/Construction_in_Toronto_May_2012.jpg
http://upload.wikimedia.org/wikipedia/commons/9/9a/SF_Japanese_Garden.JPG
http://upload.wikimedia.org/wikipedia/commons/3/32/Climbing_in_Joshua_Tree_NP.jpg
http://www.flickr.com/photos/pagedooley/7899921242/
http://upload.wikimedia.org/wikipedia/commons/f/f3/Symfonicky_orchestr_hl._m._Prahy_FOK.jpg
http://upload.wikimedia.org/wikipedia/commons/5/57/Chess-king.JPG
http://upload.wikimedia.org/wikipedia/commons/6/62/ElBulliKitchen.jpg
http://farm7.staticflickr.com/6020/5994637447_01d1e6107a_o.jpg
http://static3.wikia.nocookie.net/__cb20100721202821/muppet/images/5/52/
Bobby_Benson_and_the_Babies.jpg
http://upload.wikimedia.org/wikipedia/commons/f/fd/Detail_Lewis_%26_Clark_at_Three_Forks.jpg
http://upload.wikimedia.org/wikipedia/commons/e/e3/Lewis_and_Clark_track_map_published_1814_LoC.jpg
http://www.fhwa.dot.gov/byways/Uploads/asset_files/000/006/078/Rugged_Point.jpg
http://www.geograph.org.uk/photo/2415063
http://www.flickr.com/photos/18203311@N08/4405479417/
http://upload.wikimedia.org/wikipedia/commons/4/45/Thomas_Eakins,_The_Agnew_Clinic_1889.jpg
http://www.flickr.com/photos/theslowlane/6111718072/
Chris Morris
Sr. Engineer
@the_chrismo

More Related Content

Similar to Big Ruby 2014: A 4 Pack of Lightning Talks

Beyond php - it's not (just) about the code
Beyond php - it's not (just) about the codeBeyond php - it's not (just) about the code
Beyond php - it's not (just) about the codeWim Godden
 
MySQL/MariaDB query optimizer tuning tutorial from Percona Live 2013
MySQL/MariaDB query optimizer tuning tutorial from Percona Live 2013MySQL/MariaDB query optimizer tuning tutorial from Percona Live 2013
MySQL/MariaDB query optimizer tuning tutorial from Percona Live 2013Sergey Petrunya
 
Oracle Diagnostics : Explain Plans (Simple)
Oracle Diagnostics : Explain Plans (Simple)Oracle Diagnostics : Explain Plans (Simple)
Oracle Diagnostics : Explain Plans (Simple)Hemant K Chitale
 
2-1 Remember the Help Desk with AFCU - Jared Flanders, Final
2-1 Remember the Help Desk with AFCU - Jared Flanders, Final2-1 Remember the Help Desk with AFCU - Jared Flanders, Final
2-1 Remember the Help Desk with AFCU - Jared Flanders, FinalJared Flanders
 
M|18 Real-time Analytics with the New Streaming Data Adapters
M|18 Real-time Analytics with the New Streaming Data AdaptersM|18 Real-time Analytics with the New Streaming Data Adapters
M|18 Real-time Analytics with the New Streaming Data AdaptersMariaDB plc
 
Advanced Query Optimizer Tuning and Analysis
Advanced Query Optimizer Tuning and AnalysisAdvanced Query Optimizer Tuning and Analysis
Advanced Query Optimizer Tuning and AnalysisMYXPLAIN
 
Big Master Data PHP BLT #1
Big Master Data PHP BLT #1Big Master Data PHP BLT #1
Big Master Data PHP BLT #1Masahiro Nagano
 
Введение в современную PostgreSQL. Часть 2
Введение в современную PostgreSQL. Часть 2Введение в современную PostgreSQL. Часть 2
Введение в современную PostgreSQL. Часть 2Dzianis Pirshtuk
 
Rolling with the Times: Using wheels, pbr, and Twine for Distributing and Ins...
Rolling with the Times: Using wheels, pbr, and Twine for Distributing and Ins...Rolling with the Times: Using wheels, pbr, and Twine for Distributing and Ins...
Rolling with the Times: Using wheels, pbr, and Twine for Distributing and Ins...doughellmann
 
Observability of InfluxDB IOx: Tracing, Metrics and System Tables
Observability of InfluxDB IOx: Tracing, Metrics and System TablesObservability of InfluxDB IOx: Tracing, Metrics and System Tables
Observability of InfluxDB IOx: Tracing, Metrics and System TablesInfluxData
 
qmx_acknowledgement_dl_sql_ex.sql
qmx_acknowledgement_dl_sql_ex.sqlqmx_acknowledgement_dl_sql_ex.sql
qmx_acknowledgement_dl_sql_ex.sqlBob Werner
 
Nestle Quick Wins Digital Commerce Conversion Workshop
Nestle Quick Wins Digital Commerce Conversion WorkshopNestle Quick Wins Digital Commerce Conversion Workshop
Nestle Quick Wins Digital Commerce Conversion WorkshopMarcos Pueyrredon
 
Beyond PHP - it's not (just) about the code
Beyond PHP - it's not (just) about the codeBeyond PHP - it's not (just) about the code
Beyond PHP - it's not (just) about the codeWim Godden
 
Work in TDW
Work in TDWWork in TDW
Work in TDWsaso70
 
Database Development Replication Security Maintenance Report
Database Development Replication Security Maintenance ReportDatabase Development Replication Security Maintenance Report
Database Development Replication Security Maintenance Reportnyin27
 
Beyond PHP - It's not (just) about the code
Beyond PHP - It's not (just) about the codeBeyond PHP - It's not (just) about the code
Beyond PHP - It's not (just) about the codeWim Godden
 

Similar to Big Ruby 2014: A 4 Pack of Lightning Talks (20)

Beyond php - it's not (just) about the code
Beyond php - it's not (just) about the codeBeyond php - it's not (just) about the code
Beyond php - it's not (just) about the code
 
Dipping to MNP DB
Dipping to MNP DBDipping to MNP DB
Dipping to MNP DB
 
MySQL/MariaDB query optimizer tuning tutorial from Percona Live 2013
MySQL/MariaDB query optimizer tuning tutorial from Percona Live 2013MySQL/MariaDB query optimizer tuning tutorial from Percona Live 2013
MySQL/MariaDB query optimizer tuning tutorial from Percona Live 2013
 
Oracle Diagnostics : Explain Plans (Simple)
Oracle Diagnostics : Explain Plans (Simple)Oracle Diagnostics : Explain Plans (Simple)
Oracle Diagnostics : Explain Plans (Simple)
 
2-1 Remember the Help Desk with AFCU - Jared Flanders, Final
2-1 Remember the Help Desk with AFCU - Jared Flanders, Final2-1 Remember the Help Desk with AFCU - Jared Flanders, Final
2-1 Remember the Help Desk with AFCU - Jared Flanders, Final
 
M|18 Real-time Analytics with the New Streaming Data Adapters
M|18 Real-time Analytics with the New Streaming Data AdaptersM|18 Real-time Analytics with the New Streaming Data Adapters
M|18 Real-time Analytics with the New Streaming Data Adapters
 
Advanced Query Optimizer Tuning and Analysis
Advanced Query Optimizer Tuning and AnalysisAdvanced Query Optimizer Tuning and Analysis
Advanced Query Optimizer Tuning and Analysis
 
Big Master Data PHP BLT #1
Big Master Data PHP BLT #1Big Master Data PHP BLT #1
Big Master Data PHP BLT #1
 
MySQL SQL Tutorial
MySQL SQL TutorialMySQL SQL Tutorial
MySQL SQL Tutorial
 
Введение в современную PostgreSQL. Часть 2
Введение в современную PostgreSQL. Часть 2Введение в современную PostgreSQL. Часть 2
Введение в современную PostgreSQL. Часть 2
 
MySQL under the siege
MySQL under the siegeMySQL under the siege
MySQL under the siege
 
Rolling with the Times: Using wheels, pbr, and Twine for Distributing and Ins...
Rolling with the Times: Using wheels, pbr, and Twine for Distributing and Ins...Rolling with the Times: Using wheels, pbr, and Twine for Distributing and Ins...
Rolling with the Times: Using wheels, pbr, and Twine for Distributing and Ins...
 
Pro PostgreSQL
Pro PostgreSQLPro PostgreSQL
Pro PostgreSQL
 
Observability of InfluxDB IOx: Tracing, Metrics and System Tables
Observability of InfluxDB IOx: Tracing, Metrics and System TablesObservability of InfluxDB IOx: Tracing, Metrics and System Tables
Observability of InfluxDB IOx: Tracing, Metrics and System Tables
 
qmx_acknowledgement_dl_sql_ex.sql
qmx_acknowledgement_dl_sql_ex.sqlqmx_acknowledgement_dl_sql_ex.sql
qmx_acknowledgement_dl_sql_ex.sql
 
Nestle Quick Wins Digital Commerce Conversion Workshop
Nestle Quick Wins Digital Commerce Conversion WorkshopNestle Quick Wins Digital Commerce Conversion Workshop
Nestle Quick Wins Digital Commerce Conversion Workshop
 
Beyond PHP - it's not (just) about the code
Beyond PHP - it's not (just) about the codeBeyond PHP - it's not (just) about the code
Beyond PHP - it's not (just) about the code
 
Work in TDW
Work in TDWWork in TDW
Work in TDW
 
Database Development Replication Security Maintenance Report
Database Development Replication Security Maintenance ReportDatabase Development Replication Security Maintenance Report
Database Development Replication Security Maintenance Report
 
Beyond PHP - It's not (just) about the code
Beyond PHP - It's not (just) about the codeBeyond PHP - It's not (just) about the code
Beyond PHP - It's not (just) about the code
 

Recently uploaded

Snow Chain-Integrated Tire for a Safe Drive on Winter Roads
Snow Chain-Integrated Tire for a Safe Drive on Winter RoadsSnow Chain-Integrated Tire for a Safe Drive on Winter Roads
Snow Chain-Integrated Tire for a Safe Drive on Winter RoadsHyundai Motor Group
 
Artificial intelligence in the post-deep learning era
Artificial intelligence in the post-deep learning eraArtificial intelligence in the post-deep learning era
Artificial intelligence in the post-deep learning eraDeakin University
 
Bun (KitWorks Team Study 노별마루 발표 2024.4.22)
Bun (KitWorks Team Study 노별마루 발표 2024.4.22)Bun (KitWorks Team Study 노별마루 발표 2024.4.22)
Bun (KitWorks Team Study 노별마루 발표 2024.4.22)Wonjun Hwang
 
Connect Wave/ connectwave Pitch Deck Presentation
Connect Wave/ connectwave Pitch Deck PresentationConnect Wave/ connectwave Pitch Deck Presentation
Connect Wave/ connectwave Pitch Deck PresentationSlibray Presentation
 
My Hashitalk Indonesia April 2024 Presentation
My Hashitalk Indonesia April 2024 PresentationMy Hashitalk Indonesia April 2024 Presentation
My Hashitalk Indonesia April 2024 PresentationRidwan Fadjar
 
SQL Database Design For Developers at php[tek] 2024
SQL Database Design For Developers at php[tek] 2024SQL Database Design For Developers at php[tek] 2024
SQL Database Design For Developers at php[tek] 2024Scott Keck-Warren
 
Pigging Solutions in Pet Food Manufacturing
Pigging Solutions in Pet Food ManufacturingPigging Solutions in Pet Food Manufacturing
Pigging Solutions in Pet Food ManufacturingPigging Solutions
 
My INSURER PTE LTD - Insurtech Innovation Award 2024
My INSURER PTE LTD - Insurtech Innovation Award 2024My INSURER PTE LTD - Insurtech Innovation Award 2024
My INSURER PTE LTD - Insurtech Innovation Award 2024The Digital Insurer
 
Benefits Of Flutter Compared To Other Frameworks
Benefits Of Flutter Compared To Other FrameworksBenefits Of Flutter Compared To Other Frameworks
Benefits Of Flutter Compared To Other FrameworksSoftradix Technologies
 
Install Stable Diffusion in windows machine
Install Stable Diffusion in windows machineInstall Stable Diffusion in windows machine
Install Stable Diffusion in windows machinePadma Pradeep
 
Swan(sea) Song – personal research during my six years at Swansea ... and bey...
Swan(sea) Song – personal research during my six years at Swansea ... and bey...Swan(sea) Song – personal research during my six years at Swansea ... and bey...
Swan(sea) Song – personal research during my six years at Swansea ... and bey...Alan Dix
 
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 3652toLead Limited
 
Build your next Gen AI Breakthrough - April 2024
Build your next Gen AI Breakthrough - April 2024Build your next Gen AI Breakthrough - April 2024
Build your next Gen AI Breakthrough - April 2024Neo4j
 
Unlocking the Potential of the Cloud for IBM Power Systems
Unlocking the Potential of the Cloud for IBM Power SystemsUnlocking the Potential of the Cloud for IBM Power Systems
Unlocking the Potential of the Cloud for IBM Power SystemsPrecisely
 
Are Multi-Cloud and Serverless Good or Bad?
Are Multi-Cloud and Serverless Good or Bad?Are Multi-Cloud and Serverless Good or Bad?
Are Multi-Cloud and Serverless Good or Bad?Mattias Andersson
 
Bluetooth Controlled Car with Arduino.pdf
Bluetooth Controlled Car with Arduino.pdfBluetooth Controlled Car with Arduino.pdf
Bluetooth Controlled Car with Arduino.pdfngoud9212
 
SIEMENS: RAPUNZEL – A Tale About Knowledge Graph
SIEMENS: RAPUNZEL – A Tale About Knowledge GraphSIEMENS: RAPUNZEL – A Tale About Knowledge Graph
SIEMENS: RAPUNZEL – A Tale About Knowledge GraphNeo4j
 

Recently uploaded (20)

Snow Chain-Integrated Tire for a Safe Drive on Winter Roads
Snow Chain-Integrated Tire for a Safe Drive on Winter RoadsSnow Chain-Integrated Tire for a Safe Drive on Winter Roads
Snow Chain-Integrated Tire for a Safe Drive on Winter Roads
 
Artificial intelligence in the post-deep learning era
Artificial intelligence in the post-deep learning eraArtificial intelligence in the post-deep learning era
Artificial intelligence in the post-deep learning era
 
Bun (KitWorks Team Study 노별마루 발표 2024.4.22)
Bun (KitWorks Team Study 노별마루 발표 2024.4.22)Bun (KitWorks Team Study 노별마루 발표 2024.4.22)
Bun (KitWorks Team Study 노별마루 발표 2024.4.22)
 
Connect Wave/ connectwave Pitch Deck Presentation
Connect Wave/ connectwave Pitch Deck PresentationConnect Wave/ connectwave Pitch Deck Presentation
Connect Wave/ connectwave Pitch Deck Presentation
 
My Hashitalk Indonesia April 2024 Presentation
My Hashitalk Indonesia April 2024 PresentationMy Hashitalk Indonesia April 2024 Presentation
My Hashitalk Indonesia April 2024 Presentation
 
SQL Database Design For Developers at php[tek] 2024
SQL Database Design For Developers at php[tek] 2024SQL Database Design For Developers at php[tek] 2024
SQL Database Design For Developers at php[tek] 2024
 
Pigging Solutions in Pet Food Manufacturing
Pigging Solutions in Pet Food ManufacturingPigging Solutions in Pet Food Manufacturing
Pigging Solutions in Pet Food Manufacturing
 
DMCC Future of Trade Web3 - Special Edition
DMCC Future of Trade Web3 - Special EditionDMCC Future of Trade Web3 - Special Edition
DMCC Future of Trade Web3 - Special Edition
 
Vulnerability_Management_GRC_by Sohang Sengupta.pptx
Vulnerability_Management_GRC_by Sohang Sengupta.pptxVulnerability_Management_GRC_by Sohang Sengupta.pptx
Vulnerability_Management_GRC_by Sohang Sengupta.pptx
 
My INSURER PTE LTD - Insurtech Innovation Award 2024
My INSURER PTE LTD - Insurtech Innovation Award 2024My INSURER PTE LTD - Insurtech Innovation Award 2024
My INSURER PTE LTD - Insurtech Innovation Award 2024
 
Benefits Of Flutter Compared To Other Frameworks
Benefits Of Flutter Compared To Other FrameworksBenefits Of Flutter Compared To Other Frameworks
Benefits Of Flutter Compared To Other Frameworks
 
Install Stable Diffusion in windows machine
Install Stable Diffusion in windows machineInstall Stable Diffusion in windows machine
Install Stable Diffusion in windows machine
 
Swan(sea) Song – personal research during my six years at Swansea ... and bey...
Swan(sea) Song – personal research during my six years at Swansea ... and bey...Swan(sea) Song – personal research during my six years at Swansea ... and bey...
Swan(sea) Song – personal research during my six years at Swansea ... and bey...
 
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365
 
Build your next Gen AI Breakthrough - April 2024
Build your next Gen AI Breakthrough - April 2024Build your next Gen AI Breakthrough - April 2024
Build your next Gen AI Breakthrough - April 2024
 
Unlocking the Potential of the Cloud for IBM Power Systems
Unlocking the Potential of the Cloud for IBM Power SystemsUnlocking the Potential of the Cloud for IBM Power Systems
Unlocking the Potential of the Cloud for IBM Power Systems
 
Are Multi-Cloud and Serverless Good or Bad?
Are Multi-Cloud and Serverless Good or Bad?Are Multi-Cloud and Serverless Good or Bad?
Are Multi-Cloud and Serverless Good or Bad?
 
Bluetooth Controlled Car with Arduino.pdf
Bluetooth Controlled Car with Arduino.pdfBluetooth Controlled Car with Arduino.pdf
Bluetooth Controlled Car with Arduino.pdf
 
SIEMENS: RAPUNZEL – A Tale About Knowledge Graph
SIEMENS: RAPUNZEL – A Tale About Knowledge GraphSIEMENS: RAPUNZEL – A Tale About Knowledge Graph
SIEMENS: RAPUNZEL – A Tale About Knowledge Graph
 
Hot Sexy call girls in Panjabi Bagh 🔝 9953056974 🔝 Delhi escort Service
Hot Sexy call girls in Panjabi Bagh 🔝 9953056974 🔝 Delhi escort ServiceHot Sexy call girls in Panjabi Bagh 🔝 9953056974 🔝 Delhi escort Service
Hot Sexy call girls in Panjabi Bagh 🔝 9953056974 🔝 Delhi escort Service
 

Big Ruby 2014: A 4 Pack of Lightning Talks

  • 1.
  • 2.
  • 4. 4,000-plus-person company Almost $400 million in revenue About $1 billion in sales last year Hundreds of millions in the bank — TIM O'SHAUGHNESSY, CEO
 CNNMONEY - JAN 2014
  • 5. 4 PACK OF LIGHTNING TALKS
  • 7. LIVINGSOCIAL PAYMENTS • Credit Card, PayPal, V.me, MasterPass, International (Adyen, iPay88) • 1.5M API calls a day • 70k-150k credit card transactions a day
  • 8. LIVINGSOCIAL PAYMENTS • Whole Foods - 1,000,000 • Starbucks - 1,500,000
  • 9.
  • 10. irb(main):004:0> Transaction.find_all_by_person_id 2 => [#<SaleTransaction id: 5, credit_card_id: 47523892, reference_transaction_id: nil, amount: #<BigDecimal: 7fb60bc0ad78,'0.2E2',9(18)>, response: nil, transaction_type: "sale", aasm_state: "processed", created_at: "2014-02-04 19:42:57", updated_at: "2014-02-04 19:43:05", type: "SaleTransaction", callback_queue_name: "medium", callback_queue_application: "pry", options: "{"country":"US","profile":null,"requires_cvv ":null,...", processor_authorization_code: "illoearumi", gateway_transaction_id: "excepturiq", success: true, failure_reason: nil, avs_response_code: "M", cvv_response_code: "M", scope_identifier: nil, person_id: 2>, #<SaleTransaction id: 6, credit_card_id: 47523892, reference_transaction_id: nil, amount: #<BigDecimal:7fb60bc0a828,'0.2E2',9(18)>, response: nil, transaction_type: "sale", aasm_state: "processed", created_at: "2014-02-04 19:43:11", updated_at: "2014-02-04 19:43:20", type: "SaleTransaction", callback_queue_name: "medium", callback_queue_application: "pry", options: "{"country":"US", "profile":null,"requires_cvv":null,...", processor_authorization_code: "errorconse", gateway_transaction_id: "eumauttene", success: true, failure_reason: nil, avs_response_code: "M", cvv_response_code: "M", scope_identifier: nil, person_id: 2>]
  • 11. [159] pry(main)> b=PB.from_people_ids 2 => +----+-------------------------+------------+ | id | created_at | aasm_state | +----+-------------------------+------------+ | 5 | 2014-02-04 19:42:57 UTC | processed | | 6 | 2014-02-04 19:43:11 UTC | processed | +----+-------------------------+------------+
  • 12.
  • 13. irb(main):080:0> b.options .=> [{"country"=>"US", "profile"=>nil, "requires_cvv"=>nil, "order"=>nil, "priority"=>nil, "use_deal_bucks"=>nil, "requires_avs"=>nil, "charge_distribution_strategy"=>nil, "device_data"=>nil}, {"country"=>"US", "profile"=>nil, "requires_cvv"=>nil, "order"=>nil, "priority"=>nil, "use_deal_bucks"=>nil, "requires_avs"=>nil, "charge_distribution_strategy"=>nil, "device_data"=>nil}]
  • 14. [33] pry(main)> b.options => [{"country"=>"US", "profile"=>nil, "requires_cvv"=>nil, "order"=>nil, "priority"=>nil, "use_deal_bucks"=>nil, "requires_avs"=>nil, "charge_distribution_strategy"=>nil, "device_data"=>nil}, {"country"=>"US", "profile"=>nil, "requires_cvv"=>nil, "order"=>nil, "priority"=>nil, "use_deal_bucks"=>nil, "requires_avs"=>nil, "charge_distribution_strategy"=>nil, "device_data"=>nil}]
  • 15. class Batch < Array def initialize(ary) self << ary flatten! end ! ! def method_missing(meth_id, *args) self.map do |t| t.send(meth_id, *args) end end end
  • 16. [14] pry(main)> b.map { |t| t.aasm_state } => ["processed", "processed"] [15] pry(main)> b.map(&:type) => ["SaleTransaction", "RefundTransaction"]
  • 17. [11] pry(main)> b.aasm_state => ["processed", "processed"] [12] pry(main)> b.type => ["SaleTransaction", "RefundTransaction"]
  • 18. class PaymentsBatch < Batch def self.from_people_ids(ary) transactions = [ary].flatten.collect do |person_id| Transaction.find_all_by_person_id(person_id) end PaymentsBatch.new(transactions) end end ! PB = PaymentsBatch ! ! [50] pry(main)> b=PB.from_people_ids 2 => #<PaymentsBatch:0x3fd8eab40048>
  • 19.
  • 20. class PaymentsBatch < Batch def report puts self.map do |t| [t.id.to_s.ljust(5), t.created_at.to_s.ljust(25), t.aasm_state.to_s.ljust(15)].join end.join("n") end end ! ! [75] pry(main)> b.report 5 2014-02-04 19:42:57 UTC 6 2014-02-04 19:43:11 UTC => nil processed processed
  • 21. class PaymentsBatch < Batch def report self.map do |t| [t.id, t.created_at, t.aasm_state] end.to_text_table end end ! [22] pry(main)> b.report => +---+-------------------------+-----------+ | 5 | 2014-02-04 19:42:57 UTC | processed | | 6 | 2014-02-04 19:43:11 UTC | processed | | 7 | 2014-02-04 19:43:42 UTC | processed | +---+-------------------------+-----------+
  • 22.
  • 23. class PaymentsBatch < Batch def report cols = [:id, :created_at, :aasm_state] h = serializable_hash(only: cols) rows = ([h.first.keys] + h.map(&:values)) rows.to_table(:first_row_is_head => true) end end ! ! [87] pry(main)> b.report => +------------+-------------------------+----+ | aasm_state | created_at | id | +------------+-------------------------+----+ | processed | 2014-02-04 19:42:57 UTC | 5 | | processed | 2014-02-04 19:43:11 UTC | 6 | | processed | 2014-02-04 19:43:42 UTC | 7 | +------------+-------------------------+----+
  • 24. [155] pry(main)> b=PB.from_people_ids 2 => [#<SaleTransaction id: 5, credit_card_id: 47523892, reference_transaction_id: nil, amount: #<BigDecimal:7ff71ecc9b48,'0.2E2', 9(18)>, response: nil, transaction_type: "sale", aasm_state: "processed", created_at: "2014-02-04 19:42:57", updated_at: "2014-02-04 19:43:05", type: "SaleTransaction", callback_queue_name: "medium", callback_queue_application: "pry", options: "{"country":"US","profile ":null,"requires_cvv":null,...", processor_authorization_code: "illoearumi", gateway_transaction_id: "excepturiq", success: true, failure_reason: nil, avs_response_code: "M", cvv_response_code: "M", scope_identifier: nil, person_id: 2>, #<SaleTransaction id: 6, credit_card_id: 47523892, reference_transaction_id: nil, amount: #<BigDecimal:7ff71ecc9620,'0.2E2', 9(18)>, response: nil, transaction_type: "sale", aasm_state: "processed", created_at: "2014-02-04 19:43:11", updated_at: "2014-02-04 19:43:20", type: "SaleTransaction", callback_queue_name: "medium", callback_queue_application: "pry", options: "{"country":"US","profile ":null,"requires_cvv":null,...", processor_authorization_code: "errorconse", gateway_transaction_id: "eumauttene", success: true, failure_reason: nil, avs_response_code: "M", cvv_response_code: "M", scope_identifier: nil, person_id: 2>] [156] pry(main)> b.report .=> +----+-------------------------+------------+ | id | created_at | aasm_state | +----+-------------------------+------------+ | 5 | 2014-02-04 19:42:57 UTC | processed | | 6 | 2014-02-04 19:43:11 UTC | processed | +----+-------------------------+------------+ LOL
  • 25. class Batch < Array def pretty_inspect report.to_s end end
  • 26. [155] pry(main)> b=PB.from_people_ids 2 => +----+-------------------------+------------+ | id | created_at | aasm_state | +----+-------------------------+------------+ | 5 | 2014-02-04 19:42:57 UTC | processed | | 6 | 2014-02-04 19:43:11 UTC | processed | +----+-------------------------+------------+
  • 27. class PaymentsBatch def email addr = 'you@re.awesome.com' Mail::Message.new(to: addr, from: addr, subject: 'PaymentsBatch', body: report.to_s).deliver end end
  • 29. chris.morris@support01:$ RAILS_ENV=production bundle exec thor grep:payments -p '(PCI|SPT) environment' -d 1 ********** support01 ********** ssh support01 'grep -P "(PCI|SPT) environment" log/production.log-20140218' 14-02-18T02:48:20 - [INFO] (#21648) SPT environment forwarding #create to PCI environment 14-02-18T02:49:22 - [INFO] (#21651) SPT environment forwarding #create to PCI environment 14-02-18T03:00:04 - [INFO] (#21648) SPT environment forwarding #create to PCI environment ********** support02 ********** ssh support02 'grep -P "(PCI|SPT) environment" log/production.log-20140218' 14-02-17T15:00:50 - [INFO] (#3844) SPT environment forwarding #create to PCI environment 14-02-17T19:18:37 - [INFO] (#3841) SPT environment forwarding #create to PCI environment 14-02-17T22:12:38 - [INFO] (#3844) SPT environment forwarding #create to PCI environment 14-02-18T01:40:56 - [INFO] (#3844) SPT environment forwarding #create to PCI environment ********** boxen01 ********** ssh boxen01 'grep -P "(PCI|SPT) environment" log/production.log-20140218' 14-02-17T19:18:37 - [INFO] (#24618) PCI environment handling #create 14-02-18T01:40:56 - [INFO] (#24618) PCI environment handling #create 14-02-18T02:49:22 - [INFO] (#24615) PCI environment handling #create ********** boxen02 ********** ssh boxen02 'grep -P "(PCI|SPT) environment" log/production.log-20140218' 14-02-17T15:00:50 - [INFO] (#8119) PCI environment handling #create 14-02-17T22:12:38 - [INFO] (#8129) PCI environment handling #create 14-02-18T02:48:20 - [INFO] (#8125) PCI environment handling #create 14-02-18T03:00:04 - [INFO] (#8122) PCI environment handling #create
  • 30. chris.morris@support01:$ RAILS_ENV=production bundle exec thor grep:payments -p '(PCI|SPT) environment' -d 1 -c --pry ********** support01 ********** ssh support01 'grep -P -C 15 "(PCI|SPT) environment" log/production.log-20140218' ********** support02 ********** ssh support02 'grep -P -C 15 "(PCI|SPT) environment" log/production.log-20140218' ********** boxen01 ********** ssh boxen01 'grep -P -C 15 "(PCI|SPT) environment" log/production.log-20140218' ********** boxen02 ********** ssh boxen02 'grep -P -C 15 "(PCI|SPT) environment" log/production.log-20140218' +-------+---------------------+--------+-------------+---------------+ | pid | timestamp | action | status_code | ip_addr | +-------+---------------------+--------+-------------+---------------+ | 3844 | 2014-02-17T15:00:50 | POST | 201 | x.x.x.x | | 8119 | 2014-02-17T15:00:50 | POST | 201 | x.x.x.x | | 3841 | 2014-02-17T19:18:36 | POST | 500 | x.x.x.x | | 24618 | 2014-02-17T19:18:37 | POST | 500 | x.x.x.x | | 8129 | 2014-02-17T22:12:38 | POST | 201 | x.x.x.x | | 3844 | 2014-02-17T22:12:38 | POST | 201 | x.x.x.x | | 24618 | 2014-02-18T01:40:56 | POST | 201 | x.x.x.x | | 21648 | 2014-02-18T02:48:20 | POST | 201 | x.x.x.x | | 8125 | 2014-02-18T02:48:20 | POST | 201 | x.x.x.x | | 24615 | 2014-02-18T02:49:22 | POST | 400 | x.x.x.x | | 21651 | 2014-02-18T02:49:22 | POST | 500 | x.x.x.x | | 21648 | 2014-02-18T03:00:04 | POST | 201 | x.x.x.x | | 8122 | 2014-02-18T03:00:04 | POST | 201 | x.x.x.x | +-------+---------------------+--------+-------------+---------------+
  • 31. From: script/grep.payments.thor @ line 81 Thor::Sandbox::Grep#payments: ! ! 76: 77: 78: 79: 80: => 81: 82: 83: 84: 85: 86: wheelhouse = Wheelhouse.new(sio) chunks = wheelhouse.chunk_it(:grep => options[:pattern], :filename => options[:save_chunks], :range_threshold_in_seconds => options[:timestamp_range_in_seconds]) puts wheelhouse.to_text_table(chunks) binding.pry if options[:pry] end end private [1] pry(#<Thor::Sandbox::Grep>)>
  • 32. [1] pry(#<Thor::Sandbox::Grep>)> chunks => [#<RequestChunk:0x00000004f39540 @action="POST", @completed=true, @controller="Api::V1::CreditCardsController#create", @ip_addr="x.x.x.x", @lines= ["14-02-18T02:48:20 - [INFO] (#21648) Started POST ... for x.x.x.x with ...", "14-02-18T02:48:20 - [INFO] (#21648) Processing by Api::V1::CreditCards...", "14-02-18T02:48:20 - [INFO] (#21648) Parameters: {"..."", "14-02-18T02:48:20 - [INFO] (#21648) SPT environment forwarding #create...", "14-02-18T02:48:22 - [INFO] (#21648) Completed 201 Created in 1279.5ms ..."] @parameters= {"foo"=> {"token"=>"e4facf2d83cf58bd78b05485e21d0923", "checkout_resource_url"=>"https://foobar.com", "return_url"=>"https://www.livingsocial.com", "country_code"=>"AU"}, "person_id"=>"12345678"}, @pid="21648", @range_threshold_in_seconds=2, @referrer="", @route="http://...", @status_code="201", @timestamp="2014-02-18T02:48:20", @user_agent="Ruby">,
  • 33.
  • 35. DIY MOCKS AND FIXTURES
  • 36. RAVE REVIEWS "You either get an F for logic or an A+ for balls.” ! ! — J. B. Rainsberger author of JUnit Recipes
  • 37. [~]$ curl -s http://www.linkedin.com/in/chrismo | grep Relevance [~]$
  • 38.
  • 40. NOT GLENN VANDERBURG => ! CODMONGER !
  • 41. FactoryGirl.define do factory :customer do external_record_type 'person' sequence(:external_id) email Faker::Internet.email end ! ! ! ! factory :address do name Faker::Name.name customer end sequence :purchase_external_id sequence :purchase_deal_id factory :purchase do currency 'USD' external_record_type 'purchase' external_id { FactoryGirl.generate(:purchase_external_id) } deal_id { FactoryGirl.generate(:purchase_deal_id) } price { rand(50) + 1 } customer association :address, :method => :build quantity { rand(4) + 1 } title Faker::Company.catch_phrase end factory :collection do purchase end end
  • 42. describe Collection do it "should not return old exported collection with end status" do [:collected, :canceled, :refused].each do |status| collection = Factory.build(:collection, :exported_at => 4.days.ago, :status => status.to_s) assert !Collection.problem_old_exports.include?(collection) end end
  • 43. describe Collection do it "should not return old exported collection with end status" do [:collected, :canceled, :refused].each do |status| collection = Factory.build(:collection, :exported_at => 4.days.ago, :status => status.to_s) assert !Collection.problem_old_exports.include?(collection) end end end NoMethodError: undefined method `customer' for nil:NilClass ./app/models/collection.rb:90:in `customer' ./app/models/collection.rb:14:in `block in <class:Collection>' ./spec/models/collection_spec.rb:43:in `block (3 levels) in <top (required)>' ./spec/models/collection_spec.rb:42:in `each' ./spec/models/collection_spec.rb:42:in `block (2 levels) in <top (required)>'
  • 44. class Collection < ActiveRecord::Base def customer purchase.customer end end NoMethodError: undefined method `customer' for nil:NilClass ./app/models/collection.rb:90:in `customer' ./app/models/collection.rb:14:in `block in <class:Collection>' ./spec/models/collection_spec.rb:43:in `block (3 levels) in <top (required)>' ./spec/models/collection_spec.rb:42:in `each' ./spec/models/collection_spec.rb:42:in `block (2 levels) in <top (required)>'
  • 45. FactoryGirl.define do ... # YOU HAD ONE JOB factory :collection do purchase end end
  • 46. require 'machinist/active_record' require 'faker' ! Sham.sham_id { |i| i } ! Customer.blueprint do external_record_type { 'person' } external_id { Sham.sham_id } email { Faker::Internet.email } end ! Address.blueprint do name Faker::Name.name customer purchase end ! Purchase.blueprint do currency {'USD'} external_record_type {'purchase'} external_id { Sham.sham_id } deal_id { Sham.sham_id } price { rand(50) + 1 } customer quantity { rand(4) + 1 } title { Faker::Company.catch_phrase } end ! Collection.blueprint do purchase end
  • 47. describe Collection, 'scope :problem_old_exports (machinist)' do it "should not return old exported collection with end status" do [:collected, :canceled, :refused].each do |status| collection = Collection.make_unsaved(:exported_at => 4.days.ago, :status => status.to_s) assert !Collection.problem_old_exports.include?(collection) end end end
  • 48. describe Collection, 'scope :problem_old_exports (machinist)' do it "should not return old exported collection with end status" do [:collected, :canceled, :refused].each do |status| collection = Collection.make_unsaved(:exported_at => 4.days.ago, :status => status.to_s) assert !Collection.problem_old_exports.include?(collection) end end end ! NoMethodError: undefined method `customer' for nil:NilClass ./app/models/collection.rb:90:in `customer' ./app/models/collection.rb:14:in `block in <class:Collection>' ./spec/models/collection_spec.rb:61:in `block (3 levels) in <top (required)>' ./spec/models/collection_spec.rb:60:in `each' ./spec/models/collection_spec.rb:60:in `block (2 levels) in <top (required)>'
  • 50.
  • 51.
  • 52. require 'faker' ! class CodmongerFixture def self.create(opts={}) record = self.new(opts); record.save!; record end end ! class AddressFixture < CodmongerFixture def self.new(opts={}) Address.new({:name => Faker::Name.name}.merge(opts)) end end ! class CustomerFixture < CodmongerFixture @@customer_id = Customer.last.external_id + 1 rescue 1 def self.new(opts={}) Customer.new({:external_record_type => 'person', :external_id => @@customer_id += 1, :email => Faker::Internet.email}.merge(opts)) end end ! class PurchaseFixture < CodmongerFixture @@purchase_id = Purchase.last.external_id + 1 rescue 1 def self.new(opts={}) customer = CustomerFixture.new Purchase.new({:currency => 'USD', :deal_id => 1, :external_record_type => 'purchase', :external_id => @@purchase_id += 1, :price => rand(50) + 1, :quantity => rand(4) + 1, :title => Faker::Company.catch_phrase, :address => AddressFixture.new(:customer => customer), :customer => customer}.merge(opts)) end end ! class CollectionFixture < CodmongerFixture def self.new(opts={}) Collection.new({:purchase => PurchaseFixture.new}.merge(opts)) end end
  • 53. describe Collection do it "should not return old exported collection with end status" do [:collected, :canceled, :refused].each do |status| collection = CollectionFixture.new(:exported_at => 4.days.ago, :status => status.to_s) assert !Collection.problem_old_exports.include?(collection) end end end 1 example, 0 failures, 1 passed
  • 54.
  • 55. FactoryGirl::Proxy::ObjectWrapper def object @object ||= @klass.new assign_object_attributes @object end
  • 56. class Collection < ActiveRecord::Base state_machine :status, :initial => lambda{ |collection| collection.customer.new? ? :received : :customer_verified} do ... end end
  • 57.
  • 58. ACTIVE RECORD IS HARD ENOUGH ! ! I DON’T NEED YOUR FREEKING DSL ON TOP OF IT
  • 59.
  • 60.
  • 61. require 'faker' ! class CodmongerFixture def self.create(opts={}) record = self.new(opts); record.save!; record end end ! class AddressFixture < CodmongerFixture def self.new(opts={}) Address.new({:name => Faker::Name.name}.merge(opts)) end end ! class CustomerFixture < CodmongerFixture @@customer_id = Customer.last.external_id + 1 rescue 1 def self.new(opts={}) Customer.new({:external_record_type => 'person', :external_id => @@customer_id += 1, :email => Faker::Internet.email}.merge(opts)) end end ! class PurchaseFixture < CodmongerFixture @@purchase_id = Purchase.last.external_id + 1 rescue 1 def self.new(opts={}) customer = CustomerFixture.new Purchase.new({:currency => 'USD', :deal_id => 1, :external_record_type => 'purchase', :external_id => @@purchase_id += 1, :price => rand(50) + 1, :quantity => rand(4) + 1, :title => Faker::Company.catch_phrase, :address => AddressFixture.new(:customer => customer), :customer => customer}.merge(opts)) end end ! class CollectionFixture < CodmongerFixture def self.new(opts={}) Collection.new({:purchase => PurchaseFixture.new}.merge(opts)) end end
  • 62. FactoryGirl.define do factory :customer do external_record_type 'person' sequence(:external_id) email Faker::Internet.email end ! ! ! ! factory :address do name Faker::Name.name customer end sequence :purchase_external_id sequence :purchase_deal_id factory :purchase do currency 'USD' external_record_type 'purchase' external_id { FactoryGirl.generate(:purchase_external_id) } deal_id { FactoryGirl.generate(:purchase_deal_id) } price { rand(50) + 1 } customer association :address, :method => :build quantity { rand(4) + 1 } title Faker::Company.catch_phrase end factory :collection do purchase end end
  • 63. require 'faker' ! class CodmongerFixture def self.create(opts={}) record = self.new(opts); record.save!; record end end ! class AddressFixture < CodmongerFixture def self.new(opts={}) Address.new({:name => Faker::Name.name}.merge(opts)) end end ! class CustomerFixture < CodmongerFixture @@customer_id = Customer.last.external_id + 1 rescue 1 def self.new(opts={}) Customer.new({:external_record_type => 'person', :external_id => @@customer_id += 1, :email => Faker::Internet.email}.merge(opts)) end end
  • 64. class PurchaseFixture < CodmongerFixture @@purchase_id = Purchase.last.external_id + 1 rescue 1 def self.new(opts={}) customer = CustomerFixture.new Purchase.new({:currency => 'USD', :deal_id => 1, :external_record_type => 'purchase', :external_id => @@purchase_id += 1, :price => rand(50) + 1, :quantity => rand(4) + 1, :title => Faker::Company.catch_phrase, :address => AddressFixture.new(:customer => customer), :customer => customer}.merge(opts)) end end ! class CollectionFixture < CodmongerFixture def self.new(opts={}) Collection.new({:purchase => PurchaseFixture.new}.merge(opts)) end end
  • 65. WHEN A DSL POOPS OUT ON YOU ! YOU CAN'T LOOK AT ITS SOURCE CODE ! BECAUSE IT'S ALL META AND STUFF.
  • 66. MOCK FRAMEWORKS NOT ALL EXPECTATIONS WERE SATISFIED
  • 67. not all expectations were satisfied unsatisfied expectations: - expected exactly once, not yet invoked: Purchase(id: integer, person_id: integer, deal_id: integer, created_at: datetime, updated_at: datetime, credit_card_id: integer, ref_code: string, charged_at: datetime, coupons_count: integer, aasm_state: string, approval_code: string, referring_purchase_id: integer, referring_purchases_count: integer, discount: decimal, referring_user_id: integer, last_transaction_id: string, collapsed: boolean, blasted_to_feed_at: datetime, fb_fan_incentive: decimal, deleted_at: datetime, latitude: float, longitude: float, ip_address: string, visa_discount: decimal, purchaser_id: integer, cheerios_discount: decimal, city_id: integer).find('1234567')
  • 68. unexpected invocation: #<Mock:0x1022aff50>.reload() satisfied expectations: - allowed any number of times, not yet invoked: #<Mock:0x1022b6df0>.available_credit(any_parameters) - allowed any number of times, not yet invoked: #<Mock:0x1022b6df0>.available_credit(any_parameters) - allowed any number of times, not yet invoked: #<Mock:0x1022b6df0>.Person(any_parameters) - allowed any number of times, not yet invoked: #<Mock:0x1022b57e8>.LocalDeal(any_parameters) - allowed any number of times, not yet invoked: #<Mock:0x1022aff50>.Purchase(any_parameters) - allowed any number of times, not yet invoked: #<Mock:0x1022aff50>.total_price(any_parameters) - allowed any number of times, not yet invoked: #<Mock:0x1022aff50>.alternate_payment(any_parameters) - allowed any number of times, invoked once: #<Mock:0x1022aff50>.person(any_parameters) - allowed any number of times, invoked once: #<Mock:0x1022aff50>.deal(any_parameters) - allowed any number of times, not yet invoked: #<Mock:0x1022aff50>.aasm_state(any_parameters) - allowed any number of times, not yet invoked: #<Mock:0x1022aff50>.id(any_parameters) - allowed any number of times, invoked once: #<Mock:0x1022aff50>.referred_purchases(any_parameters)
  • 69. purchase = stub() purchase.stubs(:id).returns(123) purchase.stubs(:quantity).returns(2) ! deal = stub() deal.stubs(:title).returns("Mr. Bones's Deal'") deal.stubs(:price).returns(12) deal.stubs(:currency_unit).returns('$') ! person = stub() person.stubs(:email).returns('chris.morris@livingsocial.com') person.stubs(:id).returns(8675309) ! purchase.stubs(:person).returns(person) purchase.stubs(:deal).returns(deal)
  • 70. PurchaseStub = Struct.new(:id, :quantity, :deal, :person) DealStub = Struct.new(:title, :price, :currency_unit) PersonStub = Struct.new(:id, :email) ! person = PersonStub.new(8675309, 'chrismo@ls.com') deal = DealStub.new("Mr. Bones's Deal'", 12, '$') purchase = PurchaseStub.new(123, 2, deal, person)
  • 71. person = OpenStruct.new(id: 8675309, email: 'chrismo@ls.com') ! deal = OpenStruct.new(title: "Mr. Bones's Deal'", price: 12, currency_unit: '$') ! purchase = OpenStruct.new(id: 123, quantity: 2, deal: deal, person: person)
  • 72. require 'ostruct' ! person = OpenStruct.new(id: 8675309, email: 'chrismo@clabs.org', available_credit: {'USD'=>5,'MYR'=>0}) ! def person.available_credit_by_currency(currency) self.available_credit[currency] end
  • 73. module PaymentProviders module DoubleFake class Payment @@db = {} ! ! ! ! def self.load_payment(id, params) @@db[id] end def initialize(purchase, hash, all_params={}) @hash = hash end def address @hash[:address] end def save @@db[@hash[:id]] = self end end end end
  • 77.
  • 78.
  • 80. TRACK YER BIG STUFF
  • 81. No tengo ni idea de lo que estoy haciendo
  • 83. dozens of translate calls / request
  • 84.
  • 85.
  • 86. module Humperdink class DirtySet attr_reader :dirty, :clean, :config ! ! ! ! ! ! def initialize(initial_contents=[], config={}) @clean = Set.new(initial_contents) @dirty = Set.new @config = config end def <<(value) @dirty << value if !@clean.include?(value) clean! if getting_messy? end def getting_messy? ... end def clean! @clean.merge(@dirty) cleaned = @dirty.to_a.dup @dirty.clear cleaned end def length # wonky -> doesn't include @dirty @clean.length end def clear @clean.clear @dirty.clear end end end
  • 87. describe Humperdink::DirtySet do let(:set) { Humperdink::DirtySet.new } ! it 'should only append to dirty' do set << ‘foo' set.dirty.should == ['foo'].to_set set.clean.should == [].to_set end ! it 'should clean! dirty things to clean' do set << 'foo' wuz_dirty = set.clean! wuz_dirty.should == [‘foo'] set.dirty.should == [].to_set set.clean.should == ['foo'].to_set end end
  • 88. it 'should only append to dirty if not in clean' do set << ‘foo' set.dirty.should == ['foo'].to_set set.clean.should == [].to_set ! set.clean! set.dirty.should == [].to_set set.clean.should == ['foo'].to_set ! end set << ‘foo' set.dirty.should == [].to_set set.clean.should == ['foo'].to_set
  • 89. it 'should initialize clean with initialize' do set = Humperdink::DirtySet.new(['a', ‘b']) set.dirty.should == [].to_set set.clean.should == ['a', 'b'].to_set end ! it 'should only append to dirty if not in clean' do set = Humperdink::DirtySet.new(['foo']) set << ‘foo' set.dirty.should == [].to_set set.clean.should == ['foo'].to_set end
  • 90. module Humperdink class RedisDirtySet < DirtySet def initialize(initial_content=[], config={}) super(initial_content, config) end ! def clean! to_save = super save(to_save) end ! def load @clean.merge(redis.smembers(@config[:key])) end ! def save(to_save) redis.sadd(@config[:key], to_save.to_a) end end end
  • 91. module Humperdink class Tracker attr_reader :set, :config ! ! ! ! def initialize(set=Set.new, config={}) @set = set @config = config end def track(data) @set << data end def tracker_enabled @config[:enabled] end # Anytime an exception happens, we want to skedaddle out of the way # and let life roll on without any tracking in the loop. def shutdown(exception) begin on_event(:shutdown, "#{exception.message}") rescue => e $stderr.puts([e.message, e.backtrace].join("n")) rescue nil end @config[:enabled] = false end end end
  • 92. class KeyTracker def initialize(redis, key) redis_set = Humperdink::RedisDirtySet.new(:redis => redis, :key => key, :max_dirty_items => 9) @tracker = Humperdink::Tracker.new(redis_set, :enabled => true) end ! def on_translate(locale, key, options = {}) begin if @tracker.tracker_enabled requested_key = normalize_requested_key(key, options) @tracker.track(requested_key) end rescue => e @tracker.shutdown(e) end end end
  • 93. module KeyTrackerBackend def key_tracker @key_tracker end ! ! def key_tracker=(value) @key_tracker = value end def translate(locale, key, options = {}) @key_tracker.on_translate(locale, key, options) if @key_tracker super end end ! def setup @redis = Redis.connect(:url => 'redis://127.0.0.1:6379/8') @redis_key = 'humperdink:example:i18n' ! tracker = KeyTracker.new(@redis, @redis_key) I18n.backend.class.class_eval { include KeyTrackerBackend } I18n.backend.key_tracker = tracker end
  • 95. module Humperdink class DirtySet ! def <<(value) @dirty << value if !@clean.include?(value) clean! if getting_messy? end ! end end
  • 96. module Humperdink class DirtySet ! def getting_messy? if @config[:max_dirty_items] && @dirty.length > @config[:max_dirty_items] return true end ! if @config[:clean_timeout] && Time.now > @time_to_clean return true end end ! end end
  • 97. module Humperdink class DirtySet def initialize(initial_contents=[], config={}) @clean = Set.new(initial_contents) @dirty = Set.new @config = config ... at_exit { clean! if @config[:clean_at_exit] } end end end
  • 99. module Resque class Worker def work(interval = 5.0, &block) loop do break if shutdown? ! if not paused? and job = reserve if @child = fork(job) ... else perform(job, &block) exit!(true) if will_fork? end ! done_working @child = nil else sleep interval end end end end end
  • 101. class KeyTracker def initialize(redis, key) redis_set = Humperdink::RedisDirtySet.new(:redis => redis, :key => key, :max_dirty_items => 9) @tracker = Humperdink::Tracker.new(redis_set, :enabled => true) @tracker.include Humperdink::ForkPiping end end
  • 102. def on_translate(locale, key, options = {}) begin if @tracker.tracker_enabled requested_key = normalize_requested_key(key, options) @tracker.track(requested_key) end rescue => e @tracker.shutdown(e) end end
  • 103. def on_translate(locale, key, options = {}) begin if @tracker.not_forked_or_parent_process if @tracker.tracker_enabled requested_key = normalize_requested_key(key, options) @tracker.track(requested_key) end else if @tracker.config[:enabled] requested_key = normalize_requested_key(key, options) @tracker.write_to_child_pipe(requested_key) end end rescue => e @tracker.shutdown(e) end end
  • 104.
  • 107. module Coverband class Middleware ! def call(env) configure_sampling record_coverage results = @app.call(env) report_coverage results end ! end end
  • 108. def configure_sampling if (rand * 100.0) > @sample_percentage @enabled = false else @enabled = true end end
  • 109. def record_coverage if @enabled unless @function_set set_trace_func proc { |event, file, line, id, binding, classname| add_file(file, line) } @function_set = true end else if @function_set set_trace_func(nil) @function_set = false end end end
  • 110. module Coverband class Middleware ! def call(env) configure_sampling record_coverage results = @app.call(env) report_coverage results end ! end end
  • 111.
  • 112. <= NOT DAN MAYER
  • 115.
  • 116.
  • 117.
  • 118.
  • 119.
  • 120.
  • 121.
  • 122.
  • 123.
  • 124.
  • 125.
  • 126.
  • 127.
  • 128.
  • 129.
  • 130.
  • 131.
  • 132. META
  • 133.
  • 134.
  • 137. CREDITS http://www.flickr.com/photos/tracy_olson/61056391/ http://imgs.xkcd.com/comics/the_general_problem.png http://upload.wikimedia.org/wikipedia/commons/a/a3/PikePlaceMarket2006.jpg http://pixabay.com/en/why-question-gorilla-monkey-234596/ http://www.flickr.com/photos/liquidator/3450997601/in/photostream/ http://upload.wikimedia.org/wikipedia/commons/c/c2/EWM_shop_2007.jpg http://upload.wikimedia.org/wikipedia/commons/5/52/Vader_2011_in_Rostock.jpg http://upload.wikimedia.org/wikipedia/commons/4/4e/Construction_in_Toronto_May_2012.jpg http://upload.wikimedia.org/wikipedia/commons/9/9a/SF_Japanese_Garden.JPG http://upload.wikimedia.org/wikipedia/commons/3/32/Climbing_in_Joshua_Tree_NP.jpg http://www.flickr.com/photos/pagedooley/7899921242/ http://upload.wikimedia.org/wikipedia/commons/f/f3/Symfonicky_orchestr_hl._m._Prahy_FOK.jpg http://upload.wikimedia.org/wikipedia/commons/5/57/Chess-king.JPG http://upload.wikimedia.org/wikipedia/commons/6/62/ElBulliKitchen.jpg http://farm7.staticflickr.com/6020/5994637447_01d1e6107a_o.jpg http://static3.wikia.nocookie.net/__cb20100721202821/muppet/images/5/52/ Bobby_Benson_and_the_Babies.jpg http://upload.wikimedia.org/wikipedia/commons/f/fd/Detail_Lewis_%26_Clark_at_Three_Forks.jpg http://upload.wikimedia.org/wikipedia/commons/e/e3/Lewis_and_Clark_track_map_published_1814_LoC.jpg http://www.fhwa.dot.gov/byways/Uploads/asset_files/000/006/078/Rugged_Point.jpg http://www.geograph.org.uk/photo/2415063 http://www.flickr.com/photos/18203311@N08/4405479417/ http://upload.wikimedia.org/wikipedia/commons/4/45/Thomas_Eakins,_The_Agnew_Clinic_1889.jpg http://www.flickr.com/photos/theslowlane/6111718072/