Your SlideShare is downloading. ×
Big Ruby 2014: A 4 Pack of Lightning Talks
Upcoming SlideShare
Loading in...5
×

Thanks for flagging this SlideShare!

Oops! An error has occurred.

×
Saving this for later? Get the SlideShare app to save on your phone or tablet. Read anywhere, anytime – even offline.
Text the download link to your phone
Standard text messaging rates apply

Big Ruby 2014: A 4 Pack of Lightning Talks

402

Published on

Make your production console a nice place to work. …

Make your production console a nice place to work.
DIY Fixtures and Mocks.
Runtime Analysis with Humperdink and Coverband.
ALL THE ANALOGIES.

Published in: Technology
0 Comments
0 Likes
Statistics
Notes
  • Be the first to comment

  • Be the first to like this

No Downloads
Views
Total Views
402
On Slideshare
0
From Embeds
0
Number of Embeds
1
Actions
Shares
0
Downloads
2
Comments
0
Likes
0
Embeds 0
No embeds

Report content
Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
No notes for slide

Transcript

  • 1. Chris Morris Sr. Engineer @the_chrismo
  • 2. 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
  • 3. 4 PACK OF LIGHTNING TALKS
  • 4. COBBLER’S PRODUCTION CONSOLE HAS NO SHOES
  • 5. LIVINGSOCIAL PAYMENTS • Credit Card, PayPal, V.me, MasterPass, International (Adyen, iPay88) • 1.5M API calls a day • 70k-150k credit card transactions a day
  • 6. LIVINGSOCIAL PAYMENTS • Whole Foods - 1,000,000 • Starbucks - 1,500,000
  • 7. 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>]
  • 8. [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 | +----+-------------------------+------------+
  • 9. 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}]
  • 10. [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}]
  • 11. 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
  • 12. [14] pry(main)> b.map { |t| t.aasm_state } => ["processed", "processed"] [15] pry(main)> b.map(&:type) => ["SaleTransaction", "RefundTransaction"]
  • 13. [11] pry(main)> b.aasm_state => ["processed", "processed"] [12] pry(main)> b.type => ["SaleTransaction", "RefundTransaction"]
  • 14. 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>
  • 15. 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
  • 16. 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 | +---+-------------------------+-----------+
  • 17. 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 | +------------+-------------------------+----+
  • 18. [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
  • 19. class Batch < Array def pretty_inspect report.to_s end end
  • 20. [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 | +----+-------------------------+------------+
  • 21. 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
  • 22. LOG SEARCH
  • 23. 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
  • 24. 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 | +-------+---------------------+--------+-------------+---------------+
  • 25. 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>)>
  • 26. [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">,
  • 27. THE END
  • 28. DIY MOCKS AND FIXTURES
  • 29. RAVE REVIEWS "You either get an F for logic or an A+ for balls.” ! ! — J. B. Rainsberger author of JUnit Recipes
  • 30. [~]$ curl -s http://www.linkedin.com/in/chrismo | grep Relevance [~]$
  • 31. NOT GLENN VANDERBURG =>
  • 32. NOT GLENN VANDERBURG => ! CODMONGER !
  • 33. 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
  • 34. 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
  • 35. 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)>'
  • 36. 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)>'
  • 37. FactoryGirl.define do ... # YOU HAD ONE JOB factory :collection do purchase end end
  • 38. 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
  • 39. 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
  • 40. 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)>'
  • 41. ! # ONE. JOB. Collection.blueprint do purchase end
  • 42. 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
  • 43. 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
  • 44. FactoryGirl::Proxy::ObjectWrapper def object @object ||= @klass.new assign_object_attributes @object end
  • 45. class Collection < ActiveRecord::Base state_machine :status, :initial => lambda{ |collection| collection.customer.new? ? :received : :customer_verified} do ... end end
  • 46. ACTIVE RECORD IS HARD ENOUGH ! ! I DON’T NEED YOUR FREEKING DSL ON TOP OF IT
  • 47. 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
  • 48. 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
  • 49. 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
  • 50. 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
  • 51. WHEN A DSL POOPS OUT ON YOU ! YOU CAN'T LOOK AT ITS SOURCE CODE ! BECAUSE IT'S ALL META AND STUFF.
  • 52. MOCK FRAMEWORKS NOT ALL EXPECTATIONS WERE SATISFIED
  • 53. 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')
  • 54. 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)
  • 55. 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)
  • 56. 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)
  • 57. 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)
  • 58. 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
  • 59. 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
  • 60. DIY — Gem
  • 61. BUILD — BUY
  • 62. BUY trades in FAMILIARITY TRANSPARENCY
  • 63. THE END
  • 64. TRACK YER BIG STUFF
  • 65. No tengo ni idea de lo que estoy haciendo
  • 66. • 2500 translation keys • 1/2 in use
  • 67. dozens of translate calls / request
  • 68. 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
  • 69. 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
  • 70. 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
  • 71. 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
  • 72. 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
  • 73. 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
  • 74. 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
  • 75. 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
  • 76. CONFIGURATION
  • 77. module Humperdink class DirtySet ! def <<(value) @dirty << value if !@clean.include?(value) clean! if getting_messy? end ! end end
  • 78. 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
  • 79. 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
  • 80. CONFIGURATION RESQUE
  • 81. 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
  • 82. module Humperdink::ForkPiping
  • 83. 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
  • 84. 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
  • 85. 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
  • 86. COVERBAND HTTPS://GITHUB.COM/DANMAYER/COVERBAND
  • 87. use Coverband::Middleware
  • 88. module Coverband class Middleware ! def call(env) configure_sampling record_coverage results = @app.call(env) report_coverage results end ! end end
  • 89. def configure_sampling if (rand * 100.0) > @sample_percentage @enabled = false else @enabled = true end end
  • 90. 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
  • 91. module Coverband class Middleware ! def call(env) configure_sampling record_coverage results = @app.call(env) report_coverage results end ! end end
  • 92. <= NOT DAN MAYER
  • 93. THE END
  • 94. ALL THE ANALOGIES
  • 95. META
  • 96. THE END
  • 97. Chris Morris Sr. Engineer @the_chrismo
  • 98. 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/
  • 99. Chris Morris Sr. Engineer @the_chrismo

×