class OpenStruct2
OpenStruct2
is a better OpenStruct
class.
To demonstrate the weakness of the original OpenStruct
, try this IRB session:
irb(main):001:0> o = OpenStruct.new => #<OpenStruct> irb(main):002:0> o.display = "Hello, World!" => "Hello, World!" irb(main):003:0> o.display #<OpenStruct display="Hello, World!">=> nil
This new OpenStruct
class allows almost any member name to be used. The only exceptions are methods starting with double underscores, such as ‘__id__` and `__send__`, and a few neccessary public methods: `clone`, `dup`, `freeze`, `hash`, `to_enum`, `to_h`, `to_s` and `inspect`, as well as `instance_eval` and `instance_exec`.
Also note that ‘empty`, `eql`, `equal`, `frozen` and `key` can be used as members but the key-check shorthand of using `?`-methods cannot be used since these have special definitions.
To offset the loss of most methods, OpenStruct
provides numerous bang-methods which can be used to manipulate the data, e.g. ‘#each!`. Currently most bang-methods route directly to the underlying hash table, so developers should keep that in mind when using this feature. A future version may add an intermediate interface to always ensure proper “CRUD”, functonality but in the vast majority of cases it will make no difference, so it is left for later consideration.
This improved version of OpenStruct
also has no issues with being cloned since it does not depend on singleton methods to work. But singleton methods are used to help boost performance. But instead of always creating singleton methods, it only creates them on the first attempt to use them.
Public Class Methods
Create autovivified OpenStruct
.
@example
o = OpenStruct2.renew o.a #=> #<OpenStruct2: {}>
# File lib/ostruct2.rb, line 45 def auto(data=nil) leet = lambda{ |h,k| new(&leet) } new(&leet) end
Constructor that is both autovivified and nested.
# File lib/ostruct2.rb, line 76 def cascade(data=nil) o = renew o.nested!(true) o.update!(data) if data o end
Create a nested OpenStruct
, such that all sub-hashes added to the table also become OpenStruct
objects.
# File lib/ostruct2.rb, line 61 def nested(data=nil) o = new o.nested!(true) o.update!(data) if data o end
Initialize new instance of OpenStruct
.
@param [Hash] data
# File lib/ostruct2.rb, line 95 def initialize(data=nil, &block) @table = ::Hash.new(&block) update!(data || {}) end
Another name for auto method.
TODO: Still wondering waht the best name is for this.
Private Class Methods
# File lib/ostruct2.rb, line 85 def const_missing(name) ::Object.const_get(name) end
Public Instance Methods
Two OpenStructs are equal if they are the same class and their underlying tables are equal.
TODO: Why not equal for other hash types, e.g. via to_h?
# File lib/ostruct2.rb, line 390 def ==(other) return false unless(other.kind_of?(__class__)) return @table == other.table #to_h end
Alias for ‘#read!`.
@param [#to_sym] key
@return [Object]
# File lib/ostruct2.rb, line 234 def [](key) read!(key) end
Alias for ‘#store!`.
@param [#to_sym] key
@param [Object] value
@return value.
# File lib/ostruct2.rb, line 247 def []=(key, value) store!(key, value) end
Because there is no means of getting the class via a BasicObject instance, we define such a method manually.
# File lib/ostruct2.rb, line 104 def __class__ OpenStruct2 end
The CRUD method for destroy.
# File lib/ostruct2.rb, line 191 def delete!(key) @table.delete(key.to_sym) end
Same as ‘#delete!`. This method provides compatibility with the original OpenStruct
class.
@deprecated Use ‘#delete!` method instead.
# File lib/ostruct2.rb, line 201 def delete_field(key) @table.delete(key.to_sym) end
Duplicate OpenStruct
object.
@return [OpenStruct] Duplicate instance.
# File lib/ostruct2.rb, line 339 def dup __class__.new(@table, &@table.default_proc) end
CRUDified each.
@return nothing
# File lib/ostruct2.rb, line 256 def each! @table.each_key do |key| yield(key, read!(key)) end end
Is the OpenStruct
void of entries?
# File lib/ostruct2.rb, line 371 def empty? @table.empty? end
Two OpenStructs are equal if they are the same class and their underlying tables are equal.
# File lib/ostruct2.rb, line 379 def eql?(other) return false unless(other.kind_of?(__class__)) return @table == other.table #to_h end
Like read but will raise a KeyError if key is not found.
# File lib/ostruct2.rb, line 208 def fetch!(key) key!(key) read!(key) end
Freeze OpenStruct
instance.
# File lib/ostruct2.rb, line 355 def freeze @table.freeze end
Is the OpenStruct
instance frozen?
@return [Boolean]
# File lib/ostruct2.rb, line 364 def frozen? @table.frozen? end
Hash number.
# File lib/ostruct2.rb, line 348 def hash @table.hash end
Duplicate underlying table when OpenStruct
is duplicated or cloned.
@param [OpenStruct] original
# File lib/ostruct2.rb, line 113 def initialize_copy(original) super @table = @table.dup end
Inspect OpenStruct
object.
@return [String]
# File lib/ostruct2.rb, line 301 def inspect "#<#{__class__}: #{@table.inspect}>" end
If key is not present raise a KeyError.
@param [#to_sym] key
@raise [KeyError] If key is not present.
@return key
# File lib/ostruct2.rb, line 222 def key!(key) return key if key?(key) ::Kernel.raise ::KeyError, ("key not found: %s" % [key.inspect]) end
Also a CRUD method like read!
, but for checking for the existence of a key.
# File lib/ostruct2.rb, line 164 def key?(key) @table.key?(key.to_sym) end
CRUD method for listing all keys.
# File lib/ostruct2.rb, line 157 def keys! @table.keys end
# File lib/ostruct2.rb, line 263 def map!(&block) to_enum.map(&block) end
Merge this OpenStruct
with another OpenStruct
or Hash object returning a new OpenStruct
instance.
IMPORTANT! This method does not act in-place like ‘Hash#merge!`, rather it works like `Hash#merge`.
@return [OpenStruct]
# File lib/ostruct2.rb, line 288 def merge!(other) o = dup other.each do |k,v| o.store!(k,v) end o end
Dispatch unrecognized member calls.
# File lib/ostruct2.rb, line 121 def method_missing(sym, *args, &blk) str = sym.to_s type = str[-1,1] name = str.chomp('=').chomp('!').chomp('?') case type when '!' # TODO: Probably should have an indirect interface to ensure proper # functonality in all cases. @table.public_send(name, *args, &blk) when '=' new_ostruct_member(name) store!(name, args.first) when '?' new_ostruct_member(name) key?(name) else new_ostruct_member(name) read!(name) end end
Get/set nested flag.
# File lib/ostruct2.rb, line 146 def nested!(boolean=nil) if boolean.nil? @nested else @nested = !!boolean end end
The CRUD method for read.
# File lib/ostruct2.rb, line 171 def read!(key) @table[key.to_sym] end
The CRUD method for create and update.
# File lib/ostruct2.rb, line 178 def store!(key, value) if @nested && Hash === value # value.respond_to?(:to_hash) value = OpenStruct2.new(value) end #new_ostruct_member(key) # this is here only for speed bump @table[key.to_sym] = value end
Create an enumerator based on ‘#each!`.
@return [Enumerator]
# File lib/ostruct2.rb, line 324 def to_enum(methname=:each!) # Why has Ruby 2 deprecated this form? #::Enumerator.new(self, methname) ::Enumerator.new do |y| __send__(methname) do |*a| y.yield *a end end end
Get a duplicate of the underlying table.
@return [Hash]
# File lib/ostruct2.rb, line 312 def to_h @table.dup end
CRUDified update method.
@return [self]
# File lib/ostruct2.rb, line 272 def update!(other) other.each do |k,v| store!(k,v) end self end
Protected Instance Methods
# File lib/ostruct2.rb, line 401 def new_ostruct_member(name) name = name.to_sym # TODO: Check `#respond_to?` is needed? And if so how to do this in BasicObject? #return name if self.respond_to?(name) (class << self; self; end).class_eval do define_method(name) { read!(name) } define_method("#{name}?") { key?(name) } define_method("#{name}=") { |value| store!(name, value) } end name end
# File lib/ostruct2.rb, line 397 def table @table end