This commit is contained in:
@@ -0,0 +1,145 @@
|
||||
require 'hashery/crud_hash'
|
||||
|
||||
module Hashery
|
||||
|
||||
# OpenHash is a Hash, but also supports open properties much like
|
||||
# OpenStruct.
|
||||
#
|
||||
# Only names that are name methods of Hash can be used as open slots.
|
||||
# To open a slot for a name that would otherwise be a method, the
|
||||
# method needs to be made private. The `#open!` method can be used
|
||||
# to handle this.
|
||||
#
|
||||
# Examples
|
||||
#
|
||||
# o = OpenHash.new
|
||||
# o.open!(:send)
|
||||
# o.send = 4
|
||||
#
|
||||
class OpenHash < CRUDHash
|
||||
|
||||
alias :object_class :class
|
||||
|
||||
#FILTER = /(^__|^\W|^instance_|^object_|^to_)/
|
||||
#methods = Hash.instance_methods(true).select{ |m| m !~ FILTER }
|
||||
#methods = methods - [:each, :inspect, :send] # :class, :as]
|
||||
#private *methods
|
||||
|
||||
#
|
||||
# Initialize new OpenHash instance.
|
||||
#
|
||||
# TODO: Maybe `safe` should be the first argument?
|
||||
#
|
||||
def initialize(default=nil, safe=false, &block)
|
||||
@safe = safe
|
||||
super(*[default].compact, &block)
|
||||
end
|
||||
|
||||
#
|
||||
# If safe is set to true, then public methods cannot be overriden
|
||||
# by hash keys.
|
||||
#
|
||||
attr_accessor :safe
|
||||
|
||||
#
|
||||
# Alias to original store method.
|
||||
#
|
||||
#alias :store! :store
|
||||
|
||||
#
|
||||
# Index `value` to `key`. Unless safe mode, will also open up the
|
||||
# key if it is not already open.
|
||||
#
|
||||
# key - Index key to associate with value.
|
||||
# value - Value to be associate with key.
|
||||
#
|
||||
# Returns +value+.
|
||||
#
|
||||
def store(key, value)
|
||||
#open!(key)
|
||||
super(key, value)
|
||||
end
|
||||
|
||||
#
|
||||
# Open up a slot that that would normally be a Hash method.
|
||||
#
|
||||
# The only methods that can't be opened are ones starting with `__`.
|
||||
#
|
||||
# methods - [Array<String,Symbol>] method names
|
||||
#
|
||||
# Returns Array of slot names that were opened.
|
||||
#
|
||||
def open!(*methods)
|
||||
# Only select string and symbols, any other type of key is allowed,
|
||||
# it just won't be accessible via dynamic methods.
|
||||
methods = methods.select{ |x| String === x || Symbol === x }
|
||||
if methods.any?{ |m| m.to_s.start_with?('__') }
|
||||
raise ArgumentError, "cannot open shadow methods"
|
||||
end
|
||||
# only public methods need be made protected
|
||||
methods = methods.map{ |x| x.to_sym }
|
||||
methods = methods & public_methods(true).map{ |x| x.to_sym }
|
||||
if @safe
|
||||
raise ArgumentError, "cannot set public method" unless methods.empty?
|
||||
else
|
||||
(class << self; self; end).class_eval{ protected *methods }
|
||||
end
|
||||
methods
|
||||
end
|
||||
|
||||
# @deprecated
|
||||
alias :omit! :open!
|
||||
|
||||
#
|
||||
# Is a slot open?
|
||||
#
|
||||
# method - [String,Symbol] method name
|
||||
#
|
||||
# Returns `true` or `false`.
|
||||
#
|
||||
def open?(method)
|
||||
methods = public_methods(true).map{ |m| m.to_sym }
|
||||
! methods.include?(method.to_sym)
|
||||
end
|
||||
|
||||
#
|
||||
# Make specific Hash methods available for use that have previously opened.
|
||||
#
|
||||
# methods - [Array<String,Symbol>] method names
|
||||
#
|
||||
# Returns +methods+.
|
||||
#
|
||||
def close!(*methods)
|
||||
(class << self; self; end).class_eval{ public *methods }
|
||||
methods
|
||||
end
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
def method_missing(s,*a, &b)
|
||||
type = s.to_s[-1,1]
|
||||
name = s.to_s.sub(/[!?=]$/, '')
|
||||
key = name.to_sym
|
||||
|
||||
case type
|
||||
when '='
|
||||
store(key, a.first)
|
||||
when '?'
|
||||
key?(key)
|
||||
when '!'
|
||||
# call an underlying private method
|
||||
# TODO: limit this to omitted methods (from included) ?
|
||||
__send__(name, *a, &b)
|
||||
else
|
||||
#if key?(key)
|
||||
retrieve(key)
|
||||
#else
|
||||
# super(s,*a,&b)
|
||||
#end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
Reference in New Issue
Block a user