saturnflyer/triad

View on GitHub
lib/triad.rb

Summary

Maintainability
A
25 mins
Test Coverage
A
100%
require "triad/version"
require "concurrent/hash"

class Triad
  include Enumerable

  class InvalidAddition < StandardError; end

  class ItemNotPresent < StandardError; end

  # stored as {key => ['Descriptor', value]}
  def initialize
    @storage = Concurrent::Hash.new
  end
  attr_reader :storage
  private :storage

  # Return the keys for a given descriptor or value
  def keys(arg = nil)
    if arg.nil?
      storage.keys
    else
      with_interest(arg).map { |key, _, _| key }
    end
  end

  # Return the descriptors for a given key or value
  def descriptors(arg = nil)
    if arg.nil?
      storage.map { |_, (descriptor, _)| descriptor }
    else
      with_interest(arg).map { |_, descriptor, _| descriptor }
    end
  end

  # Return the values for a given key or descriptor
  def values(arg = :__no_argument_given__)
    if arg == :__no_argument_given__
      storage.map { |_, (_, value)| value }.uniq
    else
      with_interest(arg).map { |_, _, value| value }
    end
  end

  # Add new entries to the object
  def <<(array)
    raise InvalidAddition.new("your array length must be 3") if array.length != 3
    array_key = array.fetch(0)
    raise InvalidAddition.new("the provided key must be a Symbol") unless array_key.is_a?(Symbol)
    raise InvalidAddition.new("the provided key already exists") if key_exists?(array_key)

    array_descriptor = array.fetch(1)
    raise InvalidAddition.new("the provided descriptor must be a String") unless array_descriptor.is_a?(String)
    array_value = array.fetch(2)

    storage[array_key] = [array_descriptor, array_value]
    self
  end

  # Alter the descriptor and value in-place for the given key
  def update(key, descriptor, value)
    raise InvalidAddition.new("the provided descriptor cannot be nil") if descriptor.nil?
    storage[key] = [descriptor, value]
  end

  def each
    storage.each do |key, (descriptor, value)|
      yield key, descriptor, value
    end
  end

  private

  def key_exists?(key)
    storage.key?(key)
  end

  def descriptor_exists?(descriptor)
    storage.values.map { |arr| arr.fetch(0) }.include?(descriptor)
  end

  def value_exists?(value)
    storage.values.map { |arr| arr.fetch(1) }.include?(value)
  end

  def with_interest(interest)
    position = if key_exists?(interest)
      0
    elsif descriptor_exists?(interest)
      1
    elsif value_exists?(interest)
      2
    else
      raise ItemNotPresent.new
    end

    storage.select { |key, array|
      [key, *array].fetch(position) == interest
    }.map { |key, array| [key, *array] }
  end
end