Hey folks — Sajjad Umar here with the April edition of Ruby on Rails Monthly!
Spring (Summer 😉) is in the air, and just like the season, the Rails ecosystem is blooming with fresh releases, helpful tools, and community highlights. Whether you’re working on something new or just catching up, this edition has plenty of inspiration to keep your momentum going. Let’s get right into it!
Introducing rubyonrails.pk
Ruby on Rails Pakistan is dedicated to bringing together Ruby on Rails enthusiasts and professionals nationwide. With rubyonrails.pk my mission is to foster a vibrant community where knowledge flows freely, ideas are exchanged, and everyone, from beginners to experts, feels empowered to grow. With resources and tutorials in native languages created by local experts, I aim to make learning Rails simple, accessible, and culturally relevant. Whether you’re starting your coding journey or sharpening your skills, RubyOnRails.pk is your home for all things Rails in Pakistan.
Ongoing Initiatives:
Job Board — Specifically Designed for Rails Talent in Pakistan
Jobs.RubyOnRails.pk — a free job board to connect talented Ruby on Rails developers in Pakistan with companies around the world.
There’s incredible Rails talent in Pakistan, but 90% of the time, they struggle to find quality remote opportunities. This platform is my small effort to bridge that gap.
If you know any companies hiring Rails devs, it would mean a lot if you could introduce them to the site or share it in your circle. It’s completely free to post right now, and your support can really help get this off the ground.
Link:
https://jobs.rubyonrails.pk
A Medium Publication
Blog.RubyOnRails.pk — a Medium publication dedicated to sharing great articles, stories, and tutorials for the Ruby on Rails community in Pakistan.
The goal is to make learning Rails simple, accessible, and locally relevant, and I’d love to feature voices from the community (and beyond!).
If you or anyone you know writes about Rails, tech, or has a story worth sharing, the blog is open for contributions. Just reach out to me at sajjadumardev@gmail.com and let’s get it published! 🚀
Would really appreciate it if you could spread the word in your circle too 🙌
Link:
https://blog.rubyonrails.pk
Upcoming Initiatives:
In no particular order, here are a few things I’m planning for rubyonrails.pk
Built by Pakistan — A dashboard showcasing apps crafted by talented Ruby on Rails developers from Pakistan. Highlighting amazing local talent and sharing their work with the global community!
If you’ve built a product with Rails and believe it has the potential to be a great SaaS or standout product — I’d love to hear about it! 🚀
Reach out at sajjadumardev@gmail.com.
Localized Ruby & Rails Video Content in Urdu & Simple English
I’m working on creating (and collaborating with creators in Pakistan) to produce localized video content for Ruby and Rails — covering everything from software engineering basics to the latest in Ruby, Rails, and advanced topics.
The goal? Make learning Rails simple, accessible, and fun in our native language, so more people can join this amazing framework!
If you’re someone interested in collaborating on video content — you know where to find me! sajjadumardev@gmail.com
Rails World CFPs closed!
Rails World CFP has been closed — excited for all the talks going to happen in Rails World 2025.
RubyUI 1.0: Reusable Copy-and-Paste Components for Rails Apps
A UI component library, crafted precisely for Ruby devs who want to stay organised and build modern apps, fast.
Check the docs here.
Introduce capture_error_reports
Motivation is to use assert_error_reported
to test that an error is raised a certain number of times. For example, when testing the retry logic for an external dependency.
This PR introduced capture_error_reports, which captures all the reports from the passed block that match the expected exception. Then you can do assertions on those reports.
Read all the details here.
Rails Guides: PostgreSQL timestamp with time zone
This addition to the Active Record PostgreSQL Rails Guide documents how to use a configuration option to change the default timestamp data type from timestamp without time zone to timestamp with time zone (timezone-aware timestamps).
Support for this data type was added to Active Record earlier but is not used by default, requiring a configuration change to use.
Read all the details here.
Use a self-join for UPDATE with outer joins on PostgreSQL and SQLite
In PostgreSQL and SQLite UPDATE when an OUTER JOIN references the updated table in the ON clause, the join condition cannot be moved to the WHERE clause without breaking the query, so the current implementation is forced to use a subquery. We can support this with a more aggressive manipulation: we duplicate the updated table into the FROM clause with a self-join on the primary key. So, transform
UPDATE "products" SET ...
LEFT JOIN "categories" ON "products"."category_id" = "categories"."id"
... other joins ...
WHERE ...
to this:
UPDATE "products" AS "alias" SET ...
FROM "products"
LEFT JOIN "categories" ON "products"."category_id" = "categories"."id"
... other joins ...
WHERE "alias"."id" = "products"."id" AND ...
The table referenced by “products”.”?” changes with this transformation. Top-level FULL|RIGHT OUTER JOIN are converted to LEFT|INNER JOIN. This should be fine given the update context.
On PostgreSQL, this appends an index scan step to the query plan, just like a subquery.
Unfortunately, it introduces a possible ambiguity: when the SET or WHERE references an unqualified column of the target table, as there are now two candidate tables. This shouldn’t happen with ActiveRecord-generated nodes, but it will affect users with handcrafted nodes.
Check the PR for more details on the fix!
Added RuboCop cache to GHA workflow templates
The update adds RuboCop cache restoration to GitHub Actions workflows for apps and plugins to significantly speed up RuboCop checks. By reusing cache between runs, large codebases can see massive performance improvements (e.g., 8 minutes down to 40 seconds).
Key points:
Cache is invalidated if the runner OS, Ruby version, or any
.rubocop.yml
, orGemfile.lock
changes.On the default branch, a new cache is created after each run to stay up-to-date.
On feature branches, cache is restored from the base branch and saved as a new entry after the run.
Only one cache per branch is kept to balance storage use and performance.
Unused cache entries are automatically deleted after 7 days.
Check out the PR here.
Optimisation: Stop escaping JSON when calling render json: every time
When render json:
is used in a controller, and there's no callback option (used for JSONP), the resulting JSON document doesn't need to be HTML-safe (no need to escape HTML entities) or embeddable into JavaScript (no need to escape U+2028 and U+2029).
This both saves a costly operation (the gsub optimized in #54484) and renders cleaner JSON.
This PR adds this optimisation, read more here.
Load lazy route set before inserting test routes
This Pull Request changes routing assertions to load lazy routes early.
In some instances, inserting test routes break route loading hooks. Without loading lazy route sets early, we miss after_routes_loaded callbacks, or risk invoking them with the test routes instead of the real ones if another load is triggered by an engine.
Allowed allocated Active Records to lookup associations
This Pull Request has been created because the allocated Active Record object can’t lookup associations. Which is incorrect.
This Pull Request changes the association cache to be lazily initialized so that lookups can happen.
Read all the details here.
Refactored ActiveRecord Signed ID to use global Rails.application.message_verifiers
This change ensures a unified configuration for all message verifiers, making it easier to rotate secrets and upgrade signing algorithms.
These changes will enable proper usage of rotate and on_rotation as shown in the example below:
# Rotate verifier for Signed ID
Rails.application.message_verifiers.rotate do |salt|
next nil if salt != 'active_record/signed_id'
secret_generator = ->(_) { 'old_secret' }
{ secret_generator: secret_generator, digest: "SHA256", serializer: JSON, url_safe: true }
end
# Enable `transitional` mode for rolling deployment
Rails.application.message_verifiers.transitional = true
# `on_rotation` callback to log the usage of old verifiers
Rails.application.message_verifiers.on_rotation do
puts "Old verifier used for verifying! Still in use!"
end
Read all the details here.
Fixed Enumerable#sole for infinite collections
This PR optimizes the Enumerable#sole method to handle infinite collections more efficiently.
Read all the details here.
Added support for filtering notes by tag in /rails/info/notes
Added support for filtering notes by tag in /rails/info/notes.
Use the drop-down box to filter the notes by “FIXME,” “TODO,” and “OPTIMIZE” annotations.
Check out the PR here.
Fixed create_or_find_by not rolling back a transaction
The create_or_find_by method doesn’t allow rolling back a transaction. This is a different behaviour from create
. The reason is that we are in the double transaction edge case, and the outer transaction doesn't see the rollback.
class User < ApplicationRecord
after_save do
raise ActiveRecord::Rollback
end
end
User.create(name: "John") #=> Correctly rolled back
User.find_or_create_by(name: "John") #=> Correctly rolled back on Rails 7.0, stopped rolling back since commit 023a3eb3c046091a5d52027393a6d29d0576da01
User.create_or_find_by(name: "John") #=> Does not roll back
Encryption: allowed turning on support_unencrypted_data at a per-attribute level
#49072 allowed you to turn support_unencrypted_data
on a global level, then turn it off for specific attributes. But it didn't allow the inverse: you couldn't turn the config off globally, and then turn it on for a specific attribute.
This PR adds support for that, which should make the setting’s behaviour less surprising.
Read all the details here.
Added must-understand directive
This Pull Request adds the must_understand helper, and adds must-understand to SPECIAL_KEYS.
Read all the details here.
Prepend structure_load_flags instead of appending them with the PostgreSQL adapter
This Pull Request changes how structure_load uses the extra_flags argument, prepending it instead of appending it (for PostgreSQL only).
Read all the details here.
ActiveRecord: Introduced with_default_isolation_level
When migrating a larger application to a new database isolation level, you might want to start enforcing the default isolation level per area (or per a base controller) in your app.
Having something like #with_default_isolation_level that takes a new level and a block could drastically help with such migrations.
class ApiV2Controller < ApplicationController
around_action :set_isolation_level
def set_isolation_level
Product.with_default_isolation_level(:read_committed) do
yield
end
end
end
# forces all controllers that subclass from ApiV2Controller to start getting new isolation level
This PR opted to store the default on the connection object rather than a thread/fiber variable, to make this work nicely with multi-database apps.
Read all the details here.
Included cookie name in length calculation
Browsers add the length of the name and content when validating that a cookie is under 4kb, so this commit updates Rails to do the same.
Check the PR here.
Used UNLINK for RedisCacheStore in ActiveSupport
Currently, ActiveSupport::Cache::RedisCacheStore uses the DEL command for deleting cache entries.
This Pull Request changes the del command to unlink command in RedisCacheStore class.
Read all the details here.
Added Cache#read_counter and Cache#write_counter
This PR introduces 2 new methods on Rails.cache: read_counter and write_counter.
The idea is that you should use them to read values that are being incremented/decremented. This is to avoid the code smell of having to pass raw: true around on read/write calls for counters (which is only necessary with some cache implementations).
Check the PR here.
Changed redirect status code in SessionsController#destroy template from 302 to 303
Fixes #54848. This Pull Request changes the status code of the redirect in the SessionsController#destroy
action template used by the generator from 302 to 303.
Check the PR here.
Adex — A Community Driven Ad-Exchange Platform
Adex.world — Grow your SaaS for free by displaying an ad banner in your app. In return, your ad will be shown across other apps in the Adex network. It’s a simple, community-driven way to get more visibility without spending a dime!
Ruby on Rails for Agile Web Development by Sajjad Umar (Me :D)
Discover the power of Ruby on Rails web development framework through the pages of “Ruby on Rails for Agile Web Development”. This book combines the robustness of Rails with the agility of development methodologies like Scrum and Kanban to help you efficiently build high-performing web applications.
And that is it for this month — I will be back with more updates next month ✌🏻