2015-12-16 00:07:31 -05:00
|
|
|
# frozen_string_literal: false
|
2004-02-19 08:42:30 -05:00
|
|
|
#
|
|
|
|
# = ostruct.rb: OpenStruct implementation
|
|
|
|
#
|
|
|
|
# Author:: Yukihiro Matsumoto
|
|
|
|
# Documentation:: Gavin Sinclair
|
|
|
|
#
|
|
|
|
# OpenStruct allows the creation of data objects with arbitrary attributes.
|
|
|
|
# See OpenStruct for an example.
|
|
|
|
#
|
1998-01-16 07:19:09 -05:00
|
|
|
|
2004-02-19 08:42:30 -05:00
|
|
|
#
|
2011-05-22 19:46:08 -04:00
|
|
|
# An OpenStruct is a data structure, similar to a Hash, that allows the
|
|
|
|
# definition of arbitrary attributes with their accompanying values. This is
|
2011-08-26 18:22:37 -04:00
|
|
|
# accomplished by using Ruby's metaprogramming to define methods on the class
|
2011-05-22 19:46:08 -04:00
|
|
|
# itself.
|
|
|
|
#
|
|
|
|
# == Examples:
|
2004-02-19 08:42:30 -05:00
|
|
|
#
|
2009-03-05 22:56:38 -05:00
|
|
|
# require 'ostruct'
|
2004-02-19 08:42:30 -05:00
|
|
|
#
|
2011-05-22 19:46:08 -04:00
|
|
|
# person = OpenStruct.new
|
|
|
|
# person.name = "John Smith"
|
|
|
|
# person.age = 70
|
|
|
|
# person.pension = 300
|
|
|
|
#
|
|
|
|
# puts person.name # -> "John Smith"
|
|
|
|
# puts person.age # -> 70
|
|
|
|
# puts person.address # -> nil
|
|
|
|
#
|
|
|
|
# An OpenStruct employs a Hash internally to store the methods and values and
|
|
|
|
# can even be initialized with one:
|
|
|
|
#
|
|
|
|
# australia = OpenStruct.new(:country => "Australia", :population => 20_000_000)
|
|
|
|
# p australia # -> <OpenStruct 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
|
|
|
|
# OpenStruct object as a method for retrieval or assignment, but can be still be
|
|
|
|
# reached through the Object#send method.
|
|
|
|
#
|
|
|
|
# measurements = OpenStruct.new("length (in inches)" => 24)
|
|
|
|
# measurements.send("length (in inches)") # -> 24
|
|
|
|
#
|
|
|
|
# data_point = OpenStruct.new(:queued? => true)
|
|
|
|
# data_point.queued? # -> true
|
|
|
|
# data_point.send("queued?=",false)
|
|
|
|
# data_point.queued? # -> false
|
2009-03-05 22:56:38 -05:00
|
|
|
#
|
2011-05-22 19:46:08 -04:00
|
|
|
# Removing the presence of a method requires the execution the delete_field
|
|
|
|
# method as setting the property value to +nil+ will not remove the method.
|
2004-02-19 08:42:30 -05:00
|
|
|
#
|
2011-05-22 19:46:08 -04:00
|
|
|
# first_pet = OpenStruct.new(:name => 'Rowdy', :owner => 'John Smith')
|
|
|
|
# first_pet.owner = nil
|
|
|
|
# second_pet = OpenStruct.new(:name => 'Rowdy')
|
2004-02-19 08:42:30 -05:00
|
|
|
#
|
2011-05-22 19:46:08 -04:00
|
|
|
# first_pet == second_pet # -> false
|
2004-02-19 08:42:30 -05:00
|
|
|
#
|
2011-05-22 19:46:08 -04:00
|
|
|
# first_pet.delete_field(:owner)
|
|
|
|
# first_pet == second_pet # -> true
|
|
|
|
#
|
|
|
|
#
|
|
|
|
# == Implementation:
|
|
|
|
#
|
2013-02-26 22:36:02 -05:00
|
|
|
# An OpenStruct utilizes Ruby's method lookup structure to find and define the
|
|
|
|
# necessary methods for properties. This is accomplished through the method
|
2011-05-22 19:46:08 -04:00
|
|
|
# method_missing and define_method.
|
|
|
|
#
|
|
|
|
# This should be a consideration if there is a concern about the performance of
|
2011-08-26 18:22:37 -04:00
|
|
|
# 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.
|
2004-02-19 08:42:30 -05:00
|
|
|
#
|
1998-01-16 07:19:09 -05:00
|
|
|
class OpenStruct
|
2004-02-19 08:42:30 -05:00
|
|
|
#
|
2011-05-22 19:46:08 -04:00
|
|
|
# Creates a new OpenStruct object. By default, the resulting OpenStruct
|
|
|
|
# object will have no attributes.
|
|
|
|
#
|
2012-10-28 17:19:50 -04:00
|
|
|
# The optional +hash+, if given, will generate attributes and values
|
|
|
|
# (can be a Hash, an OpenStruct or a Struct).
|
2011-05-22 19:46:08 -04:00
|
|
|
# For example:
|
2004-02-19 08:42:30 -05:00
|
|
|
#
|
|
|
|
# require 'ostruct'
|
|
|
|
# hash = { "country" => "Australia", :population => 20_000_000 }
|
|
|
|
# data = OpenStruct.new(hash)
|
|
|
|
#
|
|
|
|
# p data # -> <OpenStruct country="Australia" population=20000000>
|
|
|
|
#
|
1998-01-16 07:19:09 -05:00
|
|
|
def initialize(hash=nil)
|
|
|
|
@table = {}
|
|
|
|
if hash
|
2012-10-28 17:19:50 -04:00
|
|
|
hash.each_pair do |k, v|
|
|
|
|
k = k.to_sym
|
|
|
|
@table[k] = v
|
1998-01-16 07:19:09 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2009-03-05 22:56:38 -05:00
|
|
|
# Duplicate an OpenStruct object members.
|
2004-02-20 05:11:25 -05:00
|
|
|
def initialize_copy(orig)
|
|
|
|
super
|
|
|
|
@table = @table.dup
|
|
|
|
end
|
|
|
|
|
2012-04-23 23:46:55 -04:00
|
|
|
#
|
|
|
|
# Converts the OpenStruct to a hash with keys representing
|
|
|
|
# each attribute (as symbols) and their corresponding values
|
|
|
|
# Example:
|
|
|
|
#
|
|
|
|
# require 'ostruct'
|
|
|
|
# data = OpenStruct.new("country" => "Australia", :population => 20_000_000)
|
|
|
|
# data.to_h # => {:country => "Australia", :population => 20000000 }
|
|
|
|
#
|
|
|
|
def to_h
|
|
|
|
@table.dup
|
|
|
|
end
|
|
|
|
|
2012-10-28 17:18:53 -04:00
|
|
|
#
|
|
|
|
# Yields all attributes (as a symbol) along with the corresponding values
|
|
|
|
# or returns an enumerator if not block is given.
|
|
|
|
# Example:
|
|
|
|
#
|
|
|
|
# require 'ostruct'
|
|
|
|
# data = OpenStruct.new("country" => "Australia", :population => 20_000_000)
|
|
|
|
# data.each_pair.to_a # => [[:country, "Australia"], [:population, 20000000]]
|
|
|
|
#
|
|
|
|
def each_pair
|
2013-10-23 11:13:57 -04:00
|
|
|
return to_enum(__method__) { @table.size } unless block_given?
|
2012-10-28 17:18:53 -04:00
|
|
|
@table.each_pair{|p| yield p}
|
|
|
|
end
|
|
|
|
|
2011-05-22 19:46:08 -04:00
|
|
|
#
|
2012-10-28 17:18:11 -04:00
|
|
|
# Provides marshalling support for use by the Marshal library.
|
2011-05-22 19:46:08 -04:00
|
|
|
#
|
2004-12-02 23:56:25 -05:00
|
|
|
def marshal_dump
|
|
|
|
@table
|
|
|
|
end
|
2011-05-22 19:46:08 -04:00
|
|
|
|
|
|
|
#
|
2012-10-28 17:18:11 -04:00
|
|
|
# Provides marshalling support for use by the Marshal library.
|
2011-05-22 19:46:08 -04:00
|
|
|
#
|
2004-12-02 23:56:25 -05:00
|
|
|
def marshal_load(x)
|
|
|
|
@table = x
|
2004-12-02 10:17:35 -05:00
|
|
|
end
|
|
|
|
|
2011-05-22 19:46:08 -04:00
|
|
|
#
|
2012-10-28 17:18:11 -04:00
|
|
|
# Used internally to check if the OpenStruct is able to be
|
2012-02-14 22:31:30 -05:00
|
|
|
# modified before granting access to the internal Hash table to be modified.
|
2011-05-22 19:46:08 -04:00
|
|
|
#
|
2015-01-08 02:07:59 -05:00
|
|
|
def modifiable
|
2009-02-27 00:23:10 -05:00
|
|
|
begin
|
|
|
|
@modifiable = true
|
|
|
|
rescue
|
2013-10-23 11:13:19 -04:00
|
|
|
raise RuntimeError, "can't modify frozen #{self.class}", caller(3)
|
2009-02-15 07:43:46 -05:00
|
|
|
end
|
|
|
|
@table
|
|
|
|
end
|
2015-01-08 02:07:59 -05:00
|
|
|
protected :modifiable
|
2009-02-15 07:43:46 -05:00
|
|
|
|
2011-05-22 19:46:08 -04:00
|
|
|
#
|
2012-10-28 17:18:32 -04:00
|
|
|
# Used internally to defined properties on the
|
2011-05-22 19:46:08 -04:00
|
|
|
# OpenStruct. It does this by using the metaprogramming function
|
2012-10-28 17:19:32 -04:00
|
|
|
# define_singleton_method for both the getter method and the setter method.
|
2011-05-22 19:46:08 -04:00
|
|
|
#
|
2015-01-08 02:07:59 -05:00
|
|
|
def new_ostruct_member(name)
|
2005-09-05 04:29:52 -04:00
|
|
|
name = name.to_sym
|
2012-10-28 17:19:32 -04:00
|
|
|
unless respond_to?(name)
|
|
|
|
define_singleton_method(name) { @table[name] }
|
2015-01-08 02:07:59 -05:00
|
|
|
define_singleton_method("#{name}=") { |x| modifiable[name] = x }
|
2004-12-02 10:17:35 -05:00
|
|
|
end
|
2009-02-15 07:43:46 -05:00
|
|
|
name
|
2004-12-02 10:17:35 -05:00
|
|
|
end
|
2015-01-08 02:07:59 -05:00
|
|
|
protected :new_ostruct_member
|
2004-12-02 10:17:35 -05:00
|
|
|
|
2015-10-14 12:57:21 -04:00
|
|
|
def respond_to_missing?(mid, include_private = false)
|
|
|
|
mname = mid.to_s.chomp("=").to_sym
|
|
|
|
@table.key?(mname) || super
|
|
|
|
end
|
|
|
|
|
2004-02-19 08:42:30 -05:00
|
|
|
def method_missing(mid, *args) # :nodoc:
|
1998-01-16 07:19:09 -05:00
|
|
|
len = args.length
|
2015-04-13 23:35:18 -04:00
|
|
|
if mname = mid[/.*(?==\z)/m]
|
1998-01-16 07:19:09 -05:00
|
|
|
if len != 1
|
2005-09-05 04:29:52 -04:00
|
|
|
raise ArgumentError, "wrong number of arguments (#{len} for 1)", caller(1)
|
1998-01-16 07:19:09 -05:00
|
|
|
end
|
2015-01-08 02:07:59 -05:00
|
|
|
modifiable[new_ostruct_member(mname)] = args[0]
|
2012-10-28 17:20:10 -04:00
|
|
|
elsif len == 0
|
2015-10-14 12:57:21 -04:00
|
|
|
if @table.key?(mid)
|
|
|
|
new_ostruct_member(mid)
|
|
|
|
@table[mid]
|
|
|
|
end
|
1998-01-16 07:19:09 -05:00
|
|
|
else
|
2013-10-23 11:14:17 -04:00
|
|
|
err = NoMethodError.new "undefined method `#{mid}' for #{self}", mid, args
|
|
|
|
err.set_backtrace caller(1)
|
|
|
|
raise err
|
1998-01-16 07:19:09 -05:00
|
|
|
end
|
|
|
|
end
|
2002-11-03 06:04:35 -05:00
|
|
|
|
2012-10-28 17:20:10 -04:00
|
|
|
# Returns the value of a member.
|
|
|
|
#
|
|
|
|
# person = OpenStruct.new('name' => 'John Smith', 'age' => 70)
|
|
|
|
# person[:age] # => 70, same as ostruct.age
|
|
|
|
#
|
|
|
|
def [](name)
|
|
|
|
@table[name.to_sym]
|
|
|
|
end
|
|
|
|
|
|
|
|
#
|
|
|
|
# Sets the value of a member.
|
|
|
|
#
|
|
|
|
# person = OpenStruct.new('name' => 'John Smith', 'age' => 70)
|
|
|
|
# person[:age] = 42 # => equivalent to ostruct.age = 42
|
|
|
|
# person.age # => 42
|
|
|
|
#
|
|
|
|
def []=(name, value)
|
2015-01-08 02:07:59 -05:00
|
|
|
modifiable[new_ostruct_member(name)] = value
|
2012-10-28 17:20:10 -04:00
|
|
|
end
|
|
|
|
|
2015-11-17 00:36:03 -05:00
|
|
|
#
|
|
|
|
# Retrieves the value object corresponding to the each +name+
|
|
|
|
# objects repeatedly.
|
|
|
|
#
|
|
|
|
# address = OpenStruct.new('city' => "Anytown NC", 'zip' => 12345)
|
|
|
|
# person = OpenStruct.new('name' => 'John Smith', 'address' => address)
|
|
|
|
# person.dig(:address, 'zip') # => 12345
|
|
|
|
# person.dig(:business_address, 'zip') # => nil
|
|
|
|
#
|
|
|
|
def dig(name, *names)
|
|
|
|
begin
|
|
|
|
name = name.to_sym
|
|
|
|
rescue NoMethodError
|
2015-12-12 16:40:50 -05:00
|
|
|
raise TypeError, "#{name} is not a symbol nor a string"
|
2015-11-17 00:36:03 -05:00
|
|
|
end
|
|
|
|
@table.dig(name, *names)
|
|
|
|
end
|
|
|
|
|
2004-02-19 08:42:30 -05:00
|
|
|
#
|
2012-02-14 22:31:30 -05:00
|
|
|
# Remove the named field from the object. Returns the value that the field
|
|
|
|
# contained if it was defined.
|
2011-05-22 19:46:08 -04:00
|
|
|
#
|
|
|
|
# require 'ostruct'
|
|
|
|
#
|
|
|
|
# person = OpenStruct.new('name' => 'John Smith', 'age' => 70)
|
|
|
|
#
|
|
|
|
# person.delete_field('name') # => 'John Smith'
|
2004-02-19 08:42:30 -05:00
|
|
|
#
|
1998-01-16 07:19:09 -05:00
|
|
|
def delete_field(name)
|
2010-11-03 01:17:25 -04:00
|
|
|
sym = name.to_sym
|
2015-09-26 19:16:37 -04:00
|
|
|
begin
|
|
|
|
singleton_class.__send__(:remove_method, sym, "#{sym}=")
|
|
|
|
rescue NameError
|
|
|
|
end
|
|
|
|
@table.delete(sym) do
|
|
|
|
raise NameError.new("no field `#{sym}' in #{self}", sym)
|
|
|
|
end
|
1998-01-16 07:19:09 -05:00
|
|
|
end
|
|
|
|
|
2005-09-05 04:29:52 -04:00
|
|
|
InspectKey = :__inspect_key__ # :nodoc:
|
|
|
|
|
2004-02-19 08:42:30 -05:00
|
|
|
#
|
|
|
|
# Returns a string containing a detailed summary of the keys and values.
|
|
|
|
#
|
1998-01-16 07:19:09 -05:00
|
|
|
def inspect
|
2005-09-05 04:29:52 -04:00
|
|
|
str = "#<#{self.class}"
|
|
|
|
|
2009-01-13 07:52:23 -05:00
|
|
|
ids = (Thread.current[InspectKey] ||= [])
|
|
|
|
if ids.include?(object_id)
|
|
|
|
return str << ' ...>'
|
|
|
|
end
|
|
|
|
|
|
|
|
ids << object_id
|
|
|
|
begin
|
2005-09-05 04:29:52 -04:00
|
|
|
first = true
|
|
|
|
for k,v in @table
|
|
|
|
str << "," unless first
|
|
|
|
first = false
|
2009-01-13 07:52:23 -05:00
|
|
|
str << " #{k}=#{v.inspect}"
|
2005-09-05 04:29:52 -04:00
|
|
|
end
|
2009-01-13 07:52:23 -05:00
|
|
|
return str << '>'
|
|
|
|
ensure
|
|
|
|
ids.pop
|
1998-01-16 07:19:09 -05:00
|
|
|
end
|
|
|
|
end
|
2005-09-05 04:29:52 -04:00
|
|
|
alias :to_s :inspect
|
2003-09-24 20:03:11 -04:00
|
|
|
|
2004-02-19 08:42:30 -05:00
|
|
|
attr_reader :table # :nodoc:
|
2015-01-05 00:27:31 -05:00
|
|
|
protected :table
|
2003-09-24 20:03:11 -04:00
|
|
|
|
2004-02-19 08:42:30 -05:00
|
|
|
#
|
2011-05-22 19:46:08 -04:00
|
|
|
# Compares this object and +other+ for equality. An OpenStruct is equal to
|
2012-10-28 17:18:11 -04:00
|
|
|
# +other+ when +other+ is an OpenStruct and the two objects' Hash tables are
|
2011-05-22 19:46:08 -04:00
|
|
|
# equal.
|
2004-02-19 08:42:30 -05:00
|
|
|
#
|
2003-09-24 20:03:11 -04:00
|
|
|
def ==(other)
|
2012-10-28 17:19:15 -04:00
|
|
|
return false unless other.kind_of?(OpenStruct)
|
2015-01-08 02:07:59 -05:00
|
|
|
@table == other.table
|
2012-10-28 17:19:15 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
#
|
|
|
|
# Compares this object and +other+ for equality. An OpenStruct is eql? to
|
|
|
|
# +other+ when +other+ is an OpenStruct and the two objects' Hash tables are
|
|
|
|
# eql?.
|
|
|
|
#
|
|
|
|
def eql?(other)
|
|
|
|
return false unless other.kind_of?(OpenStruct)
|
2015-01-08 02:07:59 -05:00
|
|
|
@table.eql?(other.table)
|
2012-10-28 17:19:15 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
# Compute a hash-code for this OpenStruct.
|
|
|
|
# Two hashes with the same content will have the same hash code
|
|
|
|
# (and will be eql?).
|
|
|
|
def hash
|
|
|
|
@table.hash
|
2003-09-24 20:03:11 -04:00
|
|
|
end
|
1998-01-16 07:19:09 -05:00
|
|
|
end
|