big ruby 2014: a 4 pack of lightning talks

138

Upload: thechrismo

Post on 16-Apr-2017

825 views

Category:

Technology


0 download

TRANSCRIPT

Page 1: Big Ruby 2014: A 4 Pack of Lightning Talks
Page 2: Big Ruby 2014: A 4 Pack of Lightning Talks
Page 3: Big Ruby 2014: A 4 Pack of Lightning Talks

Chris Morris Sr. Engineer

@the_chrismo

Page 4: Big Ruby 2014: A 4 Pack of Lightning Talks

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

Page 5: Big Ruby 2014: A 4 Pack of Lightning Talks

4 PACK OF LIGHTNING TALKS

Page 6: Big Ruby 2014: A 4 Pack of Lightning Talks

COBBLER’S PRODUCTION CONSOLE HAS NO SHOES

Page 7: Big Ruby 2014: A 4 Pack of Lightning Talks

LIVINGSOCIAL PAYMENTS

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

• 1.5M API calls a day

• 70k-150k credit card transactions a day

Page 8: Big Ruby 2014: A 4 Pack of Lightning Talks

LIVINGSOCIAL PAYMENTS

• Whole Foods - 1,000,000

• Starbucks - 1,500,000

Page 9: Big Ruby 2014: A 4 Pack of Lightning Talks
Page 10: Big Ruby 2014: A 4 Pack of Lightning Talks

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>]

Page 11: Big Ruby 2014: A 4 Pack of Lightning Talks

[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 | +----+-------------------------+------------+

Page 12: Big Ruby 2014: A 4 Pack of Lightning Talks
Page 13: Big Ruby 2014: A 4 Pack of Lightning Talks

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}]

Page 14: Big Ruby 2014: A 4 Pack of Lightning Talks

[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}]

Page 15: Big Ruby 2014: A 4 Pack of Lightning Talks

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

Page 16: Big Ruby 2014: A 4 Pack of Lightning Talks

[14] pry(main)> b.map { |t| t.aasm_state } => ["processed", "processed"] [15] pry(main)> b.map(&:type) => ["SaleTransaction", "RefundTransaction"]

Page 17: Big Ruby 2014: A 4 Pack of Lightning Talks

[11] pry(main)> b.aasm_state => ["processed", "processed"] [12] pry(main)> b.type => ["SaleTransaction", "RefundTransaction"]

Page 18: Big Ruby 2014: A 4 Pack of Lightning Talks

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>

Page 19: Big Ruby 2014: A 4 Pack of Lightning Talks
Page 20: Big Ruby 2014: A 4 Pack of Lightning Talks

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 processed 6 2014-02-04 19:43:11 UTC processed => nil

Page 21: Big Ruby 2014: A 4 Pack of Lightning Talks

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 | +---+-------------------------+-----------+

Page 22: Big Ruby 2014: A 4 Pack of Lightning Talks
Page 23: Big Ruby 2014: A 4 Pack of Lightning Talks

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 | +------------+-------------------------+----+

Page 24: Big Ruby 2014: A 4 Pack of Lightning Talks

