blobject 0.2.3 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +1 -1
- data/.pryrc +1 -1
- data/Gemfile.lock +42 -0
- data/README.markdown +157 -0
- data/{README.md → README.md_} +0 -0
- data/Rakefile +1 -1
- data/benchmarks/benchmark.rb +100 -0
- data/benchmarks/results +12 -0
- data/blob_defn.png +0 -0
- data/blobject.gemspec +4 -3
- data/console +7 -0
- data/lib/blobject.rb +218 -168
- data/lib/blobject/version.rb +1 -1
- data/makefile +4 -0
- data/spec/blobject_spec.rb +241 -119
- data/spec/env.rb +12 -0
- data/spec/exec +7 -0
- data/spec/sample_data/sample.json +1 -0
- data/spec/sample_data/sample.yaml +6 -0
- data/spec/sample_data/sample2.json +1 -0
- data/spec/sample_data/sample2.yaml +8 -0
- metadata +51 -15
- data/.rspec +0 -2
- data/benchmark.rb +0 -71
- data/benchmark0 +0 -20
- data/spec/spec_helper.rb +0 -12
    
        data/.pryrc
    CHANGED
    
    
    
        data/Gemfile.lock
    ADDED
    
    | @@ -0,0 +1,42 @@ | |
| 1 | 
            +
            PATH
         | 
| 2 | 
            +
              remote: .
         | 
| 3 | 
            +
              specs:
         | 
| 4 | 
            +
                blobject (0.3.1)
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            GEM
         | 
| 7 | 
            +
              remote: http://rubygems.org/
         | 
| 8 | 
            +
              specs:
         | 
| 9 | 
            +
                ansi (1.4.2)
         | 
| 10 | 
            +
                builder (3.0.0)
         | 
| 11 | 
            +
                coderay (1.0.6)
         | 
| 12 | 
            +
                columnize (0.3.6)
         | 
| 13 | 
            +
                debugger (1.1.3)
         | 
| 14 | 
            +
                  columnize (>= 0.3.1)
         | 
| 15 | 
            +
                  debugger-linecache (~> 1.1.1)
         | 
| 16 | 
            +
                  debugger-ruby_core_source (~> 1.1.2)
         | 
| 17 | 
            +
                debugger-linecache (1.1.1)
         | 
| 18 | 
            +
                  debugger-ruby_core_source (>= 1.1.1)
         | 
| 19 | 
            +
                debugger-ruby_core_source (1.1.3)
         | 
| 20 | 
            +
                method_source (0.7.1)
         | 
| 21 | 
            +
                minitest (3.0.1)
         | 
| 22 | 
            +
                minitest-reporters (0.7.1)
         | 
| 23 | 
            +
                  ansi
         | 
| 24 | 
            +
                  builder
         | 
| 25 | 
            +
                  minitest (>= 2.0, < 4.0)
         | 
| 26 | 
            +
                  ruby-progressbar
         | 
| 27 | 
            +
                pry (0.9.9.6)
         | 
| 28 | 
            +
                  coderay (~> 1.0.5)
         | 
| 29 | 
            +
                  method_source (~> 0.7.1)
         | 
| 30 | 
            +
                  slop (>= 2.4.4, < 3)
         | 
| 31 | 
            +
                ruby-progressbar (0.0.10)
         | 
| 32 | 
            +
                slop (2.4.4)
         | 
| 33 | 
            +
             | 
| 34 | 
            +
            PLATFORMS
         | 
| 35 | 
            +
              ruby
         | 
| 36 | 
            +
             | 
| 37 | 
            +
            DEPENDENCIES
         | 
| 38 | 
            +
              blobject!
         | 
| 39 | 
            +
              debugger
         | 
| 40 | 
            +
              minitest
         | 
| 41 | 
            +
              minitest-reporters
         | 
| 42 | 
            +
              pry
         | 
    
        data/README.markdown
    ADDED
    
    | @@ -0,0 +1,157 @@ | |
| 1 | 
            +
            
         | 
| 2 | 
            +
            
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            Data structures which __just work__
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            ## About
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            A Blobject is a thin wrapper around a hash
         | 
| 9 | 
            +
             | 
| 10 | 
            +
             | 
| 11 | 
            +
            They are *freeform* which means you can do this...
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                data = Blobject.new
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                data.name   = "Johnny"
         | 
| 16 | 
            +
                data.number = 316
         | 
| 17 | 
            +
             | 
| 18 | 
            +
            like an OpenStruct, the members are not predefined attributes
         | 
| 19 | 
            +
             | 
| 20 | 
            +
            unlike OpenStruct, Blobjects can be arbitrarily *complex* which means you can do this...
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                data = Blobject.new
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                data.name.first   = "Johnny"
         | 
| 25 | 
            +
                data.name.surname = "Begood"
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                data.my.object.with.deep.nested.members = "happy place"
         | 
