How to add esbuild to the rails asset pipeline

This are the steps I needed to do to add esbuild to an application that used the vanilla rails asset pipeline with sprockets before.

Preparations

  1. update Sprockets to version 4
  2. add a .nvmrc with your preferred node version (and install it)
  3. add gems jsbundling-rails and foreman to your Gemfile:
    gem 'jsbundling-rails'
    group :development, :test do
      gem 'foreman'
      # ...
    end
    
  4. bundle install
  5. run bin/rails javascript:install:esbuild in a console to prepare esbuild.
  6. run yarn install...

Understanding Ruby's def keyword

This StackOverflow question about nested function definitions in Ruby imparts a good understanding of Ruby's def.

PostgreSQL: "WHERE NOT <column> = '<value>'" statements do not include NULL values

Sometimes we write plain SQL queries in migrations so we don't have to mock ActiveRecord classes. These two migrations do the same:

class Migration1 < ActiveRecord::Migration[5.2]
  class User < ActiveRecord::Base; end

  def up
    add_column :users, :trashed, :boolean
    
    User.update_all(trashed: false)
  end
end

class Migration2 < ActiveRecord::Migration[5.2]
  def up
    add_column :users, :trashed, :boolean

    update("UPDATE users SET trashed = #{quoted_false}")
  end
end

The plain SQL migration is less code, but h...

Regular Expressions: Excessive backtracking can get yourself in trouble

Two weeks ago, Cloudflare was struck by a global outage that lasted ~30 minutes. The incident was rooted on a CPU exhaustion caused by a single regular expression containing some catastrophic backtracking:

.*(?:.*=.*)

This is a small reminder do keep using the lazy operator ? whenever possible and furthermore be aware that regular expressions should not only be unit-tested but also evaluated in terms of performance. See <https://makandracards.com/makandra/494822-regul...

How to combine "change", "up", and "down" in a Rails migration

Rails migrations allow you to use a change method whose calls are automatically inverted for the down path. However, if you need to some path-specific logic (like SQL UPDATE statements) you can not define up and down methods at the same time.

If you were to define define all 3 of them, Rails would only run change and ignore up and down. However, Rails 4+ features a helper method called reversible:

class MyMigration < ActiveRecord::Migration

  def cha...

Howto: Write a proper git commit message

Seven Rules

  1. Separate subject from body with a blank line
  2. Limit the subject line to 50 characters (max. 72), include reference (unique story ID) to requirements tracker (Linear in our case)
  3. Capitalize the subject line
  4. Do not end the subject line with a period
  5. Use the imperative mood in the subject line
  6. Wrap the body at 72 characters
  7. Use the body to explain what and why vs. how

5. Use the imperative mood in the subject line (partially extracted)

If applied, this commit will your subject line here

Good:

*...

Terminator setup for Procfile-based applications for more comfortable debugging

We use foreman to start all necessary processes for an application, which are declared in a Procfile. This is very convenient, but the outputs of all processes get merged together. Especially while debugging you might not want other processes to flood your screen with their log messages.

The following setup allows you to start Terminator in a split view with the Rails server running in the left pane and all remaining processes running via foreman in the right pane. It was heavily inspired by [this card](https://makandracards.com/makandr...

esbuild: Make your Rails application show build errors

Building application assets with esbuild is the new way to do it, and it's great, especially in combination with Sprockets (or Propshaft on Rails 7).
You might be missing some convenience features, though.

Here we cover one specific issue:
Once you have started your development Rails server and esbuild with the --watch option (if you used jsbundling-rails to set up, you probably use bin/dev), esbuild will recompile your assets upon change, but build errors will only be printed to the terminal. Your application won't complain about them ...

RubyMine users: you should be using bookmarks

RubyMine allows bookmarking lines of code. This is super-helpful when working on a complex problem.
I've been using this feature for a few years now, and so should you! :)

Here are the default Linux/Windows keystrokes. See the documentation for other keybindings.

Add an anonymous bookmark

F11

A gray checkmark will be shown in the gutter on the left.
If you press F11 again on a bookmarked line, the bookmark will be removed.

Add a named bookmark ("mnemonic")

Ctrl ...

Documenting your Rails project's Node.js version in .nvmrc

Not all versions of Node.js are compatible with each other. Also npm packages may require a minimum or maximum version of Node.js. We use nvm on our development PCs so we can operate multiple versions of Node.js in parallel.

To make sure that all developers use a compatible version of Node.js, your Rails project should declare the required Node.js in a file called .nvmrc.

When a .nvmrc exists, developers can cd in your project directory and activate...

skorks/nesty

Nested exceptions for Ruby:

When you rescue an error and then re-raise your own, you don't have to lose track of what actually occured, you can keep/nest the old error in your own and the stacktrace will reflect the cause of the original error.

This is awesome when you classes convert exception classes. I now always subclass Nesty::NestedStandardError instead of StandardError for my own error classes.

About Exception#cause

Ruby 2.1 has a built-in mechanism with Exception#cause, which serves a similiar purpos...

Capybara: Finding invisible elements and how to test that an element is not visible

When Capybara locates elements in the DOM, by default it allows only accessing visible elements -- when you are using a driver that supports it (e.g. Selenium, not the default Rack::Test driver).

Consider the following HTML:

<div class="test1">One<div>
<div class="test2">Two</div>

With some CSS:

.test1 { display: block }
.test2 { display: none }

We will be using Capybara's find below, but this applies to any Capybara finder methods.

Default: visible: :visible or visible: true

As described above, by defa...

Rails: When to use :inverse_of in has_many, has_one or belongs_to associations

When you have two models in a has_many, has_one or belongs_to association, the :inverse_of option in Rails tells ActiveRecord that they're two sides of the same association.

Example with a has_many / belongs_to association:

class Forum < ActiveRecord::Base
  has_many :posts, inverse_of: :forum
end

class Post < ActiveRecord::Base
  belongs_to :forum, inverse_of: :posts
end

Knowing the other side of the same association Rails can optimize object loading so forum and forum.posts[0].forum will reference the same o...

Traverse an ActiveRecord relation along an association

The Edge Rider gem gives your relations a method #traverse_association which
returns a new relation by "pivoting" around a named association.

Say we have a Post model and each Post belongs to an author:

class Post < ActiveRecord::Base
  belongs_to :author
end

To turn a relation of posts into a relation of its authors:

posts = Post.where(:archived => false)
authors = posts.traverse_association(:author)

You can traverse multiple associations in a single call.
E....

8 steps for fixing other people's code

Guide how to make fixes in other people's GitHub repositories. It's basically "Open Source Development 101".

Way back in mid-2007, when Rails 1.2 was the new hotness and GitHub was still a year away from crawling out of the primordial internet soup, prolific open source contributor Dr Nic wrote an article titled “8 steps for fixing other people’s code”. (...)

Here in the fantastical future world of 2012, while we still don’t have hoverboards or household nuclear fusion, we do have some great tools that make fixing other people’s code...

JavaScript Sentry: How to check if errors will be reported

One really simple way to check whether JavaScript Sentry integration was successful (raven-js or @sentry/browser), is to create an erroneous event handler like this:

<h1 onClick="throw new Error('JavaScript Sentry was successfully integrated')">
  My Website
</h1>

… and clicking on the element afterwards.

If your site has a strict CSP, see Using inline event handlers with a strict Content Security Policy (CSP).

Does <html> or <body> scroll the page?

Scrolling overflowing elements with JavaScript

HTML elements with overflow-y: auto or overflow-y: scroll will get a scrollbar when their content is higher than their own height.

When you scroll an element , the element's scrollTop property is updated with the scrollbar's new position. You can also set element.scrollTop = 30 to scroll the element to a vertical pixel position counted from the top.

Scrolling the main viewport with JavaScript

The browser's main document viewport is also scrollable by default. The element that ...

Trigger a link's click action with Javascript

Use the click method on the DOM element:

let link = document.querySelector('a')
link.click()

RSpec: Inferring spec type from file location

RSpec Rails can automatically mix in different behaviors to your tests based on their type tag, for example enabling you to call get and
post in specs with the tag type: :request.

Alternatively you can skip these tags by setting the config config.infer_spec_type_from_file_location! in the spec_helper.rb. This will automatically choose the right type context based on the file location of the test.

For instan...

Rails 6.1: where.not changes behaviour from NOR to NAND

Since Rails 6.1, if we use where.not with multiple attributes, it applies logical NAND (NOT(A) OR NOT(B)) instead of NOR (NOT(A) AND NOT(B)). If you do not take care, this change will increase the matched set.

Examples

"Don't send newsletters neither to admins nor to trashed users!" becomes "Don't send newsletters to trashed admins".

User.where.not(role: 'admin', trashed: true)
# Before Rails 6.1, with NOR
=> "SELECT "users".* FROM "users" WHERE "users"."role" != 'admin' AND "users"."trashed" != TRUE"
# Equivale...

RSpec: Define negated matcher

You can use RSpec::Matchers.define_negated_matcher to define a negated version of an existing matcher.

This is particularly useful in composed matcher expressions or to create more expressive and meaningful matchers.

Examples

RSpec::Matchers.define_negated_matcher :not_change, :change

describe 'A negated matcher' do
  it 'can be used to chain negated changes' do
    expect { subject.maybe_change(object) }
      .to not_change(object, :attr_1)
      .and not_change(contract, :attr_2)
  end
end
RSpec::...

Browsers will not send a referrer when linking from HTTPS to HTTP

  • When your site is on HTTPS and you are linking or redirecting to a HTTP site, the browser will not send a referrer.
  • This means the target site will see your traffic as "direct traffic", i.e. they cannot distinguish such hits from a user who directly typed in the URL.

Reasons for this behavior

It's probably because of this RFC:

Clients SHOULD NOT include a Referer header field in a (non-secure) HTTP request if the referring page was transferr...

Ruby: Using named groups in Regex

An alternative of using a multiple assignment for a Regex are named groups. Especially when your Regex becomes more complicates it is easier to understand and to process.

Note:

  • In case a string does not match the pattern, .match will return nil.
  • With Ruby 2.4 the result of .match can be transformed to a Hash with named_captures. This allows you to use methods like slice or fetch on the result.

Example with a mult...

CSS Grid Display allows defining number of grid columns based on child width

An element with display: grid can define its grid-template-columns based on (preferred) child width using the repeat function with auto-fill or auto-fit, like so:

grid-template-columns: repeat(auto-fit, 100px)

auto-fill and auto-fit behave differently if you use rules with dynamic sizing, like minmax(100px, 1fr). Simply put, auto-fill will create as many columns as possible, including empty ones, while auto-fit hides empty columns.

See the linked page for more details.