[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

Page 25: Big Ruby 2014: A 4 Pack of Lightning Talks

class Batch < Array def pretty_inspect report.to_s end end

Page 26: Big Ruby 2014: A 4 Pack of Lightning Talks

[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 | +----+-------------------------+------------+

Page 27: Big Ruby 2014: A 4 Pack of Lightning Talks

class PaymentsBatch def email addr = '[email protected]' Mail::Message.new(to: addr, from: addr, subject: 'PaymentsBatch', body: report.to_s).deliver end end

Page 28: Big Ruby 2014: A 4 Pack of Lightning Talks

LOG SEARCH

Page 29: Big Ruby 2014: A 4 Pack of Lightning Talks

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

Page 30: Big Ruby 2014: A 4 Pack of Lightning Talks

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 | +-------+---------------------+--------+-------------+---------------+

Page 31: Big Ruby 2014: A 4 Pack of Lightning Talks

From: script/grep.payments.thor @ line 81 Thor::Sandbox::Grep#payments: ! 76: wheelhouse = Wheelhouse.new(sio) 77: chunks = wheelhouse.chunk_it(:grep => options[:pattern], 78: :filename => options[:save_chunks], 79: :range_threshold_in_seconds => options[:timestamp_range_in_seconds]) 80: puts wheelhouse.to_text_table(chunks) => 81: binding.pry if options[:pry] 82: end 83: end 84: 85: private 86: ![1] pry(#<Thor::Sandbox::Grep>)>

Page 32: Big Ruby 2014: A 4 Pack of Lightning Talks

[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">,

Page 33: Big Ruby 2014: A 4 Pack of Lightning Talks
Page 34: Big Ruby 2014: A 4 Pack of Lightning Talks

THE END

Page 35: Big Ruby 2014: A 4 Pack of Lightning Talks

DIY MOCKS AND FIXTURES

Page 36: Big Ruby 2014: A 4 Pack of Lightning Talks

RAVE REVIEWS

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

!

!

— J. B. Rainsberger author of JUnit Recipes

Page 37: Big Ruby 2014: A 4 Pack of Lightning Talks

[~]$ curl -s http://www.linkedin.com/in/chrismo | grep Relevance [~]$

Page 38: Big Ruby 2014: A 4 Pack of Lightning Talks
Page 39: Big Ruby 2014: A 4 Pack of Lightning Talks

NOT GLENN VANDERBURG =>

Page 40: Big Ruby 2014: A 4 Pack of Lightning Talks

NOT GLENN VANDERBURG =>

!

CODMONGER !

Page 41: Big Ruby 2014: A 4 Pack of Lightning Talks

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

Page 42: Big Ruby 2014: A 4 Pack of Lightning Talks

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

Page 43: Big Ruby 2014: A 4 Pack of Lightning Talks

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)>'

Page 44: Big Ruby 2014: A 4 Pack of Lightning Talks

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)>'

Page 45: Big Ruby 2014: A 4 Pack of Lightning Talks

FactoryGirl.define do ... # YOU HAD ONE JOB factory :collection do purchase end end

Page 46: Big Ruby 2014: A 4 Pack of Lightning Talks

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

Page 47: Big Ruby 2014: A 4 Pack of Lightning Talks

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

Page 48: Big Ruby 2014: A 4 Pack of Lightning Talks

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)>'

Page 49: Big Ruby 2014: A 4 Pack of Lightning Talks

!# ONE. JOB. Collection.blueprint do purchase end

Page 50: Big Ruby 2014: A 4 Pack of Lightning Talks
Page 51: Big Ruby 2014: A 4 Pack of Lightning Talks
Page 52: Big Ruby 2014: A 4 Pack of Lightning Talks

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

Page 53: Big Ruby 2014: A 4 Pack of Lightning Talks

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

Page 54: Big Ruby 2014: A 4 Pack of Lightning Talks
Page 55: Big Ruby 2014: A 4 Pack of Lightning Talks

FactoryGirl::Proxy::ObjectWrapper

def object @object ||= @klass.new assign_object_attributes @object end

Page 56: Big Ruby 2014: A 4 Pack of Lightning Talks

class Collection < ActiveRecord::Base state_machine :status, :initial => lambda{ |collection| collection.customer.new? ? :received : :customer_verified} do ... end end

Page 57: Big Ruby 2014: A 4 Pack of Lightning Talks
Page 58: Big Ruby 2014: A 4 Pack of Lightning Talks

ACTIVE RECORD IS

HARD ENOUGH !

!

I DON’T NEED YOUR FREEKING

DSL ON TOP OF IT

Page 59: Big Ruby 2014: A 4 Pack of Lightning Talks
Page 60: Big Ruby 2014: A 4 Pack of Lightning Talks
Page 61: Big Ruby 2014: A 4 Pack of Lightning Talks

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

Page 62: Big Ruby 2014: A 4 Pack of Lightning Talks

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

Page 63: Big Ruby 2014: A 4 Pack of Lightning Talks

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

Page 64: Big Ruby 2014: A 4 Pack of Lightning Talks

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

Page 65: Big Ruby 2014: A 4 Pack of Lightning Talks

WHEN A DSL POOPS OUT ON YOU

!

YOU CAN'T LOOK AT ITS SOURCE CODE

