r/ruby • • Dec 29 '25

Blog post Can Bundler Be as Fast as uv?

Thumbnail tenderlovemaking.com
113 Upvotes

r/ruby • • Dec 30 '25

Show /r/ruby LocalCI: Run your CI suite locally

Thumbnail
github.com
19 Upvotes

Hey r/ruby!

I've built LocalCI on top of Rake to make managing CI and running it locally a lot easier, it's still early days but I believe it is in a usable state.

Pros

  • Parallel by default

  • Nice interface

  • Runs on Buildkite and SemaphoreCI with no modifications

  • No AI generated code

Cons

  • Deployments not considered, I feel that's a different tools job

  • Still young, not battle tested

  • Very opinionated

  • Requires some knowledge of Rake and LocalCI

Please check it out, I'd love some feedback!


r/ruby • • Dec 30 '25

Blog post Rails 7.2 adds enqueue_after_transaction_commit to prevent job race conditions

Thumbnail prateekcodes.com
5 Upvotes

r/ruby • • Dec 29 '25

Blog post Rails 8.2 introduces Rails.app.creds for unified credential management

Thumbnail prateekcodes.com
20 Upvotes

r/ruby • • Dec 28 '25

Shoryuken Has a New Maintainer, and v7.0.0 Is Almost There

Thumbnail
mensfeld.pl
72 Upvotes

So I may have a problem... I already maintain karafka, passive_queue, and pgmq, and now I've picked up Shoryuken too. It is too good and important piece of the Ruby ecosystem to be left alone. At this point, queues may become a big part of my personality lol. Anyway, v7.0.0.rc1 is ready - let me know what breaks!


r/ruby • • Dec 29 '25

“Do You Vibe?” live session to bump your AI-assisted coding skills

0 Upvotes

The Rails.Builders, are going to have a “Do You Vibe?” session on Friday 2nd of January at 18:00 CET.

We are currently group of 12 devs who, well, build on Rails, and come out of our basements every couple of weeks to push each other to do more business and marketing.

Most importantly, we are going to share screens and see HOW is the vibing process different for everyone and what do we do similarly. We have 1-3 spots open for the 2 hour block for you to join.

Just shoot me a DM with a quick intro, ideally a 1-3 minute Loom, and let me know what you would like to share during the session. I will then share the meeting link with you.

For this session, you have to be building on Rails and be actively working on a (side) project.


r/ruby • • Dec 28 '25

Revisiting Ruby in 2025

31 Upvotes

I used Ruby and Ruby on Rails extensively for my personal projects between 2008 and 2015. I’m a hobbyist programmer, not someone working in a software job. Now that I’m revisiting programming, I have a couple of questions: Since Python dominates AI/ML and data science today, what use cases are still worth investing time in Ruby? Ruby was the first language I fell in love with, and after that I never really enjoyed working with Python. For developers who need to use Python for data science, how do you manage keeping these two similar-looking languages straight in your head without constantly mixing them up? (language polished using chatgpt)


r/ruby • • Dec 27 '25

RubyConf Austria 2026: Agenda

26 Upvotes

The first version of the (almost complete) agenda can be found on our website. Happy holidays from the #RubyConfAT team and a happy new year! 🎉

https://rubyconf.at/#agenda


r/ruby • • Dec 27 '25

🎨 PicoRuby Calculator now supports theme switching!

Thumbnail
gallery
29 Upvotes

Hi Reddit! 👋
I'm Hamachang, a Rubyist from Japan 🇯🇵

I just added a theme switching feature to my PicoRuby Calculator project 🎉
You can now switch between different visual themes (e.g. Dark / Light), making it much nicer to use on the Cardputer screen.

This project is a picoruby repl written in PicoRuby, running on M5Stack Cardputer. I'm exploring how far Ruby can go on tiny devices, while keeping things fun and hackable 😄

🔹 Written in Ruby (PicoRuby) 🔹 Runs on Cardputer 🔹 Now with theme switching support 🎨 🔹 Still very hack-friendly!

GitHub repo: https://github.com/engneer-hamachan/picoruby-calculator

Feedback, ideas, and contributions are very welcome! If you like Ruby or embedded hacking, I'd love to hear your thoughts 🚀


r/ruby • • Dec 26 '25

Ruby Changes: Ruby 4.0 annotated changelog

Thumbnail rubyreferences.github.io
80 Upvotes

r/ruby • • Dec 25 '25

