Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Building a Better OpenStruct (WindyCityRails 2016)

7b5a451ee25044b9c869e3e98b79425d?s=47 Ariel Caplan
September 16, 2016

Building a Better OpenStruct (WindyCityRails 2016)

Talk abstract:

OpenStruct, part of Ruby’s standard library, is prized for its beautiful API. It provides dynamic data objects with automatically generated getters and setters. Unfortunately, OpenStruct also carries a hefty performance penalty.

Recently, Rubyists have tried various approaches to speed up OpenStruct or provide alternatives. We will study these attempts, learning how to take advantage of the tools in our ecosystem while advancing the state of the Ruby community.

Sometimes, we can have our cake and eat it too. But it takes creativity, hard work, and willingness to question why things are the way they are.

Link from the talk:

http://jamesgolick.com/2013/4/14/mris-method-caches.html

7b5a451ee25044b9c869e3e98b79425d?s=128

Ariel Caplan

September 16, 2016
Tweet

More Decks by Ariel Caplan

Other Decks in Technology

Transcript

  1. OPENSTRUCT BUILDING A BETTER ARIEL CAPLAN

  2. OPENSTRUCT LET’S TALK ABOUT

  3. OPENSTRUCT IS RUBY’S JAVASCRIPT OBJECT

  4. OPENSTRUCT IS RUBY’S JAVASCRIPT OBJECT require 'ostruct' open_struct = OpenStruct.new(foo:

    :bar) open_struct.baz = 4 open_struct[:something] = 'whatever'
  5. OPENSTRUCT IS RUBY’S JAVASCRIPT OBJECT require 'ostruct' open_struct = OpenStruct.new(foo:

    :bar) open_struct.baz = 4 open_struct[:something] = 'whatever' open_struct.foo => :bar open_struct.baz => 4 open_struct.quux => nil
  6. OPENSTRUCT IS RUBY’S JAVASCRIPT OBJECT require 'ostruct' open_struct = OpenStruct.new(foo:

    :bar) open_struct.baz = 4 open_struct[:something] = 'whatever' open_struct[:foo] => :bar open_struct["baz"] => 4 open_struct[:quux] => nil open_struct.foo => :bar open_struct.baz => 4 open_struct.quux => nil
  7. WHY USE OPENSTRUCT? 3 common use cases for OpenStruct (Erik

    Michaels-Ober's list): • Consume an API • Configuration object • Simple test double
  8. CONSUME AN API

  9. CONSUME AN API hash = JSON.parse(APICall.execute) results = OpenStruct.new(hash) SIMPLE

    STRATEGY: PASS RESPONSE INTO AN OPENSTRUCT
  10. CONSUME AN API hash = JSON.parse(APICall.execute) results = OpenStruct.new(hash) class

    APICall < OpenStruct def self.execute response = Net::HTTP.get(URI('domain.com/endpoint')) new(JSON.parse(response)) end end results = APICall.execute SIMPLE STRATEGY: PASS RESPONSE INTO AN OPENSTRUCT ADVANCED STRATEGY: SUBCLASS OPENSTRUCT
  11. CONSUME AN API { "thing": [1, 2, 3], "other_thing": "hello"

    } If the API response is…
  12. CONSUME AN API { "thing": [1, 2, 3], "other_thing": "hello"

    } If the API response is… Instead of… response['thing'] => [1, 2, 3]
  13. CONSUME AN API { "thing": [1, 2, 3], "other_thing": "hello"

    } If the API response is… response.thing => [1, 2, 3] You can write this instead! Instead of… response['thing'] => [1, 2, 3]
  14. CONSUME AN API You can also define your own methods!

    class User < OpenStruct def name "#{first_name} #{last_name}" end end api_call = JSON.parse(API.get('/users/1')) # => {"first_name"=>"Ariel","last_name"=>"Caplan"} User.new(api_call).name # => "Ariel Caplan"
  15. CONFIGURATION OBJECT

  16. CONFIGURATION OBJECT MyGem.configure do |configuration| configuration.setting = true end

  17. CONFIGURATION OBJECT class MyGem def self.configure yield configuration end def

    self.configuration @configuration ||= OpenStruct.new end end MyGem.configure do |configuration| configuration.setting = true end
  18. TEST DOUBLE class Order def initialize(payment_gateway, products) @payment_gateway = payment_gateway

    @products = products end def pay payment_gateway.total = total_cost payment_gateway.charge end def total_cost # sum up the products end end
  19. TEST DOUBLE class Order def initialize(payment_gateway, products) @payment_gateway = payment_gateway

    @products = products end def pay payment_gateway.total = total_cost payment_gateway.charge end def total_cost # sum up the products end end Expensive to test!
  20. TEST DOUBLE # Stub out methods payment_gateway = OpenStruct.new(charge: :PAID)

    # Run the test payment_result = Order.new(payment_gateway, products).pay # Assert a value is returned assert_equal payment_result, :PAID # Assert a value is set on the OpenStruct assert_equal payment_gateway.total, total_cost(products)
  21. HOW DOES IT WORK?

  22. WARNING! CODE HAS BEEN BRUTALLY EDITED

  23. HOW DOES IT WORK?

  24. HOW DOES IT WORK? • Under the hood, OpenStruct defines

    attribute setter/getter methods on the object’s singleton class.
  25. HOW DOES IT WORK? • Under the hood, OpenStruct defines

    attribute setter/getter methods on the object’s singleton class. foo = Object.new def foo.bar "hello from bar" end foo.bar => "hello from bar" foo.singleton_methods => [:bar]
  26. HOW DOES IT WORK? • Under the hood, OpenStruct defines

    attribute setter/getter methods on the object’s singleton class.
  27. HOW DOES IT WORK? • Under the hood, OpenStruct defines

    attribute setter/getter methods on the object’s singleton class. OpenStruct.new(foo: :bar)
  28. HOW DOES IT WORK? • Under the hood, OpenStruct defines

    attribute setter/getter methods on the object’s singleton class. def initialize(hash=nil) @table = {} if hash hash.each_pair do |k, v| k = k.to_sym @table[k] = v new_ostruct_member(k) end end end OpenStruct.new(foo: :bar)
  29. HOW DOES IT WORK? • Under the hood, OpenStruct defines

    attribute setter/getter methods on the object’s singleton class. def initialize(hash=nil) @table = {} if hash hash.each_pair do |k, v| k = k.to_sym @table[k] = v new_ostruct_member(k) end end end OpenStruct.new(foo: :bar) open_struct.baz = 4
  30. HOW DOES IT WORK? • Under the hood, OpenStruct defines

    attribute setter/getter methods on the object’s singleton class. def initialize(hash=nil) @table = {} if hash hash.each_pair do |k, v| k = k.to_sym @table[k] = v new_ostruct_member(k) end end end OpenStruct.new(foo: :bar) open_struct.baz = 4 open_struct.baz=(4)
  31. HOW DOES IT WORK? • Under the hood, OpenStruct defines

    attribute setter/getter methods on the object’s singleton class. def initialize(hash=nil) @table = {} if hash hash.each_pair do |k, v| k = k.to_sym @table[k] = v new_ostruct_member(k) end end end def method_missing(mid, *args) mname = mid.id2name if mname.chomp!('=') @table[new_ostruct_member(mname)] = args[0] else @table[mid] end end OpenStruct.new(foo: :bar) open_struct.baz = 4 open_struct.baz=(4)
  32. HOW DOES IT WORK? • Under the hood, OpenStruct defines

    attribute setter/getter methods on the object’s singleton class. def initialize(hash=nil) @table = {} if hash hash.each_pair do |k, v| k = k.to_sym @table[k] = v new_ostruct_member(k) end end end def method_missing(mid, *args) mname = mid.id2name if mname.chomp!('=') @table[new_ostruct_member(mname)] = args[0] else @table[mid] end end OpenStruct.new(foo: :bar) open_struct.baz = 4 open_struct.baz=(4)
  33. HOW DOES IT WORK? • Under the hood, OpenStruct defines

    attribute setter/getter methods on the object’s singleton class. def initialize(hash=nil) @table = {} if hash hash.each_pair do |k, v| k = k.to_sym @table[k] = v new_ostruct_member(k) end end end def method_missing(mid, *args) mname = mid.id2name if mname.chomp!('=') @table[new_ostruct_member(mname)] = args[0] else @table[mid] end end OpenStruct.new(foo: :bar) open_struct.baz = 4 open_struct.baz=(4) open_struct.baz
  34. HOW DOES IT WORK? • Under the hood, OpenStruct defines

    attribute setter/getter methods on the object’s singleton class. def new_ostruct_member(name) name = name.to_sym unless respond_to?(name) define_singleton_method(name) { @table[name] } define_singleton_method("#{name}=") { |x| @table[name] = x } end name end
  35. HOW DOES IT WORK? • Under the hood, OpenStruct defines

    attribute setter/getter methods on the object’s singleton class. def new_ostruct_member(name) name = name.to_sym unless respond_to?(name) define_singleton_method(name) { @table[name] } define_singleton_method("#{name}=") { |x| @table[name] = x } end name end This is the singleton class version of attr_accessor :foo but it uses the internal @table, not instance variables!
  36. • Under the hood, OpenStruct defines attribute setter/getter methods on

    the object’s singleton class. HOW DOES IT WORK?
  37. • No 2 OpenStructs share a set of methods •

    Under the hood, OpenStruct defines attribute setter/getter methods on the object’s singleton class. HOW DOES IT WORK?
  38. • No 2 OpenStructs share a set of methods •

    The methods must be defined each time! • Under the hood, OpenStruct defines attribute setter/getter methods on the object’s singleton class. HOW DOES IT WORK?
  39. OPENSTRUCT IS SLOW

  40. • Q: How slow is OpenStruct? OPENSTRUCT IS SLOW

  41. • Q: How slow is OpenStruct? • A: OPENSTRUCT IS

    SLOW
  42. • Q: How slow is OpenStruct? • A: OPENSTRUCT IS

    SLOW • Translation: 10-40x slower than an explicitly defined class
  43. • Q: How slow is OpenStruct? • A: OPENSTRUCT IS

    SLOW • Translation: 10-40x slower than an explicitly defined class • Q: Why is OpenStruct slow?
  44. • Q: How slow is OpenStruct? • A: OPENSTRUCT IS

    SLOW • Translation: 10-40x slower than an explicitly defined class • Q: Why is OpenStruct slow? • Defining methods is slow
  45. • Q: How slow is OpenStruct? • A: OPENSTRUCT IS

    SLOW • Translation: 10-40x slower than an explicitly defined class • Q: Why is OpenStruct slow? • Defining methods is slow • method_missing is slow
  46. • Q: How slow is OpenStruct? • A: OPENSTRUCT IS

    SLOW • Translation: 10-40x slower than an explicitly defined class • Q: Why is OpenStruct slow? • Defining methods is slow • method_missing is slow • In < 2.1, reset the global method cache, no longer a problem
  47. http://jamesgolick.com/2013/4/14/
 mris-method-caches.html

  48. IN RUBY >= 2.1, WE CAN CREATE OPENSTRUCTS WITHOUT SLOWING

    DOWN THE REST OF OUR APPLICATION!
  49. BUT OPENSTRUCT ITSELF IS STILL SLOW…

  50. HI EVERYONE! MY NAME IS ARIEL CAPLAN. YOU CAN FIND

    ME ONLINE AT @AMCAPLAN AND AMCAPLAN.NINJA
  51. I WORK FOR WE PAY PEOPLE TO USE HIGH-QUALITY, LOW-COST

    HEALTHCARE.
  52. None
  53. WE USE A SYSTEM OF

  54. WE USE A SYSTEM OF MICROSERVICES

  55. WE USE A SYSTEM OF MICROSERVICES AND

  56. WE USE A SYSTEM OF MICROSERVICES AND EXTERNAL APIS

  57. WE USE A SYSTEM OF MICROSERVICES AND EXTERNAL APIS COMMUNICATING

    VIA JSON
  58. WE USE A SYSTEM OF MICROSERVICES AND EXTERNAL APIS COMMUNICATING

    VIA JSON PARSED INTO RUBY HASHES
  59. WE USE A SYSTEM OF MICROSERVICES AND EXTERNAL APIS COMMUNICATING

    VIA JSON PARSED INTO RUBY HASHES FED INTO OPENSTRUCTS
  60. PROFILING
 INDICATED THAT OPENSTRUCT WAS A MAJOR CULPRIT

  61. WE WANTED TO KEEP OPENSTRUCT BECAUSE IT GAVE US FLEXIBILITY

  62. CAN WE BUILD A BETTER OPENSTRUCT?

  63. NO.

  64. THANK YOU! ARIEL CAPLAN @AMCAPLAN

  65. None
  66. YES.

  67. 4 STORIES

  68. ROUND 1: OpenFastStruct

  69. ” “ — Ruby Weekly, March 26, 2015

  70. ” “ — https://github.com/arturoherrero/ofstruct

  71. — https://github.com/arturoherrero/ofstruct/blob/master/lib/ofstruct.rb def initialize(args = {}) @members = {} update(args)

    end def update(args) args.each { |k, v| assign(k, v) } end
 def assign(key, value) @members[key.to_sym] = value end def method_missing(name, *args) @members.fetch(name) do if name[-1] == "=" assign(name[0..-2], args.first) else assign(key, self.class.new) end end end
  72. — https://github.com/arturoherrero/ofstruct/blob/master/lib/ofstruct.rb def initialize(args = {}) @members = {} update(args)

    end def update(args) args.each { |k, v| assign(k, v) } end
 def assign(key, value) @members[key.to_sym] = value end def method_missing(name, *args) @members.fetch(name) do if name[-1] == "=" assign(name[0..-2], args.first) else assign(key, self.class.new) end end end open_fast_struct.baz # baz exists
  73. — https://github.com/arturoherrero/ofstruct/blob/master/lib/ofstruct.rb def initialize(args = {}) @members = {} update(args)

    end def update(args) args.each { |k, v| assign(k, v) } end
 def assign(key, value) @members[key.to_sym] = value end def method_missing(name, *args) @members.fetch(name) do if name[-1] == "=" assign(name[0..-2], args.first) else assign(key, self.class.new) end end end open_fast_struct.baz = 4 open_fast_struct.baz # baz exists
  74. — https://github.com/arturoherrero/ofstruct/blob/master/lib/ofstruct.rb def initialize(args = {}) @members = {} update(args)

    end def update(args) args.each { |k, v| assign(k, v) } end
 def assign(key, value) @members[key.to_sym] = value end def method_missing(name, *args) @members.fetch(name) do if name[-1] == "=" assign(name[0..-2], args.first) else assign(key, self.class.new) end end end open_fast_struct.baz = 4 open_fast_struct.baz # baz does not exist open_fast_struct.baz # baz exists
  75. OPENFASTSTRUCT DOESN’T PAY THE UPFRONT COST OF DEFINING METHODS

  76. OPENFASTSTRUCT IS GOOD WHEN PROPERTIES ARE NOT ACCESSED REPEATEDLY

  77. OPENFASTSTRUCT DOESN’T WORK IN OUR APP DUE TO DIFFERENT BEHAVIOR

  78. ” “ — https://github.com/arturoherrero/ofstruct

  79. ” “ — https://github.com/arturoherrero/ofstruct

  80. OpenStruct.new.foo => nil OpenFastStruct.new.foo => #<OpenFastStruct>

  81. OpenStruct.new.foo => nil OpenFastStruct.new.foo => #<OpenFastStruct> if open_struct_response.foo

  82. OpenStruct.new.foo => nil OpenFastStruct.new.foo => #<OpenFastStruct> if open_struct_response.foo “ falsey

  83. OpenStruct.new.foo => nil OpenFastStruct.new.foo => #<OpenFastStruct> if open_struct_response.foo “ falsey

    if open_fast_struct_response.foo
  84. OpenStruct.new.foo => nil OpenFastStruct.new.foo => #<OpenFastStruct> if open_struct_response.foo “ falsey

    “ truthy if open_fast_struct_response.foo
  85. A DROP-IN REPLACEMENT SHOULD BE A DROP-IN REPLACEMENT

  86. CAN WE DO BETTER?

  87. ROUND 2: PersistentOpenStruct

  88. SAMPLE USAGE PersistentOpenStruct

  89. SAMPLE USAGE PersistentOpenStruct class Animal < PersistentOpenStruct def speak "The

    #{type} makes a #{sound} sound!" end end
  90. SAMPLE USAGE PersistentOpenStruct class Animal < PersistentOpenStruct def speak "The

    #{type} makes a #{sound} sound!" end end dog = Animal.new(type: 'dog', sound: 'woof') # => #<Animal type="dog", sound="woof"> dog.speak # => "The dog makes a woof sound!" dog.ears = 'droopy' dog[:nose] = ['cold', 'wet'] dog['tail'] = 'waggable' dog # => #<Animal type="dog", sound="woof", ears="droopy", nose=["cold", "wet"], tail="waggable">
  91. SAMPLE USAGE PersistentOpenStruct class Animal < PersistentOpenStruct def speak "The

    #{type} makes a #{sound} sound!" end end dog = Animal.new(type: 'dog', sound: 'woof') # => #<Animal type="dog", sound="woof"> dog.speak # => "The dog makes a woof sound!" dog.ears = 'droopy' dog[:nose] = ['cold', 'wet'] dog['tail'] = 'waggable' dog # => #<Animal type="dog", sound="woof", ears="droopy", nose=["cold", "wet"], tail="waggable"> Animal.instance_methods(false) # => [:speak, :type=, :type, :sound=, :sound, :ears=, :ears,
 :nose=, :nose, :tail=, :tail]
  92. HOW IT WORKS PersistentOpenStruct def new_ostruct_member(name) name = name.to_sym unless

    respond_to?(name) self.class.send(:define_method, name) { @table[name] } self.class.send(:define_method, "#{name}=") { |x| @table[name] = x } end name end def new_ostruct_member(name) name = name.to_sym unless respond_to?(name) define_singleton_method(name) { @table[name] } define_singleton_method("#{name}=") { |x| @table[name] = x } end name end OpenStruct PersistentOpenStruct
  93. HOW IT WORKS PersistentOpenStruct def new_ostruct_member(name) name = name.to_sym unless

    respond_to?(name) self.class.send(:define_method, name) { @table[name] } self.class.send(:define_method, "#{name}=") { |x| @table[name] = x } end name end def new_ostruct_member(name) name = name.to_sym unless respond_to?(name) define_singleton_method(name) { @table[name] } define_singleton_method("#{name}=") { |x| @table[name] = x } end name end OpenStruct PersistentOpenStruct def new_ostruct_member(name) name = name.to_sym unless respond_to?(name) define_singleton_method(name) { @table[name] } define_singleton_method("#{name}=") { |x| @table[name] = x } end name end def new_ostruct_member(name) name = name.to_sym unless respond_to?(name) self.class.send(:define_method, name) { @table[name] } self.class.send(:define_method, "#{name}=") { |x| @table[name] = x } end name end
  94. None
  95. THIS IS A TERRIBLE HACK.

  96. THIS IS A TERRIBLE HACK. IT MAY HAVE SECURITY IMPLICATIONS.

  97. THIS IS A TERRIBLE HACK. IT MAY HAVE SECURITY IMPLICATIONS.

    AND IT MADE OUR APP 10% FASTER.
  98. Comparison: RegularClass: 3673779.6 i/s PersistentOpenStruct: 764359.4 i/s - 4.81x slower

    OpenFastStruct: 546808.8 i/s - 6.72x slower OpenStruct: 155130.1 i/s - 23.68x slower
  99. WHERE’S THE MATH?

  100. THE INFORMATION YOU CRAVE USING THE NOTATION YOU LOVE!

  101. THE INFORMATION YOU CRAVE • Let n = number of

    methods to define USING THE NOTATION YOU LOVE!
  102. THE INFORMATION YOU CRAVE • Let n = number of

    methods to define • Let o = objects to create USING THE NOTATION YOU LOVE!
  103. THE INFORMATION YOU CRAVE • Let n = number of

    methods to define • Let o = objects to create • Using PersistentOpenStruct: O(n) USING THE NOTATION YOU LOVE!
  104. THE INFORMATION YOU CRAVE • Let n = number of

    methods to define • Let o = objects to create • Using PersistentOpenStruct: O(n) • Using OpenStruct: O(no) USING THE NOTATION YOU LOVE!
  105. MATH PROVIDES A USEFUL FRAMEWORK FOR THINKING ABOUT PROBLEMS. AS

    ENGINEERS, WE NEED ANSWERS GROUNDED IN REALITY.
  106. None
  107. BENCHMARK YOUR LIBRARY

  108. BENCHMARK YOUR LIBRARY BENCHMARK YOUR APP

  109. BENCHMARK YOUR LIBRARY BENCHMARK YOUR APP BENCHMARK ALL THE THINGS!

  110. PERSISTENTOPENSTRUCT IS GOOD WHEN REPEATEDLY CREATING DATA OBJECTS WITH THE

    SAME SHAPE
  111. ROUND 3: OpenStruct

  112. None
  113. None
  114. INTRODUCED IN RUBY 2.3!

  115. THIS IS A DRAMATIC IMPROVEMENT WHEN SOME KEYS ARE NEVER

    ACCESSED
  116. ROUND 4: DynamicClass

  117. SAMPLE USAGE DynamicClass

  118. SAMPLE USAGE DynamicClass Animal = DynamicClass.new do def speak "The

    #{type} makes a #{sound} sound!" end end
  119. SAMPLE USAGE DynamicClass Animal = DynamicClass.new do def speak "The

    #{type} makes a #{sound} sound!" end end dog = Animal.new(type: 'dog', sound: 'woof') # => #<Animal:0x007fdb2b818ba8 @type="dog", @sound="woof"> dog.speak # => "The dog makes a woof sound!" dog.ears = 'droopy' dog[:nose] = ['cold', 'wet'] dog['tail'] = 'waggable' dog # => #<Animal:0x007fc26b1841d0 @type="dog", @sound="woof", @ears="droopy", @nose=["cold", "wet"], @tail=“waggable">
  120. SAMPLE USAGE DynamicClass Animal = DynamicClass.new do def speak "The

    #{type} makes a #{sound} sound!" end end dog = Animal.new(type: 'dog', sound: 'woof') # => #<Animal:0x007fdb2b818ba8 @type="dog", @sound="woof"> dog.speak # => "The dog makes a woof sound!" dog.ears = 'droopy' dog[:nose] = ['cold', 'wet'] dog['tail'] = 'waggable' dog # => #<Animal:0x007fc26b1841d0 @type="dog", @sound="woof", @ears="droopy", @nose=["cold", "wet"], @tail=“waggable"> Animal.instance_methods(false) # => [:to_h, :speak, :type=, :type, :sound=, :sound, :ears=, :ears,
 :nose=, :nose, :tail=, :tail]
  121. HOW IT WORKS DynamicClass class << self def attributes @attributes

    ||= Set.new end def add_methods!(key) class_exec do attr_writer key unless method_defined?("#{key}=") attr_reader key unless method_defined?("#{key}") attributes << key end end end
  122. HOW IT WORKS DynamicClass

  123. HOW IT WORKS DynamicClass def initialize(attributes = {}) attributes.each_pair do

    |key, value| __send__(:"#{key}=", value) end end
  124. HOW IT WORKS DynamicClass def method_missing(mid, *args) if (mname =

    mid[/.*(?==\z)/m]) self[mname] = args.first else self[mid] end end def initialize(attributes = {}) attributes.each_pair do |key, value| __send__(:"#{key}=", value) end end
  125. HOW IT WORKS DynamicClass def method_missing(mid, *args) if (mname =

    mid[/.*(?==\z)/m]) self[mname] = args.first else self[mid] end end def initialize(attributes = {}) attributes.each_pair do |key, value| __send__(:"#{key}=", value) end end animal.baz = 4
  126. HOW IT WORKS DynamicClass def method_missing(mid, *args) if (mname =

    mid[/.*(?==\z)/m]) self[mname] = args.first else self[mid] end end def initialize(attributes = {}) attributes.each_pair do |key, value| __send__(:"#{key}=", value) end end animal.baz = 4 animal.baz
  127. HOW IT WORKS DynamicClass def method_missing(mid, *args) if (mname =

    mid[/.*(?==\z)/m]) self[mname] = args.first else self[mid] end end def []=(key, value) key = key.to_sym instance_variable_set(:"@#{key}", value) unless self.class.attributes.include?(key) self.class.add_methods!(key) end end def [](key) instance_variable_get(:"@#{key}") end def initialize(attributes = {}) attributes.each_pair do |key, value| __send__(:"#{key}=", value) end end animal.baz = 4 animal.baz
  128. HOW IT WORKS DynamicClass def method_missing(mid, *args) if (mname =

    mid[/.*(?==\z)/m]) self[mname] = args.first else self[mid] end end def []=(key, value) key = key.to_sym instance_variable_set(:"@#{key}", value) unless self.class.attributes.include?(key) self.class.add_methods!(key) end end def [](key) instance_variable_get(:"@#{key}") end def initialize(attributes = {}) attributes.each_pair do |key, value| __send__(:"#{key}=", value) end end animal.baz = 4 animal.baz Instead of an internal hash @table, we use instance variables just as you would in a standard class!
  129. IS IT FAST? Comparison: RegularClass: 3757567.8 i/s DynamicClass: 1250634.2 i/s

    - 3.00x slower PersistentOpenStruct: 766792.7 i/s - 4.90x slower OpenFastStruct: 525565.1 i/s - 7.15x slower OpenStruct: 147361.4 i/s - 25.50x slower
  130. DYNAMICCLASS IS GOOD FOR THE SAME PURPOSES AS PERSISTENTOPENSTRUCT BUT

    IT’S FASTER
  131. DYNAMICCLASS WORKS INTERNALLY THE WAY THAT PERSISTENTOPENSTRUCT REALLY SHOULD

  132. None
  133. PERSISTENTOPENSTRUCT MADE ME HAPPY.

  134. PERSISTENTOPENSTRUCT MADE ME HAPPY. DYNAMICCLASS
 MADE ME HAPPIER.

  135. PERSISTENTOPENSTRUCT MADE ME HAPPY. DYNAMICCLASS
 MADE ME HAPPIER. THAT’S THE

    BOTTOM LINE.
  136. ROUNDUP: WHAT HAVE WE LEARNED?

  137. ROUNDUP: WHAT HAVE WE LEARNED?

  138. ROUNDUP: WHAT HAVE WE LEARNED? • Even the Standard Library

    may have room for improvement!
  139. ROUNDUP: WHAT HAVE WE LEARNED? • Even the Standard Library

    may have room for improvement! • By focusing on a particular use case, you might create something that works better than a standard tool.
  140. ROUNDUP: WHAT HAVE WE LEARNED? • Even the Standard Library

    may have room for improvement! • By focusing on a particular use case, you might create something that works better than a standard tool. • Benchmark, benchmark, benchmark!
  141. ROUNDUP: WHAT HAVE WE LEARNED? • Even the Standard Library

    may have room for improvement! • By focusing on a particular use case, you might create something that works better than a standard tool. • Benchmark, benchmark, benchmark! • Experiments are probably the most satisfying, internally rewarding activity in all of programming. Sometimes you gain something useful, other times it’s just fun.
  142. ROUNDUP: WHAT HAVE WE LEARNED? • Even the Standard Library

    may have room for improvement! • By focusing on a particular use case, you might create something that works better than a standard tool. • Benchmark, benchmark, benchmark! • Experiments are probably the most satisfying, internally rewarding activity in all of programming. Sometimes you gain something useful, other times it’s just fun. • Whoever you are, you have something to offer!
  143. THANK YOU! ARIEL CAPLAN @AMCAPLAN AMCAPLAN.NINJA