Checklist for Implementing Design

We have a long-standing checklist for merge requests. However, it hardly matches the intricate requirements for design. This checklist fills the gap.

Before starting implementing, look at all designs: are there components similar to yours? Have they already been implemented? Can you build on this prior art when implementing yours?

Checklist: I confirm my design implementation

  • has been tested manually by me
  • adheres to the code style of the project (e.g. BEM)
  • avoids "magic numbers" (don't say e.g. ...

How to fix parallel_tests with Redis on powerful machines

When you have a powerful machine with many CPU cores, you might run into an error like

ERR DB index is out of range (Redis::CommandError)

This is because Redis defaults to at most 16 databases (0 to 15) and running tests in parallel might exceed that (your tests might run on databases 1..n or 2..(n+1)).

You can increase that limit:

  1. Get number of CPUs of your machine.

    nproc --all
    
  2. Open up Redis configuration file.

    sudo vim /etc/redis/redis.conf
    
  3. Find databases row and increase it, e.g. set to 32:

...

How to evaluate CSS media queries in JavaScript

To make CSS rules dependent on the screen size, we use media queries:

@media (max-width: 500px) {
  // rules for screen widths of 500px or smaller
}

Browsers will automatically enable and disable the conditional rules as the screen width changes.

To detect responsive breakpoints from JavaScript, you may use the global matchMedia() function. It is supported in all brow...

Jasmine: Testing complex types for equality

Jasmine comes with two matchers that test for equality. The first is toBe:

expect(first).toBe(second)

toBe passes when first === second. Unfortunately this is useless for non-primitive values because JavaScript is a horrible language.

However, Jasmine comes with another matcher toEqual:

expect(first).toEqual(second)

This matcher behaves as a human would expect for types like the following:

  • Arrays
  • Objects
  • Nested array/object constructs
  • Regular expressions...

RSpec: Run a single spec (Example or ExampleGroup)

RSpec allows you to mark a single Example/ExampleGroup so that only this will be run. This is very useful when using a test runner like guard.

Add the following config to spec/spec_helper.rb:

RSpec.configure do |config|
  # These two settings work together to allow you to limit a spec run
  # to individual examples or groups you care about by tagging them with
  # `:focus` metadata. When nothing is tagged with `:focus`, all examples
  # get run.
  config.filter_run_including :focus => true
  config.run_all_when_everything_filtere...

Heads up: Quering array columns only matches equally sorted arrays

Given you have an array column like this:

create_table "users", force: :cascade do |t|
  t.integer "movie_ids", default: [], array: true
end

You might think that the following queries yield the same result:

User.where(movie_ids: [16, 17])
User.where(movie_ids: [17, 16])

Turn's out - they are not! They do care about array ordering more than I do.

To query for identical arrays independent of their order you have to either:

  1. Sort both the query and database content. If you're on Rails 7.1 you can use the new [`normal...

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

Heads Up: Selenium 4 uses a binary to determine the chromedriver

I recently stumbled over a problem that my feature tests broke in CI because of a mismatching chromedriver version.

In this specific project we have a fixed Chromium version in a Debian 12 environment instead of Chrome. The tests however used a recent chrome version instead.

$ chromedriver --version
ChromeDriver 117.0.5938.149 (e3344ddefa12e60436fa28c81cf207c1afb4d0a9-refs/branch-heads/5938@{#1539})
$ chromium --version
Chromium 117.0.5938.149 built on Debian 12.1, running on Debian 12.1

> WARN Selenium [:selenium_manager] The chromed...

RSpec: Scoping custom matchers to example groups

When you find yourself in the situation that you would like to define a custom matcher in your specs, but you do not want to define a global matcher since you need it only for your specific test, there are two ways to do it:

Custom matcher for a single group

If you're only going to include a matcher once, you can also use the matcher macro within an example group:

describe "group" do
  
  matcher :be_just_like do |expected|
    match {|ac...

How to write modular code

Or: How to avoid and refactor spaghetti code

Please note that I tried to keep the examples small. The effects of the methods in this card are of course much more significant with real / more complex code.

What are the benefits of more modular code?

Code is written once but read often (by your future self and other developers who have to understand it in order to make changes for example). With more modular code you reduce the scope of what has to be understood in order to change something. Also, naming things gives you the opportunity t...

Use <input type="number"> for numeric form fields

Any form fields where users enter numbers should be an <input type="number">.

Numeric inputs have several benefits over <input type="text">:

  • On mobile or tablet devices, number fields show a special virtual keyboard that shows mostly digit buttons.
  • Decimal values will be formatted using the user's language settings.
    For example, German users will see 1,23 for <input type="number" value="1.23">.
  • Values in the JavaScript API or when submitting forms to the server will always use a point as decimal separator (i.e. "1.23" eve...

How to push to Git without running CI on GitLab CI, GitHub Actions, or Travis CI

If a project ist configured to spawn CI runners for tests or deployment when pushing to the Repo, a habit of pushing WIP commits regularly may conflict with that.
Here are two solutions that allow you to keep pushing whenever you feel like it.

Special commit message

To skip a CI run, simply add [ci skip] or [skip ci] to your commit message. Example:

git commit -m "wip authentication [ci skip]"

Git push options (GitLab)

In addition to that, GitLab CI supports Git push options. Instead of changing your commit message, ...

Bash: How to count and sort requests by IP from the access logs

Example

87.140.79.42 - - [23/Jan/2024:09:00:46 +0100] "GET /monitoring/pings/ HTTP/1.1" 200 814 "-" "Ruby"
87.140.79.42 - - [23/Jan/2024:09:00:46 +0100] "GET /monitoring/pings/ HTTP/1.1" 200 814 "-" "Ruby"
87.140.79.41 - - [23/Jan/2024:09:00:46 +0100] "GET /monitoring/pings/ HTTP/1.1" 200 814 "-" "Ruby"
87.140.79.42 - - [23/Jan/2024:09:00:46 +0100] "GET /monitoring/pings/ HTTP/1.1" 200 814 "-" "Ruby"

Goal

Count and sort the number of requests for a single IP address.

Bash Command

awk '{ print $1}' test.log | sort...

Ruby: How to use global variables for a conditional debugger

You can share a state in Ruby with global variables. Even if you should avoid them whenever possible, for debugging an application this could be temporary quite handy.

Example:

class User

  after_save { byebug if $debug; nil }

  def lock
   self.locked = true
   save
  end

end
Rspec.describe User do

  let(:user) { create(:user) } 

  before do
   # Many users are created and saved in this hook, but we don't want the debugger to stop for them...

ActiveRecord: When aggregating nested children, always exclude children marked for destruction

When your model is using a callback like before_save or before_validation to calculate an aggregated value from its children, it needs to skip those children that are #marked_for_destruction?. Otherwise you will include children that have been ticked for deletion in a nested form.

Wrong way

class Invoice < ApplicationRecord
  has_many :invoice_items
  accepts_nested_attributes_for :invoice_items, :allow_destroy => true # the critical code 1/2
  before_save :calculate_and_store_amount                              # the crit...

Heads up: Byebug has problems with zeitwerk

I encountered a unlucky behavior of byebug 11.1.3 (the most recent version at time of writing) when using it with Rails 6 and it's new autoloading component, zeitwerk. There already is a issue for that, so I hope it will be fixed with a future release.

The following test succeeds:

  context 'factories' do
    let(:test_case) { FactoryBot.create(:test_case) }
    it 'are valid' do
      expect(test_case).to be_valid
    end
  end

But when I did the same in byebug the foll...

RSpec 3 allows chaining multiple expectations

When you are using lambdas in RSpec to assert certain changes of a call, you know this syntax:

expect { playlist.destroy }.to change { Playlist.count }.by(-1)

While you can define multiple assertions through multiple specs, you may not want to do so, e.g. for performance or for the sake of mental overhead.

Multiple expectations on the same subject

RSpec allows chaining expectations simply by using and.

expect { playlist.destroy }
  .to change { Playlist.count }.by(-1)
  .and not_change { Video.count }

...

Why two Ruby Time objects are not equal, although they appear to be

So you are comparing two Time objects in an RSpec example, and they are not equal, although they look equal:

expected: Tue May 01 21:59:59 UTC 2007,
     got: Tue May 01 21:59:59 UTC 2007 (using ==)

The reason for this is that Time actually tracks fractions of a second, although #to_s doesn't say so and even though you probably only care about seconds. This means that two consecutive calls of Time.now probably return two inequal values.

Consider freezing time in your tests so it is not dependent on the speed of the executi...

JavaScript: Sharing content with the native share dialog

Mobile Chrome and Safari support the "web share API" which allow you to use the native share functionality of an Android or iOS phone. Some desktop OSs like Windows or MacOS also support native share dialogs. See Can I Use for a detailed support matrix.

When clicking a share button using this API, the browser will automatically show all installed applications that support content sharing, such as Whatsapp, Facebook, Twitter, e-mail etc.

The API is extremely simple to use:

if ...

Devise: Don't forget to lock users with soft delete

There are two ways to lock a user in devise.

  1. Using the lockable module
  2. Customizing the user account status validation when logging in.

It depends on your requirements which methods works best.

Locking a user on soft delete

We recommend to use option 2 when you want to couple the lock to the m...

Using Rationals to avoid rounding errors in calculations

Ruby has the class Rational which allows you to store exact fractions. Any calculation on these variables will now use fractional calculations internally, until you convert the result to another data type or do a calculation which requires an implicit conversion.

Example use case:

Lets say you want to store the conversion factor from MJ to kWh in a variable, which is 1/3.6. Using BigDecimals for this seems like a good idea, it usually helps with rounding errors over a float, but the...

Rails: Preloading associations in loaded records

Sometimes you want to fetch associations for an ActiveRecord that you already loaded, e.g. when it has deeply nested associations.

Edge Rider gives your models a static method preload_associations. The method can be used to preload associations for loaded objects like this:

class UsersController < ApplicationController
  def show
    @user = User.find(params[:id])
    @user.preload_associations(threads: { posts: :author }, messages: :sender)
  end
end

The attached initializers re...

RSpec: Efficiently rerunning failed examples during development

Note

Don't use reruns as a mean to work around flaky tests. You should always try to fix those instead of rerunning them regularly.

Setup

Configure RSpec to persist the result of your test runs to a file. This is necessary to be able to rerun examples.
Add this to your spec/spec_helper.rb :

  config.example_status_persistence_file_path = 'spec/examples.txt'

Rerun all failed examples using --only-failures

bundle exec rspec --only-failures (or `...

RSpec: Applying stubs only within a block

When you mocked method calls in RSpec, they are mocked until the end of a spec, or until you explicitly release them.

You can use RSpec::Mocks.with_temporary_scope to have all mocks applied inside a block to be released when the block ends.
Example:

RSpec::Mocks.with_temporary_scope do
  allow(Rails).to receive(:env).and_return('production'.inquiry)
  puts Rails.env # prints "production"
end
puts Rails.env # prints "test"

Note that, when overriding pre-existing mocks inside the block, they are not reverted to the previously ...