| 28 | 
            +
             | 
| 29 | 
            +
            You can test to see if a member is defined:
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                data.something_here?
         | 
| 32 | 
            +
                  => false
         | 
| 33 | 
            +
             | 
| 34 | 
            +
             | 
| 35 | 
            +
             | 
| 36 | 
            +
            ## Used for Configuration
         | 
| 37 | 
            +
             | 
| 38 | 
            +
            Consider a configuration object which contains credentials for a third-party api.
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                third_party_api:
         | 
| 41 | 
            +
                  secret_key: 'S3CR3T'
         | 
| 42 | 
            +
                  endpoint: 'http://services.thirdparty.net/api'
         | 
| 43 | 
            +
             | 
| 44 | 
            +
            With a hash, usage looks like this:
         | 
| 45 | 
            +
                
         | 
| 46 | 
            +
                CONFIG[:third_party_api][:endpoint]
         | 
| 47 | 
            +
             | 
| 48 | 
            +
            With a Blobject, usage looks like this:
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                CONFIG.third_party_api.endpoint
         | 
| 51 | 
            +
             | 
| 52 | 
            +
            References to the endpoint are scattered throughout the codebase, then one day the endpoint is separated into its constituent parts to aide in testing and staging.
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                third_party_api:
         | 
| 55 | 
            +
                  secret_key: 'S3CR3T'
         | 
| 56 | 
            +
                  protocol: 'http'
         | 
| 57 | 
            +
                  hostname: 'services.thirdparty.net'
         | 
| 58 | 
            +
                  path: '/api'
         | 
| 59 | 
            +
             | 
| 60 | 
            +
            Using a blobject we can easily avoid having to refactor our code...
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                CONFIG = Blobject.from_yaml(File.read('./config.yml'))
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                CONFIG.third_party_api.instance_eval do
         | 
| 65 | 
            +
                  def endpoint
         | 
| 66 | 
            +
                    "#{protocol}://#{hostname}#{path}"
         | 
| 67 | 
            +
                  end
         | 
| 68 | 
            +
                end
         | 
| 69 | 
            +
             | 
| 70 | 
            +
             | 
| 71 | 
            +
            ## Serialization
         | 
| 72 | 
            +
             | 
| 73 | 
            +
            Blobjects can be used to easily build complex payloads.
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                person = Blobject.new
         | 
| 76 | 
            +
             | 
| 77 | 
            +
                person.name = first: 'David', last: 'Platt'
         | 
| 78 | 
            +
                
         | 
| 79 | 
            +
                person.address.tap do |address|
         | 
| 80 | 
            +
                  address.street = "..."
         | 
| 81 | 
            +
                  address.city   = "..."
         | 
| 82 | 
            +
                end
         | 
| 83 | 
            +
             | 
| 84 | 
            +
                person.next_of_kin.address.city = '...'
         | 
| 85 | 
            +
             | 
| 86 | 
            +
                # after the payload is constructed it can be frozen to prevent modification
         | 
| 87 | 
            +
                person.freeze
         | 
| 88 | 
            +
             | 
| 89 | 
            +
            A nice pattern in most cases is to use an initialization block...
         | 
| 90 | 
            +
             | 
| 91 | 
            +
                Blobject.new optional_hash_of_initial_data do |b|
         | 
| 92 | 
            +
                  b.name = ...
         | 
| 93 | 
            +
                end.freeze
         | 
| 94 | 
            +
             | 
| 95 | 
            +
             | 
| 96 | 
            +
            ## Deserialization
         | 
| 97 | 
            +
             | 
| 98 | 
            +
             | 
| 99 | 
            +
            Suppose you receive a payload from an api which may or may not contain an address and city...
         | 
| 100 | 
            +
             | 
| 101 | 
            +
                payload = Blobject.from_json request[:payload]
         | 
| 102 | 
            +
             | 
| 103 | 
            +
                # if the payload does have an address...
         | 
| 104 | 
            +
                city = payload.address.city
         | 
| 105 | 
            +
                  => 'Liverpool'
         | 
| 106 | 
            +
             | 
| 107 | 
            +
                # if the payload does not have an address or city
         | 
| 108 | 
            +
                city = payload.address.city
         | 
| 109 | 
            +
                  => nil
         | 
| 110 | 
            +
                # rather than request[:payload][:address][:city] which would raise
         | 
| 111 | 
            +
                # NoMethodError: undefined method `[]' for nil:NilClass
         | 
| 112 | 
            +
             | 
| 113 | 
            +
             | 
| 114 | 
            +
            Also, you don't need to concern yourself whether hash keys are symbols or strings.
         | 
| 115 | 
            +
             | 
| 116 | 
            +
             | 
| 117 | 
            +
             | 
| 118 | 
            +
            ## Performance
         | 
| 119 | 
            +
             | 
