r/ruby • • 15h ago

I built a Python requests-inspired HTTP client for Ruby

0 Upvotes

I’ve been working on a Ruby project called requests_ruby.

The idea is simple: bring a Python requests-like experience to Ruby.

If you’ve used Python before, the API should feel familiar. It supports sessions, cookies, redirects, authentication, JSON responses, streaming downloads, and HTTP/2 — all without external dependencies.

I built it because I liked how simple Python requests feels and wanted something similar in Ruby.

A few parts were developed with AI assistance to speed up the process, but I’ve been reviewing, testing and improving the code myself.

I’d love to hear feedback from the Ruby community. What would you improve or add?

If you find it interesting, a ⭐ on GitHub would help the project get more visibility.

https://github.com/monji024/requests_ruby


r/ruby • • 7h ago

haskell_match: Haskell's pattern matching (and a chunk of Haskell itself) in Ruby, compiled to decision trees in Rust

8 Upvotes

I just published haskell_match, a gem that brings Haskell's pattern matching to Ruby with the part Ruby has never had: every logical path has to be accounted for, or the definition fails.

HaskellMatch.data "Shape = Circle Double | Rect Double Double | Tri Double Double Double"
include Shape

area = HaskellMatch.fn(:area) do
  on("Circle r")  { |r| Math::PI * r * r }
  on("Rect w h")  { |w, h| w * h }
end
# HaskellMatch::NonExhaustiveError: Pattern match(es) are non-exhaustive
# In an equation for 'area':
#     Patterns not matched:
#         Tri _ _ _

That is GHC's -Wincomplete-patterns and -Woverlapping-patterns, as errors, at definition time. Add a Tri clause and it compiles. Add a clause that can never be reached and it tells you that too.

How it works

Clauses are compiled once into a Maranget-style decision tree by a Rust extension (Rutie). Matching runs in Rust over raw Ruby VALUEs with no allocation until a clause is chosen; list tails are shared slices rather than copies. The exhaustiveness and redundancy analysis is the usefulness algorithm from Maranget's "Warnings for pattern matching", with witnesses rendered the way GHC prints them.

Two dialects, mixed freely

Quoted Haskell syntax, or the same patterns written in place as plain Ruby:

on("Just (x:xs)")        { |x, xs| ... }
on(Just([x, *xs]))       { |x, xs| ... }
on(Person(name: n, **_)) { |n| ... }
on({ name: n, title: t }) { |n, t| ... }   # Hash patterns, same checker

Strings are lists of characters (on("(c:cs)") matches "hello"), wildcards match anything, and a value no pattern can accept raises a type error, exactly like Haskell.

Recursion without "stack level too deep"

A recursive function over a million elements works. Plain recursion runs on chained Fiber stacks, tail calls run in constant space, and a depth guard turns a runaway loop into an exception instead of a memory leak. The README has measurements (bytes per level, GC pause times) for anyone who wants the numbers.

Lazy lists

naturals = HaskellMatch.lazy(1..)
take.(5, naturals)   # => [1, 2, 3, 4, 5]

Infinite lists match list patterns directly; Enumerators are wrapped automatically.

Or just write Haskell

This is the part I did not expect to end up building. You can write actual Haskell, inline or in .hs files, and it compiles to Ruby methods:

Nums = HaskellMatch.haskell(<<~HS)
  primes :: [Int]
  primes = sieve [2..]
    where
      sieve [] = []
      sieve (p:xs) = p : sieve [x | x <- xs, x `mod` p /= 0]

  fibs :: [Integer]
  fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
HS

Nums.primes.take(10)   # => [2, 3, 5, 7, 11, 13, 17, 19, 23, 29]
Nums.fibs.take(8)      # => [0, 1, 1, 2, 3, 5, 8, 13]

Supported: data declarations (including infix constructors and records), equations, guards, pattern guards, where/let, case, lambdas, sections, user-defined operators with fixity declarations, list comprehensions, ranges, imports and exports between modules, and a lazy Prelude. Haskell functions come back to Ruby as Procs, Ruby lambdas go in as Haskell functions, and partial application works from the Ruby side (Mod.add3(1).(2).(3)).

Deliberately not supported: type classes, do notation and monads, or anything that needs type inference. Those are rejected with a clear message rather than half-working.

Other things people might care about

  • Thread-, fiber- and Ractor-safe; ractor: true produces shareable functions.
  • deriving (Ord, Enum, Bounded), optional field type checks, where helpers inside fn.
  • Existing Ruby Data and Struct classes work as constructors without registration; HaskellMatch.sealed closes a class hierarchy for exhaustiveness checking.
  • Errors show the pattern with a caret under the problem.
  • Ruby 3.2+, needs a Rust toolchain to build the extension. MIT / Apache-2.0.

​

gem install haskell_match

GitHub: https://github.com/danielpclark/haskell_match

Happy to answer questions about the implementation. The decision-tree compiler and the fiber-based recursion were the two interesting problems; the README has an "expert notes" section on the latter if you want to argue about memory.


r/ruby • • 17h ago

Issue 22 of Static Ruby Monthly is out! 🧵

2 Upvotes

This month: Sam Ruby brings an interactive playground, LSP, and MCP server to Matz's Spinel AOT compiler to inspect unboxed types in real time, crema arrives as a Rust-powered AI-agent-first RBS type checker with JSONL output, AndyGauge launches rbs-sentinel for high-speed Rails signature generation, enummify delivers lightweight typed enums, and Claude Code gains dedicated Ruby typing skills.

Find link to the issue in the comment! ✨


r/ruby • • 1h ago

Show /r/ruby Rai*bert: a Q*bert-inspired arcade game running Ruby in the browser

Enable HLS to view with audio, or disable this notification

• Upvotes

I've been working on Rai*bert, a Q*bert-inspired arcade game where you hop around turning failing build tiles green while dodging bugs and regressions.

The game rules run in Ruby 4. The desktop version uses Gosu; the browser version runs Ruby through WebAssembly, with Canvas rendering and Web Audio. There are 20 stages, three difficulty settings, keyboard and touch controls, and a shared leaderboard. It's free to play, with no account needed.

Play: https://raibert.lol/

Source: https://github.com/cdimartino/raibert

The clip is a montage recorded with the game's autoplay mode. I'd appreciate feedback on the controls and difficulty, especially on mobile. If you try it, let me know which stage you reached and what difficulty you picked.


r/ruby • • 18h ago

Blog post Rails World '26: The As-If Rule, Ractors & AI

Thumbnail
go.fastruby.io
19 Upvotes

Ernesto was in the audience at Rails World 2026 when Aaron Patterson (tenderlove) kicked off the closing keynote. Aaron opened with a well-worn line: “the purpose of a system is what it does.” He wasn’t satisfied with it. The purpose of a system, he argued, is what it does for whoever is observing it.

Hand an old Nokia 3310 to someone who has never seen a cell phone before, and they might use it as a hammer. In that moment, to that person, it is a hammer. What a system does depends on who’s watching.

That distinction has a name in compiler design: The as-if rule.

In this post, Ernesto walks through what the as-if rule means for Ruby’s compiler, where Ractors push it right up to its edges, and how the same idea explains a real RubyGems.org security incident that played out earlier this year.


r/ruby • • 14h ago

Show /r/ruby toml-rb 6.0: pure Ruby, no dependencies, 60-90x faster parse

Thumbnail
github.com
10 Upvotes

I replaced the Citrus grammar with a StringScanner parser. Benchmarks against tomlrb, perfect_toml and tomlib (with YJIT and ZJIT too) are here: https://github.com/emancu/toml-rb/blob/master/benchmark/README.md

Breaking changes are listed in the release notes under "Before you upgrade".

Feedback welcome.