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

Let's Subclass Hash - What's the Worst That Could Happen?

Let's Subclass Hash - What's the Worst That Could Happen?

Have you ever been tempted to subclass a core class like Hash or String? Or have you read blog posts about why you shouldn't do that, but been left confused as to the specifics? As a maintainer of Hashie, a gem that commits this exact sin, I'm here to tell you why you want to reach for other tools instead of the subclass.

In this talk, you'll hear stories from the trenches about what can go wrong when you subclass core classes. We'll dig into Ruby internals and you will leave with a few new tools for tracking down seemingly inexplicable performance issues and bugs in your applications.

See the accompanying blog post at michaeljherold.com.

Michael Herold

November 14, 2018
Tweet

More Decks by Michael Herold

Other Decks in Technology

Transcript

  1. View Slide

  2. View Slide

  3. View Slide

  4. View Slide

  5. Let’s Subclass Hash
    What’s the worst that could happen?

    View Slide

  6. My name is Michael Herold.
    Please tweet me at @mherold or
    say [email protected]

    View Slide

  7. View Slide

  8. @mherold

    View Slide

  9. This talk is about a little gem
    called …
    @mherold

    View Slide

  10. This talk is about a little gem
    called … Hashie
    @mherold

    View Slide

  11. “Hashie is a collection of classes
    and mixins that make hashes
    more powerful.”
    @mherold

    View Slide

  12. “Hashie is a collection of classes
    and mixins that make hashes
    more powerful.”
    @mherold

    View Slide

  13. @mherold

    View Slide

  14. @mherold
    @mherold

    View Slide

  15. @mherold
    @mherold

    View Slide

  16. @mherold

    View Slide

  17. –Uncle Ben
    “With great power comes
    great responsibility.”
    @mherold

    View Slide

  18. –Alexander Pope
    “To err is human …”
    @mherold

    View Slide


  19. @mherold

    View Slide

  20. 1. Indifferent Access
    2. Mash keys
    3. Destructuring a Dash
    @mherold

    View Slide

  21. 1. Indifferent Access
    @mherold

    View Slide

  22. View Slide

  23. class MyHash < Hash
    end
    @mherold

    View Slide

  24. class MyHash < Hash
    include Hashie::Extensions::MergeInitializer
    end
    @mherold

    View Slide

  25. Merge Initializer
    @mherold
    hash = MyHash.new(
    cat: 'meow',
    dog: { name: 'Rover', sound: 'woof' }
    )

    View Slide

  26. Merge Initializer
    @mherold
    hash = MyHash.new(
    cat: 'meow',
    dog: { name: 'Rover', sound: 'woof' }
    )
    hash[:cat] #=> "meow"

    View Slide

  27. Merge Initializer
    @mherold
    hash = MyHash.new(
    cat: 'meow',
    dog: { name: 'Rover', sound: 'woof' }
    )
    hash[:cat] #=> "meow"
    hash[:dog] #=> {:name=>"Rover", :sound=>"woof"}

    View Slide

  28. class MyHash < Hash
    include Hashie::Extensions::MergeInitializer
    end
    @mherold

    View Slide

  29. class MyHash < Hash
    include Hashie::Extensions::MergeInitializer
    include Hashie::Extensions::IndifferentAccess
    end
    @mherold

    View Slide

  30. Indifferent Access
    @mherold
    hash = MyHash.new(
    cat: 'meow',
    'dog' => { name: 'Rover', sound: 'woof' }
    )

    View Slide

  31. Indifferent Access
    @mherold
    hash = MyHash.new(
    cat: 'meow',
    'dog' => { name: 'Rover', sound: 'woof' }
    )
    hash['cat'] == hash[:cat] #=> true

    View Slide

  32. Indifferent Access
    @mherold
    hash = MyHash.new(
    cat: 'meow',
    'dog' => { name: 'Rover', sound: 'woof' }
    )
    hash['cat'] == hash[:cat] #=> true
    hash['dog'] == hash[:dog] #=> true

    View Slide

  33. class MyHash < Hash
    include Hashie::Extensions::MergeInitializer
    include Hashie::Extensions::IndifferentAccess
    end
    @mherold

    View Slide

  34. class MyHash < Hash
    include Hashie::Extensions::MergeInitializer
    include Hashie::Extensions::IndifferentAccess
    end
    hash = MyHash.new(
    cat: 'meow',
    'dog' => { name: 'Mango', sound: 'woof' }
    )
    @mherold

    View Slide

  35. class MyHash < Hash
    include Hashie::Extensions::MergeInitializer
    include Hashie::Extensions::IndifferentAccess
    end
    hash = MyHash.new(
    cat: 'meow',
    'dog' => { name: 'Mango', sound: 'woof' }
    )
    new_dog = hash[:dog].merge(breed: 'Blue Heeler')
    #=> NoMethodError: undefined method `convert!'
    @mherold

    View Slide

  36. @mherold

    View Slide

  37. module Hashie::Extensions::IndifferentAccess
    def merge(*)
    super.convert!
    end
    end
    @mherold

    View Slide

  38. module Hashie::Extensions::IndifferentAccess
    def merge(*)
    super.convert!
    end
    def convert!
    # ...
    end
    end
    @mherold

    View Slide

  39. What is happening?
    @mherold

    View Slide

  40. hash = MyHash.new(
    cat: 'meow',
    'dog' => { name: 'Mango', sound: 'woof' }
    )
    @mherold

    View Slide

  41. hash = MyHash.new(
    cat: 'meow',
    'dog' => { name: 'Mango', sound: 'woof' }
    )
    hash.respond_to?(:convert!) #=> true
    @mherold

    View Slide

  42. hash = MyHash.new(
    cat: 'meow',
    'dog' => { name: 'Mango', sound: 'woof' }
    )
    hash.respond_to?(:convert!) #=> true
    hash[:dog].respond_to?(:convert!) #=> true
    @mherold

    View Slide

  43. We need to go deeper.
    @mherold

    View Slide

  44. Pry + Byebug =
    @mherold

    View Slide

  45. module Hashie::Extensions::IndifferentAccess
    def merge(*)
    super.convert!
    end
    end
    @mherold

    View Slide

  46. module Hashie::Extensions::IndifferentAccess
    def merge(*)
    super.tap { |result| binding.pry }.convert!
    end
    end
    @mherold

    View Slide

  47. module Hashie::Extensions::IndifferentAccess
    def merge(*)
    super.tap { |result| binding.pry }.convert!
    end
    end
    hash.merge(breed: 'Blue Heeler’)
    @mherold

    View Slide

  48. module Hashie::Extensions::IndifferentAccess
    def merge(*)
    super.tap { |result| binding.pry }.convert!
    end
    end
    hash.merge(breed: 'Blue Heeler’)
    134: def merge(*args)
    => 135: super.tap { |result| binding.pry }.convert!
    136: end
    [1] pry(#)>
    @mherold

    View Slide

  49. @mherold

    View Slide

  50. self.class #=> Hash
    @mherold

    View Slide

  51. self.class #=> Hash
    result.class #=> Hash
    @mherold

    View Slide

  52. self.class #=> Hash
    result.class #=> Hash
    respond_to?(:convert!) #=> true
    @mherold

    View Slide

  53. self.class #=> Hash
    result.class #=> Hash
    respond_to?(:convert!) #=> true
    result.respond_to?(:convert!) #=> false
    @mherold

    View Slide

  54. self.class #=> Hash
    result.class #=> Hash
    respond_to?(:convert!) #=> true
    result.respond_to?(:convert!) #=> false
    singleton_class.ancestors
    #=> […, Hashie::Extensions::IndifferentAccess, …]
    @mherold

    View Slide

  55. self.class #=> Hash
    result.class #=> Hash
    respond_to?(:convert!) #=> true
    result.respond_to?(:convert!) #=> false
    singleton_class.ancestors
    #=> […, Hashie::Extensions::IndifferentAccess, …]
    result.singleton_class.ancestors
    #=> No indifferent access
    @mherold

    View Slide

  56. module Hashie::Extensions::IndifferentAccess
    def merge(*)
    super.convert!
    end
    end
    @mherold

    View Slide

  57. module Hashie::Extensions::IndifferentAccess
    def merge(*)
    - super.convert!
    end
    end
    @mherold

    View Slide

  58. module Hashie::Extensions::IndifferentAccess
    def merge(*)
    - super.convert!
    + result = super
    + IndifferentAccess.inject!(result)
    + result.convert!
    end
    end
    @mherold

    View Slide

  59. hash = MyHash.new(
    cat: 'meow',
    'dog' => { name: 'Mango', sound: 'woof' }
    )
    @mherold

    View Slide

  60. hash = MyHash.new(
    cat: 'meow',
    'dog' => { name: 'Mango', sound: 'woof' }
    )
    new_dog = hash[:dog].merge(breed: 'Blue Heeler')
    #=> {"name"=>"Rover", "sound"=>"woof",
    "breed"=>"Blue Heeler"}
    @mherold

    View Slide

  61. Why was this a problem?
    @mherold

    View Slide

  62. Hash has 178 public methods
    @mherold

    View Slide

  63. View Slide

  64. View Slide

  65. 2. Mash keys
    @mherold

    View Slide

  66. View Slide

  67. Hashie is almost synonymous
    with Mash
    @mherold

    View Slide

  68. View Slide

  69. View Slide

  70. Mash
    @mherold
    mash = Hashie::Mash.new
    mash.name? # => false
    mash.name # => nil
    mash.name = "My Mash”
    mash.name # => "My Mash"
    mash.name? # => true
    mash.inspect # =>

    View Slide

  71. Mash
    @mherold
    def method_missing(method_name, *args, &blk)
    return self.[](method_name, &blk) if key?(method_name)
    end

    View Slide

  72. Mash
    @mherold
    def method_missing(method_name, *args, &blk)
    return self.[](method_name, &blk) if key?(method_name)
    name, suffix = method_name_and_suffix(method_name)
    end

    View Slide

  73. Mash
    @mherold
    def method_missing(method_name, *args, &blk)
    return self.[](method_name, &blk) if key?(method_name)
    name, suffix = method_name_and_suffix(method_name)
    case suffix
    when ‘='.freeze then assign_property(name, args.first)
    end
    end

    View Slide

  74. Mash
    @mherold
    def method_missing(method_name, *args, &blk)
    return self.[](method_name, &blk) if key?(method_name)
    name, suffix = method_name_and_suffix(method_name)
    case suffix
    when ‘='.freeze then assign_property(name, args.first)
    when ‘?'.freeze then !!self[name]
    end
    end

    View Slide

  75. Mash
    @mherold
    def method_missing(method_name, *args, &blk)
    return self.[](method_name, &blk) if key?(method_name)
    name, suffix = method_name_and_suffix(method_name)
    case suffix
    when ‘='.freeze then assign_property(name, args.first)
    when ‘?'.freeze then !!self[name]
    when ‘!'.freeze then initializing_reader(name)
    end
    end

    View Slide

  76. Mash
    @mherold
    def method_missing(method_name, *args, &blk)
    return self.[](method_name, &blk) if key?(method_name)
    name, suffix = method_name_and_suffix(method_name)
    case suffix
    when ‘='.freeze then assign_property(name, args.first)
    when ‘?'.freeze then !!self[name]
    when ‘!'.freeze then initializing_reader(name)
    when ‘_'.freeze then underbang_reader(name)
    end
    end

    View Slide

  77. Mash
    @mherold
    def method_missing(method_name, *args, &blk)
    return self.[](method_name, &blk) if key?(method_name)
    name, suffix = method_name_and_suffix(method_name)
    case suffix
    when ‘='.freeze then assign_property(name, args.first)
    when ‘?'.freeze then !!self[name]
    when ‘!'.freeze then initializing_reader(name)
    when ‘_'.freeze then underbang_reader(name)
    else self[method_name]
    end
    end

    View Slide

  78. The README used to say “use it
    for JSON responses” …
    @mherold

    View Slide

  79. … so that’s what people do.
    @mherold

    View Slide

  80. response = HTTP.get(“http://myawesomeapi.com”)
    @mherold

    View Slide

  81. response = HTTP.get(“http://myawesomeapi.com”)
    json = JSON.parse(response.body)
    @mherold

    View Slide

  82. response = HTTP.get(“http://myawesomeapi.com”)
    json = JSON.parse(response.body)
    mash = Hashie::Mash.new(json)
    @mherold

    View Slide

  83. But remember: a Mash is a Hash
    @mherold

    View Slide

  84. Hash has 178 public methods
    @mherold

    View Slide

  85. Would any of these conflict?
    @mherold
    class
    count
    hash
    length
    trust
    zip

    View Slide

  86. mash = Hashie::Mash.new(
    name: ‘Millenium Biltmore’,
    zip: ‘90071’
    )
    @mherold

    View Slide

  87. mash = Hashie::Mash.new(
    name: ‘Millenium Biltmore’,
    zip: ‘90071’
    )
    mash.zip
    #=> [[["name", "Millenium Biltmore"]], [["zip", “90071"]]]
    @mherold

    View Slide

  88. Enumerable#zip
    @mherold

    View Slide

  89. The method is not missing
    @mherold

    View Slide

  90. … so it behaves unexpectedly.
    @mherold

    View Slide

  91. What should we do?
    @mherold

    View Slide

  92. View Slide

  93. Hashie::Extension::MethodAccessWithOverride
    @mherold
    class MyMash < Hashie::Mash
    end

    View Slide

  94. Hashie::Extension::MethodAccessWithOverride
    @mherold
    class MyMash < Hashie::Mash
    include Hashie::Extensions::MethodAccessWithOverride
    end

    View Slide

  95. Hashie::Extension::MethodAccessWithOverride
    @mherold
    class MyMash < Hashie::Mash
    include Hashie::Extensions::MethodAccessWithOverride
    end
    mash = MyMash.new

    View Slide

  96. Hashie::Extension::MethodAccessWithOverride
    @mherold
    class MyMash < Hashie::Mash
    include Hashie::Extensions::MethodAccessWithOverride
    end
    mash = MyMash.new
    mash.awesome = 'sauce'

    View Slide

  97. Hashie::Extension::MethodAccessWithOverride
    @mherold
    class MyMash < Hashie::Mash
    include Hashie::Extensions::MethodAccessWithOverride
    end
    mash = MyMash.new
    mash.awesome = 'sauce'
    mash['awesome'] #=> ‘sauce'

    View Slide

  98. Hashie::Extension::MethodAccessWithOverride
    @mherold
    class MyMash < Hashie::Mash
    include Hashie::Extensions::MethodAccessWithOverride
    end
    mash = MyMash.new
    mash.awesome = 'sauce'
    mash['awesome'] #=> 'sauce'
    mash.zip = 'a-dee-doo-dah'

    View Slide

  99. Hashie::Extension::MethodAccessWithOverride
    @mherold
    class MyMash < Hashie::Mash
    include Hashie::Extensions::MethodAccessWithOverride
    end
    mash = MyMash.new
    mash.awesome = 'sauce'
    mash['awesome'] #=> 'sauce'
    mash.zip = 'a-dee-doo-dah'
    mash.zip #=> 'a-dee-doo-dah'

    View Slide

  100. Hashie::Extension::MethodAccessWithOverride
    @mherold
    class MyMash < Hashie::Mash
    include Hashie::Extensions::MethodAccessWithOverride
    end
    mash = MyMash.new
    mash.awesome = 'sauce'
    mash['awesome'] #=> 'sauce'
    mash.zip = 'a-dee-doo-dah'
    mash.zip #=> 'a-dee-doo-dah'
    mash.__zip
    #=> [[['awesome', 'sauce'], ['zip', 'a-dee-doo-dah']]]

    View Slide

  101. 3. Destructuring a Dash
    @mherold

    View Slide

  102. View Slide

  103. View Slide

  104. View Slide

  105. ruby = {
    name: ‘Ruby 2.5’,
    release_date: ‘Christmas’
    }
    @mherold

    View Slide

  106. ruby = {
    name: ‘Ruby 2.5’,
    release_date: ‘Christmas’
    }
    { **ruby, name: ‘Ruby 2.6’ }
    #=> {:name=>"Ruby 2.6", :release_date=>”Christmas"}
    @mherold

    View Slide

  107. Dash
    @mherold
    class PersonHash < Hashie::Dash
    end

    View Slide

  108. Dash
    @mherold
    class PersonHash < Hashie::Dash
    property :name
    property :nickname
    end

    View Slide

  109. Dash
    @mherold
    class PersonHash < Hashie::Dash
    property :name
    property :nickname
    end
    PersonHash.new(foo: ‘bar’)
    #=> NoMethodError: The property 'foo' is not defined

    View Slide

  110. @mherold

    View Slide

  111. @mherold
    sam = PersonHash.new(name: ‘Samwise’, nickname: ‘Sam’)

    View Slide

  112. @mherold
    sam = PersonHash.new(name: ‘Samwise’, nickname: ‘Sam’)
    result = { **sam, height: ‘1.66m’ }
    #=> {:name=>"Samwise", :nickname=>"Sam", :height=>"1.66m"}

    View Slide

  113. @mherold
    sam = PersonHash.new(name: ‘Samwise’, nickname: ‘Sam’)
    result = { **sam, height: ‘1.66m’ }
    #=> {:name=>"Samwise", :nickname=>"Sam", :height=>"1.66m"}
    result[:height]
    #=> NoMethodError: The property 'height' is not defined

    View Slide

  114. @mherold
    sam = PersonHash.new(name: ‘Samwise’, nickname: ‘Sam’)
    result = { **sam, height: ‘1.66m’ }
    #=> {:name=>"Samwise", :nickname=>"Sam", :height=>"1.66m"}
    result[:height]
    #=> NoMethodError: The property 'height' is not defined
    { height: ‘1.66m’, **sam }[:height]
    #=> “1.66m”

    View Slide

  115. @mherold
    sam = PersonHash.new(name: ‘Samwise’, nickname: ‘Sam’)
    result = { **sam, height: ‘1.66m’ }
    #=> {:name=>"Samwise", :nickname=>"Sam", :height=>"1.66m"}
    result[:height]
    #=> NoMethodError: The property 'height' is not defined
    { height: ‘1.66m’, **sam }[:height]
    #=> “1.66m”
    { **sam.to_h, height: ‘1.66m’ }[:height]
    #=> “1.66m”

    View Slide

  116. Why?
    @mherold

    View Slide

  117. What happens when we
    double-splat?
    @mherold

    View Slide

  118. class Test
    def to_hash
    { foo: ‘bar’ }
    end
    end
    @mherold

    View Slide

  119. class Test
    def to_hash
    { foo: ‘bar’ }
    end
    end
    { **Test.new, baz: ‘quux’ }
    => {:foo=>"bar", :baz=>”quux"}
    @mherold

    View Slide

  120. View Slide

  121. What happens when we
    double-splat inside a Hash
    literal?
    @mherold

    View Slide

  122. @mherold
    { **sam, height: ‘1.66m’ }

    View Slide

  123. @mherold
    “{ **sam, height: ‘1.66m’ }”

    View Slide

  124. @mherold
    RubyVM::InstructionSequence.compile(
    “{ **sam, height: ‘1.66m’ }”
    )

    View Slide

  125. @mherold
    RubyVM::InstructionSequence.compile(
    “{ **sam, height: ‘1.66m’ }”
    ).disasm

    View Slide

  126. @mherold
    puts RubyVM::InstructionSequence.compile(
    “{ **sam, height: ‘1.66m’ }”
    ).disasm

    View Slide

  127. @mherold
    puts RubyVM::InstructionSequence.compile(
    “{ **sam, height: ‘1.66m’ }”
    ).disasm
    == disasm: #@:1 (1,0)-(1,26)>=================
    0000 putspecialobject 1 ( 1)[Li]
    0002 putself
    0003 opt_send_without_block ,
    0006 opt_send_without_block ,
    0009 opt_send_without_block ,
    0012 putspecialobject 1
    0014 swap
    0015 putobject :height
    0017 putstring "1.66m"
    0019 opt_send_without_block ,
    0022 leave

    View Slide

  128. @mherold
    puts RubyVM::InstructionSequence.compile(
    “{ **sam, height: ‘1.66m’ }”
    ).disasm
    == disasm: #@:1 (1,0)-(1,26)>=================
    0000 putspecialobject 1 ( 1)[Li]
    0002 putself
    0003 opt_send_without_block ,
    0006 opt_send_without_block ,
    0009 opt_send_without_block ,
    0012 putspecialobject 1
    0014 swap
    0015 putobject :height
    0017 putstring "1.66m"
    0019 opt_send_without_block ,
    0022 leave

    View Slide

  129. Look for core_hash_merge_kwd
    in Ruby’s source code
    @mherold

    View Slide

  130. @mherold
    static VALUE
    core_hash_merge_kwd(int argc, VALUE *argv)
    {
    VALUE hash, kw;
    rb_check_arity(argc, 1, 2);
    hash = argv[0];
    kw = rb_to_hash_type(argv[argc-1]);
    if (argc < 2) hash = kw;
    rb_hash_foreach(kw, argc < 2 ? kwcheck_i : kwmerge_i, hash);
    return hash;
    }

    View Slide

  131. @mherold
    static VALUE
    core_hash_merge_kwd(int argc, VALUE *argv)
    {
    VALUE hash, kw;
    rb_check_arity(argc, 1, 2);
    hash = argv[0];
    kw = rb_to_hash_type(argv[argc-1]);
    if (argc < 2) hash = kw;
    rb_hash_foreach(kw, argc < 2 ? kwcheck_i : kwmerge_i, hash);
    return hash;
    }

    View Slide

  132. Ruby’s VM casts the value to a
    hash …
    @mherold

    View Slide

  133. … but only when it isn’t already a
    Hash.
    @mherold

    View Slide

  134. Recall: a Dash is a Hash.
    @mherold

    View Slide

  135. The VM does not call #to_hash
    @mherold

    View Slide

  136. @mherold
    { **sam, height: ‘1.66m’ }
    #=> {:name=>"Samwise", :nickname=>"Sam", :height=>"1.66m"}

    View Slide

  137. @mherold
    { **sam, height: ‘1.66m’ }
    #=> {:name=>"Samwise", :nickname=>"Sam", :height=>"1.66m"}
    sam.merge(height: ‘1.66m’)
    #=> NoMethodError: The property 'height' is not defined

    View Slide

  138. @mherold

    View Slide

  139. @mherold
    static VALUE
    core_hash_merge_kwd(int argc, VALUE *argv)
    {
    VALUE hash, kw;
    rb_check_arity(argc, 1, 2);
    hash = argv[0];
    kw = rb_to_hash_type(argv[argc-1]);
    if (argc < 2) hash = kw;
    rb_hash_foreach(kw, argc < 2 ? kwcheck_i : kwmerge_i, hash);
    return hash;
    }

    View Slide

  140. The VM does not call #merge
    @mherold

    View Slide

  141. Dash’s property logic exists in
    Ruby so it isn’t run.
    @mherold

    View Slide

  142. Unfortunately, we can’t “fix” this.
    @mherold

    View Slide

  143. So we wrote it up in the
    README.
    @mherold

    View Slide


  144. @mherold

    View Slide

  145. @mherold

    View Slide

  146. 1. Indifferent Access
    2. Mash keys
    3. Destructuring a Dash
    @mherold

    View Slide

  147. class MyHash < Hash
    @mherold

    View Slide

  148. Your interface is suddenly 173
    methods (and counting)
    @mherold

    View Slide

  149. Do you think you can catch all
    the corner cases?
    @mherold

    View Slide

  150. (If so, please contact me - we’d
    love another co-maintainer! )
    @mherold

    View Slide

  151. But wait …

    View Slide

  152. A wild PSA appears!

    View Slide

  153. Hashie::Mash
    @mherold

    View Slide


  154. @mherold

    View Slide

  155. View Slide

  156. View Slide

  157. Gem Name Total Downloads Rank
    omniauth 199
    inspec 262
    elasticsearch-api 264
    elasticsearch-transport 265
    restforce 567
    chef-zero 716
    elasticsearch-model 782
    ridley 890
    zendesk_api 911
    Data from the 2018-11-12 RubyGems.org data dump
    Queries can be found at https://michaeljherold.com/rubyconf2018
    @mherold

    View Slide


  158. @mherold

    View Slide

  159. You might not need Hashie::Mash
    @mherold

    View Slide

  160. json = JSON.parse(<{
    "foo": "bar",
    "bazes": [
    "baz",
    "quux"
    ]
    }
    JSON
    @mherold

    View Slide

  161. json = JSON.parse(<{
    "foo": "bar",
    "bazes": [
    "baz",
    "quux"
    ]
    }
    JSON
    parsed = Hashie::Mash.new(json, object_class: OpenStruct)
    #=> # foo="bar">
    @mherold

    View Slide

  162. json = JSON.parse(<{
    "foo": "bar",
    "bazes": [
    "baz",
    "quux"
    ]
    }
    JSON
    parsed = Hashie::Mash.new(json, object_class: OpenStruct)
    #=> # foo="bar">
    parsed.foo #=> "bar"
    @mherold

    View Slide

  163. json = JSON.parse(<{
    "foo": "bar",
    "bazes": [
    "baz",
    "quux"
    ]
    }
    JSON
    parsed = Hashie::Mash.new(json, object_class: OpenStruct)
    #=> # foo="bar">
    parsed.foo #=> "bar"
    parsed['foo'] #=> "bar"
    @mherold

    View Slide

  164. json = JSON.parse(<{
    "foo": "bar",
    "bazes": [
    "baz",
    "quux"
    ]
    }
    JSON
    parsed = Hashie::Mash.new(json, object_class: OpenStruct)
    #=> # foo="bar">
    parsed.foo #=> "bar"
    parsed['foo'] #=> "bar"
    parsed[:foo] #=> "bar"
    @mherold

    View Slide

  165. json = JSON.parse(<{
    "foo": "bar",
    "bazes": [
    "baz",
    "quux"
    ]
    }
    JSON
    parsed = Hashie::Mash.new(json, object_class: OpenStruct)
    #=> # foo="bar">
    parsed.foo #=> "bar"
    parsed['foo'] #=> "bar"
    parsed[:foo] #=> "bar"
    parsed.bazes #=> ["baz", “quux"]
    @mherold

    View Slide

  166. Hashie::Mash
    @mherold

    View Slide


  167. @mherold

    View Slide

  168. require ‘json’
    require ‘ostruct’
    @mherold

    View Slide

  169. json = <{
    "foo": "bar",
    "bazes": [
    "baz",
    "quux"
    ]
    }
    JSON
    @mherold

    View Slide

  170. json = <{
    "foo": "bar",
    "bazes": [
    "baz",
    "quux"
    ]
    }
    JSON
    parsed = JSON.parse(json, object_class: OpenStruct)
    #=> #
    @mherold

    View Slide

  171. json = <{
    "foo": "bar",
    "bazes": [
    "baz",
    "quux"
    ]
    }
    JSON
    parsed = JSON.parse(json, object_class: OpenStruct)
    #=> #
    parsed.foo #=> "bar"
    @mherold

    View Slide

  172. json = <{
    "foo": "bar",
    "bazes": [
    "baz",
    "quux"
    ]
    }
    JSON
    parsed = JSON.parse(json, object_class: OpenStruct)
    #=> #
    parsed.foo #=> "bar"
    parsed['foo'] #=> "bar"
    @mherold

    View Slide

  173. json = <{
    "foo": "bar",
    "bazes": [
    "baz",
    "quux"
    ]
    }
    JSON
    parsed = JSON.parse(json, object_class: OpenStruct)
    #=> #
    parsed.foo #=> "bar"
    parsed['foo'] #=> "bar"
    parsed[:foo] #=> "bar"
    @mherold

    View Slide

  174. json = <{
    "foo": "bar",
    "bazes": [
    "baz",
    "quux"
    ]
    }
    JSON
    parsed = JSON.parse(json, object_class: OpenStruct)
    #=> #
    parsed.foo #=> "bar"
    parsed['foo'] #=> "bar"
    parsed[:foo] #=> "bar"
    parsed.bazes #=> ["baz", “quux"]
    @mherold

    View Slide


  175. @mherold

    View Slide

  176. My name is Michael Herold.
    Please tweet me at @mherold or
    say [email protected]

    View Slide

  177. View Slide