ostruct2 0.1.0 → 0.2.0

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/.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: