Working With Structured Data in Ruby
Many Ruby programs work with collections of information. A program may manage book titles, member records, task lists, order entries, or imported text. Ruby provides several useful structures for storing and processing this information.
Arrays and hashes are central to this work. Files allow data to remain available between program runs. Validation helps prevent incomplete or incorrect values from entering later processing steps.
Arrays for Ordered Collections
An array stores values in order.
topics = ["variables", "methods", "classes"]
Each item has a position.
puts topics[0]
puts topics[1]
Ruby arrays provide methods for adding and removing values.
topics << "modules"
topics.delete("variables")
They can also be filtered:
long_names = topics.select { |topic| topic.length > 7 }
Or converted:
labels = topics.map { |topic| topic.upcase }
These methods return new arrays, which makes it easier to create clear processing steps.
Hashes for Related Values
A hash stores values under keys.
member = {
name: "Marlow Reed",
city: "Portland",
active: true
}
Values are read through their keys:
puts member[:name]
puts member[:city]
Hashes work well when one record contains several named fields.
A collection of records may be stored as an array of hashes:
members = [
{ name: "Marlow Reed", active: true },
{ name: "Tessa Cole", active: false },
{ name: "Orin Blake", active: true }
]
Active records can be selected:
active_members = members.select { |member| member[:active] }
Names can then be collected:
names = active_members.map { |member| member[:name] }
Build Processing Steps
Data processing is easier to review when each step has a clear name.
records = [
" ruby basics ",
"",
"methods",
" classes"
]
A crowded expression may work:
result = records.map(&:strip).reject(&:empty?).map(&:capitalize)
A step-by-step version is often easier to inspect:
cleaned = records.map(&:strip)
non_empty = cleaned.reject(&:empty?)
formatted = non_empty.map(&:capitalize)
Both approaches are valid. The second version helps when debugging or explaining the process.
Grouping Data
Ruby’s group_by method creates groups based on a rule.
words = ["array", "class", "object", "attribute", "method"]
grouped = words.group_by { |word| word[0] }
The result is a hash where each key contains a group of words.
puts grouped["a"]
Records can also be grouped by status:
tasks = [
{ title: "Read chapter", status: "open" },
{ title: "Write notes", status: "done" },
{ title: "Review code", status: "open" }
]
by_status = tasks.group_by { |task| task[:status] }
Sorting Records
Arrays can be sorted by one field.
books = [
{ title: "Objects", pages: 180 },
{ title: "Files", pages: 120 },
{ title: "Modules", pages: 210 }
]
sorted = books.sort_by { |book| book[:pages] }
For reverse order:
descending = books.sort_by { |book| -book[:pages] }
Reading Data From Files
Text files are a simple way to store information.
Suppose topics.txt contains:
arrays
hashes
files
validation
Ruby can read the file into an array:
topics = File.readlines("topics.txt", chomp: true)
The chomp: true option removes newline characters.
The program can then process the values:
cleaned = topics.map(&:strip).reject(&:empty?)
puts cleaned
Writing Data to Files
Ruby can write a string to a file:
File.write("summary.txt", "Ruby study notes")
To write several lines:
topics = ["arrays", "hashes", "files"]
File.open("topics.txt", "w") do |file|
topics.each do |topic|
file.puts topic
end
end
The block automatically closes the file when the operation is finished.
Appending New Records
To add information without replacing existing content, use append mode:
File.open("topics.txt", "a") do |file|
file.puts "classes"
end
Validate Before Processing
Validation checks whether data follows the expected rules.
def valid_name?(name)
!name.to_s.strip.empty?
end
A record can be reviewed before it is stored:
record = { name: "Elian", age: 28 }
if valid_name?(record[:name])
puts "Record accepted"
else
puts "Name is required"
end
Several checks can be collected:
def validate_member(member)
errors = []
errors << "Name is required" if member[:name].to_s.strip.empty?
errors << "Age must be positive" unless member[:age].to_i > 0
errors
end
Usage:
errors = validate_member(name: "", age: -2)
if errors.empty?
puts "Record accepted"
else
puts errors
end
Convert Hashes Into Objects
As a program expands, hashes may be replaced with objects.
class Member
attr_reader :name, :age
def initialize(name, age)
@name = name
@age = age
end
end
A hash can be converted:
data = { name: "Ronan Vale", age: 34 }
member = Member.new(data[:name], data[:age])
Objects provide a clear place for behavior related to the data.
Create a Data Pipeline
A small data pipeline may include:
- reading raw lines;
- cleaning values;
- validating records;
- converting records into objects;
- grouping or sorting them;
- preparing output.
Each step can be placed in its own method or class.
class TopicImporter
def initialize(path)
@path = path
end
def call
File.readlines(@path, chomp: true)
.map(&:strip)
.reject(&:empty?)
end
end
Practice Exercise
Create a small member directory that:
- reads names and cities from a text file;
- converts each line into a hash;
- rejects incomplete records;
- sorts records by name;
- groups records by city;
- writes a summary into another file.
Begin with input such as:
Avery Stone,Denver
Milo West,Austin
Tessa Lane,Denver
Structured data work is easier when each stage is visible. Use arrays for ordered collections, hashes for named fields, objects for data with behavior, and files for stored information. Add validation before later processing. These habits make Ruby programs easier to understand and maintain.