Bookmarklet to generate a commit message for an issue in Linear.app

Your commit messages should include the ID of the issue your code belongs to.
Our preferred syntax prefixes the issue title with its ID in brackets, e.g. [FOO-123] Avatars for users.
Here is how to generate that from an issue in Linear.

Add a new link to your browser's bookmarks bar with the following URL.

javascript:(() => {
  if (document.querySelector('[data-view-id="issue-view"]')) {
    const [id, ...words] = document.title.split(' ')
    prompt('Commit message:', `[${id}] ${words.join(' ')}`)
  } else {
    alert('Open issue v...

Regular Expressions: Quantifier modes

When you repeat a subpattern with a *, + or {...} operator, you may choose between greedy, lazy and possessive modes.

Switching modes may affect the result and performance of your regular expressions. In the worst case, an ill-suited mode may make your regular expression so slow that it can DoS your application (Examples are the ActiveRecord's PostgreSQL CVE-2021-22880 or the [Cloudflare outage 2019](https://makandracards.com/makandra/77515-regular-expressions-excessive-backtracking...

Rails: Comparison of assignable_values and Active Record enum types

We are using assignable_values for managing enum values in Rails. Nevertheless Rails is adding more support for enum attributes, allowing to have a closer look at the current feature set in comparison to our still preferred option assignable_values.

Active Record enum attribute interface

By default Rails is mapping enum attributes to integers:
...

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

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

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

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

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

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