Double loading issue with Ruby default gems

Ruby includes many standard gems that are bundled into the Ruby installation. Here is an example for the gem strscan that will be displayed as default:

gem list strscan     

*** LOCAL GEMS ***

strscan (default: 3.0.1)

It is still possible to have newer version of a gem installed beside the default version:

gem install strscan  
Fetching strscan-3.0.3.gem
Building native extensions. This could take a while...
Successfully installed strscan-3.0.3
1 gem installed
gem list strscan   

*** LOC...

How to debug issues with zeitwerk and Rails

In case you have trouble with the zeitwerk autoloader, you can check out the documentation Autoloading and Reloading Constants and Classic to Zeitwerk HOWTO for some debugging hints.

For myself it was useful to print the registered constants and the file references during the boot. Therefore you need to add Rails.autoloaders.log! at the end of your config/application.rb file. You could also run `bin...

Waiting for page loads and AJAX requests to finish with Capybara

If you're using the Capybara webdriver, steps sometimes fail because the browser hasn't finished loading the next page yet, or it still has a pending AJAX request. You'll often see workarounds like

When I wait for the page to load
Then ...

Workarounds like this do not work reliably, will result in flickering tests and should be avoided. There is no known reliable way to detect if the browser has finished loading the page.

Solution

Instead you should wait until you can observe the result of a page load. E.g. if y...

ActiveType 1.2 supports "change_association"

With ActiveType 1.2 you can modify associations (has_many etc.) after they have been defined.

One common use case for this is to change an association inside a form model, like this:

class Credential < ActiveRecord::Base
end

class User < ActiveRecord::Base
  has_many :credentials
end

class SignUpCredential < ActiveType::Record[Credential]
end

class SignUp < ActiveType::Record[User]
  change_association :credentials, class_name: 'SignUpCredential'
end

Now, if you load `credentials...

CSS: Don't target multiple vendor-prefixed pseudo-elements in a single rule

Some pseudo-elements need to be addressed with vendor prefixes. E.g. ::selection is not supported by Firefox, you need to use ::-moz-selection instead.

What you cannot do is to define a single CSS rule to address both the standard and vendor-prefixed form:

::selection, ::-moz-selection {
  background-color: red;
}

This rule will be ignored by all browsers. The reason is that if a browser doe...

Rails: namespacing models with table_name_prefix instead of table_name

When you want to group rails models of a logical context, namespaces are your friend. However, if you have a lot of classes in the same namespace it might be tedious to specify the table name for each class seperately:

class Accounting::Invoice < ApplicationRecord
  self.table_name = 'accounting_invoices'
  ...
end

class Accounting::Payment < ApplicationRecord
  self.table_name = 'accounting_payments'
  ...
end

A replacement for the self.table_name-assignment is the table_name_prefix in the module definition:

modu...

Project maintenance: four levels of code quality

Code quality can be measured in four levels:

  1. (Working code)
  2. Reliable code (minimum)
  3. Readable code (ok for short-lived code)
  4. Changeable code (standard level)

The code quality of a project directly impacts its maintainability.

Generally you should aim for level 3. If the code will stay for less than a few months, it may stay at level 2. Never go below level 1.

0. Working code

You have implemented that feature and it works. Congrats! You have reached level zero, which means three levels of code quality lie ahead.

First, m...

Rails: Remove Blank Values from Collections

tl;dr

Since Rails 6.1+ you can use .compact_blank or .compact_blank! to remove blank values from collections (e.g. arrays).

Remove nil values from an array

['foo', nil].compact
# => ['foo']

# You can use the splat operator to ignore nil values when constructing an array
['foo', *nil]
# => ['foo']

Remove blank values from collections

Array

array = [1, "", nil, 2, " ", [], {}, false, true]

# Any Rails version
array.reject(&:blank?)
# => [1, 2, true]

# Since Rails 6.1+
array.compact_blank
# ...

Ruby constant lookup: The good, the bad and the ugly

In Ruby, classes and modules are called constants. This card explains how Ruby resolves the meaning of a constant.

The good

E. g. in the following example, Array could mean either Foo::Array or simply Array:

class Foo
  def list
    Array.new
  end
end

What Ruby does here is to see if the name Array makes sense inside of Foo::, and if that fails, resolves it to ::Array (without a namespace).

The bad

This is relevant for old Ruby versions. Ruby 2.5+ removes top-level constant lookup whi...

CSS Support Guide for Email Clients

CSS support in major e-mail clients is horrible.

This will give you an overview what you will not be able to use across all clients.

See also

Postgresql: Paginate and count in one query using window functions

When paginating records, we usually need to know the number of total records in order to render pagination links. Popular pagination libraries like will_paginate or Kaminari do this for us by simply issuing an extra query, like this:

SELECT post.* FROM posts LIMIT 20 OFFSET 100;

SELECT COUNT(*) FROM posts;   

This is fine most of the time. But rarely, you might have very complicated WHERE conditions or a subquery that takes time to run. In thes...

JavaScript: Testing whether the browser is online or offline

You can use the code below to check whether the browser can make connections to the current site:

await isOnline() // resolves to true or false

The code

The isOnline() function below checks if you can make real requests by re-fetching your site's favicon. If the favicon cannot be downloaded within 6 seconds, it considers your connection to be offline.

async function isOnline({ path, timeout } = {}) {
  if (!navigator.onLine) return false

  path ||= document.querySelect...

How to solve Selenium focus issues

Selenium cannot reliably control a browser when its window is not in focus, or when you accidentally interact with the browser frame. This will result in flickering tests, which are "randomly" red and green. In fact, this behavior is not random at all and completely depends on whether or not the browser window had focus at the time.

This card will give you a better understanding of Selenium focus issues, and what you can do to get your test suite stable again.

Preventing accidental interaction with the Selenium window
--------------------...

Regex: Be careful when trying to match the start and/or end of a text

Ruby has two different ways to match the start and the end of a text:

  • ^ (Start of line) and $ (End of line)
  • \A (Start of string) and \z (End of string)

Most often you want to use \A and \z.

Here is a short example in which we want to validate the content type of a file attachment. Normally we would not expect content_type_1 to be a valid content type with the used regular expression image\/(jpeg|png). But as ^ and $ will match lines, it matches both content_type_1 and content_type_2. Using \A and \z will wo...

Squashing several Git commits into a single commit

This note shows how to merge an ugly feature branch with multiple dirty WIP commits back into the master as one pretty commit.

Squashing commits with git rebase

What we are describing here will destroy commit history and can go wrong. For this reason, do the squashing on a separate branch:

git checkout -b squashed_feature

This way, if you screw up, you can go back to your original branch, make another branch for squashing and try again.

Tip

If you didn't make a backup branch and something ...

You are not using filter_map often enough

Somewhat regularly, you will need to filter a list down to some items and then map them to another value.

You can of course chain map and compact, or select/filter and map, but Ruby 2.7 introduced a method for this exact purpose: filter_map.

So instead of

>> [1, 2, 3, 4, 5, 6].map { |i| i * 2 if i.even? }.compact
=> [4, 8, 12]

or

>> [1, 2, 3, 4, 5, 6].select(&:even?).map { |i| i * 2 }
=> [4, 8, 12]

you can just do

>> [1,...

Josh McArthur: Fancy Postgres indexes with ActiveRecord

I recently wanted to add a model for address information but also wanted to add a unique index to those fields that is case-insensitive.
The model looked like this:

create_table :shop_locations do |t|
  t.string :street
  t.string :house_number
  t.string :zip_code
  t.string :city
  t.belongs_to :shop
end

But how to solve the uniqueness problem?

Another day, another undocumented Rails feature!

This time, it’s that ActiveRecord::Base.connection.add_index supports an undocumented option to pass a string argument as the v...

ruby-sass: Do not use comments between selector definitions

Sass lets you easily specify multiple selectors at once like this:

.some-block
  &.has-hover,
  &:hover
    outline: 1px solid red

This will add a red outline on either real hover or when the has-hover class is present. However, adding a comment will void the definition of that line:

.some-block
  &.has-hover, // From hoverable.js <-- DON'T
  &:hover
    outline: 1px solid red

... will simply drop the &.has-hover part in ruby-sass(deprecated). [sassc](https://rubygems.org/g...

Rails: How to find records with empty associations

Imagine these models and associations:

class Deck < ApplicationRecord
  has_many :cards
end

class Card < ApplicationRecord
  belongs_to :deck, optional: true
end

Now you want to find all Decks without any Card or all Cards without a Deck.

Rails 6.1+

Rails 6.1 introduced a handy method ActiveRecord#missing to find records without given associations.

Deck.where.missing(:cards)
SELECT "decks".*
FROM "dec...

Do not forget mailer previews

When changing code in mailers, updating the corresponding mailer preview can be forgotten very easily.

Mailer previews can be tested like other code as well and I sometimes add the following tests to test suites:

# Make sure to require the previews
Dir[Rails.root.join('spec/mailers/previews/*.rb')].each { |file| require(file) }


ActionMailer::Preview.all.index_with(&:emails).each do |preview, mails|
mails.each do |mail|
    describe preview do
      specify "##{mail} works" do
        expect { preview.call(mail...

Rails: Default HTTP status codes when redirecting

When redirecting you should take care to use the right HTTP status code.

From controllers

When redirecting from a controller, the default status code is 302 Found (aka Moved Temporarily):

red...

Show a JS fiddle in fullscreen

If you have a JS fiddle, you can open it in fullscreen by appending /show to the URL.

Example: https://jsfiddle.net/b275g910/3 => https://jsfiddle.net/b275g910/3/show

Project management best practices: Budget control

When starting a project we always make a good estimate of all known requirements, and plan budgets and available developers accordingly.

Requirements change. Budgets usually don't.

To make sure a project stays on track, we update our estimates once a month and compare them to the remaining budget. If this doesn't match any more, we have to act.

To update an estimate, do the following:

  • Start with the most recent estimate for the project.
  • Which stories have been completed? Set their estimate to zero.
  • Have any requirements cha...

Choosing the right gems for your project

Adding a gem means you take over the liability towards the external code.

Checklist

Based on "To gem, or not to gem":

  • Gem is really needed (prefer writing your own code for simple requirements without many edge cases)
  • Gem is tested well (coverage and quality)
  • Gem has a good code quality
  • Gem's licence fits to the project requirement
  • Try to avoid gems that do much more than your requireme...