Sending newsletters via rapidmail with SMTP and one-click unsubscribe

If you need to implement newsletter sending, rapidmail is a solid option.

Support is very fast, friendly and helpful, and the initial setup is simple. Since rapidmail works via SMTP, you can simply ask the Ops team to configure SMTP credentials for your application.

You also do not need to use rapidmail’s built-in newsletter feature. Instead, you can send emails as transactional mails, which allows you to keep the entire newsletter logic inside your application.

One thing to keep an ey...

Locally testing a website on its real domain

In rare circumstances, you want to use a websites full domain (say https://mywebsite.com) while testing in dev mode. This can be useful if you need to test third party integrations like embeds, cookie banners, Google Tag Manger or other integrations that allowlist your actual domain.

To achieve this, we will have to

  • make our system resolve mywebsite.com to localhost,
  • run a reverse proxy on localhost:443 with a (locally signed and accepted) SSL certificate for mywebsite.com,
  • forward the traffic to our actual dev server.

On l...

TypeScript: Get in Shape with Structural Typing

TypeScript basically uses structural typing, which is conceptually quite similar to duck typing, but with static compile-time type checking. We'll explore what this means in practice.

TypeScript is a superset of JavaScript, meaning TypeScript compiles down to native JavaScript syntax and checks type consistency only at compile time.

Idea of Structural Typing

TypeScript only wants to know whether the shapes of two objects are identical:

interface Point2D {
  x: number;
  y: number;
}

function printPoint(p: Point2D) {
  c...

Rubocop fails with undefined StringIO

If your rubocop run fails with a stack like

rubocop-1.61.0/lib/rubocop/server/socket_reader.rb:36:in `ensure in read!': undefined method `string' for nil:NilClass (NoMethodError)
        Cache.stderr_path.write(stderr.string)
                                      ^^^^^^^
...
rubocop-1.61.0/lib/rubocop/server/socket_reader.rb:27:in `read!': uninitialized constant RuboCop::Server::SocketReader::StringIO (NameError)

        stderr = StringIO.new
                 ^^^^^^^^

this card might help you.

In old versions of rubocop stringio w...

How to accept more than 4k query parameters in Rails

I have a form with a dynamic number of fields. Submitting it worked fine until I tried out a very large version of it. The development log was not very helpful:

Invalid or incomplete POST params

As it turned out, the following exception did not reach the log output

Rack::QueryParser::QueryLimitError
Error Message: total number of query parameters (6313) exceeds limit (4096)

If you ever happen to be in the same position, this is how to increase the limit of allowed query parameters:

# config/initializers/rack_query_parser.rb
...

How to iterate over all ActiveRecord models

Sometimes you have a maintenance script where you want to iterate over all ActiveRecord models. Rails provides this out of the box:

# script/maintenance_task.rb

# Load all models eagerly, otherwise you might only get a subset
Rails.application.eager_load!

ApplicationRecord.descendants.select(&:table_exists?).each do |model|
  # ...
end

Caution

If you iterate over individual records, please provide a progress indicator: See [https://makandracards.com/makandra/625369-upload-run-scripts-production](https://makandracards.com/...

Migrating from rbenv / nvm to mise

Install mise

Follow the installation guidelines at https://mise.jdx.dev/getting-started.html.

Remove rbenv configuration

Search for rbenv config in .bashrc and .profile and remove it:

eval "$(rbenv init - bash)"

Search for rbenv config in .profile and remove it:

source /home/$user/.rbenvrc

Remove nvm configuration

Search for nvm config in .bashrc and remove it:

export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"  # This loads nvm
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_...

How to inspect Rails view cache keys (when using Redis)

When your Rails application is using Redis as its cache store, this is how you can list existing keys:

  1. Check: Rails.cache should return an ActiveSupport::Cache::RedisCacheStore.
  2. Rails.cache.redis.with(&:keys) lists existing keys. Cached views start with "views/".
    Caution! The list of keys may be huge in production.

Making minimal updates to DOM trees using morphdom / idiomorph

When you replace parts of the DOM with new HTML, using .innerHTML = newHtml is usually the simplest and fastest option. It comes at the price of your DOM elements losing state, like input values, scroll position, progress in a video player, or even more complex state for custom elements.

One option to avoid this are libraries like morphdom (as used by Phoenix Liveviews) or idiomorph (as used by Rails' Turbo).

It lets you write

morphdo...

Custom Angular Test Bootstrap

Compatibility: Angular 20+ with Jasmine 5.x and Karma 6.x

As a default Angular CLI auto-generates test bootstrap via angular:test-bed-init via injecting it as a dynamic virtual module.

Custom Bootstrap

Override the main test option in angular.json → projects.{app}.architect.test.options.main: <test entry file> and [then initialize th...

Faux-disabled fields in HTML forms

You want to prevent input to a form field, but all the solutions have side effects:

  • The [readonly] attribute is only available for text fields, but not for checkboxes, selects, buttons, etc.
  • The [disabled] attribute is available for all kinds of fields, but will no longer include the field value in the submitted form data.
  • pointer-events: none still allows keyboard input, and does not indicate disabledness visually, or to screen readers.

Ye...

File System Access API: Recursive Directory Traversal

The File System Access API is a new capability of modern browsers that allows us to iterate over selected folders and files on a user's machine. Browser support is not great yet, but if the feature is only relevant for e.g. a single admin user it could still be worth using it prior to wider adaption instead of building yet another ZIP upload form.

Below is a simple compiler that i used to evaluate this feature.

!...

Sentry Local Logging in Ruby

Enable local logging for Sentry when:

  • Debugging Sentry event capture locally
  • Testing error handling without polluting production metrics
  • Developing background jobs and want to see what Sentry captures

How to enable

To capture and log Sentry events locally during development without sending to the server, add this to config/initializers/sentry.rb inside the Sentry.init block:

if Rails.env.development?
  # Use dummy transport to prevent actual transmission to Sentry
  config.transport.transport_class = Sentry::DummyTran...

Unpoly: Passing Data to Compilers

Quick reference for passing data from Rails to JavaScript via Unpoly compilers.

Haml Attribute Syntax

# Ising hash rockets and string symbols (method calls)
= form.text_field :name, 'date-picker': true

# Curly braces or brackets (elements) 
%div.container{ id: 'main', 'data-value': '123' }

Simple Values: data-* Attributes

Use for: Scalar values (IDs, strings, booleans)

%span.user{ 'data-age': '18', 'data-first-name': 'Bob' }
up.compiler('.user', (element, data) => {
  console.log(...

Unpoly: Compiler Selector Patterns

Quick guide for frequently used compiler selector patterns of Unpoly.

1. BEM Component Pattern

When: Reusable UI components with multiple child elements

Examples: toggleable.js, collapsible.js, searchable_select.js

up.compiler('.toggleable', (toggleable) => {
  const checkbox = toggleable.querySelector('.toggleable--checkbox')
  const content = toggleable.querySelector('.toggleable--content')
  // ...
})
%td.toggleable.-inverted.-ignore-when-not-empty
  .toggleable--content
    = form.text_fie...

Format your JavaScript with prettier

prettier calls itself an opinionated code formatter. I recommend using it for your JavaScript and TypeScript code.

prettier only concerns itself with the formatting of your JavaScript (and also some other file types like json or yaml). When you use it, it has an opinion on every single whitespace and linebreak, as well as a few other things. You renamed a variable and now your line is a bit longer than looks good? prettier will reformat your code.

This might not work for you if you have strong opinions yourself....

Stabilize integrations tests with flakiness introduced by Turbo / Stimulus / Hotwire

If you run a Rails app that is using Turbo, you might observe that your integration tests are unstable depending on the load of your machine. We have a card "Fixing flaky E2E tests" that explains various reasons for that in detail.

Turbo currently ships with three modules:

  • Turbo Drive accelerates links and form submissions by negating the need for full page reloads.
  • Turbo Frames decompose pages into independent contexts, which scope navigation and can be lazily loaded.
  • T...

Processing GitLab Merge Requests within RubyMine

GitLab has a RubyMine plugin that enables you to review and process merge requests within RubyMine!

Setup

  1. Open RubyMine settings (Ctrl + Alt + S) > Plugins > Search for "GitLab" > Install
    • (You might need to re-open settings afterwards.)
  2. In the RubyMine settings > Version Control > GitLab > Connect your GitLab account with "+"

Working with merge requests

  1. From the Actions menu (Ctrl + Shift + A), choose "View merge...

Improve accessibility with [aria-required] in SimpleForm

SimpleForm comes with an option browser_validations which could be used to give fields that have a presence validation the HTML required attribute. We usually turn it off due to difficulties controlling its behavior and appearance. Instead we only mark required fields with an asterisk (*) next to its label. Blind users probably only discover the validation issue after submitting the form due to the now displayed error messages.

A compromise with better acce...

RSpec: Marking sections in long examples

RSpec examples can get quite long, especially in feature specs. This makes them hard to read & understand. Also, when executing them, it may take seconds to see any progress.

To improve this, I have successfully been using a little "step" helper in my tests. It marks semantic sections, structuring an example while improving documentation. When the test runs, each step prints its message (or a dot, depending on your formatter).

# spec/support/step_helper.rb
module StepHelper

  # Use this helper to label groups of related actions in l...

Testing for Performance: How to Ensure Your Web Vitals Stay Green

Frontend performance and user experience are orthogonal to feature development. If care is not taken, adding features usually degrades frontend performance over time.

Many years, frontend user experience has been hard to quantify. However, Google has developed some metrics to capture user experience on the web: the Web Vitals. The Core Web Vitals are about "perceived loading speed" (Largest Contentful Paint), reactivity (Interaction to Next Paint) and visual stability (Content Layout Shift).

I have recent...

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