Class: SuperHash
- 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:
-
propagation of values to descendant classes;
-
overriding of values by a subclass; and
-
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
-
#parents ⇒ Object
readonly
Returns the value of attribute parents.
Instance Method Summary collapse
-
#==(other) ⇒ Object
methods that override Hash methods.
- #[](key) ⇒ Object
- #[]=(key, value) ⇒ Object (also: #store)
- #clear ⇒ Object
- #default ⇒ Object
- #default=(value) ⇒ Object
- #delete(key) ⇒ Object
- #delete_if ⇒ Object
- #each ⇒ Object (also: #each_pair)
- #each_key ⇒ Object
- #each_value ⇒ Object
- #empty? ⇒ Boolean
- #fetch(*args) ⇒ Object
- #has_value?(val) ⇒ Boolean (also: #value?)
- #index(val) ⇒ Object
- #indexes(*ks) ⇒ Object (also: #indices)
-
#inherits_key?(k) ⇒ Boolean
methods that are not overrides of Hash methods.
-
#initialize(parents = [], default = nil) ⇒ SuperHash
constructor
A new instance of SuperHash.
- #invert ⇒ Object
- #key?(k) ⇒ Boolean (also: #has_key?, #include?, #member?)
- #keys ⇒ Object
- #own ⇒ Object
- #own_keys ⇒ Object
- #owns_key?(k) ⇒ Boolean
- #rehash ⇒ Object
- #reject ⇒ Object
- #reject! ⇒ Object
- #replace(hash) ⇒ Object
- #shift ⇒ Object
- #size ⇒ Object (also: #length)
- #sort ⇒ Object
- #to_a ⇒ Object
- #to_hash ⇒ Object
- #to_s ⇒ Object
- #update(h) ⇒ Object
- #values ⇒ Object
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
#parents ⇒ Object (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 |
#clear ⇒ Object
218 219 220 |
# File 'lib/redshift/util/superhash.rb', line 218 def clear delete_if {true} end |
#default ⇒ Object
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_if ⇒ Object
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 |
#each ⇒ Object 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_key ⇒ Object
256 257 258 259 |
# File 'lib/redshift/util/superhash.rb', line 256 def each_key keys.each { |k| yield k } self end |
#each_value ⇒ Object
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
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?
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
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 |
#invert ⇒ Object
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?
328 329 330 |
# File 'lib/redshift/util/superhash.rb', line 328 def key? k (@hash.key? k) || (!! @parents.find {|parent| parent.key?(k)} ) end |
#keys ⇒ Object
335 336 337 |
# File 'lib/redshift/util/superhash.rb', line 335 def keys (@hash.keys + (@parents.collect { |parent| parent.keys }).flatten).uniq end |
#own ⇒ Object
187 188 189 |
# File 'lib/redshift/util/superhash.rb', line 187 def own @hash end |
#own_keys ⇒ Object
191 192 193 |
# File 'lib/redshift/util/superhash.rb', line 191 def own_keys @hash.keys end |
#owns_key?(k) ⇒ Boolean
195 196 197 |
# File 'lib/redshift/util/superhash.rb', line 195 def owns_key? k @hash.key? k end |
#rehash ⇒ Object
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 |
#reject ⇒ Object
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 |
#shift ⇒ Object
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 |
#size ⇒ Object Also known as: length
377 378 379 |
# File 'lib/redshift/util/superhash.rb', line 377 def size keys.size end |
#sort ⇒ Object
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_a ⇒ Object
390 391 392 |
# File 'lib/redshift/util/superhash.rb', line 390 def to_a to_hash.to_a end |
#to_hash ⇒ Object
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_s ⇒ Object
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 |
#values ⇒ Object
409 410 411 |
# File 'lib/redshift/util/superhash.rb', line 409 def values keys.collect { |k| self[k] } end |