1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 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
|