Code splitting in esbuild: Caveats and setup

Code splitting is a feature of esbuild that can keep huge libraries out of the main bundle.

How code splitting works

Like Webpack esbuild lets you use the await import() function to load code on demand:

// application.js
const { fun } = await import('library.js')

fun()

However, esbuild's code splitting is disabled by default. The code above would simply inline (copy) `l...

Don't assert exceptions in feature specs

As we are slowly switching from Cucumber scenarios to RSpec feature specs, you might be tempted to write assertions like this one:

feature 'authorization for cards management' do
  let(:guest_user) { create(:user, :guest) }

  scenario "rejects guest users from adding new cards", js: true do
    sign_in guest_user

    expect { visit new_cards_path }.to raise_error(Consul::Powerless)
  end
end

While this might work under certain circumstances¹, there is a good chance you'll see two exceptions when running this single spec:

  • ...

Byebug cheatsheet

Context and further resources

Even though you can get 90% of debugging done with up to 5 basic byebug commands, it comes in handy with it's features for many use cases beyond that to make your life easier.

For this cheatsheat I tried to structure the most useful commands by different use cases, such that a practical oriented overview of all the commands can be gathered by going over this cheatsheet. For some commands I added some tips for their usage and further details on their subcommands

  • For most of the commands shortl...

How to open files from better_errors with RubyMine on Linux

I recently noticed that better_errors allows you to to open files from within your favorite editor. However it was not so easy to get rubymine:// links to work on Gnome/Linux. Here is how it finally worked for me:

Step 1: Add a Desktop launcher

Add this file to ~/.local/share/applications/rubymine.desktop:

[Desktop Entry]
Version=1.0
T...

Chromedriver: Connect local chromedriver with docker

Debugging your integration tests, that run a headless Chrome inside a docker image, is tricky.

In many cases you can connect your Chrome to a remote docker container like docker-selenium, which should be the preferred way when you try to inspect a page within your integration test.

Otherwise you might be able to start your docker container with --net=host and access your local chromedriver in the host address space host.docker.internal.

If both options above don't work for you here is a...

Allow capybara to click on labels instead of inputs for checkboxes

Within Capybara you most certainly use the #check- and #uncheck-method to (un)check checkboxes.
But there's one problem, if you want to test a custom styled checkbox, which hides its <input>-Tag:

  • The methods cannot (un)check checkboxes without an visible <input>.
  • The error message will be something like: Unable to find visible checkbox "Some label" that is not disabled

Solution 1

Use the keyword argument allow_label_click: true within the method call.
So instead of check('Some label'), use `check('Some label', allow...

The numericality validator does not care about your BigDecimal precision

Looking at the source code of the validates_numericality_of validator, it becomes clear that it converts the attribute in question to either an integer or float:

if configuration[:only_integer]
  unless raw_value.to_s =~ /\A[+-]?\d+\Z/
    record.errors.add(attr_name, configuration[:message] || ActiveRecord::Errors.default_error_messages[:not_a_number])
    next
  end
  raw_value = raw_value.to_i
else
 begin
    raw_value = Kernel.Float(raw_val...

Preventing users from uploading malicious content

When you allow file uploads in your app, a user might upload content that hurts other users.

Our primary concern here is users uploading .html or .svg files that can run JavaScript and possibly hijack another user's session.

A secondary concern is that malicious users can upload executables (like an .exe or .scr file) and use your server to distribute it. However, modern operating systems usually warn before executing files that were downloaded from t...

Rails: Fixing ETags that never match

Every Rails response has a default ETag header. In theory this would enable caching for multiple requests to the same resource. Unfortunately the default ETags produced by Rails are effectively random, meaning they can never match a future request.

Understanding ETags

When your Rails app responds with ETag headers, future requests to the same URL can be answered with an empty response if the underlying content ha...

Modern CSS supports individual transform properties

tl;dr

Individual transform properties are great because they allow you to write more readable and maintainable CSS, especially when applying multiple transformations and/or when animating transforms.

For ages, CSS transforms had to be defined using the transform property. For a single transformation, this was something like transform: scale(1.5), and multiple transformations could be applied by chaining them.

.example {
  transform: scale(1.5) rotate(45deg) translateY(-50%);
}

All modern browsers (Chrome & Edge ...

Jasmine: Preventing unhandled promise rejections from failing your test

You have an async function that rejects:

async function failingFunction() {
  throw new Error("Something went wrong")
}

When you call that function in a test, your test will fail:

it('has a test', function() {
  failingFunction() // this will fail your test
})

The failure message will look like this:

Unhandled promise rejection: Error: Something went wrong

You can fix this by expecting the state of the returned promise:

it('has a test', async function() {
  await expectAsync(failingFunction()).toBeRej...

Sidekiq 7: Rate limiting with capsules

Sidekiq 7 adds a new feature called capsules.

Use cases:

  • a chrome queue limited to 1 for e.g. PDF processing to not overload the application server
  • an api queue, that limits a queue to 2 to protect the API server from too many requests in parallel

Example:

Sidekiq.configure_server do |config|
  # Edits the default capsule
  config.queues = %w[critical default low]
  config.concurrency = 5

  # Define a new capsule which ...

How to create a multiline map in SASS/SCSS

If you want to to create maps within SASS/SCSS-files, it normally works like this:

$some-map: (key1: value1, key2: value2)

However, some maps can get big really fast, if they are being used to contain all of the project's icon names and their sizes for example.
Therefore splitting a map into multiple lines, like we do it in Ruby with big hashes, would become really handy.

Unfortunately SASS doesn't support multiline maps. There has been an open issue since 2011 and it hasn't been...

Rubymine: Configure CTRL + ALT + SHIFT + c to work with "Test Source Roots"

To navigate between test and test subject Rubymine requires you to set the test root sources as Test Sources Root.

In case you are using the keyboard shortcut "CTRL + ALT + SHIFT + c" to copy the reference path + you have set the "Test Sources Root" for your test folders, you might consider setting this keyboard to "Copy From Repository Root". This will return the path `spec/foo_spec....

How Haml 6 changes attribute rendering, and what to do about it

Haml 6 was a major rewrite with performance in mind. To achieve a performance improvement of 1.7x, some design trade-offs had to be made. The most notable change might be the simplified attribute rendering.

In Haml 5, attribute rendering knew two special cases: an attribute with value true would be rendered without a value, an attribute with a falsy value would not be rendered at all. All other values would just be rendered as attribute values.

According to the Haml maintai...

Rails cache connection settings

If you're using a Redis cache in Rails (e.g. :redis_cache_store), it's possible to configure additional parameters for your Redis connection.

Example config for Rails 7.2

config.cache_store = :redis_cache_store, {
  pool: { timeout: 0.5 },
  read_timeout: 0.2, # default 1 second
  write_timeout: 0.2, # default 1 second
  # Attempt two reconnects with some wait time in between
  reconnect_attempts: [1, 5], # default `1` attempt in Redis 5+
  url: REDIS_URL,
  error_handler: ->(method:, returning:, exception:) {
    Sentry.captur...

Using Capybara finder methods with arbitrary matching conditions

Capybara has a variety of finder methods like find_button to help you look up DOM elements. There are also matchers like have_field to make expectations during tests.

These methods also have a number of options to influence the lookup. E.g. the :disabled option lets you control whether Capybara will match disabled fields.

If you have a matching condition that cannot be expressed by the existing Capybara opt...

Simple Form: Rendering errors without an appropriate attribute

Usually you add errors to :base in ActiveRecord, in case no appropriate attribute could be used to add the error.

Simple Form doesn't render errors on :base by default, but here a few options how you can render these on demand. For all the options below we use the following example with a Simple Form Bootstrap configuration:

- @user = Backend::User.new
- @user.errors.add(:base, 'First error')
- @user.errors.add...

Project management best practices: Technical debt summary

Maintaining larger projects makes it more difficult to balance refactoring and upgrade tasks according to its actual value. Consider to create and periodically maintain a summary, which helps you and your team in the decision which refactoring task should be taken next.

Template

Here is an template on how you might categorize your tasks:

| Technical debt | Estimated Efforts | Visible customer value| Customer value explained| Developer value|Developer value explained|
|-----------------------------|----------------|----------...

Rails: Assigning associations via HTML forms

Let's say we have posts with an attribute title that is mandatory.

Our example feature request is to tag these posts with a limited number of tags. The following chapters explain different approaches in Rails, how you can assign such an association via HTML forms. In most cases you want to use Option 4 with assignable values.

The basic setup for all options looks like this:

config/routes.rb

Rails.application.routes.draw do
  root "posts#index"
  resources :posts, except: [:show, :destroy]
end

**db/migrate/...

Rails: Rescuing exceptions for specific exception types

By default most exceptions in Rails will render a 500 error page and will create a new issue in your error monitoring. There are some built-in rules in Rails that

  • render a different error than 500
  • will rescue the exception and not create an issue in your error monitoring

A good example is ActiveRecord::NotFound: You don't want an exception in your error monitoring when users navigate to e.g. a blog post t...

Beware when using ActiveSupport time and date calculation methods

The pitfall

Rails Active Support provides some helpful methods for calculating times and dates, like Duration#ago or Duration#from_now. But beware when using those, because they wont give you Dates or Times but ActiveSupport::TimeWithZone instances. As the class name hints, you now have to be awa...

Rails: Composing an ETag from multiple records

Rails offers the fresh_when method to automatically compute an ETag from the given record, array of records or scope of records:

class UsersController < ApplicationController
  def show
    @user = User.find(params[:id])
    fresh_when @user
  end  
  
  
  def index
    @users = User.all.to_a
    fresh_when @users
  end
end

When your view also displays other records (typically associations), those other records should be included in the ETag. You can do so by passing an array of ETaggable objects to fresh_when.

...

Rails: Your index actions probably want strict_loading

By activating strict_loading you force developers to address n+1 queries by preloading all associations used in the index view. Using an association that is not preloaded will raise an ActiveRecord::StrictLoadingViolationError.

I think it's a good default to activate strict_loading in your controllers' #index actions. This way, when a change introduces an n+1 query, you...