| 120 | 
            +
            The runtime performance of something as low level as blobject deserves consideration.
         | 
| 121 | 
            +
             | 
| 122 | 
            +
            see `/benchmarks`
         | 
| 123 | 
            +
             | 
| 124 | 
            +
            ITERATIONS: 1000000
         | 
| 125 | 
            +
             | 
| 126 | 
            +
             | 
| 127 | 
            +
            BENCHMARK: assign
         | 
| 128 | 
            +
             | 
| 129 | 
            +
                        user       system     total       real
         | 
| 130 | 
            +
            Object:     0.190000   0.000000   0.190000 (  0.229685)
         | 
| 131 | 
            +
            Hash:       0.220000   0.000000   0.220000 (  0.230500)
         | 
| 132 | 
            +
            OpenStruct: 0.520000   0.000000   0.520000 (  0.529861)
         | 
| 133 | 
            +
            Blobject:   0.790000   0.000000   0.790000 (  0.808610)
         | 
| 134 | 
            +
            Hashie:     8.270000   0.030000   8.300000 (  9.291184)
         | 
| 135 | 
            +
             | 
| 136 | 
            +
             | 
| 137 | 
            +
            BENCHMARK: read
         | 
| 138 | 
            +
             | 
| 139 | 
            +
                        user       system     total       real
         | 
| 140 | 
            +
            Hash:       0.160000   0.000000   0.160000 (  0.165141)
         | 
| 141 | 
            +
            Object:     0.170000   0.000000   0.170000 (  0.170228)
         | 
| 142 | 
            +
            OpenStruct: 0.340000   0.000000   0.340000 (  0.342430)
         | 
| 143 | 
            +
            Blobject:   0.410000   0.000000   0.410000 (  0.410574)
         | 
| 144 | 
            +
            Hashie:     1.880000   0.000000   1.880000 (  1.921718)
         | 
| 145 | 
            +
             | 
| 146 | 
            +
            Host CPU: 2.13GHz Core2
         | 
| 147 | 
            +
             | 
| 148 | 
            +
            A Blobject is three-four times slower than an equivalent Object.
         | 
| 149 | 
            +
             | 
| 150 | 
            +
             | 
| 151 | 
            +
            ## Limitations
         | 
| 152 | 
            +
             | 
| 153 | 
            +
            * will not work with basic objects unless #class and #freeze are implemented
         | 
| 154 | 
            +
            * cyclic blobject graphs result in infinite recursion StackOverflow
         | 
| 155 | 
            +
            * Ruby 1.8.7 is not supported. Testing rubies...
         | 
| 156 | 
            +
              * mri 1.9.3-p194
         | 
| 157 | 
            +
              * mri 1.9.2-p290
         | 
    
        data/{README.md → README.md_}
    RENAMED
    
    | 
            File without changes
         | 
    
        data/Rakefile
    CHANGED
    
    | @@ -1,2 +1,2 @@ | |
| 1 1 | 
             
            require 'bundler'
         | 
| 2 | 
            -
            Bundler::GemHelper.install_tasks
         | 
| 2 | 
            +
            Bundler::GemHelper.install_tasks
         | 
| @@ -0,0 +1,100 @@ | |
| 1 | 
            +
            require 'benchmark'
         | 
| 2 | 
            +
            require 'hashie'
         | 
| 3 | 
            +
            require 'ostruct'
         | 
| 4 | 
            +
            require_relative '../lib/blobject'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
             | 
| 7 | 
            +
            iterations = ARGV[0] || 1000000
         | 
| 8 | 
            +
             | 
| 9 | 
            +
             | 
| 10 | 
            +
            class A # a foo-bar class the we'll use when benchmarking objects
         | 
| 11 | 
            +
              attr_accessor :member1
         | 
| 12 | 
            +
            end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
             | 
| 15 | 
            +
            object   = A.new
         | 
| 16 | 
            +
            blobject = Blobject.new
         | 
| 17 | 
            +
            hash     = {}
         | 
| 18 | 
            +
            hashie   = Hashie::Mash.new
         | 
| 19 | 
            +
            ostruct  = OpenStruct.new
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            value = "data"
         | 
| 22 | 
            +
             | 
| 23 | 
            +
             | 
| 24 | 
            +
             | 
| 25 | 
            +
            puts "\n\nITERATIONS: #{iterations}\n\n"
         | 
| 26 | 
            +
            puts "\nBENCHMARK: assign\n=====================\n\n"
         | 
| 27 | 
            +
             | 
| 28 | 
            +
             | 
| 29 | 
            +
             | 
| 30 | 
            +
            Benchmark.bm do |benchmark|
         | 
| 31 | 
            +
             | 
| 32 | 
            +
              benchmark.report("Object: ") do
         | 
| 33 | 
            +
                iterations.times do
         | 
| 34 | 
            +
                  object.member1 = value
         | 
| 35 | 
            +
                end
         | 
| 36 | 
            +
              end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
              benchmark.report("Hash: ") do
         | 
| 39 | 
            +
                iterations.times do
         | 
| 40 | 
            +
                  hash[:member1] = value
         | 
| 41 | 
            +
                end
         | 
| 42 | 
            +
              end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
              benchmark.report("Blobject: ") do
         | 
| 45 | 
            +
                iterations.times do
         | 
| 46 | 
            +
                  blobject.member1 = value
         | 
| 47 | 
            +
                end
         | 
| 48 | 
            +
              end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
              benchmark.report("Hashie: ") do
         | 
| 51 | 
            +
                iterations.times do
         | 
| 52 | 
            +
                  hashie.member1 = value
         | 
| 53 | 
            +
                end
         | 
| 54 | 
            +
              end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
              benchmark.report("OpenStruct: ") do
         | 
| 57 | 
            +
                iterations.times do
         | 
| 58 | 
            +
                  ostruct.member1 = value
         | 
| 59 | 
            +
                end
         | 
| 60 | 
            +
              end
         | 
| 61 | 
            +
            end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
             | 
| 64 | 
            +
             | 
| 65 | 
            +
            puts "\n\nBENCHMARK: read\n=====================\n\n"
         | 
| 66 | 
            +
             | 
| 67 | 
            +
             | 
| 68 | 
            +
             | 
| 69 | 
            +
            Benchmark.bm do |benchmark|
         | 
| 70 | 
            +
             | 
| 71 | 
            +
              benchmark.report("Object: ") do
         | 
| 72 | 
            +
                iterations.times do
         | 
| 73 | 
            +
                  value = object.member1
         | 
| 74 | 
            +
                end
         | 
| 75 | 
            +
              end
         | 
| 76 | 
            +
             | 
| 77 | 
            +
              benchmark.report("Hash: ") do
         | 
| 78 | 
            +
                iterations.times do
         | 
| 79 | 
            +
                  value = hash[:member1]
         | 
| 80 | 
            +
                end
         | 
| 81 | 
            +
              end
         | 
| 82 | 
            +
             | 
| 83 | 
            +
              benchmark.report("Blobject: ") do
         | 
| 84 | 
            +
                iterations.times do
         | 
| 85 | 
            +
                  value = blobject.member1
         | 
| 86 | 
            +
                end
         | 
| 87 | 
            +
              end
         | 
| 88 | 
            +
             | 
| 89 | 
            +
              benchmark.report("Hashie: ") do
         | 
| 90 | 
            +
                iterations.times do
         | 
| 91 | 
            +
                  value = hashie.member1
         | 
| 92 | 
            +
                end
         | 
| 93 | 
            +
              end
         | 
| 94 | 
            +
             | 
| 95 | 
            +
              benchmark.report("OpenStruct: ") do
         | 
| 96 | 
            +
                iterations.times do
         | 
| 97 | 
            +
                  value = ostruct.member1
         | 
| 98 | 
            +
                end
         | 
| 99 | 
            +
              end
         | 
| 100 | 
            +
            end
         | 
    
        data/benchmarks/results
    ADDED
    
    
    
        data/blob_defn.png
    ADDED
    
    | Binary file | 
    
        data/blobject.gemspec
    CHANGED
    
    | @@ -14,9 +14,10 @@ Gem::Specification.new do |s| | |
| 14 14 |  | 
| 15 15 | 
             
              s.rubyforge_project = "blobject"
         | 
| 16 16 |  | 
| 17 | 
            -
              
         | 
| 18 | 
            -
              s.add_development_dependency ' | 
| 19 | 
            -
              s.add_development_dependency ' | 
| 17 | 
            +
              s.add_development_dependency 'minitest'
         | 
| 18 | 
            +
              s.add_development_dependency 'minitest-reporters'
         | 
| 19 | 
            +
              s.add_development_dependency 'pry'
         | 
| 20 | 
            +
              s.add_development_dependency 'debugger'
         | 
| 20 21 |  | 
| 21 22 | 
             
              s.files         = `git ls-files`.split("\n")
         | 
| 22 23 | 
             
              s.test_files    = `git ls-files -- {test,spec,features}/*`.split("\n")
         | 
    
        data/console
    ADDED
    
    
    
        data/lib/blobject.rb
    CHANGED
    
    | @@ -1,209 +1,259 @@ | |
| 1 | 
            -
            require 'blobject/version'
         | 
| 2 1 | 
             
            require 'json'
         | 
| 3 | 
            -
             | 
| 4 | 
            -
             | 
| 5 | 
            -
              Blobject.new *parameters, &block
         | 
| 6 | 
            -
            end
         | 
| 2 | 
            +
            require 'yaml'
         | 
| 3 | 
            +
            require_relative 'blobject/version'
         | 
| 7 4 |  | 
| 8 5 | 
             
            class Blobject
         | 
| 9 6 |  | 
| 10 | 
            -
               | 
| 11 | 
            -
             | 
| 12 | 
            -
             | 
| 13 | 
            -
             | 
| 14 | 
            -
             | 
| 15 | 
            -
             | 
| 16 | 
            -
               | 
| 17 | 
            -
             | 
| 18 | 
            -
             | 
| 19 | 
            -
             | 
| 20 | 
            -
                 | 
| 21 | 
            -
             | 
| 22 | 
            -
             | 
| 23 | 
            -
             | 
| 24 | 
            -
             | 
| 25 | 
            -
                   | 
| 26 | 
            -
                 | 
| 27 | 
            -
             | 
| 7 | 
            +
              # filter :to_ary else Blobject#to_ary returns a
         | 
| 8 | 
            +
              # blobject which is not cool, especially if you are puts.
         | 
| 9 | 
            +
              ProhibitedNames = [:to_ary]
         | 
| 10 | 
            +
             | 
| 11 | 
            +
              module Error; end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
              def initialize hash = {}
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                @hash = hash
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                @hash.keys.each do |key|
         | 
| 18 | 
            +
                  unless key.class <= Symbol
         | 
| 19 | 
            +
                    value = @hash.delete key
         | 
| 20 | 
            +
                    key = key.to_sym
         | 
| 21 | 
            +
                    @hash[key] = value
         | 
| 22 | 
            +
                  end
         | 
| 23 | 
            +
                end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                __visit_subtree__ do |name, node|
         | 
| 26 | 
            +
                  if node.class <= Hash
         | 
| 27 | 
            +
                    @hash[name] = Blobject.new node
         | 
| 28 | 
            +
                  end
         | 
| 28 29 | 
             
                end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                yield self if block_given?
         | 
| 32 | 
            +
              end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
              def inspect
         | 
| 29 35 |  | 
| 30 | 
            -
                 | 
| 36 | 
            +
                @hash.inspect
         | 
| 37 | 
            +
              end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
              def hash
         | 
| 31 40 |  | 
| 32 | 
            -
                 | 
| 33 | 
            -
                return self
         | 
| 41 | 
            +
                @hash
         | 
| 34 42 | 
             
              end
         | 
| 35 | 
            -
             | 
| 36 | 
            -
              def  | 
| 43 | 
            +
             | 
| 44 | 
            +
              def to_hash
         | 
| 37 45 |  | 
| 38 | 
            -
                 | 
| 39 | 
            -
             | 
| 46 | 
            +
                h = hash.dup
         | 
| 47 | 
            +
                __visit_subtree__ do |name, node|
         | 
| 48 | 
            +
                  h[name] = node.to_hash if node.respond_to? :to_hash
         | 
| 40 49 | 
             
                end
         | 
| 41 | 
            -
                
         | 
| 42 | 
            -
             | 
| 43 | 
            -
             | 
| 44 | 
            -
             | 
| 45 | 
            -
             | 
| 46 | 
            -
             | 
| 47 | 
            -
             | 
| 48 | 
            -
             | 
| 49 | 
            -
             | 
| 50 | 
            -
             | 
| 51 | 
            -
             | 
| 52 | 
            -
             | 
| 53 | 
            -
             | 
| 54 | 
            -
             | 
| 55 | 
            -
             | 
| 56 | 
            -
             | 
| 57 | 
            -
             | 
| 58 | 
            -
             | 
| 59 | 
            -
             | 
| 60 | 
            -
                       | 
| 61 | 
            -
             | 
| 62 | 
            -
             | 
| 63 | 
            -
             | 
| 64 | 
            -
             | 
| 65 | 
            -
             | 
| 66 | 
            -
             | 
| 67 | 
            -
             | 
| 68 | 
            -
             | 
| 69 | 
            -
             | 
| 70 | 
            -
             | 
| 71 | 
            -
             | 
| 72 | 
            -
             | 
| 73 | 
            -
             | 
| 74 | 
            -
             | 
| 75 | 
            -
             | 
| 76 | 
            -
             | 
| 77 | 
            -
             | 
| 78 | 
            -
             | 
| 79 | 
            -
                   | 
| 80 | 
            -
             | 
| 81 | 
            -
                   | 
| 50 | 
            +
                h
         | 
| 51 | 
            +
              end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
              # method_missing is only called the first time an attribute is used. successive calls use
         | 
| 54 | 
            +
              # memoized getters, setters and checkers
         | 
| 55 | 
            +
              def method_missing method, *params, &block
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                __tag_and_raise__ NoMethodError.new(method) if ProhibitedNames.include?(method)
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                case
         | 
| 60 | 
            +
                # assignment in conditionals is usually a bad smell, here it helps minimize regex matching
         | 
| 61 | 
            +
                when (name = method[/^\w+$/, 0]) && params.length == 0
         | 
| 62 | 
            +
                  # the call is an attribute reader
         | 
| 63 | 
            +
                  return nil if frozen? and not @hash.has_key?(method)
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                  self.class.send :__define_attribute__, name
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                  return send(method) if @hash.has_key? method
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                  parent          = self
         | 
| 70 | 
            +
                  nested_blobject = self.class.new
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                  store_in_parent = lambda do
         | 
| 73 | 
            +
                    parent.send "#{name}=", nested_blobject
         | 
| 74 | 
            +
                    nested_blobject.send :remove_instance_variable, :@store_in_parent
         | 
| 75 | 
            +
                  end
         | 
| 76 | 
            +
             | 
| 77 | 
            +
                  nested_blobject.instance_variable_set :@store_in_parent, store_in_parent
         | 
| 78 | 
            +
             | 
| 79 | 
            +
                  return nested_blobject
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                when (name = method[/^(\w+)=$/, 1]) && params.length == 1
         | 
| 82 | 
            +
                  # the call is an attribute writer
         | 
| 83 | 
            +
             | 
| 84 | 
            +
                  self.class.send :__define_attribute__, name
         | 
| 85 | 
            +
                  return send method, params.first
         | 
| 86 | 
            +
             | 
| 87 | 
            +
                when (name = method[/^(\w+)\?$/, 1]) && params.length == 0
         | 
| 88 | 
            +
                  # the call is an attribute checker
         | 
| 89 | 
            +
             | 
| 90 | 
            +
                  self.class.send :__define_attribute__, name
         | 
| 91 | 
            +
                  return send method
         | 
| 82 92 | 
             
                end
         | 
| 83 | 
            -
             | 
| 93 | 
            +
             | 
| 84 94 | 
             
                super
         | 
| 85 95 | 
             
              end
         | 
| 86 | 
            -
             | 
| 87 | 
            -
              def  | 
| 88 | 
            -
                
         | 
| 89 | 
            -
                hash.each do |key, value|
         | 
| 90 | 
            -
                  @hash[key.to_s.to_sym] = self.class.__blobjectify__ value
         | 
| 91 | 
            -
                end 
         | 
| 96 | 
            +
             | 
| 97 | 
            +
              def respond_to? method
         | 
| 92 98 |  | 
| 93 | 
            -
                self
         | 
| 94 | 
            -
             | 
| 95 | 
            -
             | 
| 96 | 
            -
             | 
| 97 | 
            -
             | 
| 99 | 
            +
                return true  if self.methods.include?(method)
         | 
| 100 | 
            +
                return false if ProhibitedNames.include?(method)
         | 
| 101 | 
            +
             | 
| 102 | 
            +
                method = method.to_s
         | 
| 103 | 
            +
             | 
| 104 | 
            +
                [/^(\w+)=$/, /^(\w+)\?$/, /^\w+$/].any? do |r|
         | 
| 105 | 
            +
                  r.match(method)
         | 
| 106 | 
            +
                end
         | 
| 98 107 | 
             
              end
         | 
| 99 | 
            -
             | 
| 100 | 
            -
              def  | 
| 101 | 
            -
                @hash | 
| 108 | 
            +
             | 
| 109 | 
            +
              def == other
         | 
| 110 | 
            +
                return @hash == other.hash if other.class <= Blobject
         | 
| 111 | 
            +
                return @hash == other      if other.class <= Hash
         | 
| 112 | 
            +
                super
         | 
| 102 113 | 
             
              end
         | 
| 103 | 
            -
             | 
| 104 | 
            -
              def  | 
| 105 | 
            -
                 | 
| 114 | 
            +
             | 
| 115 | 
            +
              def [] name
         | 
| 116 | 
            +
                
         | 
| 117 | 
            +
                send name
         | 
| 106 118 | 
             
              end
         | 
| 107 | 
            -
             | 
| 108 | 
            -
              def  | 
| 109 | 
            -
                 | 
| 119 | 
            +
             | 
| 120 | 
            +
              def []= name, value
         | 
| 121 | 
            +
                
         | 
| 122 | 
            +
                send "#{name.to_s}=", value
         | 
| 110 123 | 
             
              end
         | 
| 111 124 |  | 
| 112 | 
            -
              def  | 
| 113 | 
            -
                 | 
| 125 | 
            +
              def freeze
         | 
| 126 | 
            +
                __visit_subtree__ { |name, node| node.freeze }
         | 
| 127 | 
            +
                @hash.freeze
         | 
| 128 | 
            +
                super
         | 
| 114 129 | 
             
              end
         | 
| 115 | 
            -
             | 
| 116 | 
            -
              def  | 
| 117 | 
            -
                 | 
| 130 | 
            +
             | 
| 131 | 
            +
              def as_json
         | 
