class Struct

Practical examples and pitfalls for Struct

Struct examples: give small records names and clear ownership

Practical notes by Ruby-Doc.org

Replace positional guessing with named fields

A batch status has a name (label) and retry count. A small Struct provides readable accessors for those fields without the overhead of defining a full class for the simple storage. Keyword initialization allows the constructor call to explicitly show how each parameter is associated with a specific field.

Example 1
BatchStatus = Struct.new(:label, :retries, keyword_init: true)
status = BatchStatus.new(label: "import", retries: 0)
status.retries += 1
p [status.label, status.retries]
Expected output
["import", 1]

The member writer makes this a mutable record. This could be suitable for objects whose state is changing while being processed. If your calls need to get a non-changing value, consider Data or a class designed for that contract. Convenient storage does not determine who may change an object or how long it should remain valid.

Understand what the duplicate shares

Duplicating this record creates a new top-level object. Creating a new record does not recursively duplicate an array or hash that was created in one of the fields of the original. The second example shows that changing the tags through the copied Job also changes the array seen by the original.

Example 2
Job = Struct.new(:tags, keyword_init: true)
original = Job.new(tags: ["queued"])
copy = original.dup
copy.tags << "review"
p original.equal?(copy)
p original.tags
Expected output
false
["queued", "review"]

When designing a true edit form, choose if your draft version of a record can have shared values with your saved version of the same record. If not, then create copies or rebuild the nested values yourself rather than using some sort of global deep-copy operation. Some members may deliberately refer to shared resources or identities that should not be duplicated.

Validate the record at the input boundary

While named fields help to make the code easier to read, there is nothing stopping someone from leaving out a field or setting a field to nil. There is also nothing preventing a person from putting an invalid number into the retry-count field. Do your best to validate that all required fields were provided when the record is constructed or at least prior to using it somewhere.

Verify that your records behave as expected regarding equality and mutability. Records used as keys in collections require very well-defined rules about identity since changes to members after insertion can alter the record's hash value and interfere with later lookup.

Where a record represents a stable key, an immutable design can make the expected behavior clearer.

API reference: Struct API reference

Related: Data · Hash

Class Struct provides a convenient way to create a simple class that can store and fetch values.

This example creates a subclass of Struct, Struct::Customer; the first argument, a string, is the name of the subclass; the other arguments, symbols, determine the members of the new subclass.

Customer = Struct.new('Customer', :name, :address, :zip)
Customer.name       # => "Struct::Customer"
Customer.class      # => Class
Customer.superclass # => Struct

Corresponding to each member are two methods, a writer and a reader, that store and fetch values:

methods = Customer.instance_methods false
methods # => [:zip, :address=, :zip=, :address, :name, :name=]

An instance of the subclass may be created, and its members assigned values, via method ::new:

joe = Customer.new("Joe Smith", "123 Maple, Anytown NC", 12345)
joe # => #<struct Struct::Customer name="Joe Smith", address="123 Maple, Anytown NC", zip=12345>

The member values may be managed thus:

joe.name    # => "Joe Smith"
joe.name = 'Joseph Smith'
joe.name    # => "Joseph Smith"

And thus; note that member name may be expressed as either a string or a symbol:

joe[:name]  # => "Joseph Smith"
joe[:name] = 'Joseph Smith, Jr.'
joe['name'] # => "Joseph Smith, Jr."

See Struct::new.

What’s Here

First, what’s elsewhere. Class Struct:

See also Data, which is a somewhat similar, but stricter concept for defining immutable value objects.

Here, class Struct provides methods that are useful for:

Methods for Creating a Struct Subclass

Methods for Querying

Methods for Comparing

Methods for Fetching

Methods for Assigning

Methods for Iterating

Methods for Converting