ostruct2 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/.ruby CHANGED
@@ -52,7 +52,7 @@ revision: 0
52
52
  created: '2010-04-21'
53
53
  summary: A Better OpenStruct
54
54
  title: OStruct2
55
- version: 0.1.0
55
+ version: 0.2.0
56
56
  name: ostruct2
57
57
  description: ! 'OStruct2 is a reimplementation of Ruby''s standard ostruct.rb library.
58
58
 
@@ -60,4 +60,4 @@ description: ! 'OStruct2 is a reimplementation of Ruby''s standard ostruct.rb li
60
60
 
61
61
  member names and cloning.'
62
62
  organization: Rubyworks
63
- date: '2012-05-20'
63
+ date: '2012-05-23'
File without changes
data/HISTORY.rdoc CHANGED
@@ -1,8 +1,24 @@
1
1
  = RELEASE HISTORY
2
2
 
3
- 0.1.0 | 2011-01-01
3
+ == 0.2.0 | 2012-05-23
4
4
 
5
- Summary of release...
5
+ This release brings the new OpenStruct to a production ready state.
6
+
7
+ Changes:
8
+
9
+ * Constructors cascade and auto/renew are now slightly different.
10
+ * Added nest/nested constructor for nests OpenStructs. (Cool!)
11
+ * Boost performace via on-demand creation of singleton methods.
12
+ * Add missing equality methods, hash method and dup/clone methods.
13
+ * Rename main class to OpenStruct2 to avoid conflicts with original.
14
+ * Require `ostruct2/ostruct` to get drop-in replacement.
15
+
16
+
17
+ == 0.1.0 | 2011-05-20
18
+
19
+ This is the initial relase of OpenSturct2.
20
+
21
+ Changes:
6
22
 
7
23
  * First release.
8
24
 
@@ -0,0 +1,7 @@
1
+ require 'ostruct2'
2
+
3
+ class OpenStruct < OpenStruct2
4
+ def __class__
5
+ OpenStruct
6
+ end
7
+ end
data/lib/ostruct2.rb CHANGED
@@ -1,29 +1,122 @@
1
- # A better OpenStruct class.
1
+ # OpenStruct2 is a better OpenStruct class.
2
2
  #
3
- class OpenStruct < BasicObject
3
+ # To demonstrate the weakness of the original OpenStruct, try this IRB session:
4
+ #
5
+ # irb(main):001:0> o = OpenStruct.new
6
+ # => #<OpenStruct>
7
+ # irb(main):002:0> o.display = "Hello, World!"
8
+ # => "Hello, World!"
9
+ # irb(main):003:0> o.display
10
+ # #<OpenStruct display="Hello, World!">=> nil
11
+ #
12
+ # This new OpenStruct class allows *almost* any member name to be used.
13
+ # The only exceptions are methods starting with double underscores,
14
+ # such as `__id__` and `__send__`, and a few neccessary public
15
+ # methods: `clone`, `dup`, `freeze`, `hash`, `to_enum`, `to_h`,
16
+ # `to_s` and `inspect`, as well as `instance_eval` and `instance_exec`.
17
+ #
18
+ # Also note that `empty`, `eql`, `equal`, `frozen` and `key` can be used as
19
+ # members but the key-check shorthand of using `?`-methods cannot be used since
20
+ # these have special definitions.
21
+ #
22
+ # To offset the loss of most methods, OpenStruct provides numerous
23
+ # bang-methods which can be used to manipulate the data, e.g. `#each!`.
24
+ # Currently most bang-methods route directly to the underlying hash table,
25
+ # so developers should keep that in mind when using this feature. A future
26
+ # version may add an intermediate interface to always ensure proper "CRUD",
27
+ # functonality but in the vast majority of cases it will make no difference,
28
+ # so it is left for later consideration.
29
+ #
30
+ # This improved version of OpenStruct also has no issues with being cloned
31
+ # since it does not depend on singleton methods to work. But singleton methods
32
+ # are used to help boost performance. But instead of always creating singleton
33
+ # methods, it only creates them on the first attempt to use them.
34
+ #
35
+ class OpenStruct2 < BasicObject
4
36
 
5
37
  class << self
6
38
  #
7
- # Create cascading OpenStruct.
39
+ # Create autovivified OpenStruct.
8
40
  #
