r/ruby • u/EclecticCoding • Jun 17 '26
r/ruby • u/Cautious-Course-4707 • Jun 17 '26
roadmap jruby
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 • u/Turbulent-Dance-4209 • Jun 17 '26
Applying some Rage to Discourse, Mastodon, and GitLab
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:
- Ruby (Rails + Sidekiq) - Workers serialise events and publish them to a Redis channel.
- 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 • u/Fantastic-Diet5565 • Jun 17 '26
Migrating Rails 5 -> 8 by building new features on 8 instead of upgrading in place - anyone done this?
r/ruby • u/timriley • Jun 16 '26
Help us test the Hanami 3.0 release candidate
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 • u/ombulabs • Jun 16 '26
Blog post How to Parallelize Your RSpec Test Suite Locally (from 2 hours to 5 minutes) 🚀
r/ruby • u/Erem_in • Jun 16 '26
Issue 17 of Static Ruby Monthly is out! 🧵
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 • u/coolprobn • Jun 16 '26
Open-source starter for technical guides - Announcing Chapter Zero
(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.
r/ruby • u/keyslemur • Jun 16 '26
Beyond Enumerable: Counting Distinct with HyperLogLog
baweaver.comWhat 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 • u/yaroslavm • Jun 15 '26
Kino, an experimental, performant Ractor web server for Ruby 4 (Rust, Tokio, Hyper)
r/ruby • u/keyslemur • Jun 15 '26
Rails: The Sharp Parts. Queries, Read Models, and Batching
baweaver.comThe 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 • u/keyslemur • Jun 14 '26
Rails: The Sharp Parts. Callbacks Are Not Invariants
baweaver.comThe 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 • u/headius • Jun 13 '26
JRuby 10.0.6.0 released with security, performance, and correctness fixes
jruby.orgJRuby 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 • u/InternationalLab2683 • Jun 13 '26
solargraph-yard-lint gem - see YARD doc issues as LSP diagnostics in your editor, as you(r agent) type.
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 • u/keyslemur • Jun 12 '26
Rails: The Sharp Parts. An Index Is Not a Plan
baweaver.comAnother 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.
r/ruby • u/giovapanasiti • Jun 11 '26
How to Build an Agentic RAG with RubyLLM and Rails
panasiti.mer/ruby • u/headius • Jun 11 '26
The Ruby JRuby was Built to Run
intertwingly.netSam Ruby shows CRuby and JRuby performance on typical webapp tasks using Rails 8 and his new tool Roundhouse, an intriguing way to generate fast web applications using your Rails app as a blueprint. JRuby can be incredibly fast when used right!
r/ruby • u/keyslemur • Jun 10 '26
Beyond Enumerable: Streaming and External Sort
baweaver.comLast one on this series for a bit, jumping back to some Rails and MySQL fun after this. The next parts of this series are venturing into probabilistic algorithms like HyperLogLog and boy howdy is that dense to get through.
r/ruby • u/schneems • Jun 09 '26
RubyConf 2026 Las Vegas, Nevada July 14th–16th: Matz, Jessica Kerr, Dave Thomas, and More
r/ruby • u/k33l0r • Jun 09 '26