
Ruby - learn 80% of a new language in a week
- Shem
- February 1, 2020
So here we go - this is the way to learn 80% of common use ruby in a week
In my day to day this is the most common thing I do. Just a bunch of data manipulation using enumerable methods provided by ruby
Working with Lists - Wrangling data efficiently
result = (97..102).to_a # Getting numbers 97 to 102
.select(&:odd?) # Keeping only the odd numbers π«π«
.map(&:chr) # Converting numbers to characters (ASCII magic) β¨
.sort { |a, b| /[aeiouy]/.match?(a) ? -1 : 1 } # Sort by vowels first π’
.map(&:upcase) # Making them shout! π
.take(2) # Selecting the first two characters
.join # Joining them to form our final string
Alright, let’s break this down:
(97..102).to_a: This part’s like summoning a gang of numbers from 97 to 102, inclusive. They’re the ASCII squad for characters ‘a’ to ‘f’. π±.select(&:odd?): Here, we’re playing favorites and keeping only the odd-numbered members. In our ASCII posse, that’s ‘a’, ‘c’, and ’e’. π.map(&:chr): Now, we’re giving each number a new identity by transforming them into characters from the ASCII table. So, our numbers 97, 99, and 101 turn into ‘a’, ‘c’, and ’e’, respectively. π.sort_by { |chr| /[aeiouy]/.match?(chr) ? 0 : 1 }: This part’s like a VIP line at a club, but for vowels. We sort our characters, but vowels like ‘a’ and ’e’ get fast passes. It’s all about that vibe! π.map(&:upcase): After the sorting party, everyone’s feeling bold and gets an uppercase makeover. Shout it from the rooftops! π’.take(2): Now, it’s time to pick our top two contenders from the sorted and uppercase crew. It’s like choosing MVPs for the game. π.join: Finally, our chosen duo joins forces to become a dynamic string duo. Together, they’re unstoppable! π
In short, our code takes a bunch of ASCII characters, selects the coolest odd ones, sorts them with VIP vowels first, dresses them up in uppercase, picks the best two, and then unites them into the unstoppable duo “AE”. π
JSON/XML/YAML Mapping - Converting between formats and objects
Alright, switching gears to Ruby and using it to work with XML and JSON! Here’s a casual walk-through of a Ruby script that juggles XML and JSON like a pro. First, we’ll create some XML, morph it into a Ruby object, tweak it a bit, and then convert it to JSON. All in one smooth ride:
require 'nokogiri'
require 'json'
# Step 1: Craft some XML
xml_data = <<-XML
<friends>
<friend>
<name>Joey</name>
<age>30</age>
</friend>
<friend>
<name>Rachel</name>
<age>29</age>
<favoriteCoffee>Latte</favoriteCoffee>
</friend>
</friends>
XML
# Step 2: Convert XML to a Ruby Hash
doc = Nokogiri::XML(xml_data)
friends_hash = doc.xpath('//friends/friend').map do |friend|
{
name: friend.xpath('name').text,
age: friend.xpath('age').text.to_i,
favoriteCoffee: friend.xpath('favoriteCoffee').text
}
end
# Step 3: Time to make some edits
friends_hash.each do |friend|
friend[:age] += 1 # Happy Birthday!
friend[:favoriteCoffee] = "Cappuccino" if friend[:favoriteCoffee].empty?
end
# Step 4: Convert our updated Ruby Hash to JSON
json_output = friends_hash.to_json
# Step 5: Show off our final masterpiece
puts json_output
Here’s the play-by-play:
- Crafting XML: We start with a mini-story about our friends, Joey and Rachel, in XML format. π
- XML to Ruby Hash: Using
Nokogiri, a gem that’s like a Swiss Army knife for XML and HTML, we parse our XML into a comfy Ruby hash. It’s like translating from Ancient Greek to modern-day English. π - Making Some Edits: Everyone gets a year older (because why not?), and we make sure everyone has a favorite coffee. Rachel’s all set, but Joey needs a coffee preference stat, so we default to Cappuccino. β
- Ruby Hash to JSON: With a flick of the
.to_jsonwand, our Ruby hash is now trendy, web-friendly JSON. π« - The Big Reveal: Finally, we print out our JSON, showcasing our friends in a new format, complete with age updates and coffee preferences. π¨οΈ
And just like that, you’ve seen Ruby transform XML into JSON, all while keeping it chill. π©β¨
Dictionary Reversal - Flipping keys and values
Alright, let’s flip things around in Ruby! We’re crafting a function that takes a hash (Ruby’s version of dictionaries or objects) as input, swaps its keys and values around, and deals with the tricky business of non-unique values. Here’s how we roll:
def swap_keys_and_values(original_hash)
# Initialize a new hash to store our swapped pairs.
swapped_hash = {}
# Loop through each key-value pair in the original hash.
original_hash.each do |key, value|
# If the value already exists as a key in our new hash, we append the current key to its list.
if swapped_hash.has_key?(value)
swapped_hash[value] = [swapped_hash[value]] unless swapped_hash[value].is_a?(Array)
swapped_hash[value] << key
else
# If the value is unique, just set it as a key with the current key as its value.
swapped_hash[value] = key
end
end
swapped_hash
end
# Let's give it a whirl with an example.
example_hash = { 'a' => 1, 'b' => 2, 'c' => 1 }
puts "Original: #{example_hash}"
swapped = swap_keys_and_values(example_hash)
puts "Swapped: #{swapped}"
Here’s the game plan:
- Initiate the swap: We start with a fresh hash where we’ll store our flipped key-value pairs.
- Flip ’em: As we stroll through the original hash, we flip each pair around. But there’s a catch - if our flipped key (originally a value) is already chilling in the new hash, we need to accommodate it by turning it into an array and inviting the new key to the party.
- Dealing with Duplicates: If we bump into a value that’s already made itself at home as a key in our new hash, we make sure it’s set up to host multiple original keys by grouping them into an array.
- Showtime: We test it out with an example where ‘a’ and ‘c’ both fancy the number 1, showcasing our function’s smooth moves in handling this love triangle.
And voilΓ ! We’ve got a Ruby function that flips keys and values while gracefully juggling those awkward moments when values aren’t unique. πβ¨
For those times when you’re in the mood for a shortcut and the uniqueness of values isn’t causing a fuss, Ruby’s got a sleek trick up its sleeve: Hash.invert. This method is like the cool, laid-back friend who makes swapping keys and values in a hash look effortless. Hereβs how it rolls:
example_hash = { 'a' => 1, 'b' => 2, 'c' => 3 }
puts "Original: #{example_hash}" # => Original: {"a"=>1, "b"=>2, "c"=>3}
swapped = example_hash.invert
puts "Swapped with invert: #{swapped}" # => Swapped with invert: {1=>"a", 2=>"b", 3=>"c"}
When you call .invert on a hash, Ruby casually flips the keys and values around. But remember, Hash.invert expects uniqueness among values because itβs not playing around with arrays for duplicate values. If two keys have the same value, one will overwrite the other in the swapped version, leaving you with a trimmed-down guest list.
So, if your hash values are as unique as snowflakes, Hash.invert is your go-to for a quick switcheroo. But if there’s even a hint of a duplicate value party, you’ll want to handle it with a bit more care, like in the detailed method we discussed earlier. ππ
Handling IO - Getting dirty
What do you mean by “dirty”? In the quirky realm of Haskell π§ββοΈ, it’s all about those interactions with the outside worldβlike mingling with APIs π‘, dabbling in database conversations πΎ, chatting up the terminal π₯οΈ, or even the randomness of rolling dice π². Haskell tags these as “dirty” because they introduce side effects, which are practically a no-go. Why? Haskell is all about predictability; it loves when the same inputs always lead to the same outputs π, keeping everything neat and clean.
Now, why is this “keep it clean” philosophy something for us Rubyists π to consider? Well, it’s about making our code more reliable, easier to debug, and a breeze to test π οΈ. By embracing pure functionsβthose that don’t mess with the outside worldβwe can significantly reduce bugs π and headaches π€―. This concept, although a staple in Haskell, is super handy in Ruby too.
In Ruby, known for its object-oriented flair, adopting a functional approach can add an extra layer of elegance and reliability to your code. It’s like giving your Ruby code a functional makeover, making it not only expressive and dynamic but also sturdy and straightforward to manage π. We’re essentially borrowing a page from Haskell’s book to sprinkle some of its deterministic magic into our Ruby projects, blending the best of both worlds for a smoother coding journey π§ββοΈπ.
Here’s what we’ll do:
- Read Data from a CSV File: Use Ruby’s CSV library to read data.
- Filter Rows Based on Criteria: Implement logic to filter rows.
- API Call for Each Row: Simulate API calls.
- Random Delay: Introduce a random delay for each simulated API call.
- Save Results to a New File: Output the processed data to a new CSV file.
- Progress Indicator: Show the progress of processing rows.
require 'csv'
require 'json'
require 'net/http'
require 'uri'
# Example CSV content (save this as input.csv in your working directory):
#
# Name,Age,Email
# John Doe,28,john@example.com
# Jane Smith,22,jane@example.com
# Don Joe,33, donjoe@example.com
# Reads data from a CSV file and returns rows as arrays of hashes.
def read_csv_data(filename)
CSV.read(filename, headers: true).map(&:to_h)
end
# Filters rows based on a condition.
def filter_data(rows, &condition)
rows.select(&condition)
end
# Fetches user data from an API for a given ID.
def fetch_user_data(id)
uri = URI("https://jsonplaceholder.typicode.com/users/#{id}")
response = Net::HTTP.get_response(uri)
sleep(rand(0.1000).round(3)) # This is our random element
JSON.parse(response.body) if response.is_a?(Net::HTTPSuccess)
rescue
{ error: "Failed to fetch data for ID #{id}" }
end
# Writes processed data to a CSV file.
def write_to_csv(filename, headers, data)
CSV.open(filename, "wb") do |csv|
csv << headers
data.each { |row| csv << row }
end
end
# Main processing function that ties everything together.
def process_csv(input_file, output_file)
data = read_csv_data(input_file)
filtered_data = filter_data(data) { |row| row['Age'].to_i > 25 }
processed_data = filtered_data.map.with_index(1) do |row, index|
user_data = fetch_user_data(index).to_json
puts "Progress: #{index}/#{filtered_data.size} rows processed."
row.values + [user_data]
end
write_to_csv(output_file, data.first.keys + ['APIResponse'], processed_data)
puts "All data processed and saved to #{output_file}."
end
# Example usage
input_file = 'input.csv'
output_file = 'output.csv'
process_csv(input_file, output_file)
Basic Project Setup - Hello World with options
To create a Ruby program that responds to command-line arguments for printing “Hello, World!” in either default, uppercase, or lowercase form, and splitting the functionality into separate files for the uppercase and lowercase options, follow these steps. The program will be structured into three files: one main file (hello_world.rb), one for uppercase (upper_case.rb), and one for lowercase (lower_case.rb).
File 1: upper_case.rb
This file handles converting the string to uppercase.
# upper_case.rb
def upper_case
puts "HELLO, WORLD!"
end
File 2: lower_case.rb
This file handles converting the string to lowercase.
# lower_case.rb
def lower_case
puts "hello, world!"
end
File 3: hello_world.rb
This is the main file that decides whether to print the default message, or in uppercase, or lowercase based on the command-line argument.
# hello_world.rb
require_relative 'upper_case'
require_relative 'lower_case'
case ARGV[0]
when "upper"
upper_case
when "lower"
lower_case
else
puts "Hello, World!"
end
Running the Program from the Console To run the program, use the command line. Navigate to the directory containing your files, and you can run the program in one of three ways:
Default Case (prints “Hello, World!”):
ruby hello_world.rbUppercase (prints “HELLO, WORLD!”):
ruby hello_world.rb upperLowercase (prints “hello, world!”):
ruby hello_world.rb lower
This setup demonstrates a basic way to split functionality across files in Ruby, allowing for a modular and flexible approach to handling different command-line arguments.
AWS S3 Interaction - File operations in the cloud
Let’s whip up a Ruby script that does a bunch of cool stuff with AWS S3, like uploading a file, shuffling it between buckets, tweaking its storage vibe, downloading it, chopping it into bite-sized pieces, and giving each piece a squeeze to compress it. We’ll use the aws-sdk-s3 gem for the heavy lifting and zlib for the squeeze play. Don’t forget to install these gems if you haven’t already:
gem install aws-sdk-s3
gem install zlib
Here’s the game plan:
- Cook up a CSV in memory: Just like making a sandwich but with data.
- Toss it into an S3 bucket: Think of it as uploading your favorite selfie.
- Move the file to another bucket: Like deciding your selfie looks better in a different album.
- Change its storage class: It’s like moving your selfie from your phone’s storage to the cloud because it’s that special.
- Grab the file back: Because sometimes you just need that selfie on your phone again.
- Cut the file into smaller bits: Like cropping your selfie into a series of mysterious close-ups.
- Squish each bit down: Compressing those bits as if trying to fit into jeans you bought a size too small.
The Script Itself
require 'aws-sdk-s3'
require 'csv'
require 'zlib'
require 'fileutils'
# Getting our S3 client ready
s3_client = Aws::S3::Client.new(region: 'your-own-special-place')
# Where we're putting stuff and what we're calling it
source_bucket = 'your-original-album'
destination_bucket = 'your-new-fancy-album'
file_name = 'amazing_data.csv'
object_key = "secret-folder/#{file_name}"
# 1. Making that CSV sandwich
csv_content = CSV.generate do |csv|
csv << ['Name', 'Age']
csv << ['Alice', '30']
csv << ['Bob', '25']
end
# 2. Selfie upload time
s3_client.put_object(bucket: source_bucket, key: object_key, body: csv_content)
# 3. Moving the selfie... I mean file
s3_client.copy_object(
copy_source: "#{source_bucket}/#{object_key}",
bucket: destination_bucket,
key: object_key
)
# 4. Giving the file its cloud storage crown
s3_client.copy_object(
copy_source: "#{destination_bucket}/#{object_key}",
bucket: destination_bucket,
key: object_key,
storage_class: 'GLACIER'
)
# 5. Bringing our precious back home
File.open(file_name, 'wb') do |file|
s3_client.get_object({ bucket: destination_bucket, key: object_key }) do |chunk|
file.write(chunk)
end
end
# 6 & 7. The chopping and squishing
File.open(file_name) do |file|
while chunk = file.read(1024) # Grabbing bits of the file
Zlib::GzipWriter.open("#{file_name}.gz") do |gz|
gz.write(chunk)
end
end
end
puts "All done! Your file's been through quite the adventure."
A few heads-ups:
- Credentials: This script figures you’ve got your AWS credentials sorted out already. They need to be set up in your environment or AWS config file.
- Bucket Names and Region: Swap out
'your-own-special-place','your-original-album', and'your-new-fancy-album'with your actual AWS details. - Error Handling: In the real world, you’d wanna catch any oopsies that happen along the way with these operations.
- Storage Class Warning: Moving things to GLACIER is cool and all, but remember, pulling stuff back out can hit your wallet and patience.
- Compression: Right now, we’re overwriting the same compressed file with each chunk. If you want separate files, tweak the script to create unique names for each.
OS Communication - Integrating with the host system
Let’s cook up a Ruby script that’s essentially a little command-line ninja π₯·, running around executing system commands and grabbing their outputs. We’ll have it do some classic moves like checking out what’s in the directory, how much disk space we’ve got left, and which processes are gobbling up all our CPU. This is like having a little helper to peek into the heart of your system’s operations.
For this, Ruby’s Open3 library is our secret weapon. It lets us run commands and capture their stdout (standard output), stderr (standard error), and status. If you haven’t used Open3 before, you might need to require it with require ‘open3’. It’s part of Ruby’s standard library, so you won’t need to install anything extra.
Here’s how you get your ninja ready:
require 'open3'
# Define a ninja move (method) to execute a command and capture its output
def execute_command(cmd)
stdout, stderr, status = Open3.capture3(cmd)
puts "Executing: #{cmd}"
if status.success?
puts "Output:\n#{stdout}"
else
puts "Error:\n#{stderr}"
end
end
# Let's send our ninja on some missions
# List directory contents
execute_command('ls -l')
# Sample Output:
# Executing: ls -l
# Output:
# total 32
# drwxr-xr-x 5 user user 4096 Jul 7 10:00 app
# -rw-r--r-- 1 user user 569 Jul 7 09:57 Gemfile
# Check disk usage
execute_command('df -h')
# Sample Output:
# Executing: df -h
# Output:
# Filesystem Size Used Avail Use% Mounted on
# /dev/sda1 50G 15G 33G 30% /
# Find CPU-hungry processes
execute_command('ps aux --sort=-%cpu | head -2')
# Sample Output:
# Executing: ps aux --sort=-%cpu | head -5
# Output:
# USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
# root 1588 7.5 0.1 247856 11944 ? Ssl 09:14 0:01 /usr/lib/packagekit/packagekitd
# user 11768 6.5 2.5 1164032 205908 ? Sl 09:15 0:03 /usr/bin/gnome-shell
# Remember, these are just examples. The real output will depend on your specific system setup and what's currently running.
Each time we call execute_command, our script runs the specified system command and prints its output or any errors it encounters. Here’s a breakdown of the missions:
ls -l This command gets a detailed list of files in the current directory, showing permissions, ownership, size, and modification date.
df -h Checks disk usage, showing how much space is used and available on your mounted filesystems, in a human-readable format.
ps aux --sort=-%cpu | head -5 Hunts down the top 5 CPU-hungry processes, giving you a snapshot of what’s keeping your CPU busy.
Remember, running system commands from a script can be powerful but also risky if you’re executing commands that could alter your system or expose sensitive information. Always double-check the commands you’re including in your scripts. Happy ninja-ing! π
Multiline Strings - Preserving formatting
Got it, let’s keep it simple and straight to the point. Ruby’s got a cool trick up its sleeve for this, using heredocs and #{} for interpolation, which naturally preserves your whitespace and indentation. Here’s a fun, emoji-packed way to craft a multiline message that keeps all its cool formatting:
# Setting the scene with some variables
guest = "Alice"
party_location = "Wonderland"
time = "6 PM"
activity = "unbirthday party"
# Crafting our complex, multiline message
message = <<~HEREDOC
Hey #{guest}! π
Guess what? You're invited to a magical party in #{party_location}! π
When? At #{time}, sharp. Don't be fashionably late!
We're throwing an #{activity}, and it wouldn't be the same without you.
See you in #{party_location}!
HEREDOC
puts message
In this snippet, <<~HEREDOC is your buddy for keeping the indentation of your string content aligned with your code’s style, without messing up the actual string’s layout. When you print message, Ruby evaluates anything inside #{}βlike variables or any Ruby expressionβkeeping your indents and line breaks just as you typed them in your code, making it perfect for those detailed, stylish outputs. πβ¨
Code Reuse with Classes - Structured project organization
Creating a Ruby project with a dynamic and colorful output based on class inheritance sounds like a fun challenge! Let’s sketch out a project where each directory contains a class that inherits from a base class with a say_your_name method. For the colorful output, we’ll use ANSI color codes in the terminal.
Project Structure:
ruby_project/
|-- base/
| `-- person.rb
|-- people/
| |-- friend.rb
| `-- family_member.rb
`-- main.rb
Step 1: The Base Class
First, define the base class Person with a say_your_name method. This class will reside in the base directory.
base/person.rb:
class Person
def say_your_name
"I'm just a basic Person."
end
end
Step 2: Inheriting Classes
Next, create classes that inherit from Person. Each class overrides the say_your_name method. These classes are placed in the people directory.
people/friend.rb:
require_relative '../base/person'
class Friend < Person
def say_your_name
"I'm a Friend!"
end
end
people/family_member.rb:
require_relative '../base/person'
class FamilyMember < Person
def say_your_name
"I'm a Family Member!"
end
end
Step 3: Dynamic Invocation and Colorful Output
Finally, implement a script (main.rb) that dynamically calls the say_your_name method of a specified class and changes the output color based on a command-line flag.
main.rb:
require_relative 'base/person'
require_relative 'people/friend'
require_relative 'people/family_member'
# ANSI color codes
colors = {
"red" => "\e[31m",
"green" => "\e[32m",
"default" => "\e[0m"
}
# Determine class and color from command-line arguments
class_name = ARGV[0] || "Person"
color = ARGV[1] || "default"
# Map of possible classes
classes = {
"Person" => Person,
"Friend" => Friend,
"FamilyMember" => FamilyMember
}
# Dynamically select and instantiate the class
person = classes[class_name].new if classes[class_name]
output = person ? person.say_your_name : "Class not found."
# Print the name with the selected color
puts "#{colors[color]}#{output}#{colors["default"]}"
Running the Program
To run the program and see the dynamic, colorful output, use the terminal. Make sure to navigate to the root of your project directory. Here are some example commands:
ruby main.rb Friend green
ruby main.rb FamilyMember red
This setup allows for easy expansion. Want to introduce more people types or colors? Just add new classes in the people directory or new entries to the colors hash in main.rb. This project design keeps things modular, making it straightforward to manage and extend.
Error Handling - Graceful data transformation inspired by monads
Implementing monads in Ruby for error handling offers a robust way to manage complex data transformations while maintaining readability and control flow. Although Ruby doesn’t have built-in support for monads like some other languages, we can still craft our own solution or use gems like dry-monads.
For this example, let’s create a simplified version of a Maybe monad, which is commonly used for handling situations that might result in nil or an error. We’ll then use this monad in a data transformation process that gracefully handles potential errors.
Step 1: Define the Monad
We’re kicking things off by crafting our very own Maybe monad. It’s a simple yet effective way to deal with those pesky nil values or errors without breaking a sweat. Our Maybe has two moods: Some for when things are looking up and we’ve got a value, and None for when we’re out of luck (think errors or just plain nil).
class Maybe
attr_reader :value
def initialize(value)
@value = value
end
def self.some(value)
new(value)
end
def self.none
new(nil)
end
def map
return self if value.nil?
Maybe.new(yield(value))
rescue
Maybe.none
end
end
Step 2: Using the Monad in Data Transformation
Let’s dive into the real world where we’ve got a task to transform some user data. Here’s the game plan: fetch the user data, pluck out the age, then add one more year because why not? Each step’s got the potential to trip up, so we’ll lean on our Maybe monad to keep things graceful.
# Pretend functions for our data adventure
def fetch_user_data(id)
# Here's where things might go sideways
return Maybe.none if id.nil? || id <= 0
# Assuming all's well, we get some user data back
Maybe.some({ name: "Alice", age: 30 })
end
def extract_age(data)
age = data[:age]
return Maybe.none if age.nil?
Maybe.some(age)
end
def increment_age(age)
Maybe.some(age + 1)
end
# Let's get this data transformation party started
def process_user_data(user_id)
fetch_user_data(user_id)
.map { |data| extract_age(data) }
.map { |age_maybe| age_maybe.map { |age| increment_age(age) } }
end
Trying it out
# Example usage
# **Scenario where everything goes right:**
# User ID is valid, user data contains age.
result = process_user_data(1)
if result.value.nil?
puts "An error occurred during the data transformation process."
else
puts "Transformed data: #{result.value.value}" # Expected output: Transformed data: 31
end
# **Scenario where something goes wrong:**
# 1. User ID is invalid (e.g., `nil` or negative)
# 2. User data does not contain age, leading to extraction failure.
result = process_user_data(-1) # Example of an invalid ID causing the fetch to fail.
if result.value.nil?
puts "An error occurred during the data transformation process." # This line will execute.
else
puts "Transformed data: #{result.value.value}"
end
Here’s the breakdown:
- We start by trying to fetch some user data. If the user ID looks fishy (like it’s
nilor less than or equal to zero), we’re already calling it quits with aMaybe.none. - If we’ve got the data, next up is pulling out the age. No age? No problem (well, actually, big problem for our process, so it’s
Maybe.noneagain). - Assuming the age is all good, we’re giving our user a bonus year with
increment_age, which is always a win, so we wrap up the result withMaybe.some.
Chaining these steps together, we’ve got a neat, tidy, and most importantly, safe way of handling our data transformation. If anything goes awry, we stop the presses and deal with it, avoiding those dreaded error messages or crashes. And that’s how you handle data transformation like a pro in Ruby! π
Multithreading and Async - Maximizing CPU usage
Let’s dial up the fun with some magic π©β¨ in our Ruby script! We’re going multithreaded to chat with jsonplaceholder in parallel, like sending a bunch of text messages at once and getting all the replies. π Here’s how you turn that code into a party:
require 'net/http'
require 'json'
require 'uri'
# URLs we'll be hitting π―
urls = [
'https://jsonplaceholder.typicode.com/posts/1',
'https://jsonplaceholder.typicode.com/posts/2',
'https://jsonplaceholder.typicode.com/posts/3'
]
# Function to fetch data from a URL π
def fetch_data(url)
uri = URI(url)
response = Net::HTTP.get(uri)
JSON.parse(response)
rescue StandardError => e
puts "Oops! Failed to fetch #{url}: #{e} π¨"
end
# Spin up a thread for each URL and initiate fetch π§΅
threads = urls.map do |url|
Thread.new do
data = fetch_data(url)
# Adding some emoji flair to the output π
puts "Fetched from #{url}: #{data['title']} π¬"
end
end
# Wait for all the party threads to finish π
threads.each(&:join)
puts "All done! Data fetched in parallel, like a boss π"
Here’s the Breakdown:
- URLs to Hit π―: We’re aiming our requests at three different posts from
jsonplaceholder. - Fetching Data π: Our
fetch_datafunction goes out to the internet to grab some JSON. If it hits a snag, it lets us know with a friendly π¨. - Thread Party π§΅: For each URL, we kick off a new thread. It’s like sending your friends out to grab snacks from different stores simultaneously.
- Emoji Output π¬: As each thread gets its data, it reports back with a message and a mailbox emoji, because who doesn’t love getting mail?
- Wrapping Up π: We make sure all our threads finish their adventures before proudly declaring victory with a π emoji.
Just run this script in your Ruby environment, and watch as your console fills up with async fun. It’s a great way to see the power of multithreading in action, with a bit of emoji sparkle to brighten the process.
Testing - be an adult, test your code
Alright! Letβs whip up a little Ruby program thatβs both nifty and easy to get along with. Weβll create a class named Greeter that has one job: saying hi to whoever you tell it to. Then, we’ll put on our testing hats π΅οΈββοΈ and write a couple of tests to make sure our Greeter isnβt being rude and is actually greeting people as it should.
The Ruby Program
First off, here’s our friendly Greeter class, saved in a file named greeter.rb:
class Greeter
def greet(name)
"Hello, #{name}!"
end
end
The Test
We’ll use MiniTest, a cool testing library that comes bundled with Ruby, so you donβt have to fuss over installing anything extra. Hereβs how to write a simple test for our Greeter, saved in a file named greeter_test.rb:
require 'minitest/autorun'
require_relative 'greeter'
class GreeterTest < Minitest::Test
def test_greet
greeter = Greeter.new
assert_equal "Hello, Alice!", greeter.greet("Alice"), "Greeter should say hello to Alice"
assert_equal "Hello, Bob!", greeter.greet("Bob"), "Greeter should say hello to Bob"
end
end
In our GreeterTest, weβre making sure our Greeter knows its manners by checking if it greets Alice and Bob correctly. The assert_equal part is basically us saying, βHey, when we tell Greeter to say hi to Alice, it should actually say βHello, Alice!ββ.
Running the Tests
To run your tests and see if Greeter passes with flying colors π, open your terminal, navigate to the directory containing greeter.rb and greeter_test.rb, and run:
ruby greeter_test.rb
If all is well, youβll see a nice and reassuring message saying something like:
Run options: --seed 12345
# Running:
.
Finished in 0.001234s, 810.2474 runs/s, 1620.4948 assertions/s.
1 runs, 2 assertions, 0 failures, 0 errors, 0 skips
This means your Greeter is a well-mannered piece of code thatβs doing its job perfectly. π If thereβs an issue, MiniTest will tell you what went wrong, so you can give Greeter a little pep talk and fix things up.
And there you have it! A super simple Ruby program with tests to back it up. Testing might seem like extra work at first, but itβs actually your best buddy, making sure your code does what you expect it to do. Happy coding! π
Wrap up
Wow, you’ve really dived deep into Ruby, haven’t you? π€Ώ You’ve played around with classes, made friends with methods, and even dressed up your code with a bit of emoji sparkle. Plus, you’ve stepped into the testing arena, ensuring your code behaves just as expected. This isn’t just fun and games; you’ve laid down a solid foundation in Ruby that’s going to make you feel right at home with other cool languages in the lineup - Python, JavaScript (JS), TypeScript (TS), Elixir, Swift, and Haskell.
Each of these languages has its own flavor and style, but the concepts you’ve grasped here, like OOP (Object-Oriented Programming), functional hints, and, oh, the art of testing, are universal. They’re like your passport, ready to take you on new coding adventures. πβοΈ
So, as you leap from Ruby’s elegant simplicity to Python’s straightforward charm, JS’s dynamic realms, TS’s strict typing parties, Elixir’s functional magic, Swift’s iOS kingdoms, and Haskell’s pure functional landscapes, remember this journey with Ruby. Itβs like your first step into a larger world of programming languages, each with unique challenges and triumphs waiting for you. π
You’ve got this! The transition might seem daunting at times, but hey, you’ve already shown you can tango with Ruby and its testing buddies. That’s no small feat! Keep that curiosity burning, and who knows what incredible projects you’ll bring to life in Python, JS, TS, Elixir, Swift, or Haskell. Happy coding, and here’s to many more lines of code that bring your ideas to life! ππ»