Reason #63 • March 4th, 2026

Data

Yesterday we looked at how Struct can be an alternative to full-blown classes for simple objects.

A more recent addition to Ruby is the Data class, which provides an even more lightweight approach to creating immutable value objects.

Ruby
Person = Data.define(:name, :birth_date)

# We can instantiate a person using ordered arguments:
person = Person.new("Alice", Date.parse("1990-01-01"))
# => #<Person name="Alice", birth_date=#<Date: 1990-01-01>>

# Or we can use keyword arguments:
person = Person.new(name: "Alice", birth_date: Date.parse("1990-01-01"))
# => #<Person name="Alice", birth_date=#<Date: 1990-01-01>>

# Unlike Struct, Data requires all attributes to be provided
person = Person.new(name: "Alice")
# => ArgumentError: missing keyword: birth_date

# We can access attributes using dot notation:
person.name # => "Alice"
person.birth_date # => #

# But we can't modify them:
person.name = "Bob" # => NoMethodError: undefined method `name='
      
JavaScript
"use strict";

class Person {
  constructor(name, birthDate) {
    const missingFields = [];
    if (name === undefined) missingFields.push("name");
    if (birthDate === undefined) missingFields.push("birthDate");
    if (missingFields.length > 0) {
      throw new Error(
        `Missing attributes: ${missingFields.join(", ")}`
      );
    }

    this.name = name;
    this.birthDate = birthDate;

    Object.freeze(this); // Freeze the object to make it immutable
  }
}

// The class we created can only be instantiated via ordered arguments:
const person = new Person("Alice", new Date("1990-01-01"));

// We can access attributes using dot notation:
person.name; // => "Alice"
person.birthDate; // => Mon Jan 01 1990 01:00:00

// But since we froze the object we can't modify them:
person.name = "Bob";
// TypeError: Cannot assign to read only property 'name' ...
      

When we need updated values on an existing Data object, we can use the with method to create a new instance with updated attributes:

Ruby
person = Person.new(name: "Alice", birth_date: Date.parse("1990-01-01"))
# => #<Person name="Alice", birth_date=#<Date: 1990-01-01>>

updated_person = person.with(name: "Ananya")
# => #<Person name="Ananya", birth_date=#<Date: 1990-01-01>>
    

Data shares many of the features of Struct, like pattern matching and defining additional methods by passing a block to Data.define.

Which should you use?

Rule of thumb: If you're good with immutability and don't need any of the extra features of Struct, use Data instead.

Immutability Caveat

The immutability of Data objects is shallow, meaning that if any of the attributes are mutable objects (like arrays or hashes), they can still be modified after the Data object is created.

History

The Data class was introduced in Ruby 3.2, released on Christmas 2022.

Reason #64 ?