!

BECAUSE IT'S ALL META AND STUFF.

Page 66: Big Ruby 2014: A 4 Pack of Lightning Talks

MOCK FRAMEWORKSNOT ALL EXPECTATIONS WERE SATISFIED

Page 67: Big Ruby 2014: A 4 Pack of Lightning Talks

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')

Page 68: Big Ruby 2014: A 4 Pack of Lightning Talks

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)

Page 69: Big Ruby 2014: A 4 Pack of Lightning Talks

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('[email protected]') person.stubs(:id).returns(8675309) !purchase.stubs(:person).returns(person) purchase.stubs(:deal).returns(deal)

Page 70: Big Ruby 2014: A 4 Pack of Lightning Talks

PurchaseStub = Struct.new(:id, :quantity, :deal, :person) DealStub = Struct.new(:title, :price, :currency_unit) PersonStub = Struct.new(:id, :email) !person = PersonStub.new(8675309, '[email protected]') deal = DealStub.new("Mr. Bones's Deal'", 12, '$') purchase = PurchaseStub.new(123, 2, deal, person)

Page 71: Big Ruby 2014: A 4 Pack of Lightning Talks

person = OpenStruct.new(id: 8675309, email: '[email protected]') !deal = OpenStruct.new(title: "Mr. Bones's Deal'", price: 12, currency_unit: '$') !purchase = OpenStruct.new(id: 123, quantity: 2, deal: deal, person: person)

Page 72: Big Ruby 2014: A 4 Pack of Lightning Talks

require 'ostruct' !person = OpenStruct.new(id: 8675309, email: '[email protected]', available_credit: {'USD'=>5,'MYR'=>0}) !def person.available_credit_by_currency(currency) self.available_credit[currency] end

Page 73: Big Ruby 2014: A 4 Pack of Lightning Talks

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

Page 74: Big Ruby 2014: A 4 Pack of Lightning Talks

DIY

Gem

Page 75: Big Ruby 2014: A 4 Pack of Lightning Talks

BUILD —

BUY

Page 76: Big Ruby 2014: A 4 Pack of Lightning Talks

BUYtrades in

FAMILIARITY

TRANSPARENCY

Page 77: Big Ruby 2014: A 4 Pack of Lightning Talks
Page 78: Big Ruby 2014: A 4 Pack of Lightning Talks
Page 79: Big Ruby 2014: A 4 Pack of Lightning Talks

THE END

Page 80: Big Ruby 2014: A 4 Pack of Lightning Talks

TRACK YER BIG STUFF

Page 81: Big Ruby 2014: A 4 Pack of Lightning Talks

No tengo ni idea de lo que estoy haciendo

Page 82: Big Ruby 2014: A 4 Pack of Lightning Talks

• 2500 translation keys

• 1/2 in use

Page 83: Big Ruby 2014: A 4 Pack of Lightning Talks

dozens of translate calls / request

