Migrating from Elasticsearch to Opensearch: searchkick instructions (without downtime!)

General

A general overview about why and how we migrate can be found under Migrating from Elasticsearch to Opensearch

This card deals with specifics concerning the use of searchkick.

Step 1: Make Opensearch available for Searchkick

In your Gemfile

# Search
gem 'searchkick'                   # needs to be > 5, to use Opensearch 2
gem 'elasticsearch'
gem 'opensearch-ruby'

in config/initializers/searchkick.rb (or wherever you have configured your Searchkick settings) add:

SEARCHKICK_CLIENT_T...

RailsStateMachine 2.2.0 released

  • Added: State machine can now use the :prefix-option to avoid name collision if you define multiple state machines on the same model, and use state names more than once
    • Previously state_machine-definitions like this:
        class Form
        
          state_machine :first_wizard_stage do
            state :completed
          end
      
          state_machine :second_wizard_stage do
            state :completed
          end
          
        end
      
      would produce the following warning:
        rails_state_machine-2.1....
      

How to: Upgrade CarrierWave to 3.x

While upgrading CarrierWave from version 0.11.x to 3.x, we encountered some very nasty fails. Below are the basic changes you need to perform and some behavior you may eventually run into when upgrading your application. This aims to save you some time understanding what happens under the hood to possibly discover problems faster as digging deeply into CarrierWave code is very fun...

Whitelists and blacklists

The following focuses on extension allowlisting, but it is the exact same thing for content type allowlisting with the `content_ty...

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

Spreewald, Cucumber: Selector for the nth element

The recommended additional setup of the spreewald gem, a useful set of cucumber steps, includes adding a file for defining custom selectors which can be used as prose within steps:

When I follow "Edit" within the controls section

Where the controls section can be any arbitrary defined css selector within selectors.rb


Often it can be useful to select the nth element of a specific selector. Luckily, this can ...

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

How to work around selenium chrome missing clicks to elements which are just barely visible

Chromedriver (or selenium-webdriver?) will not reliably scroll elements into view before clicking them, and actually not click the element because of that.

We've seen this happen for elements which are just barely in the viewport (e.g. the upper 2px of a 40px button). Our assumption is that the element is considered visible (i.e. Capybara::Selenium::ChromeNode#visible? returns true for such elements) but the Selenium driver wants to actually click the center of the element which is outside of the viewport.

We don't know who exactly i...

Split your parallel tests by execution time and keep execution logs up to date

Both knapsack and parallel_tests have the option to split groups by historic execution time. The required logs for this might be outdated since you manually have to update and push them into your repository.

The following card includes an option how you can keep them consistently up to date with no extra effort locally and/or remotely.

How to always split by execution logs

Parallel Tests

The parallel_tests gem has the option flag `--group...

Lightning Talk: Coverage based Test Case Prioritization in Ruby on Rails

For my computer science bachelor's thesis I programmed and evaluated a CLI Test Case Prioritization (TCP) tool for makandra. It has been written as a Ruby Gem and was tested and evaluated against one Ruby on Rails project. This card will summarize and present the research results, the evaluation and the programmed CLI tool.

The code has been published for educational purposes on GitHub. The german bachelor's thesis has also been included for download at the end.


...

Chromedriver issue #4550 breaks the user agent for device emulation via device name

Newest versions of Chromedriver breaks the user agent for device emulation via device name. In previous versions the user agent of the emulated device was set. In the newest versions the user agent differs from the emulated device.

In Capybara an affected config looks like following:

Capybara.register_driver :mobi...

Capybara: How to find the focused element

Capybara allows you to filter elements that are focused.

page.find(:fillable_field, focused: true) # Filtering only fillable inputs for performance reasons
page.find(:xpath, '//*', focused: true) # Filter all fields

Legacy approach

In older version, it was possible to use the :focus pseudo-class. This seems not to work in newer versions anymore.

find(':focus')

How to transition the height of elements with unknown/auto height

If you want to collapse/expand elements with dynamic content (and thus unknown height), you can not transition between height: 0 and height: auto.

Doing it properly, with modern CSS features

In the past, you might have resorted to bulky JavaScript solutions or CSS hacks like transitioning between max-height: 0 and max-height: 9999px. All of them were awkward and/or have several edge cases.

With modern CSS, there is actually a way to do it properly:
Just use a display: grid container which transitions its grid row height betwe...

Use -webkit-line-clamp to natively truncate long (multi-line) texts with an ellipsis

Note: You won't need this for single lines of text. In this case it is better to just use the text-overflow property: Use CSS "text-overflow" to truncate long texts

You can use -webkit-line-clamp in your CSS/SASS to natively render an ellipsis (...) after a specific amount of lines for a multi-line text in your HTML.
Earlier, it was necessary to implement JavaScript solutions like Superclamp.js to enable this because the browser support has been rather limited...

Using git patchfiles to speed up similar implementation tasks

Sometimes you'll find yourself with a set of tasks that require similar code for different models. For example, if you start working at a new application that allows CRUDing pears and apples, each commit might look similar to this:

commit 41e3adef10950b324ae09e308f632bef0dee3f87 (HEAD -> ml/add-apples-12345)
Author: Michael Leimstaedtner <michael.leimstaedtner@acme.com>
Date:   Fri Aug 11 09:42:34 2023 +0200

    Add Apples as a new fruit

diff --git a/app/models/apple.rb b/app/models/apple.rb
new file mode 100644
index 0000000..a51...

Balance your texts today with text-wrap: balance

So you have a heading that is just barely wider than the container it should fit into, and it wraps a single word to a new line and it's not really pretty?
Cry no more, for you can use text-wrap: balance today to fix that. At least in some browsers.

When browsers encounter a text-wrapping element with text-wrap: balance style, they will try breaking to a new line sooner, if it balances out the width of lines.

text-wrap: unset text-wrap: balance
Image ![Image](/makandr...

Git: Splitting up changes into several commits

Splitting up commits makes the process of reviewing often easier, since you can create several merge requests or review every commit one by one.

So when you find out that you have portions of the code that you initially didn't intend to change or when you do some refactoring along the current changes, you can use one of the following processes to split up the changes into several commits in a logical order:

#1 Splitting up the last n commits into m commits
#2 Adding changes to a previous commit
2.1 While adding new changes
2.2 S...

Geordi 9.6.0 released: new "did you know?" feature

Geordi will now suggest related commands and options (hand-picked). If a command has suggestions, a single suggestion is printed with a 10% probability after a successful command.

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