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

How to automatically optimize SVG files with esbuild

SVG files often contain redundant information, like editor metadata or hidden elements. When esbuild handles your static assets, you can easily optimize SVG files using svgo as a plugin. Here is how to do it.

Adding svgo as an esbuild plugin

  1. Add the svgo package to your project's package.json
  2. Adjust your esbuild.config.js like so:
const esbuild = require('esbuild')
const svgo = require('svgo')

const options = {
  // ...
  loader: {
    // ...
    '.svg': 'copy', // this may be different...

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

RSpec: Executing specs by example id (or "nesting index")

There are several ways to run a single spec. I usually copy the spec file path with the line number of the example and pass it to the RSpec binary: bin/rspec spec/models/user_spec.rb:30 (multiple line numbers work as well: :30:36:68). Another is to tag the example with focus: true or to run the example by matching its name.

In this card I'd like to ...

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

How to use your favorite font in Slack

In Slack, the settings dialog only offers a fixed selection of fonts. You can use any font you like using the /slackfont command.

Fonts need to be installed on your machine. Webfonts beyond those provided by Slack won't magically work unless you install them locally.
Only the chat font can be changed. The monospaced font used for code blocks isn't easily customizable.

Example usage

  • /slackfont Comic Neue to use "Comic Neue" (if installed)
  • /slackfont system-ui to use your desktop's system font in Slack.
  • /slackfont (withou...

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

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

Reverse lookup a fixture name by its id and table name

To reverse lookup a fixture by its table name and id, use the following approach on ActiveRecord::FixtureSet:

table = 'users'       # Specify the fixture table name
id = 123122           # Specify the ID to look for

# Find the fixture that matches the given ID
ActiveRecord::FixtureSet.all_loaded_fixtures[table].fixtures.find { |key, value| value['id'] == id }

Result Example:

[
  "one", # Fixture name
  #<ActiveRecord::Fixture:0x00007e79990234c8>, # ActiveRecord::Fixture object
  @fixture= { ... }, # The raw fixtu...

Rails: Looking up constants by their name string

TL;DR: Rails ships two methods to convert strings to constants, constantize and safe_constantize. Neither is safe for untrusted user input. Before you call either method you must validate the input string against an allowlist. The only difference between the two methods is that unresolvable constants raise an error with constantize, but return nil with safe_constantize. If you validate the input string against an allowlist, an error should never happen.

Preventing Dangerous Lookups

Suppose an application uses eit...

How to disable logging for ActiveStorage's Disk Service routes

In development, we store files using ActiveStorage's disk service. This means that stored files are served by your Rails application, and every request to a file results in (at least!) one non-trivial log entry which can be annoying. Here is how to disable those log entries.

Example

Here is an example of what loading a single <img> in an example application writes to the Rails log.

Started GET "/rails/active_storage/blobs/redirect/..." for ::1 at ...
Processing by ActiveStorage::Blobs::RedirectController#show as SVG
  Parameter...

How the Date Header Affects Cookie Expiration and Caching

tl;dr

When a cookie includes an Expires attribute or an HTTP response includes caching headers like Expires or Cache-Control, their validity depends on the server's Date header if present. Otherwise, the browser uses its local time. This can lead to issues in tests with mocked time or inconsistent cache behavior.

Cookie Expires depends on the Date header or browser time

When a cookie includes an Expires attribute, the browser evaluates the expiration date relative to a reference time:

  1. If the HTTP response ...

How to enable Rails' file_fixture helper in FactoryBot

In FactoryBot factories, Rails' file_fixture is not available by default. To enable it, include a support module from rspec-rails:

FactoryBot::SyntaxRunner.include(RSpec::Rails::FileFixtureSupport)

That includes ActiveSupport::Testing::FileFixtures, where file_fixture is defined, but also configures the file_fixture_path so that you can actually use file_fixture.

TestProf II: Factory therapy for your Ruby tests—Martian Chronicles, Evil Martians’ team blog

Some key highlights and points from the linked article TestProf II: Factory therapy for your Ruby tests.

The Problem with Factories in Ruby Tests

  • Factories are used to easily generate test data.
  • However, they can unintentionally slow down test suites by creating unnecessary or excessive associated data (factory cascades).

Understanding Factory-Induced Slowdowns

  • Factories often create additional data (e.g., associated records) th...

Text fragments in the browser URI fragment

Text fragments allow linking directly to a specific portion of text in a web document, without requiring the author to annotate it with an ID, using particular syntax in the URL fragment. Supporting browsers are free to choose how to draw attention to the linked text, e.g. with a color highlight and/or scrolling to the content on the page. This is useful because it allows web content authors to deep-link to other content they don't control, without relying on the presence of IDs to make that possible. Building on top of that, it could be u...

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

How to eager load a single directory with Zeitwerk

Zeitwerk is the new autoloader of Rails. It is mandatory starting with Rails 7.0.

Sometimes, a model needs to know all its descendants. They might be organized in a subdirectory:

# Example
app/models/design.rb
app/models/design/light.rb
app/models/design/dark.rb
...

Now imagine that some external code needs to iterate all design subclasses.

To eager load all designs, use this line:

Rails.autoloaders.main.eager_load_dir(Rails.root.join 'app/models/design')

Make sure that app/models/design.rb is not required manually ...

Rails: Using normalizes without copying code

Rails 7.1 added the normalizes method which can be used to normalize user input.

It lets you define the fields you want to normalize and how to normalize them. In the example below, the Movie#title attribute is stripped from leading and trailing whitespace automatically:

class Movie < ApplicationRecord
  normalizes :title, with: -> {  _1.strip }
end

Tip

Normalization lambdas are not called for nil values by default. To normalize nil values, pa...

A different testing approach with Minitest and Fixtures

Slow test suites are a major pain point in projects, often due to RSpec and FactoryBot. Although minitest and fixtures are sometimes viewed as outdated, they can greatly improve test speed.

We adopted a project using minitest and fixtures, and while it required some initial refactoring and establishing good practices, the faster test suite was well worth it! Stick with me to explore how these tools might actually be a good practice.

So, why is this setup faster? Partially, it's because minitest is more lightweight than RSpec, which...

Timeouts for long-running SQL queries

While the main goal always is to prevent long-running queries in the first place, automatic timeouts can serve as a safety net to terminate problematic queries automatically if a set time limit is exceeded. This prevents single queries from taking up all of your database’s resources and reduces the need for manual intervention that might destabilize or even crash the application.

As Rails does not set a timeout on database statements by default, the following query will run for an entire day:

ActiveRecord::Base.connection.execute("S...