Module: Structured

Defined in:
lib/structured.rb

Overview

Sets up a class to receive an initializing hash and to populate information about the class from that hash. The expected hash elements are self-documenting and type-checking to facilitate future generation of hash elements.

The basic usage is to include the Structured module in a class, which gives the class a method ClassMethods#element, used to declare elements expected in the initializing hash. Once an element is declared, a few things happen:

  • The element is looked for upon initialization

  • If found, the element’s value is type-checked and possibly converted to a new object. In particular:

    • If the expected type is a Structured object, then the value is expected to be a hash, which is used as input to construct the expected Structured object. This subsidiary Structured object has its @parent instance variable set so that a complete two-way tree of objects is maintained.

    • If the expected type is an Array of Structured objects, then the value is expected to be an array of hashes, each of which is converted to the expected Structured object. The @parent variable is also set.

    • If the expected type is a Hash including Structured object, then the value is similarly converted to a hash of Structured objects. As an added benefit, besides @parent being set, hash values have the @key instance variable set, so that the values are aware of the hash key with which they are associated.

  • An instance variable @[element] is set to the given value.

As a result, at the end of the initialization of a Structured object, it will have instance variables set corresponding to all the defined elements.

Customization of Structured Classes

The above explanation is default behavior, and several customizations are available.

  • Methods receive_[element] can be defined, taking a single parameter. By default, the method sets an instance variable @[name] with the parameter value. Classes may override this method to provide different initialization actions. (Alternately, classes can accept the default initialization methods and override #initialize for further processing.)

  • Methods receive_parent and receive_key can be similarly redefined to change the processing of parent Structured objects and hash keys, respectively.

  • To process unknown elements, call ClassMethods#default_element to specify their expected type. (It should typically be just a class name, as that method’s documentation explains.) Then define receive_any to handle undefined elements, for example by placing them in a hash. For these elements, the @key instance variable is also set for them if the expected type is a Structured class.

Please read the documentation for Structured::ClassMethods for more on defining expected elements, type checking, and so on.

Subfiles as Input

The Structured class provides automatic support for separating inputs into YAML subfiles. This is useful for including complex objects in a file. Two types of subfile inputs are supported: those for object hashes, and those for arrays.

To include a subfile as part of an object hash, include the key ‘read_file` in the hash, with the value being the file to be read. (Other keys besides `read_file` may be included.) The subfile should itself contain YAML for a hash with further keys for the object.

To include multiple subfiles in an array, set the first element of the array to the string ‘read_file`, and then the other elements of the array should be filenames. These subfiles should contain YAML for arrays.

Consider a Structured object for a book, containing elements for the title, subtitle, and an array of authors. The input file could look like this:

---
title: A Book
subtitle: Containing Many Pages
author:
  - John Q. Public
  - Jane Doe

Using the subfile feature, the input file could instead look like:

---
title: A Book
read_file: subtitle_file.yaml
author:
  - read_file
  - author_file.yaml

This would instruct Structured to read hash keys out of ‘subtitle_file.yaml`, and to read array elements out of `author_file.yaml`. These two files, in turn, should look like:

# subtitle_file.yaml
---
subtitle: Containing Many Pages

# author_file.yaml
---
- John Q. Public
- Jane Doe

When incorporated, Structured will combine these subfiles as if they were a single object specification.

Defined Under Namespace

Modules: ClassMethods Classes: InputError

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#keyObject (readonly)

Returns the value of attribute key.



221
222
223
# File 'lib/structured.rb', line 221

def key
  @key
end

#parentObject (readonly)

Returns the value of attribute parent.



206
207
208
# File 'lib/structured.rb', line 206

def parent
  @parent
end

Class Method Details

.included(base) ⇒ Object

Includes ClassMethods.



815
816
817
818
819
820
# File 'lib/structured.rb', line 815

def self.included(base)
  if base.is_a?(Class)
    base.extend(ClassMethods)
    base.reset_elements
  end
end

.trace(note) ⇒ Object

Enable tracing of object creation.



825
826
827
828
829
830
831
832
833
834
835
# File 'lib/structured.rb', line 825

def self.trace(note)
  begin
    @trace_stack.push(note)
    return yield
  rescue InputError => e
    e.structured_stack ||= @trace_stack.dup
    raise e
  ensure
    @trace_stack.pop
  end
end

Instance Method Details

#initialize(hash, parent = nil) ⇒ Object

Initializes the object based on an initialization hash. All methods that include Structured should retain this initialization signature to the extent possible, because downstream Structured objects expect to be initialized this way.

Parameters:

  • hash

    The initializing hash for this object.

  • parent (defaults to: nil)

    The parent object to this Structured object.



169
170
171
172
173
174
175
176
# File 'lib/structured.rb', line 169

def initialize(hash, parent = nil)
  Structured.trace(self.class) do
    pre_initialize
    receive_parent(parent) if parent
    self.class.build_from_hash(self, hash)
    post_initialize
  end
end

#input_err(text) ⇒ Object

Raises an InputError.

Raises:



240
241
242
# File 'lib/structured.rb', line 240

def input_err(text)
  raise InputError, text
end

#post_initializeObject

Subclasses may override this method to provide post-initialization routines, run after the initializing hash is processed. This may be useful for global data checks (that depend on several values).



190
191
# File 'lib/structured.rb', line 190

def post_initialize
end

#pre_initializeObject

Subclasses may override this method to provide pre-initialization routines, run before the initializing hash is processed.



182
183
# File 'lib/structured.rb', line 182

def pre_initialize
end

#receive_any(element, val) ⇒ Object

Processes an undefined element in the initializing hash. By default, this raises an error, but classes may override this method to use the undefined elements.

a string.

Parameters:

  • element

    The unknown element name. For a YAML file, this is typically

  • val

    The value associated with the unknown element.

Raises:

  • (NameError)


233
234
235
# File 'lib/structured.rb', line 233

def receive_any(element, val)
  raise NameError, "Unexpected element for #{self.class}: #{element}"
end

#receive_key(key) ⇒ Object

Processes the key object for this Structured class. The key is automatically given when this Structured object is a subsidiary of another, within a key-value hash. It is also automatically given when this Structured object is created while processing a default element.

By default, this method sets @key to the given object. Classes may override this method to do other things with the key object.



217
218
219
# File 'lib/structured.rb', line 217

def receive_key(key)
  @key = key
end

#receive_parent(parent) ⇒ Object

Processes the parent object for this Structured class. The parent is automatically given for subsidiary Structured objects, triggering a call to this method.

By default, @parent is set to the given object. Classes may override this method to do other things with the parent object (for example, test the parent object type).



202
203
204
# File 'lib/structured.rb', line 202

def receive_parent(parent)
  @parent = parent
end