class WAB::Impl::Data

The class representing the canonical data structure in WAB. Typically the Data instances are factory created by the Shell and will most likely not be instance of this class but rather a class that is a duck-type of this class (has the same methods and behavior).

Attributes

native[R]
root[R]

Public Class Methods

detect_string(s) click to toggle source
# File lib/wab/impl/data.rb, line 16
def self.detect_string(s)
  if WAB::Utils.uuid_format?(s)
    WAB::UUID.new(s)
  elsif WAB::Utils.wab_time_format?(s)
    begin
      DateTime.parse(s).to_time
    rescue
      s
    end
  elsif s.downcase.start_with?('http://')
    begin
      URI(s)
    rescue
      s
    end
  else
    s
  end
end
new(value, repair=false, check=true) click to toggle source

This method should not be called directly. New instances should be created by using a Shell#data method.

Creates a new Data instance with the initial value provided. The value must be a Hash or Array. The members of the Hash or Array must be nil, boolean, String, Integer, Float, BigDecimal, Array, Hash, Time, WAB::UUID, or the Ruby URI::HTTP.

value

initial value

repair

flag indicating invalid value should be repaired if possible

# File lib/wab/impl/data.rb, line 46
def initialize(value, repair=false, check=true)
  if repair
    value = fix(value)
  elsif check
    validate(value)
  end
  @root = value
end

Public Instance Methods

deep_dup() click to toggle source

Make a deep copy of the Data instance.

# File lib/wab/impl/data.rb, line 138
def deep_dup()
  # avoid validation by using a empty Hash for the intial value.
  c = Data.new({})
  c.instance_variable_set(:@root, deep_dup_value(@root))
  c
end
detect() click to toggle source

Detects and converts strings to Ruby objects following the rules:

Time

“2017-01-05T15:04:33.123456789Z”, zulu only

UUID

“b0ca922d-372e-41f4-8fea-47d880188ba3”

URI

opo.technology/sample”, HTTP only

# File lib/wab/impl/data.rb, line 154
def detect()
  return detect_hash(@root) if @root.is_a?(Hash)
  detect_array(@root) if @root.is_a?(Array)
end
each(&block) click to toggle source

Each child of the Data instance is provided as an argument to a block when the each method is called.

# File lib/wab/impl/data.rb, line 125
def each(&block)
  each_node([], @root, block)
end
each_leaf(&block) click to toggle source

Each leaf of the Data instance is provided as an argument to a block when the each method is called. A leaf is a primitive that has no children and will be nil, a Boolean, String, Numberic, Time, WAB::UUID, or URI.

# File lib/wab/impl/data.rb, line 133
def each_leaf(&block)
  each_leaf_node([], @root, block)
end
get(path) click to toggle source

Gets the Data element or value identified by the path where the path elements are separated by the '.' character. The path can also be a array of path node identifiers. For example, child.grandchild is the same as ['child', 'grandchild'].

# File lib/wab/impl/data.rb, line 90
def get(path)
  node = Utils.get_node(@root, path)

  return node unless node.is_a?(Hash) || node.is_a?(Array)
  Data.new(node, false, false)
end
has?(path) click to toggle source

Returns true if the Data element or value identified by the path exists where the path elements are separated by the '.' character. The path can also be a array of path node identifiers. For example, child.grandchild is the same as ['child', 'grandchild'].

# File lib/wab/impl/data.rb, line 63
def has?(path)
  return (@root.is_a?(Hash) && @root.has_key?(path)) if path.is_a?(Symbol)

  path = path.to_s.split('.') unless path.is_a?(Array)
  node = @root
  path.each { |key|
    if node.is_a?(Hash)
      key = key.to_sym
      return false unless node.has_key?(key)
      node = node[key]
    elsif node.is_a?(Array)
      i = key.to_i
      return false if 0 == i && '0' != key && 0 != key
      len = node.length
      return false unless -len <= i && i < len
      node = node[i]
    else
      return false
    end
  }
  true
end
json(indent=0) click to toggle source

Encode the data as a JSON string.

# File lib/wab/impl/data.rb, line 146
def json(indent=0)
  Oj.dump(@root, mode: :wab, indent: indent)
end
set(path, value, repair=false) click to toggle source

Sets the node value identified by the path where the path elements are separated by the '.' character. The path can also be a array of path node identifiers. For example, child.grandchild is the same as ['child', 'grandchild']. The value must be one of the allowed data values described in the initialize method.

For arrays, the behavior is similar to an Array#[] with the exception of a negative index less than the negative length in which case the value is prepended (Array#unshift).

path

path to location to be set

value

value to set

repair

flag indicating invalid value should be repaired if possible

# File lib/wab/impl/data.rb, line 110
def set(path, value, repair=false)
  raise WAB::Error, 'path can not be empty.' if path.empty?
  if value.is_a?(WAB::Data)
    value = value.native
  elsif repair
    value = fix_value(value)
  else
    validate_value(value)
  end
  node = @root
  Utils.set_value(node, path, value)
end

Private Instance Methods

