skip to content

Standard Library Tour

The standard library beyond core classes: Time and Date, shell commands with Open3, outbound calls with Net::HTTP, and Forwardable. Interviewers check you reach for these before adding a gem.

on this pageshow

explore

questions

24

In Ruby's Net::HTTP, what does Net::HTTP.get return compared with Net::HTTP.get_response, and how do you check the status code?

level: juniorimportance: must knowfreq 58%

answer

  1. one returns a String, one an object
  2. get is get_response(...).body
  3. no exception on a 404
  4. is_a?(Net::HTTPSuccess)
  5. res.code is the String "200"

basics

~10 s

Net::HTTP.get returns only the response body as a String, whatever the status, so a 404 page arrives without an exception. Net::HTTP.get_response returns a Net::HTTPResponse subclass whose class and String code carry the status.

solid answer

~30 s

`Net::HTTP.get(uri)` is literally `get_response(uri).body`: it opens a connection, sends one GET, closes it and hands back the body String. It never raises for a 4xx or 5xx status, so an error page looks like data. `Net::HTTP.get_response(uri)` returns the response object instead, an instance of a subclass such as `Net::HTTPOK` or `Net::HTTPNotFound`. You branch on it with `res.is_a?(Net::HTTPSuccess)` or `case res when Net::HTTPSuccess`, read `res.code` (the String `"200"`, not an Integer), headers with `res["Content-Type"]` and the body with `res.body`. `res.value` returns nil for a 2xx and raises `Net::HTTPClientException` for a 4xx or `Net::HTTPFatalError` for a 5xx. Neither helper follows redirects.

code

ruby · 13 lines
ruby
require "net/http"

uri = URI("https://api.example.com/v1/forecast?city=Oslo")
res = Net::HTTP.get_response(uri)

case res
when Net::HTTPSuccess
  puts res.body
when Net::HTTPRedirection
  warn "moved to #{res["Location"]}"
else
  warn "forecast failed: #{res.code} #{res.message}"
end

go deeper

for a junior

Know that Net::HTTP.get gives you only a body String and that get_response gives you an object with code, headers and body; show one status check.

for a middle

Explain the response class hierarchy, why is_a?(Net::HTTPSuccess) beats listing codes, that code is a String, and what value raises for each family.

for a senior

Point out that silently parsing an error body is a data-quality bug, insist on status checks at every call site, and handle redirects with a hop limit.

for a principal

Argue for one shared wrapper that turns non-2xx responses into typed errors, so call sites cannot forget the check and failures are logged consistently.