Page 84: Big Ruby 2014: A 4 Pack of Lightning Talks
Page 85: Big Ruby 2014: A 4 Pack of Lightning Talks
Page 86: Big Ruby 2014: A 4 Pack of Lightning Talks

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 [email protected]?(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

Page 87: Big Ruby 2014: A 4 Pack of Lightning Talks

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

Page 88: Big Ruby 2014: A 4 Pack of Lightning Talks

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 ! set << ‘foo' set.dirty.should == [].to_set set.clean.should == ['foo'].to_set end

Page 89: Big Ruby 2014: A 4 Pack of Lightning Talks

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

Page 90: Big Ruby 2014: A 4 Pack of Lightning Talks

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

Page 91: Big Ruby 2014: A 4 Pack of Lightning Talks

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

Page 92: Big Ruby 2014: A 4 Pack of Lightning Talks

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

Page 93: Big Ruby 2014: A 4 Pack of Lightning Talks

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

Page 94: Big Ruby 2014: A 4 Pack of Lightning Talks

CONFIGURATION

Page 95: Big Ruby 2014: A 4 Pack of Lightning Talks

module Humperdink class DirtySet ! def <<(value) @dirty << value if [email protected]?(value) clean! if getting_messy? end ! end end

Page 96: Big Ruby 2014: A 4 Pack of Lightning Talks

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

Page 97: Big Ruby 2014: A 4 Pack of Lightning Talks

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

Page 98: Big Ruby 2014: A 4 Pack of Lightning Talks

CONFIGURATIONRESQUE

Page 99: Big Ruby 2014: A 4 Pack of Lightning Talks

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

Page 100: Big Ruby 2014: A 4 Pack of Lightning Talks

module Humperdink::ForkPiping

Page 101: Big Ruby 2014: A 4 Pack of Lightning Talks

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

Page 102: Big Ruby 2014: A 4 Pack of Lightning Talks

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

Page 103: Big Ruby 2014: A 4 Pack of Lightning Talks

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

Page 104: Big Ruby 2014: A 4 Pack of Lightning Talks
Page 105: Big Ruby 2014: A 4 Pack of Lightning Talks

COVERBANDHTTPS://GITHUB.COM/DANMAYER/COVERBAND

Page 106: Big Ruby 2014: A 4 Pack of Lightning Talks

use Coverband::Middleware

Page 107: Big Ruby 2014: A 4 Pack of Lightning Talks

module Coverband class Middleware ! def call(env) configure_sampling record_coverage results = @app.call(env) report_coverage results end ! end end

Page 108: Big Ruby 2014: A 4 Pack of Lightning Talks

def configure_sampling if (rand * 100.0) > @sample_percentage @enabled = false else @enabled = true end end

Page 109: Big Ruby 2014: A 4 Pack of Lightning Talks

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

Page 110: Big Ruby 2014: A 4 Pack of Lightning Talks

module Coverband class Middleware ! def call(env) configure_sampling record_coverage results = @app.call(env) report_coverage results end ! end end

Page 111: Big Ruby 2014: A 4 Pack of Lightning Talks
Page 112: Big Ruby 2014: A 4 Pack of Lightning Talks

<= NOT DAN MAYER

Page 113: Big Ruby 2014: A 4 Pack of Lightning Talks

THE END

Page 114: Big Ruby 2014: A 4 Pack of Lightning Talks

ALL THE ANALOGIES

Page 115: Big Ruby 2014: A 4 Pack of Lightning Talks
Page 116: Big Ruby 2014: A 4 Pack of Lightning Talks
Page 117: Big Ruby 2014: A 4 Pack of Lightning Talks
Page 118: Big Ruby 2014: A 4 Pack of Lightning Talks
Page 119: Big Ruby 2014: A 4 Pack of Lightning Talks
Page 120: Big Ruby 2014: A 4 Pack of Lightning Talks
Page 121: Big Ruby 2014: A 4 Pack of Lightning Talks
Page 122: Big Ruby 2014: A 4 Pack of Lightning Talks
Page 123: Big Ruby 2014: A 4 Pack of Lightning Talks
Page 124: Big Ruby 2014: A 4 Pack of Lightning Talks
Page 125: Big Ruby 2014: A 4 Pack of Lightning Talks
Page 126: Big Ruby 2014: A 4 Pack of Lightning Talks
Page 127: Big Ruby 2014: A 4 Pack of Lightning Talks
Page 128: Big Ruby 2014: A 4 Pack of Lightning Talks
Page 129: Big Ruby 2014: A 4 Pack of Lightning Talks
Page 130: Big Ruby 2014: A 4 Pack of Lightning Talks
Page 131: Big Ruby 2014: A 4 Pack of Lightning Talks
Page 132: Big Ruby 2014: A 4 Pack of Lightning Talks

META

Page 133: Big Ruby 2014: A 4 Pack of Lightning Talks
Page 134: Big Ruby 2014: A 4 Pack of Lightning Talks
Page 135: Big Ruby 2014: A 4 Pack of Lightning Talks

THE END

Page 136: Big Ruby 2014: A 4 Pack of Lightning Talks

Chris Morris Sr. Engineer

@the_chrismo

Page 137: Big Ruby 2014: A 4 Pack of Lightning Talks

CREDITShttp://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/

Page 138: Big Ruby 2014: A 4 Pack of Lightning Talks

Chris Morris Sr. Engineer

@the_chrismo