Unpoly 3.11.0 released

Unpoly 3.11.0 is a big release, shipping many features and quality-of-life improvements requested by the community. Highlights include a complete overhaul of history handling, form state switching and the preservation of [up-keep] elements. We also reworked major parts of the documentation and stabilized 89 experimental features.

We had to make some breaking changes...

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...

Adding comments to ambiguous database columns

The DB schema is the most important source of truth for your application and should be very self-explanatory. If determining the true meaning of a DB column requires historical research in your issue tracker or reverse engineering of your source code you might consider adding a comment.

Both PostgreSQL and MySQL support comments in the DB schema:

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

  1. Go to fontsource.org and search for the font you want to add (or a font that suits your application).
  2. 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...

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...

Web performance snippets: little scripts that return performance metrics

Use these snippets when you want to measure yourself.

Currently available:

Core Web Vitals

Largest Contentful Paint (LCP)
Largest Contentful Paint Sub-Parts (LCP)
Quick BPP (image entropy) check
Cumulative Layout Shift (CLS)

Loading

Time To First Byte
Scripts Loading
Resources hints
Find Above The Fold Lazy Loaded Images
Find non Lazy Loaded Images outside of the viewport
Find render-blocking resources
Image Info
Fonts Preloaded, Loaded, and Used Above The Fold
First And Third Party Script Info
First And Third Party Script Timings
I...

Extracting parts of forms into Unpoly modals/popups

Say you wrap your index view in a form to apply different filters like pagination or a search query. On submit, your index view changes when the filters are applied (through up-submit and up-target). Now you want to enable more data-specific filters using a separate "Filters" button that opens a popup to not overload your UI.

Filter bar:

Filter popup:

The problem is tha...

Fuzzy scoping in Rails with PostgreSQL

When you want to filter records in a model where a string column roughly matches a given term, you can use PostgreSQL’s trigram similarity search.

Writing a fuzzy query in Rails

User.where("similarity(name, ?) > 0.3", "John")

This finds all users where the name is similar to "John" with a similarity score above 0.3.

You can tune the threshold:

  • Closer to 1.0 = stricter match
  • Closer to 0.0 = looser match

Ordering by best match

User
  .where("similarity(name, ?) > 0.3", "John")
  .order(Arel.sql("similarity(n...

Sidekiq: How to check the maximum client Redis database size

You can check the maximum client Redis database size in Sidekiq with this command.

Sidekiq.redis { |redis| puts redis.info.fetch('maxmemory_human') }
#=> 512.00M

If you just want the maximum database size for a known Redis database URL you can use the Redis Ruby client or the Redis CLI:

Redis database size via Ruby client

irb(main):002> Redis.new(url: 'redis://localhost:16380/1').info.fetch('maxmemory_human')
=> "512.00M"

Redis database size via CLI

$ redis-c...

Rails: Configuring the default sorting behaviour

In Rails, the implicit_order_column (added in Rails 6) is a configuration option that helps you define the default sorting behavior of ActiveRecord queries when no explicit ORDER BY clause is provided. This option allows you to specify a column that Rails will use to automatically sort records in a particular order when no specific ordering is given.

Since the id is typically the primary key and automatically indexed, Rails will default t...

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...

A "text-wrap: balance" fallback approach

Here is a workaround for when you want to use text-wrap: balance but must also render nicely for browsers that don't support it (mostly Safari <17.5).

Step 1: Put some <br>s into your HTML

First, place explicit line breaks that should work for most cases.
This depends on your use case and is affected by e.g. container widths or user content.

<div class="my-element">
  <p>
    Average score
    <br>
 ...

Implementing upload progress and remove button with ActiveStorage DirectUpload

DirectUpload allows you to upload files to your file storage without having to wait for the form to submit. It creates AJAX requests to persist the file within your form and wraps them in a little API. This card will show you how to use it in order to create in-place file uploads with progress and a remove button.

This is basic functionality, you may add additional elements, styles and logic to make this look fancy, but the core functionality is the same. I created a file upload that looks like this:

![Image](/makandra/625023/attachments/3...

Implementing authentication and authorization for ActiveStorage blobs/files

ActiveStorage does not provide any built-in way of implementing authentication for the available DirectUpload endpoint in Rails. When using DirectUpload as JS wrapper in the frontend, be aware that its Rails endpoint is public by default, effectively allowing anyone to upload an unlimited amount of files to your storage.

The DirectUploadController from @rails/activestorage bypasses your form controller because it uploads the file using an AJAX request that runs directly, before any form roundtrip happens. This is a comfortable solutio...

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:

  1. 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
  1. Preprocess this version for all existing records `bundle exec rails runner 'User.find_each { |user| user.avatar.variant(:preview).proc...

ActiveStorage: How to copy / clone an attachment from one record to another

Given there is a user with an attachable avatar:

class User < ApplicationRecord
  has_one_attached :avatar
end

You can copy the avatar from one user to another user with the code below:

user_1 = User.first
user_2 = User.create!(
  avatar: {
    io: StringIO.new(user_1.avatar.download),
    filename: user_1.avatar.blob.filename.to_s,
    content_type: user_1.avatar.blob.content_type.to_s,
  }
)

Note: For large attachments you might need to use a different approach to avoid memory issues.

Using Low-Level Prompts for High-Accuracy AI Coding

The key to unlocking the full potential of LLMs in coding lies in crafting precise prompts. The main challenge is learning how to structure prompts effectively to guide the model toward accurate results. Further evidence supporting this is the fact that Aider already writes ~70% of its own code (as of 02/2025). However, when starting out, your results may fall short of efficiently generating large portions of your code with the...

Shell script to magically configure display setup

Here is a bash script that I use to auto-configure displays on Ubuntu 24.04 with Xorg.

Background

  • Ubuntu always sets the primary display to the 1st (i.e. internal) display whenever I connect to a new Dock/Hub.
    • I want my primary display to be the large display.
    • My notebook is always placed left of external displays, so the 2nd display will be the center (or only) external display and should be primary.
  • I also want all my displays to be placed horizontally, but bottom-aligned (the default would be aligned at their top edges)....

Debug your Postgres SQL query plan

When debugging slow SQL queries, it’s helpful to understand the database engine's query plan. Whenever you execute a declarative SQL query, the database generates a "query plan" that outlines the exact steps the engine will take to execute the query. Most of the time, we don’t need to worry about this plan because SQL engines are highly optimized and can generate efficient execution strategies automatically. However, if a query is slow, inspecting the generated plan can help identify bottlenecks and optimization opportunities.

If you're usi...

Rails: Accessing strong parameters

Rails wraps your parameters into an interface called StrongParameters. In most cases, your form submits your data in a nested structure which goes hand in hand with the strong parameters interface.

Example:

curl -X POST -d "user[name]=bob" https://example.com/users
class UsersController
  def create
    User.create!(params.expect(user: [:name])) # Or User.create!(params.require(:user).permit(:name))
  end
end

This works well most of the time...

Rails 8 introduces `params.expect`

The new params.expect method in Rails 8 improves parameter filtering, addressing issues with malformed input and enhancing security. It provides a cleaner, more explicit way to enforce the structure and types of incoming parameters.

What changed

  • Replaces require and permit: Combines both methods for concise parameter validation.
  • Explicit Array Handling: Requires double array syntax to define arrays of hashes, improving clarity.
  • Enhanced Validation: Ensures expected parameter structure, rejecting malformed input wi...

Rails console tricks

Also see the list of IRB commands.

Switching the context

Changes the "default receiver" of expressions. Can be used to simulate a "debugger situation" where you are "inside" an object. This is especially handy when needing to call private methods – just invoke them, no need to use send.

  • Switch to an object: chws $object
  • Reset to main: chws
  • Show current context: cwws (usually shown in IRB prompt)

[Technical details](https://technology.doximity.com/articles/the-hidden-gems-of-r...