7 Ruby Standard libraries you should get to grips with
Pete Hawkins
Posted on October 22, 2021
Ruby has a lot of useful things in its Standard Library.
Learning what’s included and how to use it can be very helpful, especially when writing small scripts where you don’t want to pull in bundler and start making a Gemfile etc.
These libraries are robust, maintained by the core team and should be extremely reliable, and outlive most Ruby gems you come across.
1. Net/HTTP and open-uri
I wanted to start with something I need in most applications I build, and that’s the ability to perform an HTTP request.
In the past I have opted to use the HTTP.rb gem, but for simple tasks it’s really useful to learn Net/http or even open-uri for simple GET requests.
require "net/http"
require "json"
# Net/HTTP GET request
uri = URI("https://swapi.dev/api/films/1/")
resp = Net::HTTP.get(uri)
json = JSON.parse(resp)
puts json["title"] # => "A New Hope"
# Net/HTTP POST request
uri = URI("https://example.org/users")
params = {
"name" => "Bob",
"email" => "bob@example.org",
}
res = Net::HTTP.post_form(uri, params)
puts res.body
# Net/HTTP Full form: delete/put/patch requests
uri = URI("http://example.org/some_path")
Net::HTTP.start(uri.host, uri.port) do |http|
request = Net::HTTP::Delete.new uri
# or request = Net::HTTP::Patch.new uri
# or request = Net::HTTP::Put.new uri
response = http.request(request) # Net::HTTPResponse object
puts response.body
end
Things are even simpler with open-uri
if you only need a GET request:
require "open-uri"
require "json"
URI.open("https://swapi.dev/api/films/1/") do |io|
json = JSON.parse(io.read)
puts json["title"] # => "A New Hope"
end
# or
uri = URI("https://swapi.dev/api/films/1/")
puts uri.open.read
2. CSV
When I’m creating an admin section, I often need to export some form of reports to measure how an application is performing. Ruby has this built in and to take some data and place it into a CSV is pretty simple.
require "csv"
module CustomerExport
module_function
def generate(customers)
CSV.generate do |csv|
# Define the headers
csv << %w[id name email country_code created_at updated_at]
# Add a new row for each customer record
customers.each do |customer|
csv <<
[
customer.id,
customer.name,
customer.email,
customer.country_code,
customer.created_at,
customer.updated_at
]
end
end
end
end
CustomerExport.generate(Customer.all)
And if you wanted to make a Rails route to download this...
module Admin
class ExportsController < AdminBaseController
def customers
customers = Customer.all
respond_to do |format|
format.csv do
filename = "customers-#{Time.zone.now.strftime("%Y-%m-%d")}"
filename += ".csv"
headers["Content-Type"] ||= "text/csv"
headers["Content-Disposition"] =
"attachment; filename=\"#{filename}\""
render plain: CustomerExport.generate(customers)
end
end
rescue => e
flash[:error] = "Failed to generate export: #{e.message}"
redirect_to admin_path
end
end
end
Parsing CSVs (for example: importing CSV files from users) is also built in and you can read the documentation for more on that.
3. SimpleDelegator
I like to use SimpleDelegator to add extra behaviour to ActiveRecord objects, such as having Stripe specific code wrapped up into StripeWrappedUser
or something similar, this avoids placing lots of methods on your ActiveRecord object, but can then augment that class with extra functionality.
require "delegate"
require "digest"
class User < Struct.new(:id, :email)
end
class BillingUser < SimpleDelegator
def billing_system_id
Digest::SHA1.hexdigest(id.to_s + email)
end
end
bob = User.new(1, "bob@example.com")
bob = BillingUser.new(bob)
puts bob.email # => bob@example.com
puts bob.billing_system_id # => 186da93f0b39990e034a80cc4b45c8ec253f2a1a
4. Struct and OpenStruct
Struct is included in Ruby core, it doesn’t need required, and is the stricter of the two objects, and also much more performant.
I find it particularly useful for two things:
- As a value object, giving a name to some piece of data, that would otherwise be a Hash.
- In tests to create mock objects that can be passed in to represent fake things, like a FakeUser.
User = Struct.new(:id, :email)
alice = User.new(1, "alice@example.org")
puts alice.id # => 1
puts alice.email # => alice@example.org
# Can also be defined with additional methods
class User < Struct.new(:id, :email)
def send_welcome_email
UserMailer.welcome(self).deliver_later
end
end
OpenStruct meanwhile is a lot more flexible and can be a neat wrapper for a Hash or JSON from an API etc.
require "ostruct"
data = OpenStruct.new(id: 5, name: "Jeffrey")
# New attributes can be set at runtime:
data.email = "jeff@example.org"
puts data.id # => 5
puts data.name # => "Jeffrey"
puts data.email # => "jeff@example.org"
5. PStore
PStore is a really simple key-value datastore and can be helpful when you need somewhere to store data in a non-production system (i.e. this is not a good replacement for ActiveRecord).
require "pstore"
store = PStore.new("my_db.pstore")
# Write some data
store.transaction do
store["person-1"] = { name: "Bob", email: "bob@example.org" }
end
# Read some data
store.transaction(true) do
puts store["person-1"] # => {:name=>"Bob", :email=>"bob@example.org"}
end
6. Minitest
When working on smaller scripts, I like to pull in a test framework and make some assertions while test-driving some code.
I usually prefer RSpec when working with Rails, but it’s more effort to set up than Minitest, which comes included with Ruby.
The nice thing about Minitest, it is just Ruby – there’s nothing magical going on and it’s very easy to understand.
require "minitest/test"
require "minitest/autorun"
class APIResponse < Struct.new(:body, :error)
def success?
error.nil?
end
end
class APIResponseTest < Minitest::Test
def test_successful_responses
resp = APIResponse.new("Well done!", nil)
assert resp.success?
end
def test_error_responses
resp = APIResponse.new(nil, StandardError.new("400 bad request"))
refute resp.success?
end
end
# Running:
# ..
# Finished in 0.000957s, 2089.8640 runs/s, 2089.8640 assertions/s.
# 2 runs, 2 assertions, 0 failures, 0 errors, 0 skips
7. Logger
Using logger can clean up a lot of your debug output when writing a script and you can then control the log level to show more/less output. Note: Use Rails.logger
in Rails.
require "logger"
logger = Logger.new(STDOUT)
# Setting :warn will only show warning, error and fatal.
logger.level = :warn
# Can tweak log output to be less verbose
logger.formatter = proc do |severity, datetime, progname, msg|
"#{severity[0...1]} #{datetime.strftime('%H:%M %b %d %Y')}: #{msg}\n"
end
logger.debug("Created logger") # wont be displayed in current log level
logger.info("Program started") # wont be displayed in current log level
logger.warn("Nothing to do!")
logger.error("Something really bad!")
# =>
# W 14:56 Oct 22 2021: Nothing to do!
# E 14:56 Oct 22 2021: Something really bad!
There’s plenty more in the Ruby Standard Library to get familiar with, but these 7 are things I come back to, time and time again.
Posted on October 22, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
December 13, 2023
December 6, 2023