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

new(args={}, level=0) click to toggle source

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

[](key) click to toggle source

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
as_json(options=nil) click to toggle source

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
class() click to toggle source

Let’s pretend we’re someone else in Rails

Calls superclass method
# File lib/dotted_hash.rb, line 184
def class
        begin
                defined?(::Rails) && @attributes[:_type] ? @attributes[:_type].camelize.constantize : super
        rescue NameError
                super
        end
end
errors() click to toggle source

Standard ActiveModel Errors

# File lib/dotted_hash.rb, line 145
def errors
        ActiveModel::Errors.new(self)
end
id() click to toggle source

Returns id of document

# File lib/dotted_hash.rb, line 136
def id
        @attributes[:id]
end
inspect() click to toggle source
# 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!(obj) click to toggle source

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
method_missing(method_name, *arguments) click to toggle source

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
persisted?() click to toggle source
# File lib/dotted_hash.rb, line 140
def persisted?
        !!id
end
recursive_assign(key, value) click to toggle source

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
respond_to?(method_name, include_private = false) click to toggle source

Always respond to write. Respond to attribute or defined method.

Calls superclass 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
to_h()
Alias for: to_hash
to_hash() click to toggle source
# 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
Also aliased as: to_h
to_indexed_json(options=nil)
Alias for: to_json
to_json(options=nil) click to toggle source

JSON string of as_json result

# File lib/dotted_hash.rb, line 177
def to_json(options=nil)
        as_json.to_json(options)
end
Also aliased as: to_indexed_json
to_key() click to toggle source

Returns key if key exists

# File lib/dotted_hash.rb, line 155
def to_key
        persisted? ? [id] : nil
end
valid?() click to toggle source

Always true

# File lib/dotted_hash.rb, line 150
def valid?
        true
end

Private Instance Methods

assign_value(key, value) click to toggle source
# 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