RSpec: Leverage the power of Capybara Finders and Matchers for view specs

View specs are a powerful tool to test several rendering paths by their cases instead of using a more costing feature spec. This is especially useful because they become quite convenient when used with Capybara::Node::Finders and Capybara::RSpecMatchers. This allows to wirte view unit specs as you can isolate specific part...

RSpec: Composing a custom matcher from existing matchers

When you find similar groups of expect calls in your tests, you can improve readability by extracting the group into its own matcher. RSpec makes this easy by allowing matchers to call other matchers.

Example

The following test checks that two variables foo and bar (1) have no lowercase characters and (2) end with an exclamation mark:

expect(foo).to_not match(/[a-z]/)
expect(foo).to end_with('!')

expect(bar).to_not match(/[a-z]/)
expect(bar).to end_with('!')

We can extract the repeated matcher chains into a custom m...

Gatekeeping: Guide for gatekeeper

If you're responsible for gatekeeping in a projects, here is a guide, what to do.
In order to reduce the number of rejects we get from clients, we want to review all code written before it goes to the staging server.

Note: This process is tailored to our specific needs and tools at makandra. While it will certainly not apply to all (especially larger teams), we think it is a helpful starting point.


First, read the [Gatekeeping for developers](https://makandracards.com/makandra/6579-gatekeeping-guide-for...

Prefer using Dir.mktmpdir when dealing with temporary directories in Ruby

Ruby's standard library includes a class for creating temporary directories. Similar to Tempfile it creates a unique directory name.

Note:

  • You need to use a block or take care of the cleanup manually
  • You can create a prefix and suffix e.g. Dir.mktmpdir(['foo', 'bar']) => /tmp/foo20220912-14561-3g93n1bar
  • You can choose a different base directory than Dir.tmpdir e.g. `Dir.mktmpdir('foo', Rails.root.join('tmp')) => /home/user/rails_example/tmp/foo20220912-14...

Debugging SPF records

While debugging a SPF record I found spf-record.de to be very helpful.

  • it lists all IPs that are covered by the SPF record
  • shows syntax errors
  • helps you debugging errors like DNS lookup limit reached
  • it also lets you test a new SPF strings before applying it. This can save you time as you don't have to loop with operations

Also the advanced check at vamsoft.com has a very good interface to test new SPF policies.

Selenium: Fix Chrome's "Unsafe Password" Warning

tl;dr

Set profile.password_manager_leak_detection to false in your Selenium Chrome options to disable password leak detection and suppress the warning.

Problem

When running Selenium tests with recent versions of Chrome and Chromedriver (e.g., version 136+), entering “unsafe” (weak or reused) passwords in forms triggers a browser warning:

"This password has appeared in a data breach…"

This alert can break automated test runs, especially in CI/CD pipelines.

Solution

You can **disable Chrome’s password leak ...

How to write complex migrations in Rails

Rails gives you migrations to change your database schema with simple commands like add_column or update.
Unfortunately these commands are simply not expressive enough to handle complex cases.

This card outlines three different techniques you can use to describe nontrivial migrations in Rails / ActiveRecord.

Note that the techniques below should serve you well for tables with many thousand rows. Once your database tables grows to millions of rows, migration performance becomes an iss...

Rails: Using custom config files with the config_for method

You can use the config.x configuration in combination with config_for to configure global settings for your Rails 4.2+ application.

Example

In your config/application.rb assign the settings from e.g. config/settings.yml as follows:

module FooApplication
  class Application < Rails::Application
    config.x.settings = config_for(:settings)
  end
end

The config/settings.yml might look as follows:

shared: &shared
  email: info@example.com
...

Careful when using Time objects for generating ETags

You can use ETags to allow clients to use cached responses, if your application would send the same contents as before.

Besides what "actually" defines your response's contents, your application probably also considers "global" conditions, like which user is signed in:

class ApplicationController < ActionController::Base
  etag { current_user&.id }
  etag { current_user&.updated_at }
end

Under the hood, Rails generates an ETag header value like W/"f14ce3710a2a3187802cadc7e0c8ea99". In doing so, all objects from that etaggers...

VCR: Alternative way of mocking remote APIs

If you need to test interaction with a remote API, check out the VCR gem as an alternative to Webmock or stubbing hell.

The idea behind VCR is that is performs real HTTP requests and logs the interaction in a .yml file. When you run the test again, requests and responses are stubbed from the log and the test can run offline.

It's a great way to mock network requests to an external service without going through the pain of log...

Your browser might silently change setTimeout(f, 0) to setTimeout(f, 4)

When you're nesting setTimeout(f, 0) calls, your browser will silently increase the delay to 5 milliseconds after the fourth level of nesting.

This is called "timeout clamping" and defined in the HTML spec:

If nesting level is greater than 5, and timeout is less than 4, then set timeout to 4.

Timeouts are clamped harder in background tabs

On a similar note, all major browsers have implemented throttling rules for setInterval and setTimeout calls from tabs...

How to: Benchmark an Active Record query with a Ruby script

Recently I needed to benchmark an Active Record query for performance measurements. I wrote a small script that runs each query to benchmark 100 times and calculates the 95th percentile.

Note: The script requires sudo permissions to drop RAM cache of PostgreSQL. Due to the number of iterations it was impractical to enter my user password that often. And I temporary edited my /etc/sudoers to not ask for the sudo password with johndoe ALL=(ALL) NOPASSWD: ALL.

# Run this script with e.g. `rails ru...

RSpec: automatic creation of VCR cassettes

You can configure VCR to automatically record/replay cassettes for any RSpec example tagged as :vcr or vcr: true.

  • If a spec is not tagged with :vcr, VCR will complain about any attempted HTTP request. This is the default behaviour. If you want to turn this off temporarily, e.g. to communicate with an actual API while writing a new spec, simply add the line c.allow_http_connections_when_no_cassette = true to the VCR.configure-block.

  • If a spec is tagged with :vcr, a cassette with an automatically determined name will be gener...

Error handling in DOM event listeners

When an event listener on a DOM element throws an error, that error will be silenced and not interrupt your program.

In particular, other event listeners will still be called even after a previous listener threw an error. Also the function that emitted the event (like element.dispatchEvent() or up.emit()) will not throw either.

In the following example two handlers are listening to the foo event. The first handler crashes, th...

RubyMine: Efficiently filtering results in the "Finder" overlay

RubyMine comes with a nice way to grep through your project's files: The finder (ctrl + shift + f). Don't be discouraged about the notice 100+ matches in n+ files if your searched keyword is too general or widely used in your project.

Image

RubyMine comes with a few ways to narrow down the resulting list, don't hesitate to apply those filters to speed up your search. Your keybinding might vary based on your personal settings.

File mask (alt + k)

If you already know the file extension of your ...

How to use Active Job to decouple your background processing from a gem

In a web application you sometimes have tasks that can not be processed during a request but need to go to the background.
There are several gems that help to you do that, like Sidekiq or Resque.

With newer Rails you can also use ActiveJob as interface for a background processing library. See here for a list of supported queueing adapters.
For ...

Using the ActiveSupport::BroadcastLogger

The ActiveSupport::BroadcastLogger allows you to log to multiple sinks. You know this behavior from from the rails server command, that both logs to standard out and the log/development.log file.

Here is an example from the ActiveSupport::BroadcastLogger API:

stdout_logger = ActiveSupport::Logger.new(STDOUT)
file_logger = ActiveSupport::Logger.new("development.log")
broadcast = ActiveSupport::BroadcastLogger.new(stdout_logger, file_logger)

broadcast.i...

Integrating or upgrading makandra-rubocop

Introduction

Most of the time it is a tedious task to apply a code style guide to an existing code base as there are likely to be a lot of conflicts. At makandra we are using makandra-rubocop to have code style checks. Here is some advice on how to add makandra-rubocop efficiently.

Note

RubyMine by default has a Rubocop inspection with rules that we don't always agree with. We recommend replacing this with makandra-rubocop or disabling the inspection.
...

Enumerators in Ruby

Starting with Ruby 1.9, most #each methods can be called without a block, and will return an enumerator. This is what allows you to do things like

['foo', 'bar', 'baz'].each.with_index.collect { |name, index| name * index }
# -> ["", "bar", "bazbaz"]

If you write your own each method, it is useful to follow the same practice, i.e. write a method that

  • calls a given block for all entries
  • returns an enumerator, if no block is given

How to write a canonical each method

To write a m...

Open UI: Future development in web components and controls

tl;dr When browsers start to adapt proposals from Open UI, it might not be necessary to use any 3rd party libraries to have nice components and controls in web applications e.g. selects. It would require only a minimum of CSS and Javascript to get them working and looking good.

The purpose of the Open UI, a W3C Community Group, is to allow web developers to style and extend built-in web UI components and controls, such as <select> dropdowns, checkboxes, radio buttons, and date/color pickers.

To do that, we’ll need to fully speci...

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:

...

A simple example with a GIN index in Rails for optimizing a ILIKE query

You can improve your LIKE / ILIKE search queries in PostgreSQL by adding a GIN index with an operate class ("opclass") to split the words into trigrams to the required columns.

Example

class AddSearchTextIndexToUsers < ActiveRecord::Migration[7.1]
  def change
    enable_extension 'pg_trgm'

    add_index :users, :search_tex...

PostgreSQL: Be careful when creating records with specific ids

In tests, it is sometimes useful to create records with specific ids. On PostgreSQL this can cause problems:

Usually, PostgreSQL uses an "autoincrement" sequences to provide sequential ids for new database rows. However, these sequences will not increment if you insert a record "by hand". This will cause an error:

record = Record.create!
record.id                             # => 100, next automatic id will be 101
Record.create!(id: record.id + 1)     # okay, but next automatic id will still be 101
Record.create!                       ...

HTTP Client in RubyMine

RubyMine has a HTTP Client that can be useful to test web APIs.
Just create a .http scratch file an write your request in it.
The request can then be executed with the "Run all requests in File" button above the file.

Some alternatives:

The format for request is like this:

Method Request-URI HTTP-Version
Header-field: Heade...