Class: SuperHash

Inherits:
Object show all
Includes:
Enumerable
Defined in:
lib/redshift/util/superhash.rb

Overview

class SuperHash

The Ruby inheritance system is a powerful way to organize methods and constants in a hierarchy of classes and modules. However, it does not provide an easy way to organize class attributes with inherited values in such a hierarchy. There is no inheritance mechanism that combines:

  1. propagation of values to descendant classes;

  2. overriding of values by a subclass; and

  3. mutability.

The closest approximations in Ruby are class variables, class instance variables, and constants.

A class variable (((@@var))) is stored in the base class in which it was defined. When its value is changed by a subclass, the change propagates to all subclasses of the base class. The value cannot be overridden just for that subclass and its descendants. This satisfies 1 and 3, but not 2.

A class instance variable (((@var))) can take on a different value in each subclass, but there is no inheritance mechanism. Its value is privately accessible by its owner (though it may be exposed by methods). However, the value does not propagate to subclasses. This satisfies 2 and 3, but not 1.

A constant is inherited and can take on different values in subclasses. However it cannot be changed and is always public. This satisfies 1 and 2, but not 3.

((SuperHash)) solves this class attribute problem and in addition is a general mechanism for defining attribute inheritance structures among objects of any type, not just classes. An example of the former is ((StateObject)), in ((examples/state-objectexamples/state-object.rb)). An example of the latter is ((AttributedNode)), in ((examples/attributed-nodeexamples/attributed-node.rb)).

