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
We want to give easy access to the table
Public Class Methods
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
# File lib/astruct/behavior.rb, line 181 def ==(other) if other.respond_to?(:table) table == other.table else false end end
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
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
# File lib/astruct/behavior.rb, line 149 def __inspect__ "#<#{__class__}#{__dump_inspect__}>" end
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
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
# File lib/astruct/behavior.rb, line 189 def freeze super @table.freeze end
The ‘method_missing()` method catches all non-tabled method calls. The AltStruct
object will return two specific errors depending on the call.
# 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
Private Instance Methods
# 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
# File lib/astruct/behavior.rb, line 218 def __define_field__(key, value) __define_accessor__(key) __set_table__(key, value) end
# 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
# File lib/astruct/behavior.rb, line 232 def __dump_specific__(keys) @table.keep_if { |key| keys.include?(key.to_sym) } end
# File lib/astruct/behavior.rb, line 236 def __dump_string__ __dump__.map { |key, value| "#{key}=#{value.inspect}" } end
# 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
# 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
# File lib/astruct/behavior.rb, line 228 def __set_table__(key, value) @table.merge!(key => value) unless key.nil? end
# File lib/astruct/behavior.rb, line 214 def __thread_ids__ Thread.current[THREAD_KEY] end