Skip to content

HTTP Methods Reference — Ruby source

A searchable reference for every HTTP request method - GET, POST, PUT, PATCH, DELETE, and more. See at a glance which are safe, idempotent, and cacheable, then compare any two methods side by side.

This is the Ruby implementation — the same logic the interactive tool runs, in a shareable, citable form.

# Source: ruby.rb
# Language: Ruby (Ruby 3.2, standard library only)
#
# HTTP request-method reference — pure, deterministic data + lookups.
#
# CosmoDev polyglot showcase port of the `http-methods` tool, ported from
# src/lib/http-methods.ts. Functionally equivalent to the TypeScript original:
# identical inputs yield identical outputs (case-insensitive lookup, flag +
# free-text filtering, and a human-readable semantic comparison report).
#
# Property flags (safe / idempotent / cacheable / hasBody) follow RFC 9110
# and the MDN reference table.
#
# License: display source — part of CosmoDev's polyglot tool pages.

# frozen_string_literal: true

module HttpMethods
  # One HTTP request method and its semantic properties.
  #
  # A Struct with keyword_init keeps construction explicit at the call site
  # (mirroring the named-argument constructors of the other ports).
  MethodEntry = Struct.new(
    :method,       # Uppercase method name, e.g. "GET".
    :safe,         # Read-only semantics — no server state change.
    :idempotent,   # Repeating the call has the same effect as a single call.
    :cacheable,    # Responses may be stored by a cache (RFC 9110 / MDN).
    :has_body,     # The method conventionally carries a request body.
    :description,
    :typical_use,
    keyword_init: true
  )

  # The nine HTTP request methods (RFC 9110 / 9111), in canonical order.
  # Frozen so the table is a single immutable source of truth; the helpers
  # hand out references into it.
  METHODS = [
    MethodEntry.new(
      method: 'GET',
      safe: true,
      idempotent: true,
      cacheable: true,
      has_body: false,
      description: 'Retrieves a representation of the target resource; a read-only request.',
      typical_use: 'Fetching a web page, reading an API resource, loading an image.'
    ),
    MethodEntry.new(
      method: 'POST',
      safe: false,
      idempotent: false,
      cacheable: true,
      has_body: true,
      description: 'Submits data to be processed, typically creating a new resource or triggering an action.',
      typical_use: 'Submitting a form, creating a record, publishing a message.'
    ),
    MethodEntry.new(
      method: 'PUT',
      safe: false,
      idempotent: true,
      cacheable: false,
      has_body: true,
      description: 'Replaces the target resource entirely with the request body.',
      typical_use: 'Updating a full record at a known URL, uploading a file by its path.'
    ),
    MethodEntry.new(
      method: 'PATCH',
      safe: false,
      idempotent: false,
      cacheable: false,
      has_body: true,
      description: 'Applies a partial modification to the target resource.',
      typical_use: 'Updating one field of a record, toggling a flag.'
    ),
    MethodEntry.new(
      method: 'DELETE',
      safe: false,
      idempotent: true,
      cacheable: false,
      has_body: false,
      description: 'Removes the target resource.',
      typical_use: 'Deleting a record or file by its URL.'
    ),
    MethodEntry.new(
      method: 'HEAD',
      safe: true,
      idempotent: true,
      cacheable: true,
      has_body: false,
      description: 'Identical to GET but returns only the response headers, no body.',
      typical_use: 'Checking existence, size, or freshness before downloading.'
    ),
    MethodEntry.new(
      method: 'OPTIONS',
      safe: true,
      idempotent: true,
      cacheable: false,
      has_body: false,
      description: 'Describes the communication options for the target resource.',
      typical_use: 'CORS preflight requests, discovering allowed methods.'
    ),
    MethodEntry.new(
      method: 'CONNECT',
      safe: false,
      idempotent: false,
      cacheable: false,
      has_body: false,
      description: 'Establishes a tunnel to the server (used with TLS/HTTPS proxies).',
      typical_use: 'Proxying encrypted connections through an intermediary.'
    ),
    MethodEntry.new(
      method: 'TRACE',
      safe: true,
      idempotent: true,
      cacheable: false,
      has_body: false,
      description: 'Performs a message loop-back test along the path to the target (debugging only).',
      typical_use: 'Diagnosing request transformations by intermediaries.'
    )
  ].freeze

  # Narrowing options for .filter_methods.
  #
  # Each boolean is tri-state: nil means "no constraint", true/false
  # constrains to that value — a distinction a bare Boolean cannot express.
  MethodFilter = Struct.new(
    :safe,
    :idempotent,
    :cacheable,
    # Free text matched case-insensitively against method, description,
    # and typical_use. Ignored when empty after trimming.
    :query,
    keyword_init: true
  )

  # Semantic diff between two methods.
  MethodComparison = Struct.new(
    :same_safety,       # Both methods share the `safe` flag.
    :same_idempotence,  # Both methods share the `idempotent` flag.
    :differences,       # One sentence per mismatched property (safe, idempotent, cacheable, has_body).
    keyword_init: true
  )

  module_function

  # Case-insensitive single-method lookup.
  #
  # Returns the entry, or nil when unknown — the Ruby analogue of TS's
  # `MethodEntry | null`.
  def get_method(name)
    n = name.strip.upcase
    METHODS.find { |m| m.method == n }
  end

  # Filter the method set by boolean flags and an optional text query.
  #
  # A method must satisfy *every* present flag AND, when a query is given,
  # match it in at least one of {method, description, typical_use}.
  def filter_methods(opts = MethodFilter.new)
    q = opts.query.to_s.strip.downcase

    METHODS.select do |m|
      # Each present flag is an AND constraint; nil means skip.
      next false unless opts.safe.nil? || m.safe == opts.safe
      next false unless opts.idempotent.nil? || m.idempotent == opts.idempotent
      next false unless opts.cacheable.nil? || m.cacheable == opts.cacheable

      next true if q.empty?

      [m.method, m.description, m.typical_use].any? { |field| field.downcase.include?(q) }
    end
  end

  # Compare two methods, surfacing where their semantics agree and differ.
  #
  # `differences` lists every mismatched property (safe, idempotent,
  # cacheable, has_body) as a human-readable sentence, reproducing the
  # canonical phrasing verbatim so output stays identical across ports.
  def compare_methods(a, b)
    differences = []
    if a.safe != b.safe
      differences << "#{a.method} is #{safe_word(a.safe)}, #{b.method} is #{safe_word(b.safe)}."
    end
    if a.idempotent != b.idempotent
      differences << "#{a.method} is #{idempotent_word(a.idempotent)}, #{b.method} is #{idempotent_word(b.idempotent)}."
    end
    if a.cacheable != b.cacheable
      differences << "#{a.method} is #{cacheable_word(a.cacheable)}, #{b.method} is #{cacheable_word(b.cacheable)}."
    end
    if a.has_body != b.has_body
      differences << "#{a.method} #{body_word(a.has_body)}, #{b.method} #{body_word(b.has_body)}."
    end

    MethodComparison.new(
      same_safety: a.safe == b.safe,
      same_idempotence: a.idempotent == b.idempotent,
      differences: differences
    )
  end

  # Phrasing helpers keep the difference-sentence wording in one place, so
  # the output stays in lock-step across every polyglot port.
  def safe_word(value) = value ? 'safe' : 'not safe'

  def idempotent_word(value) = value ? 'idempotent' : 'not idempotent'

  def cacheable_word(value) = value ? 'cacheable' : 'not cacheable'

  def body_word(value) = value ? 'takes a body' : 'does not take a body'
end

Also available in 13 other languages

Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →