class SDStruct

Alternative to OpenStruct that is more strict and go deeper.

@author Adrian Setyadi

Constants

VERSION

Attributes

table[R]

Public Class Methods

new(hash = nil, opts = {}) click to toggle source

Creates a new SDStruct object. By default, the resulting SDStruct object will have no attributes.

The optional hash, if given, will generate attributes and values (can be a Hash, an SDStruct or a Struct). For example:

require 'sd_struct' # or require 'sd_struct/base'
hash = { "name" => "Matz", "coding language" => :ruby, :age => "old" }
data = SDStruct.new(hash)

p data # -> #<SDStruct .name="Matz", ['coding language']=:ruby, .age="old">
# File lib/sd_struct/base.rb, line 44
def initialize(hash = nil, opts = {})
  opts = {
    deep:           true,
    symbolize_keys: true
  }.merge(opts)

  @deep = opts[:deep]
  @table = {}
  if hash
    hash.each_pair do |k, v|
      @table[naturalize(k)] = structurize(v) # @deep is used in this method
    end
  end
end

Public Instance Methods

==(other) click to toggle source
# File lib/sd_struct/base.rb, line 177
def ==(other)
  return false unless other.kind_of?(self.class)
  @table == other.table
end
[](name) click to toggle source

Returns the value of a member.

person = SDStruct.new('name' => 'Matz', 'lang' => 'ruby')
person[:lang] # => ruby, same as person.lang
# File lib/sd_struct/base.rb, line 130
def [](name)
  @table[name] || @table[naturalize(name)]
end
[]=(name, value) click to toggle source

Sets the value of a member.

person = SDStruct.new('name' => 'Matz', 'lang' => 'python')
person[:lang] = 'ruby' # => equivalent to person.lang = 'ruby'
person.lang # => ruby
# File lib/sd_struct/base.rb, line 141
def []=(name, value)
  unless self[name].nil? || value.is_a?(self[name].class)
    warn("You're assigning a value with different type as the previous value.")
  end
  @table[new_struct_member(naturalize(name))] = structurize(value)
end
delete_field(name) click to toggle source

Deletes specified field or key

# File lib/sd_struct/base.rb, line 226
def delete_field(name)
  name = @table.has_key?(name) ? name : naturalize(name)
  @table.delete(name) do
    raise NameError.new("no field `#{name}' in #{self}", name)
  end
  singleton_class.__send__(:remove_method, name, "#{name}=")
end
Also aliased as: delete_key
delete_key(name)
Alias for: delete_field
dig(*args) click to toggle source

Digs the content of @table which is a hash

@param [Symbol] multiple symbols @return [SDStruct,Hash,Array,String,Integer,Float,Boolean,nil] first matched result

# File lib/sd_struct/deep_search.rb, line 68
def dig(*args)
  @table.dig(*args)
end
dig_deep(*args) click to toggle source

Digs deep into array until non-Array and non-Hash primitive data is found

@param [Symbol] multiple symbols @return [String,Integer,Float,Boolean,nil] first matched result

# File lib/sd_struct/deep_search.rb, line 11
def dig_deep(*args)
  full_args = args.dup
  parent_key = args.shift
  result = nil
  if parent_key.is_a?(Integer)
    result = dig(parent_key)
    unless result.nil? || args.length.zero?
      result = result.dig_deep(*args)
    end
  end
  if result.nil?
    each do |x|
      if x.respond_to?(:dig_deep) || x.is_a?(Array)
        result = x.dig_deep(*full_args)
      end
      return result unless result.nil?
    end
  end
  return result
end
each_pair() { |p| ... } click to toggle source

Yields all attributes (as a symbol) along with the corresponding values or returns an enumerator if not block is given. Example:

require 'sd_struct'
data = SDStruct.new("name" => "Matz", "coding language" => :ruby)
data.each_pair.to_a  # => [[:name, "Matz"], ["coding language", :ruby]]
# File lib/sd_struct/base.rb, line 90
def each_pair
  return to_enum(__method__) { @table.size } unless block_given?
  @table.each_pair{|p| yield p}
end
eql?(other) click to toggle source

Compares this object and other for equality. An SDStruct is eql? to other when other is an SDStruct and the two objects' Hash tables are eql?.

# File lib/sd_struct/base.rb, line 187
def eql?(other)
  return false unless other.kind_of?(self.class)
  @table.eql?(other.table)
end
fields()
Alias for: non_spaced_keys
find(key_str, opts = {}) click to toggle source

Finds value with keys specified like xpath

@param [String] key string @param [Hash] option Hash @return [SDStruct,Hash,Array,String,Integer,Float,Boolean,nil] first matched result

