Class: DataStruct

Inherits:
Object
  • Object
show all
Defined in:
lib/datastruct.rb

Constant Summary collapse

VERSION =
"0.0.6"

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(*args, **kwargs) ⇒ DataStruct

Returns a new instance of DataStruct.



58
59
60
61
# File 'lib/datastruct.rb', line 58

def initialize(*args, **kwargs)
  self.update(*args)
  self.update(**kwargs)
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(name, *args, &block) ⇒ Object (private)

This makes the struct accept the defined properties as instance methods



290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
# File 'lib/datastruct.rb', line 290

def method_missing(name, *args, &block)
  property = name
  set = false

  if is_setter?(property)
    property = getter(name)
    set = true
  end

  if valid_property? property
    if set
      @data[property] = args.first
    else
      @data[property]
    end
  else
    super
  end
end

Class Method Details

.from_array(array) ⇒ Object

A more ideomatic way of calling new(*array)



45
46
47
# File 'lib/datastruct.rb', line 45

def self.from_array(array)
  self.new(*array)
end

.from_hash(hash) ⇒ Object

A more ideomatic way of calling new(**hash)



52
53
54
55
56
# File 'lib/datastruct.rb', line 52

def self.from_hash(hash)
  hash = symbol_keys(hash)

  self.new(**hash)
end

.new(*args, &block) ⇒ Object

Makes sure @data is set before initialize is called

The @data instance variable is set to a hash with each key in PROPERTIES set to nil before the constructor is called. This avoids a host of annoying issues when users override the constructor.



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# File 'lib/datastruct.rb', line 23

def self.new(*args, &block)
  instance = allocate()

  data_container = {}

  if instance.class.const_defined? :PROPERTIES
    properties = instance.class::PROPERTIES
  else
    properties = []
  end

  properties.each { |key| data_container[key] = nil }
  instance.instance_variable_set(:@data, data_container)

  instance.send(:initialize, *args, &block)

  return instance
end

Instance Method Details

#==(other) ⇒ Object

Delegates to Hash#==



66
67
68
69
70
71
72
# File 'lib/datastruct.rb', line 66

def ==(other)
  if not other.instance_of? self.class
    false
  else
    @data == other.instance_variable_get(:@data)
  end
end

#[](key) ⇒ Object

Does the same as #get, but returns nil instead of raising KeyError

Parameters:

  • key (String, Symbol)

Returns:

  • (Object)


113
114
115
116
117
# File 'lib/datastruct.rb', line 113

def [](key)
  get(key)
rescue KeyError
  nil
end

#dupObject

Returns a shallow copy



87
88
89
# File 'lib/datastruct.rb', line 87

def dup
  self.class.from_hash(@data.dup)
end

#eachObject

Delegates to Hash#each

See Also:



80
81
82
# File 'lib/datastruct.rb', line 80

def each(*args, &block)
  @data.each(*args, &block)
end

#get(property) ⇒ Object

Returns a property using its getter method

Parameters:

  • property (String, Symbol)

Raises:

  • (KeyError)

    on invalid property name



97
98
99
100
101
102
103
104
105
# File 'lib/datastruct.rb', line 97

def get(property)
  property = property.to_sym

  if not valid_property? property
    fail KeyError, "Property not defined: #{property}"
  end

  self.send(property)
end

#inspectObject

Produces a text representation of the object



122
123
124
125
126
127
128
129
130
131
132
# File 'lib/datastruct.rb', line 122

def inspect
  text = "#<#{self.class.to_s}"

  text << self.class::PROPERTIES.reduce("") { |a, key|
    a << " #{key}=#{self.get(key).inspect}"
  }

  text << ">"

  return text
end

#merge(other) ⇒ Hash

Delegates to Hash#merge

Returns:

  • (Hash)


277
278
279
# File 'lib/datastruct.rb', line 277

def merge(other)
  @data.merge(other)
end

