module AltStruct::Behavior

An AltStruct is a data structure, similar to a Hash, that allows the definition of arbitrary attributes with their accompanying values. This is accomplished by using Ruby’s meta-programming to define methods on the class itself.

Examples:

require 'astruct'

class Profile < AltStruct

end

person = Profile.new name: "John Smith"
person.age = 70

puts person.name     # => "John Smith"
puts person.age      # => 70
puts person.dump     # => { :name => "John Smith", :age => 70 }

An AltStruct employs a Hash internally to store the methods and values and can even be initialized with one:

australia = AltStruct.new(
  country: "Australia",
  population: 20_000_000
)
puts australia.inspect
  # => <AltStruct country="Australia", population=20000000>

Hash keys with spaces or characters that would normally not be able to use for method calls (e.g. ()[]*) will not be immediately available on the AltStruct object as a method for retrieval or assignment, but can be still be reached through the ‘Object#send` method.

measurements = AltStruct.new "length (in inches)" => 24
measurements.send "length (in inches)"  # => 24

data_point = AltStruct.new :queued? => true
data_point.queued?                       # => true
data_point.send "queued?=", false
data_point.queued?                       # => false

Removing the presence of a method requires the execution the delete_field or delete (like a hash) method as setting the property value to nil will not remove the method.

first_pet = AltStruct.new :name => 'Rowdy', :owner => 'John Smith'
first_pet.owner = nil
second_pet = AltStruct.new :name => 'Rowdy'

first_pet == second_pet   # -> false

first_pet.delete_field(:owner)
first_pet == second_pet   # -> true

Implementation:

An AltStruct utilizes Ruby’s method lookup structure to and find and define the necessary methods for properties. This is accomplished through the method ‘method_missing` and `define_singleton_method`.

This should be a consideration if there is a concern about the performance of the objects that are created, as there is much more overhead in the setting of these properties compared to using a Hash or a Struct.

Constants

INSPECT_DELIMITER
NESTED_INSPECT
SUFFIX_PATTERN
UNSETABLE_PATTERN
WRAP_PATTERN

Attributes

table[R]

We want to give easy access to the table

Public Class Methods

new(pairs = {}) click to toggle source

Create a new field for each of the key/value pairs passed. By default the resulting OpenStruct object will have no attributes. If no pairs are passed avoid any work.

require "astruct"
hash = { "country" => "Australia", :population => 20_000_000 }
data = AltStruct.new hash

p data # => <AltStruct country="Australia" population=20000000>

If you happen to be inheriting then you can define your own ‘@table` ivar before the `super()` call. AltStruct will respect your `@table`.

# File lib/astruct/behavior.rb, line 115
def initialize(pairs = {})
  @table ||= {}
  __iterate_set_over__(pairs) unless pairs.empty?
end

Public Instance Methods

==(other) click to toggle source
# File lib/astruct/behavior.rb, line 181
def ==(other)
  if other.respond_to?(:table)
    table == other.table
  else
    false
  end
end
__delete__(key) click to toggle source

The ‘delete()` method removes a key/value pair on the @table and on the singleton class. It also mimics the Hash#delete method.

# File lib/astruct/behavior.rb, line 157
def __delete__(key)
  __singleton_class__.send(:remove_method, key)
  __singleton_class__.send(:remove_method, "#{key}=")
  @table.delete(key.to_sym)
end
Also aliased as: delete_field, delete
__dump__(*keys) click to toggle source

The ‘dump()` takes the table and out puts in it’s natural hash format. In addition you can pass along a specific set of keys to dump.

# File lib/astruct/behavior.rb, line 142
def __dump__(*keys)
  if keys.empty? then @table else __dump_specific__(keys) end
end
Also aliased as: marshal_dump, dump, to_hash
__freeze__()
Alias for: freeze
__inspect__() click to toggle source
# File lib/astruct/behavior.rb, line 149
def __inspect__
  "#<#{__class__}#{__dump_inspect__}>"
end
Also aliased as: inspect, to_sym
__load__(pairs) click to toggle source