deep_dup_value(value) click to toggle source
# File lib/wab/impl/data.rb, line 278
def deep_dup_value(value)
  if value.is_a?(Hash)
    c = {}
    value.each_pair { |k, v| c[k] = deep_dup_value(v) }
  elsif value.is_a?(Array)
    c = value.map { |v| deep_dup_value(v) }
  else
    value_class = value.class
    c = if value.nil? ||
            TrueClass  == value_class ||
            FalseClass == value_class ||
            Integer    == value_class ||
            Float      == value_class ||
            String     == value_class ||
            WAB::Utils.pre_24_fixnum?(value)
          value
        else
          value.dup
        end
  end
  c
end
detect_array(a) click to toggle source
# File lib/wab/impl/data.rb, line 305
def detect_array(a)
  a.each_index { |i| detect_elememt(a, i) }
end
detect_elememt(collection, key) click to toggle source
# File lib/wab/impl/data.rb, line 309
def detect_elememt(collection, key)
  item = collection[key]

  case item
  when Hash
    detect_hash(item)
  when Array
    detect_array(item)
  when String
    element = Data.detect_string(item)
    collection[key] = element unless element == item
  end
end
detect_hash(h) click to toggle source
# File lib/wab/impl/data.rb, line 301
def detect_hash(h)
  h.each_key { |k| detect_elememt(h, k) }
end
each_leaf_node(path, value, block) click to toggle source
# File lib/wab/impl/data.rb, line 268
def each_leaf_node(path, value, block)
  if value.is_a?(Hash)
    value.each_pair { |k, v| each_leaf_node(path + [k], v, block) }
  elsif value.is_a?(Array)
    value.each_index { |i| each_leaf_node(path + [i], value[i], block) }
  else
    block.call(path, value)
  end
end
each_node(path, value, block) click to toggle source
# File lib/wab/impl/data.rb, line 259
def each_node(path, value, block)
  block.call(path, value)
  if value.is_a?(Hash)
    value.each_pair { |k, v| each_node(path + [k], v, block) }
  elsif value.is_a?(Array)
    value.each_index { |i| each_node(path + [i], value[i], block) }
  end
end
fix(value) click to toggle source

Fix values by returing either the value or the fixed alternative.

# File lib/wab/impl/data.rb, line 212
def fix(value)
  value_class = value.class
  if Hash == value_class
    value = fix_hash(value)
  elsif Array == value_class
    value = value.map { |v| fix_value(v) }
  elsif value.respond_to?(:to_h) && 0 == value.method(:to_h).arity
    value = value.to_h
    raise WAB::TypeError unless value.is_a?(Hash)
    value = fix(value)
  else
    raise WAB::TypeError
  end
  value
end
fix_hash(hsh) click to toggle source
# File lib/wab/impl/data.rb, line 249
def fix_hash(hsh)
  old = hsh
  hsh = {}
  old.each_pair { |k, v|
    k = k.to_sym unless k.is_a?(Symbol)
    hsh[k] = fix_value(v)
  }
  hsh
end
fix_value(value) click to toggle source
# File lib/wab/impl/data.rb, line 228
def fix_value(value)
  return value if valid_class(value)

  value_class = value.class
  if Hash == value_class
    value = fix_hash(value)
  elsif Array == value_class
    value = value.map { |v| fix_value(v) }
  elsif value.respond_to?(:to_h) && 0 == value.method(:to_h).arity
    value = value.to_h
    raise WAB::TypeError unless value.is_a?(Hash)
    value = fix(value)
  elsif value.respond_to?(:to_s)
    value = value.to_s
    raise StandardError.new('Data values must be either a Hash or an Array') unless value.is_a?(String)
  else
    raise WAB::TypeError, "#{value_class} is not a valid Data value."
  end
  value
end
valid_class(value) click to toggle source
# File lib/wab/impl/data.rb, line 196
def valid_class(value)
  value_class = value.class
  value.nil? ||
    TrueClass  == value_class ||
    FalseClass == value_class ||
    Integer    == value_class ||
    Float      == value_class ||
    String     == value_class ||
    Time       == value_class ||
    BigDecimal == value_class ||
    URI::HTTP  == value_class ||
    WAB::UUID  == value_class ||
    WAB::Utils.pre_24_fixnum?(value)
end
validate(value) click to toggle source

Raise an exception if the value is not a suitable data element. If the repair flag is true an attempt is made to fix the value if possible by replacing non-symbol keys with Symbols and converting unsupported objects with a to_h or a to_s.

value

value to validate

# File lib/wab/impl/data.rb, line 167
def validate(value)
  if value.is_a?(Hash)
    validate_hash(value)
  elsif value.is_a?(Array)
    value.each { |v| validate_value(v) }
  else
    raise WAB::TypeError
  end
  value
end
validate_hash(hsh) click to toggle source
# File lib/wab/impl/data.rb, line 189
def validate_hash(hsh)
  hsh.each_pair { |k, v|
    raise WAB::KeyError unless k.is_a?(Symbol)
    validate_value(v)
  }
end
validate_value(value) click to toggle source
# File lib/wab/impl/data.rb, line 178
def validate_value(value)
  return value if valid_class(value)
  if value.is_a?(Hash)
    validate_hash(value)
  elsif value.is_a?(Array)
    value.each { |v| validate_value(v) }
  else
    raise WAB::TypeError, "#{value_class} is not a valid Data value."
  end
end