Class: DefinedHash

Inherits:
Hash
  • Object
show all
Defined in:
lib/defined_hash.rb

Overview

A hash with defined keys

This is somewhat similar to the ‘Dash’ provided by Hashie, but it has hashidator validations baked in, and in addition, supports the idea of optional columns. It also allows for sensible merging of such values. Property definition is based on the hashidator validation syntax.

Examples:

class Person < DefinedHash
  property :name,       String    # name is a string
  property :emails,     [String]  # Array of strings for email addresses
  property :addresses,  [Address] # Supports nesting of defined hashes
end

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(attributes = {}) ⇒ DefinedHash

Returns a new instance of DefinedHash.



19
20
21
22
23
# File 'lib/defined_hash.rb', line 19

def initialize(attributes={})
  attributes.each do |name, value|
    self[name] = value
  end
end

Class Method Details

.inherited(subclass) ⇒ Object



146
147
148
149
# File 'lib/defined_hash.rb', line 146

def self.inherited(subclass)
  subclass.instance_variable_set('@properties', self.instance_variable_get('@properties').dup) unless self.instance_variable_get('@properties').nil?
  subclass.instance_variable_set('@validations', self.instance_variable_get('@validations').dup) unless self.instance_variable_get('@validations').nil?
end

.propertiesObject



109
110
111
# File 'lib/defined_hash.rb', line 109

def self.properties
  (@properties || {})
end

.property(name, type, opts = {}) ⇒ Object

Define a property for the hash

This is used to define the hash schema. Property definitions look somewhat similar to DataMapper ones, but the types actually define hashidator validation classes, as well as defining the schema.

Parameters:

  • name (Symbol)

    The name of the property

  • type (Object)

    The type of the property

  • opts (Hash) (defaults to: {})

    a customizable set of options

Options Hash (opts):

  • :optional (Boolean) — default: false

    If true, this property won’t be validated if it isn’t there.

Returns:

  • nil



90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/defined_hash.rb', line 90

def self.property(name, type, opts={})
  # use a DefinedHash's inbuilt validations if we have a defined hash
  klass_validator = class_validator_for(type)
  klass_validator = (Array === klass_validator and
                     Class === klass_validator.first and
                     klass_validator.first < DefinedHash) ? [class_validator_for(klass_validator.first)] : klass_validator

  # optional validations done via proc
  validator = opts.fetch(:optional, false) ? proc { |v| v.nil? ? true : klass_validator } : klass_validator

  (@properties ||= {})[name]  = type
  (@validations ||= {})[name] = validator
  nil
end

.property_namesObject



105
106
107
# File 'lib/defined_hash.rb', line 105

def self.property_names
  properties.keys.map { |p| p.to_s }
end

.validationsObject



113
114
115
# File 'lib/defined_hash.rb', line 113

def self.validations
  (@validations || {})
end

Instance Method Details

#[](name) ⇒ Object

Looks up a value from the hash

Parameters:

  • name (Symbol, String)

    Property to return value of

Returns:

  • (Object)

    the value



32
33
34
35
36
37
38
39
40
# File 'lib/defined_hash.rb', line 32

def [](name)
  if Symbol === name
    super(name)
  elsif (self.class.property_names.include?(name.to_s))
    super(name.to_s.to_sym)
  else
    nil
  end
end

#[]=(name, value) ⇒ Object

Assign a value to the hash

Assigns a value to the defined hash. This does a certain amount of typecasting, for example converting properties into arrays, or DefinedHashes

Parameters:

  • name (Symbol, String)

    Property name to assign

  • value (Object)

    Value to assign

Returns:

  • (Object)

    The value just assigned.



54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/defined_hash.rb', line 54

def []=(name, value)
  if (property_name = self.class.property_names.detect { |pn| pn == name.to_s })
    property_name = property_name.to_sym # we know it's a defined name, so this isn't a leak.
    property      = self.class.properties[property_name]
    case property
    when Array
      value = self.class.typecast_value_to_array(property, value)
      if has_key?(property_name)
        self[property_name].concat( value )
      else
        super(property_name, value)
      end
    when Class
      super(property_name, self.class.typecast_value_to_class(property, value))
    else
      super(property_name, value)
    end
  end
  # ignore properties without names
  value
end

#merge!(hash) ⇒ self

Merges in another hash, destroying original values

Parameters:

  • hash (Hash)

    The hash to merge

Returns:

  • (self)

    The new hash



131
132
133
134
135
136
# File 'lib/defined_hash.rb', line 131

def merge!(hash)
  hash.each do |key, value|
    self[key] = value
  end
  self
end

#to_hashObject



138
139
140
141
142
143
144
# File 'lib/defined_hash.rb', line 138

def to_hash
  out = {}
  keys.each do |k|
    out[k] = self[k]
  end
  out
end

#valid?Boolean

validates the hash with hashidator

Returns:

  • (Boolean)

    true/false depending on validation



122
123
124
# File 'lib/defined_hash.rb', line 122

def valid?
  Hashidator.validate(self.class.validations, self)
end