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
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
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
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)>'
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)>'
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
56. class Collection < ActiveRecord::Base
state_machine :status, :initial => lambda{ |collection|
collection.customer.new? ? :received : :customer_verified} do
...
end
end
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
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)
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
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
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