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

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

Michael Herold
November 14, 2018

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. Let’s Subclass Hash
    What’s the worst that could happen?

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  7. @mherold
    @mherold

    View full-size slide

  8. @mherold
    @mherold

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  12. 1. Indifferent Access
    @mherold

    View full-size slide

  13. class MyHash < Hash
    end
    @mherold

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  25. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

  28. What is happening?
    @mherold

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  32. We need to go deeper.
    @mherold

    View full-size slide

  33. Pry + Byebug =
    @mherold

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  37. 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 full-size slide

  38. self.class #=> Hash
    @mherold

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  43. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  48. 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 full-size slide

  49. Why was this a problem?
    @mherold

    View full-size slide

  50. Hash has 178 public methods
    @mherold

    View full-size slide

  51. 2. Mash keys
    @mherold

    View full-size slide

  52. Hashie is almost synonymous
    with Mash
    @mherold

    View full-size slide

  53. 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 full-size slide

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

    View full-size slide

  55. 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 full-size slide

  56. 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 full-size slide

  57. 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 full-size slide

  58. 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 full-size slide

  59. 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 full-size slide

  60. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  67. Hash has 178 public methods
    @mherold

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  71. Enumerable#zip
    @mherold

    View full-size slide

  72. The method is not missing
    @mherold

    View full-size slide

  73. … so it behaves unexpectedly.
    @mherold

    View full-size slide

  74. What should we do?
    @mherold

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  80. 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 full-size slide

  81. 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 full-size slide

  82. 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 full-size slide

  83. 3. Destructuring a Dash
    @mherold

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  91. @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 full-size slide

  92. @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 full-size slide

  93. @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 full-size slide

  94. Why?
    @mherold

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  104. @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 full-size slide

  105. @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 full-size slide

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

    View full-size slide

  107. @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 full-size slide

  108. @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 full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  112. The VM does not call #to_hash
    @mherold

    View full-size slide

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

    View full-size slide

  114. @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 full-size slide

  115. @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 full-size slide

  116. The VM does not call #merge
    @mherold

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  121. class MyHash < Hash
    @mherold

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  125. But wait …

    View full-size slide

  126. A wild PSA appears!

    View full-size slide

  127. Hashie::Mash
    @mherold

    View full-size slide

  128. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  133. 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 full-size slide

  134. 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 full-size slide

  135. 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 full-size slide

  136. Hashie::Mash
    @mherold

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  142. 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 full-size slide

  143. 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 full-size slide

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

    View full-size slide