| 132 | 
            +
                
         | 
| 133 | 
            +
                to_hash
         | 
| 118 134 | 
             
              end
         | 
| 119 | 
            -
             | 
| 120 | 
            -
              def  | 
| 121 | 
            -
                 | 
| 135 | 
            +
             | 
| 136 | 
            +
              def to_json
         | 
| 137 | 
            +
                
         | 
| 138 | 
            +
                as_json.to_json
         | 
| 122 139 | 
             
              end
         | 
| 123 | 
            -
             | 
| 124 | 
            -
              def  | 
| 125 | 
            -
                hash = @hash
         | 
| 140 | 
            +
             | 
| 141 | 
            +
              def as_yaml
         | 
| 126 142 |  | 
| 127 | 
            -
                 | 
| 128 | 
            -
             | 
| 129 | 
            -
             | 
| 130 | 
            -
             | 
| 131 | 
            -
                    hash[key] = value.map do |v|
         | 
| 132 | 
            -
                      v.instance_of?(Blobject) ? v.to_hash : v
         | 
| 133 | 
            -
                    end
         | 
| 134 | 
            -
                  end
         | 
| 135 | 
            -
                end
         | 
| 143 | 
            +
                to_hash
         | 
| 144 | 
            +
              end
         | 
| 145 | 
            +
             | 
| 146 | 
            +
              def to_yaml
         | 
| 136 147 |  | 
| 137 | 
            -
                 | 
| 148 | 
            +
                as_yaml.to_yaml
         | 
| 138 149 | 
             
              end
         | 
| 139 | 
            -
             | 
| 140 | 
            -
              def  | 
| 141 | 
            -
                 | 
| 150 | 
            +
             | 
| 151 | 
            +
              def self.from_json json
         | 
| 152 | 
            +
                
         | 
| 153 | 
            +
                from_json!(json).freeze
         | 
| 142 154 | 
             
              end
         | 
| 143 | 
            -
             | 
| 144 | 
            -
              def  | 
| 145 | 
            -
                 | 
| 155 | 
            +
             | 
| 156 | 
            +
              def self.from_json! json
         | 
| 157 | 
            +
                
         | 
| 158 | 
            +
                __from_hash_or_array__(JSON.parse(json))
         | 
| 146 159 | 
             
              end
         | 
| 147 | 
            -
             | 
| 160 | 
            +
             | 
| 148 161 | 
             
              def self.from_yaml yaml
         | 
| 149 | 
            -
                 | 
| 150 | 
            -
             | 
| 151 | 
            -
              
         | 
| 152 | 
            -
              def to_json *params
         | 
| 153 | 
            -
                @hash.to_json *params
         | 
| 162 | 
            +
                
         | 
| 163 | 
            +
                from_yaml!(yaml).freeze
         | 
| 154 164 | 
             
              end
         | 
| 155 | 
            -
             | 
| 156 | 
            -
              def self. | 
| 157 | 
            -
                 | 
| 165 | 
            +
             | 
| 166 | 
            +
              def self.from_yaml! yaml
         | 
| 167 | 
            +
                
         | 
| 168 | 
            +
                __from_hash_or_array__(YAML.load(yaml))
         | 
| 158 169 | 
             
              end
         | 
| 159 | 
            -
             | 
| 160 | 
            -
             | 
| 161 | 
            -
             | 
| 162 | 
            -
             | 
| 163 | 
            -
             | 
| 164 | 
            -
                 | 
| 165 | 
            -
             | 
| 166 | 
            -
             | 
| 167 | 
            -
                   | 
| 170 | 
            +
             | 
| 171 | 
            +
            private
         | 
| 172 | 
            +
            # to avoid naming collisions private method names are prefixed and suffix with double unerscores (__)
         | 
| 173 | 
            +
             | 
| 174 | 
            +
              def __visit_subtree__ &block
         | 
| 175 | 
            +
                
         | 
| 176 | 
            +
                @hash.each do |name, node|
         | 
| 177 | 
            +
             | 
| 178 | 
            +
                  if node.class <= Array
         | 
| 179 | 
            +
                    node.flatten.each do |node_node|
         | 
| 180 | 
            +
                      block.call(nil, node_node, &block)
         | 
| 181 | 
            +
                    end
         | 
| 182 | 
            +
                  end
         | 
| 183 | 
            +
             | 
| 184 | 
            +
                  block.call name, node, &block
         | 
| 168 185 | 
             
                end
         | 
| 169 186 | 
             
              end
         | 
| 170 | 
            -
             | 
| 171 | 
            -
               | 
| 172 | 
            -
             | 
| 173 | 
            -
             | 
| 174 | 
            -
              
         | 
| 175 | 
            -
             | 
| 176 | 
            -
                 | 
| 187 | 
            +
             | 
| 188 | 
            +
              # errors from this library can be handled with rescue Blobject::Error
         | 
| 189 | 
            +
              def __tag_and_raise__ e
         | 
| 190 | 
            +
                raise e
         | 
| 191 | 
            +
              rescue
         | 
| 192 | 
            +
                e.extend Blobject::Error
         | 
| 193 | 
            +
                raise e
         | 
| 177 194 | 
             
              end
         | 
| 178 195 |  | 
| 179 | 
            -
             | 
| 180 | 
            -
             | 
| 181 | 
            -
               | 
| 182 | 
            -
             | 
| 183 | 
            -
                 | 
| 184 | 
            -
             | 
| 185 | 
            -
                   | 
| 186 | 
            -
                     | 
| 196 | 
            +
              class << self
         | 
| 197 | 
            +
             | 
| 198 | 
            +
              private
         | 
| 199 | 
            +
             | 
| 200 | 
            +
                def __from_hash_or_array__ hash_or_array
         | 
| 201 | 
            +
             | 
| 202 | 
            +
                  if hash_or_array.class <= Array
         | 
| 203 | 
            +
                    return hash_or_array.map do |e|
         | 
| 204 | 
            +
                      if e.class <= Hash
         | 
| 205 | 
            +
                         Blobject.new e
         | 
| 206 | 
            +
                      else
         | 
| 207 | 
            +
                        e
         | 
| 208 | 
            +
                      end
         | 
| 209 | 
            +
                    end
         | 
| 187 210 | 
             
                  end
         | 
| 188 | 
            -
             | 
| 189 | 
            -
                   | 
| 211 | 
            +
             | 
| 212 | 
            +
                  Blobject.new hash_or_array
         | 
| 190 213 | 
             
                end
         | 
| 191 | 
            -
             | 
| 192 | 
            -
                 | 
| 193 | 
            -
             | 
| 194 | 
            -
             | 
| 214 | 
            +
             | 
| 215 | 
            +
                def __define_attribute__ name
         | 
| 216 | 
            +
             | 
| 217 | 
            +
                  __tag_and_raise__ NameError.new("invalid attribute name #{name}") unless name =~ /^\w+$/
         | 
| 218 | 
            +
                  name = name.to_sym
         | 
| 219 | 
            +
             | 
| 220 | 
            +
                  methods = self.instance_methods
         | 
| 221 | 
            +
             | 
| 222 | 
            +
                  setter_name = (name.to_s + '=').to_sym
         | 
| 223 | 
            +
                  unless methods.include? setter_name
         | 
| 224 | 
            +
                    self.send :define_method, setter_name do |value|
         | 
| 225 | 
            +
                      begin
         | 
| 226 | 
            +
                        value = self.class.new(value) if value.class <= Hash
         | 
| 227 | 
            +
                        @hash[name] = value
         | 
| 228 | 
            +
                      rescue  ex
         | 
| 229 | 
            +
                        __tag_and_raise__(ex)
         | 
| 230 | 
            +
                      end
         | 
| 231 | 
            +
                      @store_in_parent.call unless @store_in_parent.nil?
         | 
| 232 | 
            +
                    end
         | 
| 195 233 | 
             
                  end
         | 
| 196 | 
            -
             | 
| 197 | 
            -
             | 
| 198 | 
            -
             | 
| 199 | 
            -
             | 
| 200 | 
            -
             | 
| 201 | 
            -
             | 
| 202 | 
            -
             | 
| 203 | 
            -
             | 
| 204 | 
            -
             | 
| 205 | 
            -
             | 
| 234 | 
            +
             | 
| 235 | 
            +
                  unless methods.include? name
         | 
| 236 | 
            +
                    self.send :define_method, name do
         | 
| 237 | 
            +
             | 
| 238 | 
            +
                      value = @hash[name]
         | 
| 239 | 
            +
             | 
| 240 | 
            +
                      if value.nil? && !frozen?
         | 
| 241 | 
            +
                        value = self.class.new
         | 
| 242 | 
            +
                        @hash[name] = value
         | 
| 243 | 
            +
                      end
         | 
| 244 | 
            +
             | 
| 245 | 
            +
                      value
         | 
| 246 | 
            +
                    end
         | 
| 206 247 | 
             
                  end
         | 
| 248 | 
            +
             | 
| 249 | 
            +
                  checker_name = (name.to_s + '?').to_sym
         | 
| 250 | 
            +
                  unless methods.include? checker_name
         | 
| 251 | 
            +
                    self.send :define_method, checker_name do
         | 
| 252 | 
            +
                      @hash.key?(name)
         | 
| 253 | 
            +
                    end
         | 
| 254 | 
            +
                  end
         | 
| 255 | 
            +
             | 
| 256 | 
            +
                  name
         | 
| 207 257 | 
             
                end
         | 
| 208 258 | 
             
              end
         | 
| 209 259 | 
             
            end
         |