Ruby's Struct class is a convenient way to create Ruby classes, which already have some attributes defined. If you are not familiar with structs, you should watch Avdi Grimm's introduction to structs!

But in many cases there is something better than structs:

Gems that Define Attributes for "Plain Old Ruby Objects"

Instead of using a specialized struct-class (which has different semantics), you could also go with normal Ruby classes. What follows is a collection of gems you could use for this purpose.

At the bottom, there are also some tips, when to use structs and what to bear in mind regarding structs.

Virtus

virtus: Attributes on Steroids for Plain Old Ruby Objects

require 'virtus' class Person include Virtus.model attribute :name attribute :age end Person.new(name: "Jan", age: 26) # => #<Person:0x00000001ad85a8 @name="Jan", @age=26>

Active Attr

active_attr: What ActiveModel left out

require 'active_attr' class Person include ActiveAttr::MassAssignment attr_accessor :name, :age end Person.new(name: "Jan", age: 26) # => #<Person:0x00000002464f18 @name="Jan", @age=26>

Fast Attributes

fast_attributes: FastAttributes adds attributes with their types to the class

require 'fast_attributes' class Person extend FastAttributes define_attributes initialize: true do attribute :name, Object attribute :age, Object end end Person.new(name: "Jan", age: 26) # => #<Person @name="Jan", @age=26>

Attrio

attrio: Attributes for plain old Ruby objects. No dependencies, only simplicity and clearness.

require 'attrio' class Person include Attrio define_attributes do attr :name attr :age end def initialize(attributes = {}) self.attributes = attributes end def attributes=(attributes = {}) attributes.each do |attr,value| self.send("#{attr}=", value) if self.respond_to?("#{attr}=") end end end Person.new(name: "Jan", age: 26) # => <Person name: "Jan", age: 26>

attr_extras

attr_extras: Takes some boilerplate out of Ruby with methods like attr_initialize.

require 'attr_extras' class Person attr_initialize :name, :age attr_reader :name, :age end Person.new("Jan", 26) # => #<Person:0x0000000216ed40 @name="Jan", @age=26>

Concord

concord: Mixin to ease compositions under ruby

require 'concord' class Person include Concord.new(:name, :age) end Person.new("Jan", 26) # => #<Person name="Jan" age=26>

Fatter Attr

fattr: fattr.rb is a "fatter attr" for ruby and borrows heavily from the metakoans.rb ruby quiz

require 'fattr' class Person fattrs :name, :age end person = Person.new person.name = "Jan" person.age = 26 person # => #<Person:0x0000000147d7a8 @name="Jan", @age=26>

Anima

anima: Object initializer from attributes hash

require 'anima' class Person include Anima.new(:name, :age) end Person.new(name: "Jan", age: 26) # => #<Person name="Jan" age=26>

KWAttr

kwattr: attr_reader + initialize with keyword arguments

require 'kwattr' class Person kwattr :name, :age end Person.new(name: "Jan", age: 26) # => #<Person:0x00000002602988 @name="Jan", @age=26>

dry-struct

dry-struct: dry-struct is a gem built on top of dry-types which provides virtus-like DSL for defining typed struct classes.

require "dry-struct" module Types include Dry::Types() end class Person < Dry::Struct attribute :name, Types::Strict::String.optional attribute :age, Types::Coercible::Integer end Person.new(name: "Jan", age: 26) # => #<Person name="Jan" age=26>

Structs are Still Useful… as Value Objects

Structs are different from normal Ruby classes, but they are still very useful for creating value objects. Value objects should be immutable and the following gems assist you in creating read-only objects with a Struct-like API:

Values

values: Simple immutable value objects for ruby (the readme is longer than the code)

require 'values' Person = Value.new(:name, :age) Person.new("Jan", 26) # => <Person name="Jan", age=26>

Immutable Struct

immutable_struct: An immutable version of Ruby's Struct class

require 'immutable_struct' Person = ImmutableStruct.new(:name, :age) Person.new("Jan", 26) # => #<struct Person name="Jan", age=26>

Value Struct

value_struct: Read-only structs in Ruby

require 'value_struct' Person = ValueStruct.new(:name, :age) Person.new("Jan", 26) # => #<ValueStruct Person name="Jan", age=26>

Why Not Structs Everywhere?

You cannot access its instance variables directly

Structs have their own methods (like [] , a getter for variables), which might not always be useful

, a getter for variables), which might not always be useful Pitfalls when creating structs with custom behavior (see below)

The different ways to initialize a Struct:

Inherit

One way to add custom methods to a struct is to directly sub-class it:

class Person < Struct.new(:name, :age) def name_and_age "#{name}, #{age}" end end

The bad thing about this is that it will add an additional entry to your ancestor chain:

Person.ancestors # => [Person, #<Class:0x00000001612140>, Struct, ...]

Block

This can be avoided by passing a block to the initializer:

Person = Struct.new(:name, :age) do def name_and_age "#{name}, #{age}" end end

However, you got a new problem with this approach: You are not in the define a class scope. This can be confusing when working with constants:

Person = Struct.new(:name, :age) do MAXIMUM_AGE = 120 end

This will create a top-level constant MAXIMUM_AGE instead of a namespaced Person::MAXIMUM_AGE one.

Reopen

The approach that avoids both problems, is a little bit more verbose, but well readable:

Person = Struct.new(:name, :age) class Person def name_and_age "#{name}, #{age}" end end

It also seems to perform slightly better than the other options.

Further Reading

More Idiosyncratic Ruby