7 Ruby Standard libraries you should get to grips with
Tags: ruby, rails, programming, tutorial
Published: 2021-10-22 13:10:46

Ruby has a lot of useful things in its [Standard Library](https://ruby-doc.org/stdlib). 

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](https://github.com/httprb/http) gem, but for simple tasks it’s really useful to learn [Net/http](https://ruby-doc.org/stdlib-3.0.2/libdoc/net/http/rdoc/Net/HTTP.html) or even [open-uri](https://ruby-doc.org/stdlib-3.0.2/libdoc/open-uri/rdoc/OpenURI.html) for simple GET requests.

```ruby
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:

```ruby
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. 

```ruby
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...

```ruby
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](https://ruby-doc.org/stdlib-3.0.2/libdoc/csv/rdoc/CSV.html) for more on that.

## 3. SimpleDelegator

I like to use [SimpleDelegator](https://ruby-doc.org/stdlib-3.0.2/libdoc/delegate/rdoc/Delegator.html) 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.

```ruby
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:

1. As a value object, giving a name to some piece of data, that would otherwise be a Hash.
2. In tests to create mock objects that can be passed in to represent fake things, like a FakeUser.

```ruby
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.

```ruby
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](https://ruby-doc.org/stdlib-3.0.2/libdoc/pstore/rdoc/PStore.html) 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). 

```ruby
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](https://ruby-doc.org/stdlib-3.0.2/libdoc/minitest/rdoc/Minitest.html), 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.

```ruby
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._

```ruby
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](https://ruby-doc.org/stdlib) to get familiar with, but these 7 are things I come back to, time and time again.