blobject 0.2.3 → 0.3.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
![](https://github.com/sjltaylor/blobject/raw/master/blobject.png)
|
2
|
+
![](https://github.com/sjltaylor/blobject/raw/master/blob_defn.png)
|
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
|