## Two one-shot helpers Ruby's standard library ships **`Net::HTTP`** (the `net-http` default gem, 0.9.1 in Ruby 4.0). `require "net/http"` also loads `uri`, so `URI("https://...")` is available straight away. Two class methods cover the simplest case, one GET to one URL: - **`Net::HTTP.get(uri)`** returns a **String**: the response body and nothing else. - **`Net::HTTP.get_response(uri)`** returns a **`Net::HTTPResponse`** object: status, headers and body together. The source makes the relationship plain: `get` is implemented as `get_response(uri_or_host, path_or_headers, port).body`. Both open a TCP connection (with TLS when the URI's scheme is `https`), send a single request, close the connection and return. Both accept either a URI plus an optional headers Hash, or a host, a path and an optional port. ## Why get hides failures Because `get` throws the response object away, it has **no way to tell you the status**. A weather API that answers `404 Not Found` with an HTML error page, or `503 Service Unavailable` with a JSON error document, still gives you a String. Code that then parses that String as a forecast fails somewhere far from the real cause, or worse, stores the error text as data. `Net::HTTP.get` does not raise for any status code; exceptions come only from the transport (a refused connection, a timeout, a TLS failure). ## What a response object carries `get_response` returns an instance of a class chosen by the status code. The classes form a hierarchy you can test with `is_a?` or `case`/`when`: | Status family | Parent class | Example subclass | `res.value` raises | |---|---|---|---| | 1xx | `Net::HTTPInformation` | `Net::HTTPContinue` | `Net::HTTPError` | | 2xx | `Net::HTTPSuccess` | `Net::HTTPOK`, `Net::HTTPCreated` | nothing, returns nil | | 3xx | `Net::HTTPRedirection` | `Net::HTTPFound` | `Net::HTTPRetriableError` | | 4xx | `Net::HTTPClientError` | `Net::HTTPNotFound` | `Net::HTTPClientException` | | 5xx | `Net::HTTPServerError` | `Net::HTTPServiceUnavailable` | `Net::HTTPFatalError` | Useful readers on the object: - **`res.code`** is a **String** such as `"404"`; comparing it with the Integer `404` is always false. - **`res.message`** is the reason phrase, for example `"Not Found"`. - **`res["Content-Type"]`** reads a header case-insensitively. - **`res.body`** is the body String. - **`res.value`** turns a non-2xx response into an exception, which suits code that treats any failure as fatal. ## Checking status idiomatically 1. Prefer the class test: `res.is_a?(Net::HTTPSuccess)` accepts 200, 201, 204 and the rest of the 2xx family without listing codes. 2. Use `case res` with `when Net::HTTPSuccess`, `when Net::HTTPRedirection` and `else` when each family needs different handling. 3. Reach for `res.code` when a specific code matters, and compare it with a String (`res.code == "429"`). 4. Call `res.value` when you would rather raise than branch; rescue `Net::HTTPClientException` or `Net::HTTPFatalError` where you can recover. ## The host-and-path form is plain HTTP Both helpers also accept a host and a path instead of a URI: `Net::HTTP.get("api.example.com", "/v1/forecast")`. That form connects to port 80 (or the port you pass third) **without TLS**, because only the URI form reads the scheme and turns on `use_ssl` for `https`. Code migrated from an http endpoint to an https one keeps working on the surface while sending traffic in clear text, or fails when the server only listens on 443. Passing a `URI` object is the safer habit: - the scheme decides TLS; - the port comes from the URI, defaulting to 443 for https; - the second argument becomes a headers Hash, for example `{"Accept" => "application/json"}`. ## Redirects are yours to follow Neither helper follows a redirect. A `301` or `302` comes back as a `Net::HTTPRedirection` subclass whose `Location` header names the new URL; following it (with a limit on hops) is your code's job. That is one visible difference from `URI.open` in open-uri, which follows redirects for you. ## Common mistakes - Treating the String from `Net::HTTP.get` as proof of success. - Comparing `res.code` with an Integer. - Expecting `get_response` itself to raise on a 500; only `value` does that. - Forgetting that each call pays for its own connection, which matters once a script makes more than a handful of requests.

  • What does Net::HTTPResponse#value return for a 200 response, and what does it raise for a 503?
    For any 2xx response `value` returns nil and does nothing else. For a 5xx such as 503 it raises `Net::HTTPFatalError`, carrying the response so a rescuer can inspect it; a 4xx raises `Net::HTTPClientException` and a 3xx `Net::HTTPRetriableError`.
  • Net::HTTP.get_response returns a Net::HTTPMovedPermanently. How do you get the new URL, and does Net::HTTP retry there?
    Read `res["Location"]`, which may be absolute or relative, and issue a new request yourself. Net::HTTP never follows redirects, so a loop with a hop limit, as the Net::HTTP documentation sketches, is the usual pattern.

saying these in an interview costs you the question

  • Net::HTTP.get raises an exception when the server answers 404
  • res.code returns an Integer such as 200
  • Net::HTTP.get_response follows redirects to the final page
  • A non-empty body from Net::HTTP.get means the call succeeded
  • get_response raises Net::HTTPFatalError on a 500 without calling value
open as a page

In Ruby, what are the differences between system, backticks (%x), exec and spawn for running an external command?

level: juniorimportance: must knowfreq 72%

basics

~20 s

system runs a command, waits, and returns true, false or nil; backticks and %x run it, wait, and return its standard output as a String; exec replaces the Ruby process; spawn starts it and returns the pid without waiting.

open as a page

In Ruby, how do you get the current time, turn a Unix timestamp into a Time, and format it with strftime or iso8601?

level: juniorimportance: must knowfreq 62%

basics

~20 s

Time.now returns the current instant in the local zone; Time.at(seconds) builds a Time from Unix epoch seconds and to_i converts back. strftime formats with directives such as %Y-%m-%d %H:%M, and iso8601 returns a machine-readable 2026-05-28T20:26:40Z string.

open as a page

