ActiveRecord: String and text fields should always validate their length

If you have a :string or :text field, you should pair it with a model validation that restricts its length.

There are two motivations for this:

  • In modern Rails, database types :string and :text no longer have a relevant size limit. Without a validation a malicious user can quickly exhaust the hard drive of your database server.
  • In legacy Rails (or database schemas migrated from legacy Rails), database types :string and :text had a database-side length constraint. When the user enters a longer string, the ActiveRecord valida...

Test your application's e-mail spam scoring with mail-tester.com

You can use mail-tester.com to check your application's e-mails for issues that might cause e-mails to be classified as spam.

They provide a one-time e-mail addresses that you can use to sign up etc. You can then check for scoring results of SpamAssassin and other potential issues.
You don't need to hit 10/10. Something around 9/10 is perfectly fine.

Note:

  • For password-protected staging sites you will get an error for links that can not be resolved. This is fine, simply check production once available.
  • ...

Insomnia helps you querying your API

Insomnia is a GUI tool to help you communicating with an API. There are also other tools to do this, e.g. Postman or the command line tool cURL.

While it is quite similar to Postman, I found the UI to be less cluttered and therefore easier to use.

Image

The usage is almost self explanatory.

You can install it v...

Postgres: DISTINCT ON lets you select only one record per ordered attribute(s) for each group

  • To retrieve only unique combinations of the selected attributes: You can omit rows, where all selected columns are equal with the DISTINCT statement.
  • To retrieve the group wise maximum of certain columns: You can keep only one record for each group with the DISTINCT ON statement, to omit equal rows within each specified group.

Use case

You have a query where you want only one record for a set of specifically ordered attributes.

How to use?

Let's say we look at the example how to query only the latest post for each user:
...

Invoices: How to properly round and calculate totals

While it might seem trivial to implement an invoice that sums up items and shows net, gross and vat totals, it actually involves a lot of rules and caveats. It is very easy to create invoices where numbers don't add up and a few cents are missing. A missing cent is a big deal for an accountant, so it is important for your invoices to list correct numbers.

Note that this is not legal advice. Also note that while this note has a number of code examples in Ruby and MySQL, the concepts apply to all programming languages and data stores.

When ...

How to negate scope conditions in Rails

Sometimes you want to find the inverse of an ActiveRecord scope. Depending on what you want to achieve, this is quite easy with Rails 7, and a bit more complicated with Rails 6 and below, or when the inverse scope may contain NULL values. [1]

Caution

Databases use a 3-valued logic. Either A = 0 or A != 0 or A IS NULL. Don't forget the third case!

There are two different ways of "inverting a scope":

As an example, consider the following model.

