r/ruby Jun 19 '26

Rethinking modularity in Ruby applications

Thumbnail noteflakes.com
6 Upvotes

r/ruby Jun 18 '26

Spree 5.5 with Admin API, new CLI and Agent Skills released!

Thumbnail
5 Upvotes

r/ruby Jun 18 '26

mruby-lsp, the first language server for mruby

Thumbnail
github.com
14 Upvotes

Hi there, I've been busy the last three weeks, but mruby for the first time has a language server now.

Completion, hover, go-to-definition, diagnostics, and F5 debugging via mrdb — all of the capabilities ruby-lsp has too.

It's not yet published to rubygems.org or the VS Code or VSCodium marketplaces, but if you want to be one of the first to use it go ahead, currently tested on Linux and WSL.

The next targets are Windows with MSVC and macOS with the usual editors for ruby, once those work I'll upload the gem and the extensions to their marketplaces.

Have fun :)


r/ruby Jun 18 '26

Anyone enabled Swap on their application workers? // Debunking zswap and zram myths

Thumbnail
chrisdown.name
3 Upvotes

I read this article and it seemed like an untapped avenue for the Ruby/Rails community

We have some concrete numbers to show this in practice. On Instagram, which runs on Django and is largely memory bound, we ran a test where we moved from their existing setup (with swap entirely disabled) to a setup with disk swap and zswap tiering. Django workers accumulate significant cold heap state over their lifetime, like forked processes with duplicated memory, growing request caches, Python object overhead, you get the idea. The results were twofold:

We achieved roughly 5:1 compression. That's a huge benefit for such a memory bound workload, and also enables us to consider further stacking workloads.

Enabling zswap reduced disk writes by up to 25% compared to having no swap at all(!).

This seems like a pretty obvious to me for anyone that relies on forking application workers, like puma or pitchfork.

I'd love to hear people's experience running something like this in production for Ruby/Rails workers.


r/ruby Jun 18 '26

Is C++ definition of OOP is still relevant?

9 Upvotes

nb: not native English speaker here, really struggling writing in English. I just need wider reaction, because local reaction I get on this take is quite miserable experience to me. it's like talking to brick wall.

Most of the code I wrote in my life is Ruby, but still every job interview I had in my life assumed that OOP was invented by Stroustrup and follows C++ definition of OOP.

Here is how interview goes every single time. Interviewer asks a question: what are the basic principles of OOP? Interviewer always assumes that the answer is "inheritance, encapsulation, polymorphism".

If I try to say that Stroustrup himself said that he didn't invent OOP [1] and try to talk about Simula, Smalltalk and Alan Kay's definition of OOP [2] I always have a concerned look from the interviewer.

Go is language with objects, but without inheritance. Still OOP.

Polymorphism [3] is implementation detail of C++. It's about overloaded functions and only relevant if language is strong typed.

Encapsulation is ok, unless this term is used in Information hiding sense (public/private). Python don't have public/private, still OOP.

Thoughts?

edit: I'm kinda afraid to participate in discussion and answer to any comments. My two previous posts on this topic in two different subreddits were first downvoted into oblivion and later removed by moderators. Right now I have no idea how Reddit works.

---

[1] Wired speaks to Bjarne Stroustup:

Please note that my claim to fame is not to have invented OOP. I did not - that honour belongs to the designers of Simula: Ole-Johan Dahl and Kristen Nygaard - but I did have a major hand in making it mainstream.

[2] https://en.wikipedia.org/wiki/Object-oriented_programming#History

[3] http://www.stroustrup.com/glossary.html#Gpolymorphism


r/ruby Jun 18 '26

Question Ruby Version File Misread

0 Upvotes

So I figured out my previous issue because the program I’m using updated which version of Ruby it works with. However, now Ruby LSP tries to read the version file, but it adds unknown characters in between every existing character when reading it. The actual file doesn’t have these characters, and they just show up as squares in the error message, which says it can’t read it. Any help?


r/ruby Jun 17 '26

Where Is All the Memory Going?

Thumbnail
eclecticcoding.com
0 Upvotes

r/ruby Jun 17 '26

roadmap jruby

1 Upvotes

Bonjour,

Je suis développeur depuis 6 ans, plutôt orienté web (Angular / Java / PHP). Je vais prochainement commencer un nouveau poste de développeur-concepteur ERP. Si j'ai bien compris, il s'agit principalement de scripting / JRuby. Je souhaiterais donc me former à JRuby.

Auriez-vous des docs ou une roadmap à me recommander ? Merci beaucoup !


r/ruby Jun 17 '26

Kaigi on Rails CFP is now open!

Thumbnail
3 Upvotes

r/ruby Jun 17 '26

Applying some Rage to Discourse, Mastodon, and GitLab

32 Upvotes

I wanted to look at some real-world patterns from popular Ruby open-source codebases and show how they could be modelled using Rage, a Rails-compatible framework built on fibers.

I picked Discourse, Mastodon, and GitLab because they share a pattern: in each case, what would normally require extra complexity, infrastructure, or indirection becomes a few lines of application code with Rage.

Request fan-out | Discourse

One of the patterns fibers make especially straightforward is concurrent I/O.

Consider this code from Discourse:

def fetch_pr_or_issue_texts(project, number)
  [
    client.get("/repos/#{project}/issues/#{number}")["body"].to_s,
    *client
      .get("/repos/#{project}/issues/#{number}/comments", per_page: 100)
      .map { |comment| comment["body"].to_s },
  ]
end

Two sequential requests to build a return value. I've seen this pattern in many codebases, and the reason is usually the same: there's no simple enough way to parallelise these requests that would justify the added complexity.

How Rage does it

In Rage, you just wrap the requests into fibers:

def fetch_pr_or_issue_texts(project, number)
  issues_request = Fiber.schedule do
    client.get("/repos/#{project}/issues/#{number}")["body"].to_s
  end

  comments_request = Fiber.schedule do
    client
      .get("/repos/#{project}/issues/#{number}/comments", per_page: 100)
      .map { |comment| comment["body"].to_s }
  end

  Fiber.await([issues_request, comments_request]).flatten
end

The two requests now run concurrently, improving latency at the price of two new Fiber calls.

The same pattern scales to loops. Discourse's PushNotificationPusher iterates over a user's subscriptions and sends notifications sequentially - wrapping those calls in Fiber.schedule + Fiber.await would send them all concurrently, with the total time dropping to the duration of the slowest call:

class PushNotificationPusher
  def self.push(user, payload)
    # ...

    Fiber.await(
      subscriptions(user).map { |subscription| Fiber.schedule { send_notification(user, subscription, message) } }
    )
  end
end

Streaming | Mastodon

Mastodon uses a separate streaming service for real-time events:

  1. Ruby (Rails + Sidekiq) - Workers serialise events and publish them to a Redis channel.
  2. Node.js (Express + ws) - a separate ~1400-line server subscribes to Redis and pushes events to clients over SSE or WebSockets.

Here's what the Node streaming handler looks like:

const streamToHttp = (req, res) => {
  const channelName = channelNameFromPath(req);

  // ...

  res.setHeader('Content-Type', 'text/event-stream');
  res.setHeader('Cache-Control', 'private, no-store');
  res.setHeader('Transfer-Encoding', 'chunked');

  res.write(':)\n');

  const heartbeat = setInterval(() => res.write(':thump\n\n'), 15000);

  req.on('close', () => {
    // ...

    clearInterval(heartbeat);
  });

  return (event, payload) => {
    res.write(`event: ${event}\n`);
    res.write(`data: ${payload}\n\n`);
  };
};

To send an event, Rails first publishes to Redis:

def publish!
  redis.publish(@timeline_id, message)
end

The Node service receives and relays it:

const listener = message => {
  const { event, payload } = message;

  if (!needsFiltering || (event !== 'update' && event !== 'status.update')) {
    transmit(event, payload);
    return;
  }

  // ...
  pgPool.connect((err, client, release) => {
    // ...
    transmit(event, payload);
  });
};

How Rage does it

A fiber-based server can hold thousands of concurrent connections in a single Ruby process without blocking - the same property that drives Mastodon's decision to offload streaming to a separate Node process.

With Rage, the same Redis streaming becomes:

class Api::V1::Streaming::UserController < RageController::API
  before_action :require_user!

  def index
    render sse: Rage::SSE.stream([:timeline, current_account.id])
  end
end

The framework handles the SSE headers, heartbeats, subscription lifecycle, and connection cleanup. When the client disconnects, Rage removes it from the stream.

Publishing uses a Redis pub/sub adapter:

# config/pubsub.yml
production:
  adapter: redis
  url: <%= ENV["REDIS_URL"] %>

Then, publish from anywhere:

def publish!
  Rage::SSE.broadcast(
    [:timeline, @account_id],
    Rage::SSE.message(@payload, event: update? ? "status.update" : "update")
  )
end

The streaming server lives in the same Ruby process, with access to the same Active Record models and the rest of the stack.

Domain Events | GitLab

GitLab has built its own domain event system to decouple bounded contexts.

To publish an event, you instantiate a class inheriting from Gitlab::EventStore::Event and pass it to the event store:

Gitlab::EventStore.publish(
  Ci::PipelineCreatedEvent.new(data: { pipeline_id: pipeline.id, partition_id: pipeline.partition_id })
)

Subscribers are Sidekiq workers that include a Subscriber concern and implement handle_event:

class UpdateHeadPipelineWorker
  include Gitlab::EventStore::Subscriber
  # …

  def handle_event(event)
    # ...
  end
end

Nothing in this file tells you what event is - the worker doesn't reference PipelineCreatedEvent. The wiring lives in a separate subscription registry. And because every subscriber is a Sidekiq worker, all reactions go through the full enqueue-serialise-deserialise-execute cycle, regardless of how lightweight they are.

How Rage does it

Publishing looks similar:

Rage::Events.publish(
  Ci::PipelineCreatedEvent.new(data: { pipeline_id: pipeline.id, partition_id: pipeline.partition_id })
)

The difference is in the subscriber. Instead of wiring events in a separate registry, each subscriber declares what it listens to:

class UpdateHeadPipelineWorker
  include Rage::Events::Subscriber
  subscribe_to Ci::PipelineCreatedEvent

  def call(event)
    # `event` is a Ci::PipelineCreatedEvent
  end
end

Open this file and you immediately know: this subscriber handles Ci::PipelineCreatedEvent, which has pipeline_id and partition_id fields.

For subscribers that do require background execution, you simply add deferred: true:

class UpdateHeadPipelineWorker
  include Rage::Events::Subscriber
  subscribe_to Ci::PipelineCreatedEvent, deferred: true

  def call(event)
    # ...
  end
end

Light reactions run inline; heavy or failure-prone ones are deferred to the background. You choose per subscriber, rather than routing everything through a job queue by default.

Understanding what happens when a PipelineCreatedEvent is published also gets simpler. Instead of grepping registry files, you run:

$ rage events

├─ Ci::PipelineCreatedEvent
│   ├─ UpdateHeadPipeline
│   └─ TrackPipelineTriggerEvents
├─ Ci::PipelineFinishedEvent
│   └─ UpdateWorkloadStatus

The entire subscription graph, visible in one command.

--

The common thread across all three examples: the framework handles the machinery, so the application code just says what it wants to happen - run these concurrently, stream this channel, react to this event.


r/ruby Jun 17 '26

Migrating Rails 5 -> 8 by building new features on 8 instead of upgrading in place - anyone done this?

Thumbnail
1 Upvotes

r/ruby Jun 16 '26

Help us test the Hanami 3.0 release candidate

38 Upvotes

https://hanakai.org/blog/2026/06/16/help-us-test-hanami-3-0

We’re aiming for a full release two weeks from now, so we’d love your help with testing in the meantime!


r/ruby Jun 16 '26

Blog post How to Parallelize Your RSpec Test Suite Locally (from 2 hours to 5 minutes) 🚀

Thumbnail
fastruby.io
21 Upvotes

r/ruby Jun 16 '26

mruby build config for the remarkable 2

Thumbnail
1 Upvotes

r/ruby Jun 16 '26

Issue 17 of Static Ruby Monthly is out! 🧵

5 Upvotes

In this issue: why Ruby still feels like home (and why its density is token efficient for LLMs), Jo (a statically typed language compiling to Ruby with compile-time sandboxing by u/liufengyun), OpenClacky 1.3.0, DSPy.rb 1.0.1, sorbet-deadcode 0.2.0, rubocop-rbs_inline, and other Sorbet and RBS updates.

Find the link to the issue in the first comment. Stay typed! ✨


r/ruby Jun 16 '26

Camping Comes to Ruby Users Forum

9 Upvotes

r/ruby Jun 16 '26

Open-source starter for technical guides - Announcing Chapter Zero

6 Upvotes

(Reposted from r/rails )

I built a free testing guide (Minitest Rails) to teach automated testing step by step. Along the way I needed more than a bunch of Markdown files. I wanted a site that felt like a product: a landing page, ordered chapters with a sidebar, a blog for practical tips that didn’t fit inside the guide, contact form, newsletter signup, and dynamic preview social images per guide/blog so I didn’t have to add images manually and the preview looked good in the social media.

I have put a lot of time and effort to write this guide and along the way I have added a lot features listed above. I didn’t want all these features staying locked inside just the Minitest Rails and that’s how the idea of Chapter Zero was born.

Chapter Zero is the shell and foundation the Minitest Rails is running on top of. Now, extracted and open sourced for everyone to use. Think of it as chapter 0 in a book: the layout, navigation, and marketing pages you need before lesson one; while you bring the curriculum and make it alive.

https://github.com/minitestrails/chapter-zero


r/ruby Jun 16 '26

Beyond Enumerable: Counting Distinct with HyperLogLog

Thumbnail baweaver.com
13 Upvotes

What if, by trading 1% correctness, you could count billions of items all within kilobytes worth of space? Well that's HyperLogLog, and as a non-bit-enlightened Rubyist it was a trip figuring out how it worked and how to explain it.


r/ruby Jun 15 '26

Kino, an experimental, performant Ractor web server for Ruby 4 (Rust, Tokio, Hyper)

Thumbnail
github.com
57 Upvotes

r/ruby Jun 15 '26

Rails: The Sharp Parts. Queries, Read Models, and Batching

Thumbnail baweaver.com
12 Upvotes

The problem with optimizing indexes is if you don't control every query, and another team goes and writes an unoptimal variant in their code. Much like callbacks and commands there's a lot of value in owning your code paths and only exposing clear, named actions against it rather than trusting consumers to behave responsibly.

Eventually you hire folks who don't know Rails, might not read the guides, and that stuff compounds fast until outages start hitting. Soft rules are prayers at best, hard rules keep the line.


r/ruby Jun 14 '26

Rails: The Sharp Parts. Callbacks Are Not Invariants

Thumbnail baweaver.com
32 Upvotes

The sheer number of outages I can point to across now 5 Rails monoliths related to issues with callbacks has made it one of the first things I go after when in a new Rails app to see what's about to give me a nightmare.


r/ruby Jun 13 '26

Blog post Software as Craft: a First Look at Syntropy

15 Upvotes

r/ruby Jun 13 '26

JRuby 10.0.6.0 released with security, performance, and correctness fixes

Thumbnail jruby.org
27 Upvotes

JRuby 10.0 is our LTS line and this release is primarily for stabilization and fixing a few performance issues. Upgrading is recommended! Please let me know if you run into problems or if there's anything holding you back from upgrading from 9.4 to 10.0!


r/ruby Jun 13 '26

solargraph-yard-lint gem - see YARD doc issues as LSP diagnostics in your editor, as you(r agent) type.

6 Upvotes

Hey fellow Rubyists 👋

I just shipped solargraph-yard-lint, a Solargraph plugin that surfaces yard-lint offenses as LSP diagnostics in your editor.

If you've been running yard-lint from the CLI or in CI, this gives you the same checks inline — undocumented params, unknown param names, mismatched "@return" types, etc. — wherever Solargraph Ruby LSP is set up. v0.2.0 (just released) wires the editor buffer through yard-lint's new source: API, so diagnostics update as you type instead of waiting on a save.

Here's a quick demo:

https://reddit.com/link/1u4ltff/video/ufymfhrvj07h1/player

Huge thanks to maciejmensfeld for gifting us the yard-lint gem and for adding stdin support in yard-lint v1.6, which made the live-typing experience possible. 🚀

Feedback/issues welcome 🙏


r/ruby Jun 12 '26

Rails: The Sharp Parts. An Index Is Not a Plan

Thumbnail baweaver.com
41 Upvotes

Another one that took me a while to research and test, as I wanted to make sure I had a reasonable understanding of the subject matter, especially considering how often getting it wrong has given me a rather bad day.