9
- def cascade(data=nil)
10
- leet = lambda{ |h,k| OpenStruct.new(&leet) }
41
+ # @example
42
+ # o = OpenStruct2.renew
43
+ # o.a #=> #<OpenStruct2: {}>
44
+ #
45
+ def auto(data=nil)
46
+ leet = lambda{ |h,k| new(&leet) }
11
47
  new(&leet)
12
48
  end
13
49
 
14
- # Is there a better name for #auto?
15
- #alias :autorenew :cascade
50
+ #
51
+ # Another name for #auto method.
52
+ #
53
+ # TODO: Still wondering waht the best name is for this.
54
+ #
55
+ alias :renew :auto
56
+
57
+ #
58
+ # Create a nested OpenStruct, such that all sub-hashes
59
+ # added to the table also become OpenStruct objects.
60
+ #
61
+ def nested(data=nil)
62
+ o = new
63
+ o.nested!(true)
64
+ o.update!(data) if data
65
+ o
66
+ end
67
+
68
+ #
69
+ # Shorter name for `nested`.
70
+ #
71
+ alias :nest :nested
72
+
73
+ #
74
+ # Constructor that is both autovivified and nested.
75
+ #
76
+ def cascade(data=nil)
77
+ o = renew
78
+ o.nested!(true)
79
+ o.update!(data) if data
80
+ o
81
+ end
82
+
83
+ private
16
84
 
17
- # Original name for #cascade method.
18
- alias :auto :cascade
85
+ def const_missing(name)
86
+ ::Object.const_get(name)
87
+ end
19
88
  end
20
89
 
90
+ #
91
+ # Initialize new instance of OpenStruct.
92
+ #
93
+ # @param [Hash] data
21
94
  #
22
95
  def initialize(data=nil, &block)
23
96
  @table = ::Hash.new(&block)
24
97
  update!(data || {})
25
98
  end
26
99
 
100
+ #
101
+ # Because there is no means of getting the class via a BasicObject instance,
102
+ # we define such a method manually.
103
+ #
104
+ def __class__
105
+ OpenStruct2
106
+ end
107
+
108
+ #
109
+ # Duplicate underlying table when OpenStruct is duplicated or cloned.
110
+ #
111
+ # @param [OpenStruct] original
112
+ #
113
+ def initialize_copy(original)
114
+ super
115
+ @table = @table.dup
116
+ end
117
+
118
+ #
119
+ # Dispatch unrecognized member calls.
27
120
  #
28
121
  def method_missing(sym, *args, &blk)
29
122
  str = sym.to_s
@@ -31,42 +124,84 @@ class OpenStruct < BasicObject
31
124
  name = str.chomp('=').chomp('!').chomp('?')
32
125
 
33
126
  case type
34
- when '='
35
- store!(name, args.first)
36
127
  when '!'
128
+ # TODO: Probably should have an indirect interface to ensure proper
129
+ # functonality in all cases.
37
130
  @table.public_send(name, *args, &blk)
131
+ when '='
132
+ new_ostruct_member(name)
133
+ store!(name, args.first)
38
134
  when '?'
135
+ new_ostruct_member(name)
39
136
  key?(name)
40
137
  else
138
+ new_ostruct_member(name)
41
139
  read!(name)
42
140
  end
43
141
  end
44
142
 
143
+ #
144
+ # Get/set nested flag.
145
+ #
146
+ def nested!(boolean=nil)
147
+ if boolean.nil?
148
+ @nested
149
+ else
150
+ @nested = !!boolean
151
+ end
152
+ end
153
+
154
+ #
45
155
  # CRUD method for listing all keys.
156
+ #
46
157
  def keys!
47
158
  @table.keys
48
159
  end
49
160
 
161
+ #
50
162
  # Also a CRUD method like #read!, but for checking for the existence of a key.
163
+ #
51
164
  def key?(key)
52
165
  @table.key?(key.to_sym)
53
166
  end
54
167
 
168
+ #
55
169
  # The CRUD method for read.
170
+ #
56
171
  def read!(key)
57
172
  @table[key.to_sym]
58
173
  end
59
174
 
175
+ #
60
176
  # The CRUD method for create and update.
177
+ #
61
178
  def store!(key, value)
179
+ if @nested && Hash === value # value.respond_to?(:to_hash)
180
+ value = OpenStruct2.new(value)
181
+ end
182
+
183
+ #new_ostruct_member(key) # this is here only for speed bump
184
+
62
185
  @table[key.to_sym] = value