This is the ‘load()` method, which works like initialize in that it will create new fields for each pair passed. It mimics the behavior of a Hash#merge.

# File lib/astruct/behavior.rb, line 123
def __load__(pairs)
  __iterate_set_over__(pairs) unless pairs.empty?
end
Also aliased as: marshal_load, load, merge
__load__!(pairs) click to toggle source

This is the ‘load!()` method, which works like Hash#merge! See: `AltStruct#load()`

# File lib/astruct/behavior.rb, line 132
def __load__!(pairs)
  __iterate_set_over__(pairs, true)
end
Also aliased as: marshal_load!, load!, merge!
delete(key)
Alias for: __delete__
delete_field(key)
Alias for: __delete__
dump(*keys)
Alias for: __dump__
freeze() click to toggle source
Calls superclass method
# File lib/astruct/behavior.rb, line 189
def freeze
  super
  @table.freeze
end
Also aliased as: __freeze__
inspect()
Alias for: __inspect__
load(pairs)
Alias for: __load__
load!(pairs)
Alias for: __load__!
marshal_dump(*keys)
Alias for: __dump__
marshal_load(pairs)
Alias for: __load__
marshal_load!(pairs)
Alias for: __load__!
merge(pairs)
Alias for: __load__
merge!(pairs)
Alias for: __load__!
method_missing(method, *arguments) click to toggle source

The ‘method_missing()` method catches all non-tabled method calls. The AltStruct object will return two specific errors depending on the call.

Calls superclass method
# File lib/astruct/behavior.rb, line 168
def method_missing(method, *arguments)
  name = method.to_s
  if name.split("").last == "=" && arguments.size == 1
    __define_field__(name.chomp!("="), arguments.first)
  else
    if name.split.last != "="
      super
    else arguments.size > 1
      raise(ArgumentError,"wrong number of arguments (#{arguments.size} for 1)")
    end
  end
end
to_hash(*keys)
Alias for: __dump__
to_sym()
Alias for: __inspect__

Private Instance Methods

__define_accessor__(key) click to toggle source
# File lib/astruct/behavior.rb, line 223
def __define_accessor__(key)
  singleton_class.send(:define_method, key) { @table[key] }
  singleton_class.send(:define_method, "#{key}=") { |v| @table[key] = v }
end
__define_field__(key, value) click to toggle source
# File lib/astruct/behavior.rb, line 218
def __define_field__(key, value)
  __define_accessor__(key)
  __set_table__(key, value)
end
__dump_inspect__() click to toggle source
# File lib/astruct/behavior.rb, line 198
def __dump_inspect__
  Thread.current[THREAD_KEY] ||= Set.new

  if __dump__.any? then " #{__dump_subinspect__}" else "" end.tap do
    __thread_ids__.delete(__object_id__)
  end
end
__dump_specific__(keys) click to toggle source
# File lib/astruct/behavior.rb, line 232
def __dump_specific__(keys)
  @table.keep_if { |key| keys.include?(key.to_sym) }
end
__dump_string__() click to toggle source
# File lib/astruct/behavior.rb, line 236
def __dump_string__
  __dump__.map { |key, value| "#{key}=#{value.inspect}" }
end
__dump_subinspect__() click to toggle source
# File lib/astruct/behavior.rb, line 206
def __dump_subinspect__
  if __thread_ids__.add?(__object_id__)
    __dump_string__.join(INSPECT_DELIMITER)
  else
    NESTED_INSPECT
  end
end
__iterate_set_over__(pairs, force = false) click to toggle source
# File lib/astruct/behavior.rb, line 240
def __iterate_set_over__(pairs, force = false)
  pairs.each do |key, value|
    if force && respond_to?(key)
      __set_table__(key, value)
    else
      __define_accessor__(key)
      __set_table__(key, value)
    end
  end
end
__set_table__(key, value) click to toggle source
# File lib/astruct/behavior.rb, line 228
def __set_table__(key, value)
  @table.merge!(key => value) unless key.nil?
end
__thread_ids__() click to toggle source
# File lib/astruct/behavior.rb, line 214
def __thread_ids__
  Thread.current[THREAD_KEY]
end