class User < ApplicationRecord
  scope :admins, -> { where(role: ['admin', '...

MySQL: Careful when using database locks in transactions

We tend to use database transactions as a magic bullet to get rid of all our concurrency problems. When things get really bad, we might even throw in some locking mechanism, but then are usually done with it.

Unfortunately, transactions semantics in databases are actually very complicated, and chances are, your making some incorrect assumptions.

The MySQL innodb engine actually has [four different modes](ht...

Open UI: Future development in web components and controls

tl;dr When browsers start to adapt proposals from Open UI, it might not be necessary to use any 3rd party libraries to have nice components and controls in web applications e.g. selects. It would require only a minimum of CSS and Javascript to get them working and looking good.

The purpose of the Open UI, a W3C Community Group, is to allow web developers to style and extend built-in web UI components and controls, such as dropdowns, checkboxes, radio buttons, and date/color pickers.

To do that, we’ll need to fully specify...

Rails: Different flavors of concatting HTML safe strings in helpers

This card describes different flavors for concatting HTML safe strings in a helper method in Rails. You might want to use the tag helper instead of the content_tag helper (the tag helper knows all self closing tags).

Example

We want to generate HTML like this:

<h1>Navigation</h1>
<ul>
  <li>Left</li>
  <li>Right</li>
</ul>

Below you ca...

Don't build randomness into your factories

Tests are about 100% control over UI interaction and your test scenario. Randomness makes writing tests hard. You will also push tests that are green for you today, but red for a colleague tomorrow.

That said, please don't do something like this:

Factory(:document) do |document|
  document.category { ['foo', 'bar', 'baz'].sample }
end

Instead do this:

Factory(:document) do |document|
  document.category 'foo'
end

The case against Faker

I even recommend to not use libraries like [Faker]...

The Self-Contained Test

One of the earliest pieces of wisdom we are given as programmers is to not write duplicate code: Don’t Repeat Yourself (or DRY if you prefer). Identical blocks of code to set up a test sure does look like repetition, so we extract it into a before block.

This is a mistake for tests.

The article explains about how sharing setup between examples make test files harder to read and evolve.

A related frustration I have is working on ultra-DRY & betterspecs-like ...

Ruby: Making your regular expressions more readable with /x and alternative delimiters

The following two hints are taken from Github's Ruby style guide:

If your regular expression mentions a lot of forward slashes, you can use the alternative delimiters %r(...), %r[...] or %r{...} instead of /.../.

 %r(/blog/2011/(.*))
 %r{/blog/2011/(.*)}
 %r[/blog/2011/(.*)]

If your regular expression is growing complex, you can use the /x modifier to ignore whitespace and comments:

regexp = %r{
  start         # some text
  \s            # white space char
  (group)    ...

Text column sizes in MySQL

Postgres works differently

See PostgreSQL: Difference between text and varchar columns for PostgreSQL-specific info

MySQL has 4 different column sizes. They are actually different data types under the hood:

type size limit schema.rb option
TINYTEXT 256 bytes size: :tiny
TEXT 65,535 bytes (default)
MEDIUMTEXT 16,777,215 bytes size: :medium
LONGTEXT 4,294,967,...

Understanding database cleaning strategies in tests

TLDR: In tests you need to clean out the database before each example. Use :transaction where possible. Use :deletion for Selenium features or when you have a lot of MyISAM tables.

Understanding database cleaning

You want to clean out your test database after each test, so the next test can start from a blank database. To do so you have three options:

  • Wrap each test in a transaction which is rolled back when you're done (through DatabaseCleaner.strategy = :transaction or `config.use_transactional_fi...

Using regular expressions in JavaScript

Regular expressions in Javascript are represented by a RegExp object. There also is a regex literal as in many other languages: /regex/. However, they are used slightly differently.

Regex literal

  • Usage: /foo+/
  • Shorthand for creating a regular expression object

RegExp() object

  • Usage: RegExp("foo+") or new RegExp("foo+")
  • No surrounding slashes required (they're the literal markers)
  • Since the argument is a string, backslashes need to be escaped as well: RegExp("\\d+")

Gotchas

  • Regex objects [never eq...

Migrating from Elasticsearch to Opensearch

Why do we migrate?

Due to a change in licensing, we cannot provide Elasticsearch versions >= 8.0.
Version 7.17.x will reach EOL status with the release of Elasticsearch version 9.
We have decided to use OpenSearch as a replacement, since it is a fork of Elasticsearch version 7.10.2, still running under the previous licensing model and wire-compatible.
A more detailed reasoning can be found on their [website](https://opensearch.o...

How to handle when an HTML <video> element cannot autoplay

HTML <video> elements can automatically start playing when the autoplay attribute is set on them. Except for when they can not, e.g. on pageload, or when the element enters the DOM without user interaction, or when the browser for some other reason decided to not start playing the video.

While there is no native "autoplay failed" event to listen to, you can wait for video data to be loaded and then check if the video actually started playing.

Example

<video autoplay>
  <source src="example.mp4" type="video/mp4" />
</video>
...

Using rack-mini-profiler (with Unpoly)

Debugging performance issues in your Rails app can be a tough challenge.

To get more detailed insights consider using the rack-mini-profiler gem.

Setup with Unpoly

Add the following gems:

group :development do
  gem 'memory_profiler'
  gem 'rack-mini-profiler'
  gem 'stackprof'
end

Unpoly will interfere with the rack-mini-profiler widget, but configuring the following works okayish:

// rack-mini-profiler + unpoly
if (process...

SAML Single Logout (SLO)

There are two ways a logout in SAML can happen: Service Provider (SP) initiated and Identity Provider (IDP) initiated logout. I'll explain how to implement both flows with devise_saml_authenticatable.

Note

SAML also supports a SOAP and an Artifact binding to do this. This guide only refers to POST and Redirect bindings. devise_saml_authenticatable does not support SOAP and Artifact bindings.

SP initiated logout (using the Redirect Binding)

When the user clicks on Logout within the app, the app can trigger...

Rails 7.1: Take care of the new production log default to standard out

Starting with Rails 7.1 the production logger is set to standard out. For applications running with opscomplete ensure to keep logging to a file as before (e.g. when running bin/rails app:update).

It should be enough to change these lines in the config/environments/production.rb back to the implementation in Rails <7.1:

-  # Log to STDOUT by default
-  config.logger = ActiveSupport::Logger.new(STDOUT)
-    .tap  { |lo...

Caution: rem in @media query definitions ignore your font-size

Note

Using rem only ever makes sense when the root font size is dynamic, i.e. you leave control to the user. Either by a) respecting their user agent defaults, or by b) offering multiple root font sizes in your application.

By defining @media queries in rem, they will accommodate to the root font size of your page. At a larger root font, breakpoints will be at larger widths, scaling with the font. However, there is a catch in case b) mentioned in the note above.

Relative length units in media queries are based on the initial value,...

Problems with git submodules in Gitlab CI

If you are using git submodules in Gitlab CI, you might run into a "The project you were looking for could not be found or you don't have permission to view it."

Gitlab added a feature that new projects are no longer allowed to be cloned inside CI runs of other repositories by default. To fix this

  • Go into the project used as a submodule
  • Go to "Settings" -> "CI/CD" (if you don't see this section, enable it in "Settings" -> "General" -> "Visibility, project features, permissions")
  • Go to "Token Access"
  • Either disable "Limit access to ...

Canceling promises

The standard way to abort async code is that your function takes a AbortSignal { signal } property. The caller can use this signal to send an abort request to your function. Upon receiving the request, your function should reject its promise with an error.

Async browser functions like fetch() reject their promises with a new DOMException('Message here', 'AbortError') when canceled.

This already has good browser support and can be polyfilled on older browsers.

Exa...

Cucumber's table diffing does not play nice with Spreewald's `patiently do`

Turns out, Cucumber::MultilineArgument::DataTable#diff! caches some stuff. Code of the following form will not work as intended:

Then('some table should look like') do |expected_table|
  patiently do
    actual_table = calculate_actual_table
    expected_table.diff!(actual_table) # not actually patient, will keep failing if it failed the first time
  end
end

Instead, simply use

expected_table.dup.diff!(actual_table)