In Ruby, how do Forwardable's def_delegators and def_delegator expose selected methods of an inner object, and what do they generate?

level: middleimportance: must knowfreq 46%

basics

~10 s

After extend Forwardable, def_delegators :@user, :name, :email defines real instance methods that call the same methods on @user, and def_delegator :@user, :name, :display_name forwards one method under a new name. Unlisted methods stay unavailable.

open as a page

In Ruby, how does a UserPresenter < SimpleDelegator wrap a user object, and what do __getobj__ and __setobj__ do?

level: middleimportance: must knowfreq 42%

basics

~10 s

SimpleDelegator.new(user) forwards every public method it does not define itself to user. A subclass adds or overrides presentation methods; getobj returns the wrapped user and setobj replaces it with another object.

open as a page

In Ruby's Net::HTTP, what are the default open_timeout and read_timeout, how do you set them, and which exceptions signal them?

level: middleimportance: must knowfreq 52%

basics

~10 s

Both default to 60 seconds. Set them as options to Net::HTTP.start or through http.open_timeout= and read_timeout= before sending; expiry raises Net::OpenTimeout or Net::ReadTimeout, both subclasses of Timeout::Error.

open as a page

In Ruby, after system or backticks runs a command, how do you check its exit status with $? and Process::Status?

level: middleimportance: must knowfreq 58%

basics

~10 s

$? holds a Process::Status for the most recently exited child in the current thread. Read $?.success? or $?.exitstatus for a normal exit, and $?.signaled? with $?.termsig when a signal killed the process.

open as a page

A Ruby app runs ffmpeg on user-uploaded video files; which calling forms keep a hostile filename from being read by a shell or as an ffmpeg option?

level: seniorimportance: must knowfreq 55%

basics

~20 s

Pass the executable and every argument separately, system("ffmpeg", "-i", path, out) or Open3.capture3 the same way, so no shell runs. Avoid backticks and interpolated strings, and make the path start with a directory, not a dash.

open as a page

A Ruby service sends dentist appointment reminders to patients in several time zones; how should it store and compute those times so daylight-saving changes never shift them?

level: seniorimportance: must knowfreq 42%

basics

~20 s

Store each appointment's local date, time and IANA zone name; resolve them to a UTC instant through a timezone object when scheduling, and build "day before at 18:00" with Date arithmetic in that zone. Fixed offsets and ENV["TZ"] swaps break.

open as a page

In Ruby, what does include Singleton do to a class, and what happens when code calls new on it?

level: juniorimportance: should knowfreq 38%

basics

~20 s

include Singleton makes new and allocate private and adds a class method instance that creates one object on first call and returns it afterwards. Calling new raises NoMethodError, and dup or clone on the instance raise TypeError.

open as a page

In Ruby's Net::HTTP, why use a Net::HTTP.start block with Net::HTTP::Get and Net::HTTP::Post objects instead of repeated Net::HTTP.get calls?

level: middleimportance: should knowfreq 45%

basics

~20 s

Net::HTTP.start opens one connection, reuses it for every request in the block and closes it when the block exits. Request objects such as Net::HTTP::Post carry headers and a body; in Ruby 4.0 you set Content-Type yourself.

open as a page

In Ruby, how do URI.parse, URI.join and URI.encode_www_form help build a request URL, and what trap hides in URI.join?

level: middleimportance: should knowfreq 36%

basics

~20 s

URI.parse turns a String into a URI::HTTP or URI::HTTPS object, URI.join resolves a relative path against a base, and URI.encode_www_form builds an escaped query. URI.join drops the base's last path segment unless it ends in a slash.

open as a page

In Ruby, how do ENV[] and ENV.fetch differ for reading configuration, and what type are ENV values?

level: middleimportance: should knowfreq 48%

basics

~10 s

ENV["NAME"] returns the value or nil; ENV.fetch("NAME") raises KeyError when it is missing, or returns a default or block result. Values are always frozen Strings, so convert numbers and booleans explicitly.

open as a page

In Ruby, how do Open3.capture3, capture2e and popen3 differ when you need a command's stderr and exit status?

level: middleimportance: should knowfreq 50%

basics

~10 s

Open3.capture3 returns [stdout, stderr, status]; capture2e returns [merged stdout and stderr, status]; popen3 hands you stdin, stdout and stderr streams plus a wait thread, for streaming large or interactive output.

