Files
news.gitea.happylizard.me/bin/gems/hashery-2.1.2/lib/hashery/crud_hash.rb
T
pizza2d1 4cee170d66
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 9s
Add bin and edit workflow
2026-09-16 13:11:16 -06:00

406 lines
8.6 KiB
Ruby

require 'hashery/core_ext'
module Hashery
# The CRUDHash is essentailly the same as the Hash class, but it reduces the
# the set of necessary methods to the fundametal CRUD requirements. All other
# methods route through these CRUD methods. This is a better general design,
# although it is, of course, a little bit slower. The utility of this class
# becomes appearent when subclassing or delegating, as only a handful of methods
# need to be changed for all other methods to work accordingly.
#
# In addition to the CRUD features, CRUDHash supports a `#key_proc`, akin to
# `#default_proc`, that can be used to normalize keys.
#
# The CRUD methods are:
#
# * key?
# * fetch
# * store
# * delete
#
# In addition to these main methods, there are these supporting "CRUD" methods:
#
# * default
# * default_proc
# * default_proc=
# * key_proc
# * key_proc=
#
class CRUDHash < ::Hash
#
# Dummy object for null arguments.
#
NA = Object.new
#
# This method is overridden to ensure that new entries pass through
# the `#store` method.
#
# hash - [#each] Single Hash, associative array or just a list of pairs.
#
def self.[](*hash)
h = new
if hash.size == 1
hash.first.each do |k,v|
h.store(k, v)
end
else
hash.each do |(k,v)|
h.store(k, v)
end
end
h
end
#
# Alternate to #new which auto-creates sub-dictionaries as needed.
# By default the `default_proc` procuced a empty Hash and is
# self-referential so every such Hash also has the same `default_proc`.
#
# args - Pass-thru arguments to `#new`.
# block - Alternate internal procedure for default proc.
#
# Examples
#
# d = CRUDHash.auto
# d["a"]["b"]["c"] = "abc" #=> { "a"=>{"b"=>{"c"=>"abc"}}}
#
# Returns `Hash`.
#
def self.auto(*args, &block)
if block
leet = lambda { |hsh, key| hsh[key] = block.call(hsh, key) }
else
leet = lambda { |hsh, key| hsh[key] = new(&leet) }
end
new(*args, &leet)
end
#
# Set `key_proc`.
#
# Examples
#
# ch = CRUDHash.new
# ch.key_proc = Proc.new{ |key| key.to_sym }
#
# Returns `Proc`.
#
def key_proc=(proc)
raise ArgumentError unless Proc === proc or NilClass === proc
@key_proc = proc
end
#
# Get/set `key_proc`.
#
# Examples
#
# ch = CRUDHash.new
# ch.key_proc
#
# Returns `Proc`.
#
def key_proc(&block)
@key_proc = block if block
@key_proc
end
#
# Allow `#default_proc` to take a block.
#
# block - The `Proc` object to set the `default_proc`.
#
# Returns `Proc`, the `default_proc`.
#
def default_proc(&block)
self.default_proc = block if block
super()
end
#
# CRUD method for checking if key exists.
#
# key - Hash key to lookup.
#
# Returns `true/false`.
#
def key?(key)
super cast_key(key)
end
#
# CRUD method for read. This method gets the value for a given key.
# An error is raised if the key is not present, but an optional argument
# can be provided to be returned instead.
#
# key - Hash key to lookup.
# default - Value to return if key is not present.
#
# Raises KeyError when key is not found and default has not been given.
#
# Returns the `Object` that is the Hash entry's value.
#
def fetch(key, *default)
super(cast_key(key), *default)
end
#
# CRUD method for create and update.
#
# key - The `Object` to act as indexing key.
# value - The `Object` to associate with key.
#
# Returns +value+.
#
def store(key, value)
super(cast_key(key), value)
end
#
# CRUD method for delete.
#
# key - Hash key to remove.
#
# Returns value of deleted Hash entry.
#
def delete(key)
super cast_key(key)
end
# END OF CRUD METHODS
#
# Like #fetch but returns the results of calling `default_proc`, if defined,
# otherwise `default`.
#
# key - Hash key to lookup.
#
# Returns value of Hash entry or `nil`.
#
def retrieve(key)
if key?(key)
fetch(key)
else
default_proc ? default_proc.call(self, key) : default
end
end
#
# Method for reading value. Returns `nil` if key is not present.
#
# Note that this method used to be the CRUD method instead of #retrieve. Complaints about
# #read being indicative of an IO object (though in my opinion that is a bad asumption) have
# led to this method's deprecation.
#
# key - Hash key to lookup.
#
# Returns value of Hash entry.
#
def read(key)
warn "The #read method as been deprecated. Use #retrieve instead."
retrieve(key)
end
#
# Update Hash with +assoc+.
#
# assoc - Two-element `Array` or a `Hash`.
#
# Returns +assoc+.
#
def <<(assoc)
case assoc
when Hash
update(assoc)
when Array
assoc.each_slice(2) do |(k,v)|
store(k,v)
end
else
raise ArgumentError # or TypeError ?
end
end
#
# Operator for `#retrieve`.
#
# key - Index key to lookup.
#
# Returns `Object` value of key.
#
def [](key)
retrieve(key)
end
#
# Operator for `#store`.
#
# key - The `Object` to act as indexing key.
# value - The `Object` to associate with key.
#
# Returns +value+.
#
def []=(key,value)
store(key,value)
end
#
# Update the Hash with another hash.
#
# other - Other hash or hash-like object to add to the hash.
#
# Returns +self+.
#
def update(other)
other.each do |k,v|
store(k, v)
end
self
end
#
# Alias for `#update`.
#
alias merge! update
#
# Merge the Hash with another hash, returning a new Hash.
#
# other - Other hash or hash-like object to add to the hash.
#
# Returns `Hash`.
#
def merge(other)
#super(other.rekey{ |key| cast_key(key) })
copy = dup
other.each{ |k,v| copy.store(k, v) }
copy
end
#
# Iterate over each hash pair.
#
def each #:yield:
if block_given?
keys.each do |k|
yield(k, retrieve(k))
end
else
to_enum(:each)
end
end
#
# Alias for #each.
#
alias each_pair each
#
# Alias for `#key?`.
#
alias has_key? key?
#
# Alias for `#key?`.
#
alias member? key?
#
# Alias for `#key?`.
#
alias include? key? # why isn't it an alias for `#has_value?` ?
#
# Replace current entries with those from another Hash,
# or Hash-like object. Each entry is run through the
# casting procedure as it is added.
#
# other - Hash-like object.
#
# Returns +self+.
#
def replace(other)
super cast(other)
end
#
# Get the values at.
#
# keys - List of keys to lookup.
#
# Returns `Array` of values.
#
def values_at(*keys)
super *keys.map{ |key| cast_key(key) }
end
# Convert CRUDHash to regular Hash.
#
# TODO: Since a CRUDHash is a subclass of Hash should #to_hash just `#dup`
# insted of converting to traditional Hash?
#
def to_hash
h = {}; each{ |k,v| h[k] = v }; h
end #unless method_defined?(:to_hash)
#
# Convert CRUDHash to regular Hash.
#
# TODO: Since a CRUDHash is a subclass of Hash should #to_h just `#dup`
# insted of converting to traditional Hash?
#
# Returns `Hash`.
#
alias :to_h :to_hash
private
#
# Cast a given `hash` in accordance to the `#key_proc`.
#
# hash - Any object the responds to `#each` like a Hash.
#
# Returns `Hash`.
#
def cast(hash)
h = {}
hash.each do |k,v|
h[cast_key(k)] = v
end
h
end
#
# Callback for normalizing hash keys.
#
# key - Index key.
#
# Returns key after passing through the `key_proc`.
#
def cast_key(key)
@key_proc ? @key_proc.call(key) : key
end
# TODO: Consider value callback procs for future version of CRUDHash.
#
# #
# # Callback for writing value.
# #
# def cast_write(value)
# @write_proc ? @write_proc.call(value) : value
# end
#
# #
# # Callback for reading value.
# #
# def cast_read(value)
# @read_proc ? @read_proc.call(value) : value
# end
end
end