A superhash is simply a hash bundled with a list of parents, which can be hashes or other hash-like objects. For all lookup methods, like ((#[])), ((#each)), ((#size)), and so on, the superhash behaves as if the parent hash entries were included in it. The inheritance search is depth-first, and in the same order as the parents list.

Destructive methods, such as ((#[]=)) and ((#delete)), do not affect the parent (however, see ((#rehash)) below), but attempt to emulate the expected effect by changing the superhash itself. Operations on a parent are immdiately reflected in the child; the parent’s data is referenced, not copied, by the child.

The equality semantics of ((SuperHash)) is the same as that of ((Hash)). The ((#==)) method returns true if and only if the receiver and the argument have the same (in the sense of ((#==))) key-value pairs. The ((eql?)) method is inherited from ((Object)). Naturally, ((SuperHash)) includes the ((Enumerable)) module.

Note that ((SuperHash)) is not very efficient. Because ((SuperHash)) is dynamic and flexible, even an operation as simple as ((#size)) requires sending ((#size)) messages to the parents. Also, the current implementation emphasizes simplicity over speed. For instance, ((#each)) requires constructing the set of all keys, which requires collecting key sets for parents, and then taking their union.

class method

—SuperHash.new parents = [], default = nil

The ((#parents)) argument can be an enumerable collection of hash-like objects, or a single hash-like object, or [] or nil. The hash-like objects must support ((find)), ((collect)), ((#keys)), ((#key?)), and ((#[])).

The precedence order of parents is the same as their order in the ((#parents)) array. In other words, the first parent in the list overrides later ones, and so on. Inheritance is by depth first.

If the ((#default)) argument is specified, it affects the ((SuperHash)) just like the ((#default)) argument in the ((Hash)) constructor. The default behavior of the child replaces the default behaviors of the parents.

overridden instance methods

The SuperHash instance methods provide a hash-like interface. Hash methods which need special explanation are documented below.

—SuperHash#clear

The implementation of ((#clear)) is to simply call (({true})).

—SuperHash#delete(key) —SuperHash#delete(key) { |key| block } —SuperHash#delete_if { |key, value| block }

If the key is inherited, these methods simply associate the default value to the key in the ((SuperHash)). Note that if the default is changed after the deletion, the key-value pair is not updated to reflect the change–the value will still be the old default.

—SuperHash#empty? —SuperHash#size

Note that ((superhashsuperhash.clearsuperhash.clear.empty?)) will not return ((true)) if there are inherited keys. The ((SuperHash)) needs to remember which parent keys have been deleted, and this is not easily distinguishable from the case in which those keys have been explicitly associated with ((nil)) (or the default value). Similar remarks apply to ((#size)).

—SuperHash#invert —SuperHash#to_hash

Returns a ((Hash)), in the first case with inverted key-value pairs, in the second case with the same key-value pairs, as the receiver.

—SuperHash#rehash

Rehashes the receiver’s ((#own)) hash and rehashes all parents (if they respond to ((#rehash))). Note that this is the only ((SuperHash)) method that modifies the parent objects.

—SuperHash#replace(hash)

Replaces the receiver’s ((#own)) hash with the argument, and replaces the receiver’s parent array with the empty array.

—SuperHash#shift

As long as the ((#own)) hash has entries, shifts them out and returns them. Raises ((ParentImmutableError)) if the receiver’s ((#own)) hash is empty.

new instance methods

((SuperHash)) defines some instance methods that are not available in ((Hash)).

—SuperHash#inherits_key? k

Returns ((true)) if and only if ((k)) is a key in a parent but not in the receiver’s ((#own)) hash.

—SuperHash#own

Returns the hash of key-value pairs that belong to the superhash and are not inherited.

—SuperHash#own_keys

Returns the array of keys in the ((#own)) hash.

—SuperHash#owns_key? k

Returns ((true)) if and only if ((k)) is a key in the ((#own)) hash.

version

SuperHash 0.3

The current version of this software can be found at ((<“redshift.sourceforge.net/superhash ”|URL:redshift.sourceforge.net/superhash>)).

license

This software is distributed under the Ruby license. See ((<“www.ruby-lang.org”|URL:www.ruby-lang.org>)).

author

Joel VanderWerf, ((<[email protected]|URL:[email protected]>))

Defined Under Namespace

Classes: ParentImmutableError

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(parents = [], default = nil) ⇒ SuperHash

Returns a new instance of SuperHash.



170
171
172
173
174
175
176
177
178
179
# File 'lib/redshift/util/superhash.rb', line 170

def initialize parents = [], default = nil
  @hash = Hash.new default
  if parents == nil
    @parents = []
  elsif parents.respond_to? :key?
    @parents = [parents]
  else
    @parents = parents
  end
end

Instance Attribute Details

#parentsObject (readonly)

Returns the value of attribute parents.



168
169
170
# File 'lib/redshift/util/superhash.rb', line 168

def parents
  @parents
end

Instance Method Details

#==(other) ⇒ Object

methods that override Hash methods



201
202
203
204
205
206
207
# File 'lib/redshift/util/superhash.rb', line 201

def ==(other)
  return false unless other.respond_to? :size and
                      size == other.size      and
                      other.respond_to? :[]
  each { |key, value| return false unless self[key] == other[key] }
  return true
end

#[](key) ⇒ Object



209
210
211
# File 'lib/redshift/util/superhash.rb', line 209

def [](key)
  fetch(key) {default}
end

#[]=(key, value) ⇒ Object Also known as: store



213
214
215
# File 'lib/redshift/util/superhash.rb', line 213

def []=(key, value)
  @hash[key] = value
end

#clearObject



218
219
220
# File 'lib/redshift/util/superhash.rb', line 218

def clear
  delete_if {true}
end

#defaultObject



222
223
224
# File 'lib/redshift/util/superhash.rb', line 222

def default
  @hash.default
end

#default=(value) ⇒ Object



226
227
228
# File 'lib/redshift/util/superhash.rb', line 226

def default=(value)
  @hash.default = value
end

#delete(key) ⇒ Object



230
231
232
233
234
235
236
237
238
239
240
# File 'lib/redshift/util/superhash.rb', line 230

def delete(key)
  if key? key
    @hash.delete(key) do
      value = fetch(key)
      @hash[key] = default
      value
    end
  else
    block_given? ? (yield key) : default
  end
end

#delete_ifObject



242
243
244
245
246
247
248
# File 'lib/redshift/util/superhash.rb', line 242

def delete_if
  each do |key, value|
    if yield key, value
      @hash.delete(key) { @hash[key] = default }
    end
  end
end

#eachObject Also known as: each_pair



250
251
252
253
# File 'lib/redshift/util/superhash.rb', line 250

def each
  keys.each { |k| yield k, fetch(k) }
  self
end

#each_keyObject



256
257
258
259
# File 'lib/redshift/util/superhash.rb', line 256

def each_key
  keys.each { |k| yield k }
  self
end

#each_valueObject



261
262
263
264
# File 'lib/redshift/util/superhash.rb', line 261

def each_value
  keys.each { |k| yield fetch(k) }
  self
end

#empty?Boolean

Returns:

  • (Boolean)


266
267
268
# File 'lib/redshift/util/superhash.rb', line 266

def empty?
  @hash.empty? && ( not @parents.find {|parent| not parent.empty?} )
end

#fetch(*args) ⇒ Object



270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
# File 'lib/redshift/util/superhash.rb', line 270

def fetch(*args)
  case args.size
  when 1
    key, = args
    @hash.fetch(key) {
      @parents.each do |parent|
        begin
          return parent.fetch(key)
        rescue IndexError
        end
      end
      if block_given?
        yield key
      else
        raise IndexError, "key not found"
      end
    }
  when 2
    if block_given?
      raise ArgumentError, "wrong # of arguments"
    end
    key, default_object = args
    @hash.fetch(key) {
      @parents.each do |parent|
        begin
          return parent.fetch(key)
        rescue IndexError
        end
      end
      return default_object
    }
  else
    raise ArgumentError, "wrong # of arguments(#{args.size} for 2)"
  end
end

#has_value?(val) ⇒ Boolean Also known as: value?

Returns:

  • (Boolean)


306
307
308
309
# File 'lib/redshift/util/superhash.rb', line 306

def has_value? val
  each { |k,v| return true if val == v }
  return false
end

#index(val) ⇒ Object



312
313
314
315
# File 'lib/redshift/util/superhash.rb', line 312

def index val
  each { |k,v| return k if val == v }
  return false
end

#indexes(*ks) ⇒ Object Also known as: indices



317
318
319
# File 'lib/redshift/util/superhash.rb', line 317

def indexes(*ks)
  ks.collect { |k| index k }
end

#inherits_key?(k) ⇒ Boolean

methods that are not overrides of Hash methods

Returns:

  • (Boolean)


183
184
185
# File 'lib/redshift/util/superhash.rb', line 183

def inherits_key? k
  !(@hash.key? k) && (!! @parents.find {|parent| parent.key? k } )
end

#invertObject



322
323
324
325
326
# File 'lib/redshift/util/superhash.rb', line 322

def invert
  h = {}
  keys.each { |k| h[fetch(k)] = k }
  h
end

#key?(k) ⇒ Boolean Also known as: has_key?, include?, member?

Returns:

  • (Boolean)


328
329
330
# File 'lib/redshift/util/superhash.rb', line 328

def key? k
  (@hash.key? k) || (!! @parents.find {|parent| parent.key?(k)} )
end

#keysObject



335
336
337
# File 'lib/redshift/util/superhash.rb', line 335

def keys
  (@hash.keys + (@parents.collect { |parent| parent.keys }).flatten).uniq
end

#ownObject



187
188
189
# File 'lib/redshift/util/superhash.rb', line 187

def own
  @hash
end

#own_keysObject



191
192
193
# File 'lib/redshift/util/superhash.rb', line 191

def own_keys
  @hash.keys
end

#owns_key?(k) ⇒ Boolean

Returns:

  • (Boolean)


195
196
197
# File 'lib/redshift/util/superhash.rb', line 195

def owns_key? k
  @hash.key? k
end

#rehashObject



339
340
341
342
343
# File 'lib/redshift/util/superhash.rb', line 339

def rehash
  @hash.rehash
  @parents.each { |parent| parent.rehash if parent.respond_to? :rehash }
  self
end

#rejectObject



345
346
347
# File 'lib/redshift/util/superhash.rb', line 345

def reject
  dup.delete_if { |k, v| yield k, v }   ## or is '&Proc.new' faster?
end

#reject!Object



349
350
351
352
353
354
355
356
357
358
359
360
# File 'lib/redshift/util/superhash.rb', line 349

def reject!
  changed = false
  
  each do |key, value|
    if yield key, value
      changed = true
      @hash.delete(key) { @hash[key] = default }
    end
  end
  
  changed ? self : nil
end

#replace(hash) ⇒ Object



362
363
364
365
# File 'lib/redshift/util/superhash.rb', line 362

def replace hash
  @hash.replace hash
  @parents.replace []
end

#shiftObject



369
370
371
372
373
374
375
# File 'lib/redshift/util/superhash.rb', line 369

def shift
  if @hash.empty?
    raise ParentImmutableError, "Attempted to shift data out of parent"
  else
    @hash.shift
  end
end

#sizeObject Also known as: length



377
378
379
# File 'lib/redshift/util/superhash.rb', line 377

def size
  keys.size
end

#sortObject



382
383
384
385
386
387
388
# File 'lib/redshift/util/superhash.rb', line 382

def sort
  if block_given?
    to_a.sort { |x, y| yield x, y }   ## or is '&Proc.new' faster?
  else
    to_a.sort
  end
end

#to_aObject



390
391
392
# File 'lib/redshift/util/superhash.rb', line 390

def to_a
  to_hash.to_a
end

#to_hashObject



394
395
396
397
398
# File 'lib/redshift/util/superhash.rb', line 394

def to_hash
  h = {}
  keys.each { |k| h[k] = fetch(k) }
  h
end

#to_sObject



400
401
402
# File 'lib/redshift/util/superhash.rb', line 400

def to_s
  to_hash.to_s
end

#update(h) ⇒ Object



404
405
406
407
# File 'lib/redshift/util/superhash.rb', line 404

def update h
  @hash.update h
  self
end

#valuesObject



409
410
411
# File 'lib/redshift/util/superhash.rb', line 409

def values
  keys.collect { |k| self[k] }
end