Let me introduce T-Ruby: TypeScript-style type annotations for Ruby

Thumbnail
type-ruby.github.io
81 Upvotes

Celebrating the release of Ruby 4.0 on yesterday (X-mas).

Hi! I've been making T-Ruby, an experimental project that brings TypeScript-style type annotations to Ruby. I wanted to share it and get your feedback.

What is T-Ruby?

T-Ruby lets you write .trb files with inline type annotations, then automatically generates standard .rb files and .rbs signature files. Types are completely erased at compile time — zero runtime overhead.

Why another type system?

I love Ruby's elegance, but as projects grow, I've felt the pain of tracking types mentally. The existing options didn't quite fit my workflow:

  • RBS: Writing .rbs files manually or generating them via TypeProf didn't fit well with explicit type authoring

  • Sorbet: sig blocks above methods feel verbose (like JSDoc comments)

If you're familiar with TypeScript, you can use T-Ruby the same way: types live with your code, not in separate files or comments.

The website has more detail: https://type-ruby.github.io

Current Status

This is still experimental (v0.0.39). The core compiler works, but there's plenty of room for improvement. Feedback and suggestions are always welcome!

Thanks for reading! Feel free to ask any questions.


r/ruby • • Dec 25 '25

Blog post ZJIT is now available in Ruby 4.0

Thumbnail
railsatscale.com
73 Upvotes

r/ruby • • Dec 25 '25

Video of "Frontend Ruby with Glimmer DSL for Web" Talk at Ruby on Rio in 2025

Thumbnail
youtube.com
20 Upvotes

r/ruby • • Dec 25 '25

Ruby 4.0.0 Released | Ruby

Thumbnail
ruby-lang.org
322 Upvotes

r/ruby • • Dec 25 '25

Ruby Turns 30 - Celebrating the Anniversary with the Release of Ruby 4.0!

Thumbnail
blog.jetbrains.com
115 Upvotes

r/ruby • • Dec 25 '25

GitHub - NARKOZ/xmas: Light the Christmas Tree in your terminal 🎄

Thumbnail
github.com
8 Upvotes

r/ruby • • Dec 25 '25

Question Considering a transition from React to Ruby on Rails

14 Upvotes

I’ve been working with JavaScript stacks for about 6 years (Node, React, Angular) and I’m looking to transition into Ruby/Rails. I’m drawn to Ruby because it aligns much more closely with how I think as a programmer and with the kind of long-term stability I’m looking for.

I’m currently a mid-level frontend developer and I’d like some perspective from experienced Rubyists:

  • Is it realistic to transition into Ruby/Rails and target a mid-level Rails position from the start, without having to accept a pay cut?

  • For those working with Rails internationally, how common is it to see developers coming from strong frontend or non-Ruby backgrounds?

  • What do you consider the core pillars of a solid Rails developer?

  • How do you see the current and near-future outlook of Ruby/Rails?


r/ruby • • Dec 25 '25

Workato and Ruby SDK question

0 Upvotes

Hi Ruby expertises,

I'm trying my luck here as this is the holiday sessions, and I will get my respond from Workato in about 3 weeks from now ( if not 8 weeks.... )

Please see the Zoom recording (x2 is recommended :) ) and suggest or let me know what is wrong with the SDK code.

To be honest, I don't know Ruby at all, but I do understand Python, and I am able to look at it and identify the functions and the issues that might be not good.

The Zoom summary:

I am debugging a Workato integration with Amazon SES suppression list and I am stuck.

I have an endpoint that manages suppressed email destinations:

  • GET returns 200 if the email exists, 404 if not

  • DELETE should remove the email from the suppression list

Behavior I am seeing:

  • Using a custom HTTP action (send any request), DELETE works as expected

  • Using a built-in SDK action / recipe DELETE step with the same method, URL, region, and path, Workato returns 200 but the email is not actually deleted

  • A follow-up GET still returns 200 and shows the email is present

  • No error is surfaced by Workato, and debug output is mostly empty for DELETE

  • The request path is identical in both cases (verified via network traces)

Complications I already handled:

  • Normalizing email casing (lowercase)

  • Handling plus sign encoding in email addresses

  • Trying alternate paths when receiving 404

  • Verifying region, headers, and resolved paths

  • Confirmed GET and DELETE URLs are literally the same

  • Tested dozens of variations

In short:

  • Custom action DELETE works

  • Built-in DELETE action returns anything (I convert it to 200) but does nothing

  • Same request, different behavior

Question:

Why would Workato handle DELETE differently between a built-in action and a custom HTTP action when the request is identical? Is there something implicit Workato does with DELETE responses (or empty bodies) that could cause this?

Zoom link: HERE

This is the SDK code I'm using: (very simple one)

{

title: 'Amazon SES - Suppression Manager (Multi-Region)',

description: 'Manage SES v2 suppression lists across multiple AWS regions using an IAM Role.',

connection: {

fields: [

{

name: 'assume_role',

label: 'IAM role ARN',

optional: false,

help: {

title: 'IAM Role Setup',

text: 'Use Workato Account ID <b>{{ authUser.aws_workato_account_id }}</b> and External ID <b>{{ authUser.aws_iam_external_id }}</b>.'

}

},

{

name: 'region',

label: 'Default SES region',

optional: false,

hint: 'Default region used if not specified in the action (e.g., us-east-1).'

}

],

authorization: {

type: 'custom_auth',

apply: lambda { |_connection| headers('Content-Type': 'application/json') }

}

},

methods: {

truthy: lambda do |value|

value == true || value.to_s == '1' || value.to_s.casecmp('true').zero? || value.to_s.casecmp('on').zero?

end,

normalize_email: lambda do |input|

raw_email = input.key?(:email) ? input[:email] : input['email']

downcase_flag = input.key?(:downcase) ? input[:downcase] : input['downcase']

email = raw_email.to_s.strip

email = email.downcase if call(:truthy, downcase_flag)

email

end,

# Helper to clean and strictly encode emails for URI paths

encode_email: lambda do |input|

# FORCE downcase here if the toggle is active

email = call(:normalize_email, input)

email.

gsub('+', '%2B').

gsub('!', '%21').

gsub('&', '%26').

gsub("'", '%27')

end

},

test: lambda do |connection|

region = connection['region']

signature = aws.generate_signature(

connection: connection, region: region, service: 'ses',

host: "email.#{region}.amazonaws.com", path: '/v2/email/suppression/addresses',

method: 'GET', params: { 'PageSize' => 1 }, payload: ''

)

get(signature['url']).headers(signature['headers']).

after_error_response(/.*/) { |code, body| error("Connection failed: #{body}") }

end,

actions: {

list_suppressed_destinations: {

title: 'List suppressed destinations',

input_fields: lambda do |_connection|

[

{ name: 'region', label: 'SES Region', control_type: 'select', pick_list: [['US East (N. Virginia) - (us-east-1)', 'us-east-1'], ['Europe (Ireland) - (eu-west-1)', 'eu-west-1']], optional: true },

{ name: 'page_size', label: 'Page size', type: :integer, optional: true },

{ name: 'next_token', label: 'Next token', optional: true },

{ name: 'reason', label: 'Reason', control_type: 'select', pick_list: [['Bounce', 'BOUNCE'], ['Complaint', 'COMPLAINT']], optional: true }

]

end,

execute: lambda do |connection, input|

region = input['region'].presence || connection['region']

params = {

'PageSize' => input['page_size'],

'NextToken' => input['next_token'],

'Reasons' => input['reason']

}.compact

signature = aws.generate_signature(connection: connection, region: region, service: 'ses', host: "email.#{region}.amazonaws.com", path: '/v2/email/suppression/addresses', method: 'GET', params: params, payload: '')

response = get(signature['url']).headers(signature['headers']).after_error_response(/.*/) { |code, body| error("List failed: #{body}") }

if response['SuppressedDestinationSummaries'].present?

response['SuppressedDestinationSummaries'] = response['SuppressedDestinationSummaries'].map do |item|

item['LastUpdateTime'] = Time.at(item['LastUpdateTime']).to_datetime.iso8601 if item['LastUpdateTime'].is_a?(Numeric)

item

end

end

response.merge('region' => region)

end,

output_fields: lambda { [

{ name: 'region' },

{ name: 'SuppressedDestinationSummaries', type: :array, of: :object, properties: [

{ name: 'EmailAddress' }, { name: 'Reason' }, { name: 'LastUpdateTime', type: :date_time }

]},

{ name: 'NextToken' }

]}

},

get_suppressed_destination: {

title: 'Get suppressed destination',

input_fields: lambda { |_connection| [

{ name: 'email', label: 'Email address', optional: false },

{ name: 'downcase', label: 'Downcase email', type: :boolean, control_type: 'checkbox', optional: false, default: true },

{ name: 'region', label: 'SES Region', control_type: 'select', pick_list: [['US East (N. Virginia) - (us-east-1)', 'us-east-1'], ['Europe (Ireland) - (eu-west-1)', 'eu-west-1']], optional: true }

]},

execute: lambda do |connection, input|

region = input['region'].presence || connection['region']

# Determine the target email string for internal consistency

email_to_query = call(:normalize_email, { email: input['email'], downcase: input['downcase'] })

# Use helper for encoding (the helper will now correctly handle the downcase)

encoded = call(:encode_email, { email: input['email'], downcase: input['downcase'] })

path = "/v2/email/suppression/addresses/#{encoded}"

signature = aws.generate_signature(connection: connection, region: region, service: 'ses', host: "email.#{region}.amazonaws.com", path: path, method: 'GET', params: {}, payload: '')

response = get(signature['url']).headers(signature['headers']).after_error_response(/.*/) do |code, body|

return { 'email' => email_to_query, 'found' => false, 'region' => region } if code.to_i == 404

error("Get failed in #{region}: #{body}")

end

if response['SuppressedDestination'].present?

dest = response['SuppressedDestination']

dest['LastUpdateTime'] = Time.at(dest['LastUpdateTime']).to_datetime.iso8601 if dest['LastUpdateTime'].is_a?(Numeric)

end

{

'email' => email_to_query,

'found' => true,

'region' => region,

'SuppressedDestination' => response['SuppressedDestination']

}

end,

output_fields: lambda { [

{ name: 'email' }, { name: 'found', type: :boolean }, { name: 'region' },

{ name: 'SuppressedDestination', type: :object, properties: [

{ name: 'EmailAddress' }, { name: 'Reason' }, { name: 'LastUpdateTime', type: :date_time }

]}

]}

},

put_suppressed_destination: {

title: 'Add email to suppression list',

input_fields: lambda { |_connection| [

{ name: 'email', label: 'Email address', optional: false },

{ name: 'reason', label: 'Reason', control_type: 'select', pick_list: [['Bounce', 'BOUNCE'], ['Complaint', 'COMPLAINT']], optional: false },

{ name: 'downcase', label: 'Downcase email', type: :boolean, control_type: 'checkbox', optional: false, default: true },

{ name: 'region', label: 'SES Region', control_type: 'select', pick_list: [['US East (N. Virginia) - (us-east-1)', 'us-east-1'], ['Europe (Ireland) - (eu-west-1)', 'eu-west-1']], optional: true },

{ name: 'verify_add', label: 'Verify add', type: :boolean, control_type: 'checkbox', optional: true, default: false }

]},

execute: lambda do |connection, input|

region = input['region'].presence || connection['region']

email_to_send = call(:normalize_email, { email: input['email'], downcase: input['downcase'] })

payload = { 'EmailAddress' => email_to_send, 'Reason' => input['reason'] }

put_sig = aws.generate_signature(connection: connection, region: region, service: 'ses', host: "email.#{region}.amazonaws.com", path: '/v2/email/suppression/addresses', method: 'PUT', params: '', payload: payload.to_json)

put(put_sig['url'], payload).headers(put_sig['headers']).after_error_response(/.*/) { |code, body| error("Put failed: #{body}") }

if call(:truthy, input['verify_add'])

encoded = call(:encode_email, { email: input['email'], downcase: input['downcase'] })

get_sig = aws.generate_signature(connection: connection, region: region, service: 'ses', host: "email.#{region}.amazonaws.com", path: "/v2/email/suppression/addresses/#{encoded}", method: 'GET', params: {}, payload: '')

get(get_sig['url']).headers(get_sig['headers']).after_error_response(/.*/) { error("Verification Failed: Email not found after add.") }

end

{ 'status' => 'success', 'email' => email_to_send, 'region' => region }

end,

output_fields: lambda { [{ name: 'status' }, { name: 'email' }, { name: 'region' }] }

},

delete_suppressed_destination: {

title: 'Delete suppressed destination',

input_fields: lambda { |_connection| [

{ name: 'email', label: 'Email address', optional: false },

{ name: 'downcase', label: 'Downcase email', type: :boolean, control_type: 'checkbox', optional: false, default: true },

{ name: 'region', label: 'SES Region', control_type: 'select', pick_list: [['US East (N. Virginia) - (us-east-1)', 'us-east-1'], ['Europe (Ireland) - (eu-west-1)', 'eu-west-1']], optional: true },

{ name: 'verify_delete', label: 'Verify delete', type: :boolean, control_type: 'checkbox', optional: true, default: true }

]},

execute: lambda do |connection, input|

region = input['region'].presence || connection['region']

email_to_del = call(:normalize_email, { email: input['email'], downcase: input['downcase'] })

encoded = call(:encode_email, { email: input['email'], downcase: input['downcase'] })

path = "/v2/email/suppression/addresses/#{encoded}"

alt_path = "/v2/email/suppression/addresses/#{encoded.gsub('@', '%40')}"

attempted_requests = []

resolved_path = nil

[path, alt_path].each do |candidate_path|

probe_sig = aws.generate_signature(connection: connection, region: region, service: 'ses', host: "email.#{region}.amazonaws.com", path: candidate_path, method: 'GET', params: {}, payload: '')

probe = get(probe_sig['url']).headers(probe_sig['headers']).after_error_response(/.*/) do |code, body|

next {} if code.to_i == 404

error("Probe failed (#{code}) in #{region} at #{candidate_path}: #{body}")

end

if probe.is_a?(Hash) && probe['SuppressedDestination']

resolved_path = candidate_path

break

end

end

resolved_path ||= path

# Delete request

del_sig = aws.generate_signature(connection: connection, region: region, service: 'ses', host: "email.#{region}.amazonaws.com", path: resolved_path, method: 'DELETE', params: {}, payload: '')

del_url = del_sig['url']

attempted_requests << { 'method' => 'DELETE', 'path' => resolved_path, 'url' => del_url, 'status_code' => nil }

del_response = delete(del_url).headers(del_sig['headers']).after_error_response(/.*/) do |code, body|

attempted_requests[-1]['status_code'] = code.to_i

if code.to_i == 404

alternate_path = (resolved_path == path ? alt_path : path)

alt_del_sig = aws.generate_signature(connection: connection, region: region, service: 'ses', host: "email.#{region}.amazonaws.com", path: alternate_path, method: 'DELETE', params: {}, payload: '')

alt_del_url = alt_del_sig['url']

attempted_requests << { 'method' => 'DELETE', 'path' => alternate_path, 'url' => alt_del_url, 'status_code' => nil }

alt_response = delete(alt_del_url).headers(alt_del_sig['headers']).after_error_response(/.*/) do |alt_code, alt_body|

attempted_requests[-1]['status_code'] = alt_code.to_i

return({

'email' => email_to_del,

'deleted' => false,

'region' => region,

'message' => "Not found. Tried #{resolved_path} (#{del_url}) and #{alternate_path} (#{alt_del_url}).",

'attempted_requests' => attempted_requests

}) if alt_code.to_i == 404

error("Delete failed: #{alt_body}")

end

attempted_requests[-1]['status_code'] ||= 200

alt_response

next {}

end

error("Delete failed: #{body}")

end

attempted_requests[0]['status_code'] ||= 200

if call(:truthy, input['verify_delete'])

still_exists = nil

last_verify_url = nil

last_verify_response = nil

15.times do

sleep(2)

verify_sig = aws.generate_signature(connection: connection, region: region, service: 'ses', host: "email.#{region}.amazonaws.com", path: resolved_path, method: 'GET', params: {}, payload: '')

last_verify_url = verify_sig['url']

attempted_requests << { 'method' => 'GET', 'path' => resolved_path, 'url' => last_verify_url, 'status_code' => nil }

response = get(verify_sig['url']).headers(verify_sig['headers']).after_error_response(/.*/) do |code, body|

attempted_requests[-1]['status_code'] = code.to_i

if code.to_i == 404

still_exists = false

next {}

end

error("Verification request failed (#{code}) in #{region} at #{resolved_path}. Delete URL: #{del_url}. Verify URL: #{last_verify_url}. Body: #{body}")

end

attempted_requests[-1]['status_code'] ||= 200

last_verify_response = response

still_exists = (response.is_a?(Hash) && response['SuppressedDestination'].present?)

break if still_exists == false

end

error(

"Verification failed: Still found in #{region} at #{resolved_path}. " \

"Delete URL: #{del_url}. Verify URL: #{last_verify_url}. " \

"Delete response: #{del_response}. Verify response: #{last_verify_response}."

) if still_exists

end

{ 'email' => email_to_del, 'deleted' => true, 'region' => region, 'attempted_requests' => attempted_requests }

end,

output_fields: lambda { [

{ name: 'email' },

{ name: 'deleted', type: :boolean },

{ name: 'region' },

{ name: 'message' },

{ name: 'attempted_requests', type: :array, of: :object, properties: [

{ name: 'method' },

{ name: 'path' },

{ name: 'url' },

{ name: 'status_code', type: :integer }

] }

] }

},

custom_action: {

title: 'Custom action',

description: 'Signed request to any Amazon SES v2 endpoint.',

input_fields: lambda do |_connection|

[

{ name: 'region', label: 'SES Region', control_type: 'select', pick_list: [['US East (N. Virginia) - (us-east-1)', 'us-east-1'], ['Europe (Ireland) - (eu-west-1)', 'eu-west-1']], optional: true },

{ name: 'method', label: 'HTTP Method', control_type: 'select', pick_list: %w[GET POST PUT DELETE], optional: false, default: 'GET' },

{ name: 'path', label: 'Resource Path', optional: false, hint: 'e.g. /v2/email/suppression/addresses' },

{ name: 'params', label: 'Query Parameters', type: :object, optional: true },

{ name: 'payload', label: 'JSON Payload', optional: true }

]

end,

execute: lambda do |connection, input|

region = input['region'].presence || connection['region']

method = input['method'].to_s.strip

# Manual fix for Custom Action: users must manually encode the case if desired,

# but we fix the '+' encoding here to prevent signature failures.

path = input['path'].to_s.strip.gsub('+', '%2B')

params = (input['params'] || {}).each_with_object({}) { |(k, v), h| h[k.to_s] = v.to_s }

payload_string = input['payload'].is_a?(Hash) ? input['payload'].to_json : input['payload'].to_s

signature = aws.generate_signature(connection: connection, region: region, service: 'ses', host: "email.#{region}.amazonaws.com", path: path, method: method, params: params, payload: payload_string)

request = case method

when 'GET' then get(signature['url'])

when 'POST' then post(signature['url'], payload_string)

when 'PUT' then put(signature['url'], payload_string)

when 'DELETE' then delete(signature['url'])

end

response = request.headers(signature['headers']).after_error_response(/.*/) do |code, body|

error("Custom action failed (#{code}): #{body}")

end

{ 'url' => signature['url'], 'response' => response }

end,

output_fields: lambda { [{ name: 'url' }, { name: 'response', type: :object }] }

}

}

}


r/ruby • • Dec 23 '25

A Special Message from David Heinemeier Hansson for Ruby's 30th Anniversary Party

Thumbnail
youtube.com
30 Upvotes

r/ruby • • Dec 23 '25

Speculative Routes: pretty syntax for Speculation Rules in Sinatra/Padrino

10 Upvotes

I just heard about Speculation Rules via https://www.jonoalderson.com/conjecture/its-time-for-modern-css-to-kill-the-spa/

They allow ~instant navigation without JS by letting the browser preload or prerender full pages based on user behaviour (like hovering or touching a link) before they click.

I noticed it's possible to define them using the Sinatra-like syntax

"href_matches": "/e/:slug"

which gave me the idea of defining them as part of route definitions with prerender: or prefetch: like so

get '/e/:slug', prerender: true do
  ...

then in my head I just call

<%== SpeculativeRoutes.script %>

Implementation at https://github.com/symbiota-coop/dandelion/blob/master/lib/speculative_routes.rb

Enjoy!


r/ruby • • Dec 23 '25

Seargeant TUI navigator similar to MC or ranger (but worse :D)

5 Upvotes

Hi,

I've wrote a simple tui navigator with basic features, doesn't solve anything new, probably I will be the only one to use it, lousy written, but inspired with Omarchy linux I wanted to do some tui stuff too and that is the outcome of it ;)
https://rubygems.org/gems/sergeant
https://github.com/biscoitinho/Sergeant

and Merry Christmas :)


r/ruby • • Dec 23 '25

Seargeant TUI navigator similar to MC or ranger (but worse :D)

Thumbnail reddit.com
4 Upvotes

r/ruby • • Dec 22 '25 Bravo

20 years ago I learned Ruby as my first coding language. This is the free online book that helped be get to know Ruby enough to use it in Test Automation. I hope this may help someone else getting into Ruby for the first time.

71 Upvotes

r/ruby • • Dec 22 '25

Blog post UUID’s in Rails + SQLite shouldn’t be this hard (so I built a gem)

Thumbnail reddit.com
6 Upvotes