Search icon CANCEL
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Conferences
Free Learning
Arrow right icon
Arrow up icon
GO TO TOP
Polished Ruby Programming

You're reading from   Polished Ruby Programming Build better software with more intuitive, maintainable, scalable, and high-performance Ruby code

Arrow left icon
Product type Paperback
Published in Jul 2021
Publisher Packt
ISBN-13 9781801072724
Length 434 pages
Edition 1st Edition
Languages
Arrow right icon
Author (1):
Arrow left icon
Jeremy Evans Jeremy Evans
Author Profile Icon Jeremy Evans
Jeremy Evans
Arrow right icon
View More author details
Toc

Table of Contents (23) Chapters Close

Preface 1. Section 1: Fundamental Ruby Programming Principles
2. Chapter 1: Getting the Most out of Core Classes FREE CHAPTER 3. Chapter 2: Designing Useful Custom Classes 4. Chapter 3: Proper Variable Usage 5. Chapter 4: Methods and Their Arguments 6. Chapter 5: Handling Errors 7. Chapter 6: Formatting Code for Easy Reading 8. Section 2: Ruby Library Programming Principles
9. Chapter 7: Designing Your Library 10. Chapter 8: Designing for Extensibility 11. Chapter 9: Metaprogramming and When to Use It 12. Chapter 10: Designing Useful Domain-Specific Languages 13. Chapter 11: Testing to Ensure Your Code Works 14. Chapter 12: Handling Change 15. Chapter 13: Using Common Design Patterns 16. Chapter 14: Optimizing Your Library 17. Section 3: Ruby Web Programming Principles
18. Chapter 15: The Database Is Key 19. Chapter 16: Web Application Design Principles 20. Chapter 17: Robust Web Application Security 21. Assessments 22. Other Books You May Enjoy

Working with Struct – one of the underappreciated core classes

The Struct class is one of the underappreciated Ruby core classes. It allows you to create classes with one or more fields, with accessors automatically created for each field. So, say you have the following:

class Artist
  attr_accessor :name, :albums
  def initialize(name, albums)
    @name = name
    @albums = albums
  end
end

Instead of that, you can write a small amount of Ruby code, and have the initializer and accessor automatically created:

Artist = Struct.new(:name, :albums)

In general, a Struct class is a little lighter on memory than a regular class, but has slower accessor methods. Struct used to be faster in terms of both initialization and reader methods in older versions of Ruby, but regular classes and attr_accessor methods have gotten faster at a greater rate than Struct has. Therefore, for maximum performance, you may want to consider using regular classes and attr_accessor methods instead of Struct classes.

One of the more interesting aspects of Struct is how it works internally. For example, unlike the new method for most other classes, Struct.new does not return a Struct instance; it returns a Struct subclass:

Struct.new(:a, :b).class
# => Class

However, the new method on the subclass creates instances of the subclass; it doesn't create future subclasses. Additionally, if you provide a string and not a symbol as the first argument, Struct will automatically create the class using that name nested under its own namespace:

Struct.new('A', :a, :b).new(1, 2).class
# => Struct::A

A simplified version of the default Struct.new method is similar to the following. This example is a bit larger, so we'll break it into sections. If a string is given as the first argument, it is used to set the class in the namespace of the receiver; otherwise, it is added to the list of fields:

def Struct.new(name, *fields)
  unless name.is_a?(String)
    fields.unshift(name)
    name = nil
  end

Next, a subclass is created. If a class name was given, it is set as a constant in the current namespace:

  subclass = Class.new(self)
  if name
    const_set(name, subclass)
  end

Then, some internal code is run to set up the storage for the members of the subclass. Then, the new, allocate, [], members, and inspect singleton methods are defined on the subclass. Finally, some internal code is run to set up accessor instance methods for each member of the subclass:

  # Internal magic to setup fields/storage for subclass
  def subclass.new(*values)
    obj = allocate
    obj.initialize(*values)
    obj
  end
  # Similar for allocate, [], members, inspect
  # Internal magic to setup accessor instance methods
  subclass
end

Interestingly, you can still create Struct subclasses the normal way:

class SubStruct < Struct
end

Struct subclasses created via the normal way operate like Struct itself, not like Struct subclasses created via Struct.new. You can then call new on the Struct subclass to create a subclass of that subclass, but the setup is similar to a Struct subclass created via Struct.new:

SubStruct.new('A', :a, :b).new(1, 2).class
# => SubStruct::A

In general, Struct is good for creating simple classes that are designed for storing data. One issue with Struct is that the design encourages the use of mutable data and discourages a functional approach, by defaulting to creating setter methods for every member. However, it is possible to easily force the use of immutable structs by freezing the object in initialize:

A = Struct.new(:a, :b) do
  def initialize(...)
    super
    freeze
  end
end

There have been feature requests submitted on the Ruby issue tracker to create immutable Struct subclasses using a keyword argument to Struct.new or via the addition of a separate Struct::Value class. However, as of Ruby 3, neither feature request has been accepted. It is possible that a future version of Ruby will include them, but in the meantime, freezing the receiver in initialize is the best approach.

You have been reading a chapter from
Polished Ruby Programming
Published in: Jul 2021
Publisher: Packt
ISBN-13: 9781801072724
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at $19.99/month. Cancel anytime