Simple gem for CLI UIs
If you want to build a small CLI application, that supports more advanced inputs than gets
, I recommend using the cli-ui gem. It's a small dependency-free library that provides basic building blocks, like an interactive prompt:
require "cli/ui"
CLI::UI::StdoutRouter.enable
puts CLI::UI.fmt "a small {{red:demo}}"
# supports h, j, k, l, arrows and even filtering
CLI::UI::Prompt.ask("Choose a plan:", options: ["small", "medium", "large"])
or a simple progress bar for long running scrip...
How to: Self-hosted fonts via NPM packages
We usually ship applications that self-host webfonts to comply with GDPR.
Many popular web fonts are available as NPM packages provided by Fontsource.
We recommend using those instead of downloading and bundling font files yourself. (See below for a list of benefits.)
Usage
- Go to fontsource.org and search for the font you want to add (or a font that suits your application).
- Click the font card to vie...
Better performance insights with gem `rails_performance`
Even if you don't make any beginner mistakes like N+1 queries or missing DB indices, some requests can have bad performance. Without good performance metrics, you probably won't notice this until it's too late.
We investigated multiple gems and found that rails_performance
(https://github.com/igorkasyanchuk/rails_performance) provides a lot of valuable information with very little setup cost. It only needs Redis which we use in the majority of our applications anyw...
Supporting multiple SAML IdPs within a single Rails application
The linked article shows how to configure omniauth-multi-provider
to support multiple SAML identity providers for a single Rails app:
To solve this, the omniauth-multi-provider gem acts as a dynamic wrapper around OmniAuth. It enables your application to load the correct IdP configuration at runtime—based on the tenant—allowing for flexible and secure SSO authentication across multiple organisations.
Using FactoryBot in Development
If you need dummy data to play around with in development, it's often faster to reuse your existing factories instead of using the UI or creating records in the Rails console. This approach saves time and gives you useful defaults and associations right out of the box.
You can use FactoryBot directly in the Rails console like this:
require 'factory_bot_rails' # Not needed if the factory_bot_rails gem is in the :development group
FactoryBot.create(:user)
You can also apply traits or override attributes:
FactoryBot.create...
Rails: Keeping structure.sql stable between developers
Why Rails has multiple schema formats
When you run migrations, Rails will write your current database schema into db/schema.rb
. This file allows to reset the database schema without running migrations, by running rails db:schema:load
.
The schema.rb
DSL can serialize most common schema properties like tables, columns or indexes. It cannot serialize more advanced database features, like views, procedures, triggers or custom ditionaries. In these cases you must switch to a SQL based schema format:
# in application.rb
config.a...
How to create a terminal progress indicators in Ruby
For long running scripts it is useful to show a indicator for the progress in the terminal. Alternatively you can use a gem like paul/progress_bar.
count = User.count
index = 0
User.find_each do |user|
printf("Progress: %.2f%%\r", index.to_f / count * 100)
user.update!(role: 'member')
index += 1
end
Preview video
Rails: Using PostgreSQL full-text search without a gem
PostgreSQL can cosplay as a full-text search engine. It doesn't have the features or fidelity of ElasticSearch or Algolia, but it's good enough if you just need to search and rank large volumes of text.
This card will teach you how to index, search and rank your Rails models in a PostgreSQL full-text index. We will do this without using any gems aside from ActiveRecord. While there are gems like pg_search or pg_fulltext, manual integration requires very...
ActiveStorage: How to add a new preprocessed named version
Given there is a user with an attachable avatar:
class User < ApplicationRecord
has_one_attached :avatar
end
If you want to add a preprocessed version follow these steps:
- Add the named version and deploy
class User < ApplicationRecord
has_one_attached :avatar do |attachable|
attachable.variant :preview, resize_to_fit: [177, 177 * 9 / 16], preprocessed: true
end
end
- Preprocess this version for all existing records `bundle exec rails runner 'User.find_each { |user| user.avatar.variant(:preview).proc...
Specify Gemfile for bundle
Bundler allows you to specify the name of the Gemfile you want to bundle with the BUNDLE_GEMFILE
environment variable.
BUNDLE_GEMFILE=Gemfile.rails.7.2 bundle
By default, bundler will look for a file called Gemfile
in your project, but there may be cases where you want to have multiple Gemfiles in your project, which cannot all be named Gemfile
. Let's say for example, you maintain a gem and want to run automated tests against multiple rails versions. When you need to bundle one of your secondary Gemfiles, the solution above ...
How to enable pretty IRB inspection for your Ruby class
When Ruby objects are inspected in any modern IRB, some objects (like ActiveRecord instances) are rendered with neat colors and line breaks.
You will not get that for custom classes by default -- which can be annoying if your inspection contains lots of meaningful information.
Here is what you need to do if you want your objects to be inspected nicely.
Implement a pretty_print
method
As an example, consider the following class.
class MyClass
# ...
def inspect
"#<#{self.class} attr1: #{attr1.inspect}, attr2: #{attr2...
Debugging Capistrano
Capistrano 3 has a doctor
task that will print information about
- Environment: Ruby, Rubygems and Bundler versions
- List of Capistrano gems and whether an update is available
- All config variables and their values
- Capistrano server config
$ bundle exec cap staging doctor
Enabling YJIT
YJIT is Ruby's default just-in-time compiler. It is considered production-ready since Ruby 3.2 (source).
To activate YJIT you need two steps:
- Your
ruby
binary needs to be compiled with YJIT support. - You need to enable YJIT.
Getting a Ruby with YJIT support
We usually install Ruby with tools like rbenv
or asdf
. This compiles the ruby
binary from the source code. Support for YJIT
will be automatically added during this compilation...
Running Rubocop as a pre-push hook
Git has the concept of hooks: bash scripts that are invoked at certain points in the Git lifecycle. One handy use is a pre-push hook that runs Rubocop. It will prevent pushing code that Rubocop finds fault with.
Configuring the hook
Git hooks are normally stored locally with a repository. They are not committed.
- Store this snippet in .git/hooks/pre-push:
if [ -f ./.rubocop.yml ]; then
echo 'Running Rubocop ...'
bundle exe...
RSpec: Increase readability with super_diff
When handling nested hashes the RSpec output is often hard to read. Here the gem super_diff
could help.
Add super_diff to your project
- Add
super_diff
to your Gemfile:
gem 'super_diff'
- Require it in your
spec_helper.rb
require 'super_diff/rspec' # For Rails applications you can replace this with 'super_diff/rspec-rails'
-
Customize colors in
spec/support/super_diff.rb
SuperDiff.configure do |config|
config.ac...
Logging multiple lines in Rails without making filtering your logs difficult
Rails' default logger prefixes each log entry with timestamp and tags (like request ID).
For multi-line entries, only the first line is prefixed which can give you a hard time when grepping logs.
Example
Rails.logger.info(<<~TEXT)
Response from example.com:
Status: 200
Body: It works!
TEXT
With that, the following is written to your log file.
I, [2024-10-04T08:12:16.576463 #1917250] INFO -- : [97e45eae-a220-412d-96ad-e9e148ead71d] Response from example.com:
Status: 200
Body: It works!
If you then run `grep...
Compare library versions as "Gem::Version" instances, not as strings
Sometimes we have to write code that behaves differently based on the version of a specific gem or the Ruby Version itself. The version comparison can often be seen with simple string comparison like so.
# ❌ Not recommended
if Rails.version > '6.1.7.8' || RUBY_VERSION > '3.1.4'
raise Error, 'please check if the monkey patch below is still needed'
end
If you are lucky, the version comparison above works by coincidence. But chances are that you are not: For example, Rails version 6.1.10.8
would not raise an error in the code ...
How to query GraphQL APIs with Ruby
While most Rails Apps are tied to at least one external REST API, machine-to-machine communication via GraphQL is less commonly seen. In this card, I'd like to give a quick intro on how to query a given GraphQL API - without adding any additional library to your existing app.
Core aspects of GraphQL
Interacting with GraphQL feels a bit like querying a local database. You are submitting queries to fetch data in a given structure (like SELECT in SQL) or mutations to alter the database (similar to POST/PUT/DELETE in REST). You can ...
Rails npm packages will use an uncommon versioning scheme
When Rails releases a new version of their gems, they also release a number of npm packages
like @rails/activestorage
or @rails/actioncable
.
Unfortunately Rails uses up to 4 digits for their gem version, while npm only allows 3 digits and a pre-release suffix.
To map gem versions and npm versions, Rails is going to use a naming scheme like this:
Gem version | npm version |
---|---|
7.0.0 |
7.0.0 |
7.0.1 |
7.0.100 |
... |
Bundler: Enforce consistent platform versions
Some rubygems come in platform-specific versions (i.e. "x86_64-linux") in addition to the usual "ruby" platform. This is often is done for gems with native extensions that want to deliver precompiled binaries for ease of installation. However, there is an issue on some versions of Bundler (at least 2.3.x) that makes it inconsistent which versions of a gem will be installed.
Why this happens
On (very) old versions of Bundler, the Gemfile.lock
could never indicate which version of a gem was installed, i.e. the Gemfile read `nokogiri (1....
Upgrading RubyGems to the last compatible version
Running gem update --system
will install the latest version of RubyGems. However the latest version might not be compatible with your current version of Ruby.
To install the latest version that is compatible with your Ruby:
- Go to the version history and find the latest compatible version (any Ruby constraint can be found in the right sidebar)
- Append that version to the
update
command. E.g. to install the latest version compatible with Ruby 2:gem update --system 3.4.22
...
Rails: Example on how to extract domain independent code from the `app/models` folder to the `lib/` folder
This cards describes an example with a Github Client on how to keep your Rails application more maintainable by extracting domain independent code from the app/models
folder to the lib/
folder. The approach is applicable to arbitrary scenarios and not limited to API clients.
Example
Let's say we have a Rails application that synchronizes its users with the Github API:
.
└── app
└── models
├── user
│ ├── github_client.rb
│ └── sychronizer.rb
└── user.rb
In this example the app folder ...
routing-filter is broken with Rails 7.1
If you are using the routing-filter gem in your Rails 7.1 app for managing URL segments for locales or suffixes, you will notice that the filters do no longer apply, routes are broken and the necessary parameters are no longer extracted. That is because routing-filter patches Rails' find_routes
-method to get the current path and apply its defined filters on it. These filters then modify the params that are handed over to your controller action. This way you receive a locale
parameter from a ...
Ruby: Following redirects with the http gem ("httprb")
When making requests using the http
gem you might want to automatically follow redirects to get the desired response. This is supported but not the default behavior.
You can do so by using the .follow
method.
This follows redirects for the following status codes: 300, 301, 302, 303, 307, 308
response = HTTP.get('https://www.example.com/redirect')
response.status # => 302
response.uri.to_s # => "https://www.example.com/redirect"
response = HTTP.follow.get('https://www.example.com/redirect')
response.status # => 200
response....