open as a page

In Ruby, how does Time arithmetic in seconds work, and when should you use Date rather than Time or the deprecated DateTime?

level: middleimportance: should knowfreq 45%

basics

~20 s

Time + and - take seconds and return a new Time; Time - Time returns a Float of seconds. For calendar days and months use Date from require "date" (Date + 1, Date >> 1); DateTime is documented as deprecated.

open as a page

In Ruby, why should you time an operation with Process.clock_gettime(Process::CLOCK_MONOTONIC) rather than subtracting two Time.now values?

level: middleimportance: should knowfreq 48%

basics

~10 s

Time.now reads the wall clock, which NTP or an operator can step forwards or backwards mid-measurement. Process.clock_gettime(Process::CLOCK_MONOTONIC) never goes backwards, so the difference of two readings is a reliable elapsed time in seconds.

open as a page

In Ruby, how do Time.parse and Time.strptime from the time library differ, and which should read externally supplied timestamps?

level: middleimportance: should knowfreq 40%

basics

~20 s

Time.parse guesses the format heuristically and fills missing parts from the current date; Time.strptime(string, format) accepts only the declared format and raises ArgumentError otherwise. For external input, prefer strptime or a strict parser such as Time.iso8601.

open as a page

In Ruby, how do Time#utc and Time#localtime differ from getutc and getlocal, and what decides the local zone?

level: middleimportance: should knowfreq 38%

basics

~20 s

Time#utc and Time#localtime convert the receiver in place and return self; getutc and getlocal return a new Time and leave the original alone. The local zone comes from the operating system or the TZ environment variable, for the whole process.

open as a page

When would you build a Ruby presenter as class UserPresenter < DelegateClass(User) instead of subclassing SimpleDelegator or User itself?

level: seniorimportance: should knowfreq 24%

basics

~20 s

DelegateClass(User) builds a Delegator subclass with real forwarding methods for User's public and protected methods, so calls skip method_missing and reflection lists them. Use it when the wrapped type is fixed; subclass User only when you create the objects yourself.

open as a page

A Ruby UserPresenter < SimpleDelegator wraps a User: why is presenter.is_a?(User) false while presenter == user is true, and what else surprises you?

level: seniorimportance: should knowfreq 28%

basics

~20 s

Delegator is a BasicObject subclass with a trimmed copy of Kernel, so class, is_a? and User === presenter describe the wrapper, while ==, eql?, hash, to_s and inspect are forwarded. Equality is therefore one-sided, and freeze freezes both objects.

open as a page

A Ruby service calls a weather API through Net::HTTP GET with read_timeout set to 5, yet a stalled call takes about 10 seconds to raise Net::ReadTimeout. Why, and what would you change?

level: seniorimportance: should knowfreq 30%

basics

~20 s

Net::HTTP retries an idempotent request such as GET once by default (max_retries is 1) after Net::ReadTimeout, so two 5-second waits elapse. Set max_retries to 0 and handle retries yourself, and remember read_timeout bounds each read, not the total.

open as a page

In Ruby, how do you use Signal.trap so a video-transcoding worker shuts down gracefully on TERM or INT, and what must the handler avoid?

level: seniorimportance: should knowfreq 35%

basics

~20 s

Without a trap, INT raises Interrupt and TERM raises SignalException in the main thread. Signal.trap("TERM") { @stopping = true } lets the worker finish the current job and exit; the handler must avoid Mutex, Monitor and buffered I/O.

open as a page

In Ruby, what does URI.open from open-uri return for an https URL, and how does it treat redirects and error statuses differently from Net::HTTP?

level: juniorimportance: nice to knowfreq 22%

basics

~10 s

URI.open returns a file-like object, a StringIO or a Tempfile for larger bodies, extended with status and content_type readers. Unlike Net::HTTP it follows redirects and raises OpenURI::HTTPError for any non-2xx status.

open as a page

In Ruby 4.0, why can require "observer" fail in a Bundler app, and how does the Observable module notify its observers?

level: middleimportance: nice to knowfreq 12%

basics

~20 s

observer became a bundled gem in Ruby 3.4, so under Bundler require "observer" raises LoadError unless the Gemfile lists it. Observable's notify_observers calls update on each registered observer, but only after changed has been called.

open as a page