# File lib/sd_struct/deep_search.rb, line 79
def find(key_str, opts = {})
  opts = {
    separator: "/"
  }.merge(opts)

  sep = Regexp.quote(opts[:separator])

  args = begin
    key_str.gsub(/^#{sep}(?!#{sep})|#{sep}+$/, '')
           .split(/#{sep}{2,}/)
           .map do |ks|
             ks.split(/#{sep}/)
               .map do |x|
                 x.strip!
                 if !!x[/\A[-+]?\d+\z/]
                   x.to_i
                 else
                   if x[/^$|^[A-Z]|\s+/]
                     x
                   else
                     x.underscore.to_sym
                   end
                 end
               end
           end
  end

  if !(parent_key = args.shift) # args == [], key_str == ""
    return
  else # e.g. args == [[], ..] or [[.., ..], [..]]
    result = dig(*parent_key) unless parent_key.empty?

    unless args.length.zero?
      args.each do |a|
        result = result.dig_deep(*a) rescue result = dig_deep(*a)
      end
    end
  end
  return result
end
hash() click to toggle source

Computes a hash-code for this SDStruct. Two hashes with the same content will have the same hash code (and will be eql?).

# File lib/sd_struct/base.rb, line 197
def hash
  @table.hash
end
initialize_copy(orig) click to toggle source

Duplicates an SDStruct object members.

Calls superclass method
# File lib/sd_struct/base.rb, line 62
def initialize_copy(orig)
  super
  @table = @table.dup
end
inspect() click to toggle source

Returns a string containing a detailed summary of the keys and values.

# File lib/sd_struct/base.rb, line 151
def inspect
  str = "#<#{self.class}"

  ids = (Thread.current[:sd_struct] ||= [])
  if ids.include?(object_id)
    return str << ' ...>'
  end

  ids << object_id
  begin
    first = true
    for k,v in @table
      str << "," unless first
      first = false
      str << " #{k[/\s+/] ? "['#{k}']" : ".#{k}"}=#{v.inspect}"
    end
    return str << '>'
  ensure
    ids.pop
  end
end
Also aliased as: to_s
keys() click to toggle source

Exposes all keys

# File lib/sd_struct/base.rb, line 219
def keys
  @table.keys
end
marshal_dump() click to toggle source

Provides marshalling support for use by the Marshal library.

# File lib/sd_struct/base.rb, line 70
def marshal_dump
  to_h
end
marshal_load(x) click to toggle source

Provides marshalling support for use by the Marshal library.

# File lib/sd_struct/base.rb, line 77
def marshal_load(x)
  @table = x.map{|a| structurize(a) }.original_to_h
end
method_missing(m_id, *args) click to toggle source
# File lib/sd_struct/base.rb, line 235
def method_missing(m_id, *args)
  m_nat = naturalize(m_id)
  if args.length.zero?
    if @table.key?(m_nat)
      @table[new_struct_member(m_nat)]
    end
  else
    err = NoMethodError.new "undefined method `#{m_id}' for #{self}", m_id, args
    err.set_backtrace caller(1)
    raise err
  end
end
non_spaced_keys() click to toggle source

Exposes keys without space(s)

# File lib/sd_struct/base.rb, line 211
def non_spaced_keys
  methods(false).select{|x| x[/^\S+[^=]$/]}
end
Also aliased as: fields
original_to_h(camelize_keys = false)
Alias for: to_h
spaced_keys() click to toggle source

Exposes keys with space(s)

# File lib/sd_struct/base.rb, line 204
def spaced_keys
  @table.keys - non_spaced_keys
end
to_h(camelize_keys = false) click to toggle source
# File lib/sd_struct/deep_convert.rb, line 6
def to_h(camelize_keys = false)
  map{|x| x.respond_to?(:to_h) ? x.to_h(camelize_keys) : x }
end
Also aliased as: original_to_h
to_json(opts = {}) click to toggle source
# File lib/sd_struct/deep_convert.rb, line 28
def to_json(opts = {})
  opts = {
    camelize_keys: true,
    exclude_blank_values: true,
    values_to_exclude: [0, [""], [{}]]
  }.merge(opts)

  to_h(opts).to_json
end
to_s()
Alias for: inspect
to_struct() click to toggle source

Changes current Hash object to SDStruct object

@return [SDStruct] SDStruct object

# File lib/sd_struct/base.rb, line 13
def to_struct
  SDStruct.new(to_h.dup)
end

Protected Instance Methods

naturalize(key) click to toggle source
# File lib/sd_struct/base.rb, line 106
def naturalize(key)
  unless key[/^[A-Z]|\s+/]
    key.to_s.underscore.to_sym
  else
    key
  end
end
new_struct_member(name) click to toggle source

Used internally to define field properties

# File lib/sd_struct/base.rb, line 98
def new_struct_member(name)
  unless respond_to?(name)
    define_singleton_method(name) { @table[name] }
    define_singleton_method("#{name}=") { |x| @table[name] = x }
  end
  name
end
structurize(value) click to toggle source

Calls to struct to a value if it is an Array or a Hash and @deep is true

# File lib/sd_struct/base.rb, line 117
def structurize(value)
  ( @deep && (value.is_a?(Hash) || value.is_a?(Array)) ) ? value.to_struct : value
end