#respond_to?(method_name) ⇒ Boolean

Returns:

  • (Boolean)


134
135
136
137
138
139
140
# File 'lib/datastruct.rb', line 134

def respond_to?(method_name)
  if valid_property?(method_name) or valid_property?(getter(method_name))
    true
  else
    super
  end
end

#set(property, value) ⇒ Object Also known as: []=

Sets the value of a property using its setter method

Parameters:

  • property (String, Symbol)
  • value (Object)

Raises:

  • (KeyError)

    on invalid property name



149
150
151
152
153
154
155
156
157
# File 'lib/datastruct.rb', line 149

def set(property, value)
  property = property.to_sym

  if not valid_property? property
    fail KeyError, "Property not defined: #{property}"
  end

  self.send(setter(property), value)
end

#to_arrayArray Also known as: to_a

Returns the properties of the object as an array

Returns:

  • (Array)


166
167
168
# File 'lib/datastruct.rb', line 166

def to_array
  self.class::PROPERTIES.map { |name| @data[name] }
end

#to_hash(string_keys: false, deep: false) ⇒ Hash Also known as: to_h

Returns the properties of the object as a hash

Parameters:

  • string_keys (Boolean) (defaults to: false)

    Return keys as strings instead of symbols

  • deep (Boolean) (defaults to: false)

    Go deep and serialize all DataStruct instances found

Returns:

  • (Hash)


180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
# File 'lib/datastruct.rb', line 180

def to_hash(string_keys: false, deep: false)
  # Builds a hash using property getters. This allows getters to override the
  # output of this function.
  data = Hash[self.class::PROPERTIES.map { |key| [key, self.get(key)] }]

  hashify = lambda { |obj|
    if obj.is_a? DataStruct and deep
      return obj.to_hash(string_keys: string_keys, deep: deep)

    elsif obj.is_a? Hash
      out = {}

      obj.each_pair { |key, value|
        # Only stringify the keys of the *top level* of the structure. We have
        # no business messing with the keys of nested hashes.
        key = key.to_s if string_keys and obj.equal? data

        out[key] = hashify[value]
      }

      return out

    elsif obj.is_a? Array
      return obj.map { |x| hashify[x] }

    else
      return obj
    end
  }

  return data if not string_keys and not deep
  return hashify[data]
end

#to_json(*args) ⇒ String

Note:

JSON must be loaded for this function to work

Dumps the properties of this object to JSON using Ruby’s JSON module

Returns:

  • (String)

See Also:



223
224
225
# File 'lib/datastruct.rb', line 223

def to_json(*args)
  self.to_hash.to_json(*args)
end

#to_yaml(*args) ⇒ String

Note:

YAML must be loaded for this function to work

Dumps the properties of this object to YAML using Ruby’s YAML module

Returns:

  • (String)

See Also:



234
235
236
# File 'lib/datastruct.rb', line 234

def to_yaml(*args)
  self.to_hash.to_yaml(*args)
end

#update(*args, **kwargs) ⇒ Object

Note:

Keyword arguments override posisional arguments

Updates the values of this object’s properties

Both positional arguments and keyword arguments are used to update the property values of the object. Positional arguments should be passed in the same order as the defined properties.

Returns:

  • nil

Raises:

  • (ArgumentError)

    on invalid property names



249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
# File 'lib/datastruct.rb', line 249

def update(*args, **kwargs)
  if args.length > self.class::PROPERTIES.length
    x = args.length
    y = self.class::PROPERTIES.length
    msg = "Too many arguments (you passed #{x} arguments for #{y} properties)"

    fail ArgumentError, msg
  end

  hash = Hash[self.class::PROPERTIES[0...args.length].zip(args)]
  hash.update(kwargs)

  hash.each_pair { |key, value|
    begin
      self.set(key, value)
    rescue KeyError => e
      fail ArgumentError, "Invalid property: #{key}"
    end
  }

  nil
end