63
186
  end
64
187
 
188
+ #
65
189
  # The CRUD method for destroy.
190
+ #
66
191
  def delete!(key)
67
192
  @table.delete(key.to_sym)
68
193
  end
69
194
 
195
+ #
196
+ # Same as `#delete!`. This method provides compatibility
197
+ # with the original OpenStruct class.
198
+ #
199
+ # @deprecated Use `#delete!` method instead.
200
+ #
201
+ def delete_field(key)
202
+ @table.delete(key.to_sym)
203
+ end
204
+
70
205
  #
71
206
  # Like #read but will raise a KeyError if key is not found.
72
207
  #
@@ -75,37 +210,83 @@ class OpenStruct < BasicObject
75
210
  read!(key)
76
211
  end
77
212
 
213
+ #
214
+ # If key is not present raise a KeyError.
215
+ #
216
+ # @param [#to_sym] key
217
+ #
218
+ # @raise [KeyError] If key is not present.
219
+ #
220
+ # @return key
221
+ #
222
+ def key!(key)
223
+ return key if key?(key)
224
+ ::Kernel.raise ::KeyError, ("key not found: %s" % [key.inspect])
225
+ end
226
+
227
+ #
228
+ # Alias for `#read!`.
229
+ #
230
+ # @param [#to_sym] key
231
+ #
232
+ # @return [Object]
78
233
  #
79
234
  def [](key)
80
235
  read!(key)
81
236
  end
82
237
 
238
+ #
239
+ # Alias for `#store!`.
240
+ #
241
+ # @param [#to_sym] key
242
+ #
243
+ # @param [Object] value
244
+ #
245
+ # @return value.
83
246
  #
84
247
  def []=(key, value)
85
248
  store!(key, value)
86
249
  end
87
250
 
251
+ #
88
252
  # CRUDified each.
253
+ #
254
+ # @return nothing
255
+ #
89
256
  def each!
90
257
  @table.each_key do |key|
91
258
  yield(key, read!(key))
92
259
  end
93
260
  end
94
261
 
262
+ #
263
+ def map!(&block)
264
+ to_enum.map(&block)
265
+ end
266
+
267
+ #
95
268
  # CRUDified update method.
269
+ #
270
+ # @return [self]
271
+ #
96
272
  def update!(other)
97
273
  other.each do |k,v|
98
274
  store!(k,v)
99
275
  end
276
+ self
100
277
  end
101
278
 
279
+ #
280
+ # Merge this OpenStruct with another OpenStruct or Hash object
281
+ # returning a new OpenStruct instance.
102
282
  #
103
283
  # IMPORTANT! This method does not act in-place like `Hash#merge!`,
104
284
  # rather it works like `Hash#merge`.
105
285
  #
286
+ # @return [OpenStruct]
287
+ #
106
288
  def merge!(other)
107
- # TODO: is there anyway to #dup a BasicObject subclass instance?
108
- o = ::OpenStruct.new(@table, &@table.default_proc)
289
+ o = dup
109
290
  other.each do |k,v|
110
291
  o.store!(k,v)
111
292
  end
@@ -113,38 +294,114 @@ class OpenStruct < BasicObject
113
294
  end
114
295
 
115
296
  #
116
- # Is the OpenStruct void of entries?
297
+ # Inspect OpenStruct object.
117
298
  #
118
- def empty?
119
- @table.empty?
299
+ # @return [String]
300
+ #
301
+ def inspect
302
+ "#<#{__class__}: #{@table.inspect}>"
120
303
  end
121
304
 
305
+ alias :to_s :inspect
306
+
122
307
  #
123
308
  # Get a duplicate of the underlying table.
124
309
  #
310
+ # @return [Hash]
311
+ #
125
312
  def to_h
126
313
  @table.dup
127
314
  end
128
315
 
316
+ # TODO: Should OpenStruct2 support #to_hash ?
317
+ #alias :to_hash :to_h
318
+
319
+ #
320
+ # Create an enumerator based on `#each!`.
321
+ #
322
+ # @return [Enumerator]
129
323
  #
130
324
  def to_enum
131
325
  ::Enumerator.new(self, :each!)
132
326
  end
133
327
 
134
328
  #
