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
|