class DottedHash
See Readme.md in gem/repository directory for usage instructions
Constants
- MAX_ATTRS
Maximum count of attributes. Use hash like this to specify each level.
MAX_ATTRS
= {1 => 20, 2 => 5, default: 10}- MAX_DEPTH
Maximum depth of whole tree, not keys (keys depth+1). Counted from 0. Not fully bulletproof, depth may be set to wrong number if careless.
- MAX_SIZE
Maximum size of document, counted from JSON result of document. It is not bulletproof, but if using simple structures, it is enough. Other structures may have much bigger representation in memory than in JSON.
- VERSION
Public Class Methods
Create new instance, recursively converting all Hashes to DottedHash
and leaving everything else alone.
# File lib/dotted_hash.rb, line 34 def initialize(args={}, level=0) raise ArgumentError, "Please pass a Hash-like object" unless args.respond_to?(:each_pair) raise RuntimeError, "Maximal depth reached" if level > MAX_DEPTH @depth = level @attributes = {} args.each_pair do |key, value| assign_value(key, value) end end
Public Instance Methods
Provides access to attribute. Use when you have spaces and other non a-z_
characters in attribute name.
# File lib/dotted_hash.rb, line 130 def [](key) @attributes[key.to_sym] end
Returns (filtered) Ruby Hash
with characters and objects only allowed in JSON.
# File lib/dotted_hash.rb, line 171 def as_json(options=nil) hash = to_hash hash.respond_to?(:with_indifferent_access) ? hash.with_indifferent_access.as_json(options) : hash.as_json(options) end
Let’s pretend we’re someone else in Rails
# File lib/dotted_hash.rb, line 184 def class begin defined?(::Rails) && @attributes[:_type] ? @attributes[:_type].camelize.constantize : super rescue NameError super end end
Standard ActiveModel Errors
# File lib/dotted_hash.rb, line 145 def errors ActiveModel::Errors.new(self) end
Returns id
of document
# File lib/dotted_hash.rb, line 136 def id @attributes[:id] end
# File lib/dotted_hash.rb, line 192 def inspect s = []; @attributes.each { |k,v| s << "#{k}: #{v.inspect}" } %Q|<DottedHash#{self.class.to_s == 'DottedHash' ? '' : " (#{self.class})"} #{s.join(', ')}>| end
Merge with another hash
# File lib/dotted_hash.rb, line 46 def merge!(obj) if obj.respond_to? :to_h hash = obj.to_h elsif obj.respond_to? :to_hash hash = obj.to_hash else raise('Merge works only with hashlike object') end hash.each do |key, value| assign_value(key, value) end self end
Delegate method to a key in underlying hash, if present, otherwise return nil
.
# File lib/dotted_hash.rb, line 86 def method_missing(method_name, *arguments) if method_name.to_s[-1] == '=' && arguments.size == 1 attribute = method_name.to_s.chop value = arguments.first assign_value(attribute, value) else @attributes[method_name.to_sym] end end
# File lib/dotted_hash.rb, line 140 def persisted? !!id end
Recursively assigns value. Also creates sub-DottedHashes if they don’t exist).
# File lib/dotted_hash.rb, line 111 def recursive_assign(key, value) return nil if key.blank? keys = key.split('.') if keys.size > 1 key = keys.shift.to_sym if !@attributes[key] assign_value(key, DottedHash.new({}, @depth+1)) end sub = @attributes[key] sub.send(:recursive_assign, keys.join('.'), value) elsif keys.size == 1 assign_value(keys.shift, value) end end
Always respond to write. Respond to attribute or defined method.
# File lib/dotted_hash.rb, line 99 def respond_to?(method_name, include_private = false) # answers to any write method if method_name.to_s[-1] == '=' true else @attributes.has_key?(method_name.to_sym) || super end end
# File lib/dotted_hash.rb, line 159 def to_hash @attributes.reduce({}) do |sum, item| sum[ item.first ] = item.last.respond_to?(:to_hash) ? item.last.to_hash : item.last sum end end
JSON string of as_json
result
# File lib/dotted_hash.rb, line 177 def to_json(options=nil) as_json.to_json(options) end
Returns key if key exists
# File lib/dotted_hash.rb, line 155 def to_key persisted? ? [id] : nil end
Always true
# File lib/dotted_hash.rb, line 150 def valid? true end
Private Instance Methods
# File lib/dotted_hash.rb, line 61 def assign_value(key, value) max_attrs = if MAX_ATTRS.is_a?(Fixnum) MAX_ATTRS elsif MAX_ATTRS.respond_to?(:[]) MAX_ATTRS[@depth] || MAX_ATTRS[:default] end if max_attrs attrs = @attributes.size + (@attributes.include?(key.to_sym) ? 0 : 1) raise RuntimeError, "Maximum number of attributes reached" if attrs > max_attrs end raise RuntimeError, "Maximal size of document reached" if self.to_json.size+value.to_json.size > MAX_SIZE if value.is_a?(Array) @attributes[key.to_sym] = value.map { |item| @attributes[key.to_sym] = item.is_a?(Hash) ? DottedHash.new(item.to_hash, @depth+1) : item } else @attributes[key.to_sym] = value.is_a?(Hash) ? DottedHash.new(value.to_hash, @depth+1) : value end end