135
- # If key is not present raise a KeyError.
329
+ # Duplicate OpenStruct object.
136
330
  #
137
- def key!(key)
138
- return key if key?(key)
139
- ::Kernel.raise ::KeyError, ("key not found: %s" % [key.inspect])
331
+ # @return [OpenStruct] Duplicate instance.
332
+ #
333
+ def dup
334
+ __class__.new(@table, &@table.default_proc)
140
335
  end
141
336
 
337
+ alias :clone :dup
338
+
142
339
  #
340
+ # Hash number.
143
341
  #
342
+ def hash
343
+ @table.hash
344
+ end
345
+
144
346
  #
145
- def inspect
146
- "#<OpenStruct: #{@table.inspect}>"
347
+ # Freeze OpenStruct instance.
348
+ #
349
+ def freeze
350
+ @table.freeze
147
351
  end
148
352
 
149
- end
353
+ #
354
+ # Is the OpenStruct instance frozen?
355
+ #
356
+ # @return [Boolean]
357
+ #
358
+ def frozen?
359
+ @table.frozen?
360
+ end
150
361
 
362
+ #
363
+ # Is the OpenStruct void of entries?
364
+ #
365
+ def empty?
366
+ @table.empty?
367
+ end
368
+
369
+ #
370
+ # Two OpenStructs are equal if they are the same class and their
371
+ # underlying tables are equal.
372
+ #
373
+ def eql?(other)
374
+ return false unless(other.kind_of?(__class__))
375
+ return @table == other.table #to_h
376
+ end
377
+
378
+ #
379
+ # Two OpenStructs are equal if they are the same class and their
380
+ # underlying tables are equal.
381
+ #
382
+ # TODO: Why not equal for other hash types, e.g. via #to_h?
383
+ #
384
+ def ==(other)
385
+ return false unless(other.kind_of?(__class__))
386
+ return @table == other.table #to_h
387
+ end
388
+
389
+ protected
390
+
391
+ def table
392
+ @table
393
+ end
394
+
395
+ def new_ostruct_member(name)
396
+ name = name.to_sym
397
+ # TODO: Check `#respond_to?` is needed? And if so how to do this in BasicObject?
398
+ #return name if self.respond_to?(name)
399
+ (class << self; self; end).class_eval do
400
+ define_method(name) { read!(name) }
401
+ define_method("#{name}?") { key?(name) }
402
+ define_method("#{name}=") { |value| store!(name, value) }
403
+ end
404
+ name
405
+ end
406
+
407
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ostruct2
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-05-20 00:00:00.000000000 Z
12
+ date: 2012-05-23 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: detroit
16
- requirement: &26950880 !ruby/object:Gem::Requirement
16
+ requirement: &22522480 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '0'
22
22
  type: :development
23
23
  prerelease: false
24
- version_requirements: *26950880
24
+ version_requirements: *22522480
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: qed
27
- requirement: &26950340 !ruby/object:Gem::Requirement
27
+ requirement: &22521760 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: '0'
33
33
  type: :development
34
34
  prerelease: false
35
- version_requirements: *26950340
35
+ version_requirements: *22521760
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: ae
38
- requirement: &26949840 !ruby/object:Gem::Requirement
38
+ requirement: &22521140 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,7 +43,7 @@ dependencies:
43
43
  version: '0'
44
44
  type: :development
45
45
  prerelease: false
46
- version_requirements: *26949840
46
+ version_requirements: *22521140
47
47
  description: ! 'OStruct2 is a reimplementation of Ruby''s standard ostruct.rb library.
48
48
 
49
49
  This new OpenStruct class addresses issues the original has with conflicting
@@ -55,16 +55,17 @@ executables: []
55
55
  extensions: []
56
56
  extra_rdoc_files:
57
57
  - HISTORY.rdoc
58
- - DEMO.rdoc
59
58
  - README.rdoc
59
+ - DEMO.md
60
60
  files:
61
61
  - .ruby
62
+ - lib/ostruct2/ostruct.rb
62
63
  - lib/ostruct2.rb
63
64
  - HISTORY.rdoc
64
- - DEMO.rdoc
65
65
  - README.rdoc
66
+ - DEMO.md
66
67
  - Config.rb
67
- homepage:
68
+ homepage: http://rubyworks.github.com/ostruct2
68
69
  licenses:
69
70
  - BSD-2-Clause
70
71
  post_install_message: