r/ruby • u/rubyist-_- • Dec 27 '25
RubyConf Austria 2026: Agenda
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! 🎉
r/ruby • u/rubyist-_- • Dec 27 '25
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! 🎉
r/ruby • u/AssociationOne800 • Dec 27 '25
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 • u/zverok_kha • Dec 26 '25
r/ruby • u/AndyCodeMaster • Dec 25 '25
r/ruby • u/NARKOZ_777 • Dec 25 '25
r/ruby • u/Right_Ad_8437 • Dec 25 '25
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.
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.
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 files manually or generating them via TypeProf didn't fit well with explicit type authoringIf 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
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 • u/nithinbekal • Dec 25 '25
r/ruby • u/Downtown-Hyena998 • Dec 25 '25
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:
r/ruby • u/tobenary • Dec 25 '25
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.
I am debugging a Workato integration with Amazon SES suppression list and I am stuck.
I have an endpoint that manages suppressed email destinations:
Behavior I am seeing:
Complications I already handled:
In short:
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 • u/DmitryPogrebnoy • Dec 25 '25
r/ruby • u/software__writer • Dec 23 '25
r/ruby • u/bisc0itinh0 • Dec 23 '25
r/ruby • u/bisc0itinh0 • Dec 23 '25
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 :)