Understanding Struct in Ruby on Rails

Ruby is known for its elegant syntax and flexibility, which allows developers to create concise and readable code. One of the lesser-known but powerful tools in Ruby is Struct, a built-in class that provides a simple way to group related attributes without the overhead of defining a full class. While not as commonly used as ActiveRecord models or POROs (Plain Old Ruby Objects), Struct can be incredibly useful in Rails applications for certain scenarios.

What is Struct?

Struct in Ruby is a shortcut to defining simple classes that bundle together a few attributes, providing getter and setter methods for those attributes automatically. It creates a new class that behaves like a lightweight object, with attributes you define when initializing.

For example, let’s say you want to create a simple object to represent a point in a 2D space with x and y coordinates. You could do this:

1
2
3
4
5
Point = Struct.new(:x, :y)

point = Point.new(10, 20)
puts point.x  # Output: 10
puts point.y  # Output: 20

Here, Struct.new generates a class Point with x and y attributes, along with methods to get and set these values. In this case, Struct saves you the trouble of explicitly defining a class with an initializer and accessors for these attributes.

When to Use Struct in Rails

While you’ll primarily work with ActiveRecord models and service objects in Rails, Struct has its place for simple, lightweight objects that don’t need the full capabilities of a model. Here are some cases where Struct can be helpful:

1. Temporary Data Storage

When you need to store data temporarily but don’t want to create a full model or class, Struct is ideal. This is especially true when the data doesn’t need to persist in the database but only exists for the duration of a request or session.

Example: Imagine you are working on a booking system and want to create a summary object for a trip without saving it to the database.

1
2
3
4
5
TripSummary = Struct.new(:destination, :duration, :price)

summary = TripSummary.new("Hawaii", "7 days", 1500)
puts summary.destination  # Output: Hawaii
puts summary.price        # Output: 1500

2. API Responses

If your Rails application interacts with external APIs, you might receive JSON responses that map to structured data. You can easily wrap these responses in a Struct to make handling them more organized and readable.

Example: Suppose you’re working with an API that returns weather data:

1
2
3
4
5
6
7
WeatherData = Struct.new(:temperature, :humidity, :wind_speed)

response = { temperature: 75, humidity: 65, wind_speed: 10 }
weather = WeatherData.new(response[:temperature], response[:humidity], response[:wind_speed])

puts weather.temperature  # Output: 75
puts weather.humidity     # Output: 65

Using Struct here helps encapsulate the response in a clear, domain-specific object.

3. Form Objects

In Rails, form objects are often used to handle complex forms that involve multiple models or non-database attributes. Instead of creating a whole new class for every form, you can use Struct to gather attributes temporarily.

Example: Let’s say you’re building a multi-step signup process. For one step, you need to collect personal information:

1
2
3
PersonalInfo = Struct.new(:first_name, :last_name, :email)

info = PersonalInfo.new("John", "Doe", "john.doe@example.com")

With this, you can pass the PersonalInfo object around, making the form process more manageable.

Enhancing Struct with Custom Methods

One of the great things about Struct is that it behaves just like a regular Ruby class, so you can add custom methods to it. This allows you to encapsulate logic related to your Struct object.

Example: Let’s enhance the TripSummary example by adding a method to calculate the total cost with taxes.

1
2
3
4
5
6
7
8
TripSummary = Struct.new(:destination, :duration, :price) do
  def total_cost_with_tax(tax_rate)
    price + (price * tax_rate)
  end
end

summary = TripSummary.new("Hawaii", "7 days", 1500)
puts summary.total_cost_with_tax(0.10)  # Output: 1650

This allows you to keep related logic within the object, just like you would with a full-fledged class.

Comparing Struct to POROs and Models

While Struct is a useful tool, it’s important to know when not to use it. Here’s a quick comparison of Struct vs. other common objects in Rails:

Aspect Struct PORO ActiveRecord Model
Complexity Lightweight, simple Can be lightweight or complex Full-featured ORM
Persistence No database interaction No database interaction Interacts with the database
Customization Easy to define with custom methods Fully customizable Fully customizable
Use Case Temporary data, API responses, form data Business logic, service objects Persistent data, business logic

Struct in Ruby on Rails is a versatile and efficient way to handle simple data structures without the need for full-blown classes or ActiveRecord models. It’s perfect for lightweight tasks like temporary data storage, handling API responses, or encapsulating form attributes. By using Struct in the right context, you can simplify your code and make it more readable while avoiding unnecessary complexity.