Ruby - learn 80% of a new language in a week

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:

  1. (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’. 🎱

  2. .select(&:odd?): Here, we’re playing favorites and keeping only the odd-numbered members. In our ASCII posse, that’s ‘a’, ‘c’, and ’e’. πŸƒ

  3. .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. πŸ”„

  4. .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! πŸŽ‰

  5. .map(&:upcase): After the sorting party, everyone’s feeling bold and gets an uppercase makeover. Shout it from the rooftops! πŸ“’

  6. .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. πŸ†

  7. .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:

  1. Crafting XML: We start with a mini-story about our friends, Joey and Rachel, in XML format. πŸ“œ
  2. 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. 🌐
  3. 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. β˜•
  4. Ruby Hash to JSON: With a flick of the .to_json wand, our Ruby hash is now trendy, web-friendly JSON. πŸ’«
  5. 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:

  1. Initiate the swap: We start with a fresh hash where we’ll store our flipped key-value pairs.
  2. 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.
  3. 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.
  4. 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:

  1. Read Data from a CSV File: Use Ruby’s CSV library to read data.
  2. Filter Rows Based on Criteria: Implement logic to filter rows.
  3. API Call for Each Row: Simulate API calls.
  4. Random Delay: Introduce a random delay for each simulated API call.
  5. Save Results to a New File: Output the processed data to a new CSV file.
  6. 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:

  1. Default Case (prints “Hello, World!”):

    ruby hello_world.rb
    
  2. Uppercase (prints “HELLO, WORLD!”):

    ruby hello_world.rb upper
    
  3. Lowercase (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:

  1. Cook up a CSV in memory: Just like making a sandwich but with data.
  2. Toss it into an S3 bucket: Think of it as uploading your favorite selfie.
  3. Move the file to another bucket: Like deciding your selfie looks better in a different album.
  4. Change its storage class: It’s like moving your selfie from your phone’s storage to the cloud because it’s that special.
  5. Grab the file back: Because sometimes you just need that selfie on your phone again.
  6. Cut the file into smaller bits: Like cropping your selfie into a series of mysterious close-ups.
  7. 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 nil or less than or equal to zero), we’re already calling it quits with a Maybe.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.none again).
  • 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 with Maybe.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_data function 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! πŸŽ‰πŸ’»

Related Posts

Valentines special - How to survive advent of AI - a practical guide from a polymath

Valentines special - How to survive advent of AI - a practical guide from a polymath

Alright, let’s rev up the engines of this wild ride through the digital age with a twist – because, frankly, just any old welding won’t cut it if the AI revolution leaves me yearning for the adrenaline rush of startup life.

Read More
πŸš€ Dynamic Programming for Dummies: The Careful Brute Force Strategy πŸš€

πŸš€ Dynamic Programming for Dummies: The Careful Brute Force Strategy πŸš€

Intro: What’s the Big Idea? 🧠 Imagine trying to solve a giant jigsaw puzzle.

Read More
Swift - learn 80% of a new language in a week

Swift - learn 80% of a new language in a week

Let’s start our Swift adventure! πŸš€ Combine this with the previous “Starting with Swift” instructions, and you’re ready for liftoff into the Swift cosmos.

Read More