How to allow testing beforeunload confirmation dialogs with modern ChromeDrivers
Starting with ChromeDriver 127, if your application displays a beforeunload confirmation dialog, ChromeDriver will immediately close it. In consequence, any automated tests which try to interact with unload prompts will fail.
This is because ChromeDriver now follows the W3C WebDriver spec which states that any unload prompts should be closed automatically.
However, this applies only to "HTTP" test sessions, i.e. what you're using by default. The spec also defines that bi-directional test se...
High-level data types with "composed_of"
I recently stumbled upon the Rails feature composed_of. One of our applications dealt with a lot of addresses and they were implemented as 7 separate columns in the DB and Rails models. This seemed like a perfect use case to try out this feature.
TLDR
The feature is still a VERY leaky abstraction. I ran into a lot of ugly edge cases.
It also doesn't solve the question of UI. We like to use
simple_form. It's currently not possible to simply write `f...
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...
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...
Jasmine: Use `throwUnless` for testing-library's `waitFor`
testing-library are widely used testing utilities libraries for javascript dependent frontend testing. The main utilities provided are query methods, user interactions, dom expectations and interacting with components of several frontend frameworks, which allows us to worry less about the details happening in the browser and focus more on user centric tests instead!
Some of the time you will find a necessity to use methods like [waitFor](https://testing-library.com/docs/dom-testing-library/api-async/...
How to combine unknown CSS selectors
You are given two CSS selectors that you do not control. How can you build a new selector that matches both of them?
item_selector = 'div'
active_selector = '.is-active'
Can't I just concat these selectors?
# Bad
new_selector = "#{item_selector}#{active_selector}"
# => "div.is-active"
Don't! This will break as soon as one of the selectors is actually a selector list.
item_selector = 'div, span, p' # <- Selector list
new_selector # => "div, span, p.is-active" (wrong)
Solution
Wrap both selectors ...
JavaScript: Listening to a class getting added
Reacting on a class getting added can be done with a mutation observer. Example:
const items = document.querySelectorAll('.item')
const expectedClass = 'active'
const activeObserver = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.target.classList.contains(expectedClass) {
// Do something
}
})
})
items.forEach(item => activeObserver.observe(item, { attributes: true, attributeFilter: ['class'] }))
Note that this is not a generic solution – it makes a few assumptions to simplif...
How to speed up JSON rendering with Rails
I was recently asked to optimize the response time of a notoriously slow JSON API endpoint that was backed by a Rails application.
While every existing app will have different performance bottlenecks and optimizing them is a rabbit hole of arbitrary depth, I'd like to demonstrate a few techniques which could help reaching actual improvements.
The baseline
The data flow examined in this card are based on an example barebone rails app, which can be used to reproduce the r...
How to query GraphQL APIs with Ruby
While most Rails Apps are tied to at least one external REST API, machine-to-machine communication via GraphQL is less commonly seen. In this card, I'd like to give a quick intro on how to query a given GraphQL API - without adding any additional library to your existing app.
Core aspects of GraphQL
Interacting with GraphQL feels a bit like querying a local database. You are submitting queries to fetch data in a given structure (like SELECT in SQL) or mutations to alter the database (similar to POST/PUT/DELETE in REST). You can ...
Simple debounce in vanilla JavaScript
Debouncing a method call delays its execution until after a specified time has passed.
If it's called again before that time has passed, its execution is delayed again.
This technique is commonly used to improve performance when code would be run more often than it needs to.
One example for that are scroll event handlers in JavaScript: You want to react to a user scrolling, but it's enough to do that when they have stopped scrolling.
Here is a small JavaScript function that you can use for that:
function debounce(callback...
Ruby: Different ways of assigning multiple attributes
This card is a short summary on different ways of assigning multiple attributes to an instance of a class.
Using positional parameters
Using parameters is the default when assigning attributes. It works good for a small number of attributes, but becomes more difficult to read when using multiple attributes.
Example:
class User
def initialize(salutation, first_name, last_name, street_and_number, zip_code, city, phone_number, email, newsletter)
@salutation = salutation
@first_name = first_name
@last_name = last_nam...
Capistrano: Configure environment specific array attributes
Using Capistrano, we usually have some array configurations in the config/deploy.rb file, like set :linked_files, %w[config/database.yml], so in this case we don't have to manage the database configuration individually on every server.
In a specific case, one of our projects supports sign in by SAML, but not every deploy target has this feature activated. Here comes a nice handy Capistrano feature, which lets us modify the default configuration for individual env...
Rails I18n scope for humanized attribute names
ActiveModel classes have a class method .human_attribute_name. This returns a human-readable form of the attribute:
Person.human_attribute_name(:first_name) # => "First name"
By default Rails will use String#humanize to format the attribute name, e.g. by replacing underscores with spaces and capitalizing the first word. You can configure different translation in your I18n locales, e.g. in config/locales/en.yml:
en:
activerecord:
attributes...
The Framework Field Guide - Fundamentals | Unicorn Utterances
I used two lab days to read the The framework field guide - Fundamentals, the first of a three part series to learn the basics of frontend technologies. I can highly suggest it for learning the fundamentals. 'The framework field guide' is written by Unicron Utterances and there side has many high quality articles on web development and computer science related to programming.
[The Framework Field Guide](https://unicorn-ut...
Active Record: Never use optional with a symbol, lambda or proc
tl;dr
Do not use the option
optionalon association declarations with a symbol, lambda or proc.
Explanation
Association declarations like belongs_to support the option optional. This option does not support symbols, lambdas or procs. If you do so, this will always result in optional: true. So your records can miss a presence validation if optional is used with a symbol, lambda or proc.
If you set t...
Bash script to list git commits by Linear ID
As we're switching from PT to Linear, I've updated the existing bash script to work for commits that are referencing Linear IDs.
A core benefit of our convention to prefix commits by their corresponding issue ID is that we can easily detect commits that belong to the same issue. You can either do that manually or use the bash script below. It can either be placed in your .bashrc or a...
Bookmarklet to generate a commit message for an issue in Linear.app
Your commit messages should include the ID of the issue your code belongs to.
Our preferred syntax prefixes the issue title with its ID in brackets, e.g. [FOO-123] Avatars for users.
Here is how to generate that from an issue in Linear.
Add a new link to your browser's bookmarks bar with the following URL.
javascript:(() => {
if (document.querySelector('[data-view-id="issue-view"]')) {
const [id, ...words] = document.title.split(' ') ;
prompt('Commit message:', `[${id}] ${words.join(' ')}`)
} else {
alert('Open issue...
Unpoly + Nested attributes in Rails: A short overview of different approaches
This card describes four variants, that add a more intuitive workflow when working with nested attributes in Rails + Unpoly:
- Without JS
- With HTML template and JS
- With HTML template and JS using dynamic Unpoly templates
- Adding Records via XHR and JS
Example
For the following examples we use a simple data model where a user has zero or more tasks.
Rails: Comparison of assignable_values and Active Record enum types
We are using assignable_values for managing enum values in Rails. Nevertheless Rails is adding more support for enum attributes, allowing to have a closer look at the current feature set in comparison to our still preferred option assignable_values.
Active Record enum attribute interface
By default Rails is mapping enum attributes to integers:
...
Text column sizes in MySQL
Postgres works differently
See PostgreSQL: Difference between text and varchar columns for PostgreSQL-specific info
MySQL has 4 different column sizes. They are actually different data types under the hood:
| type | size limit | schema.rb option |
|---|---|---|
| TINYTEXT | 256 bytes | size: :tiny |
| TEXT | 65,535 bytes | (default) |
| MEDIUMTEXT | 16,777,215 bytes | size: :medium |
| LONGTEXT | 4,294,967,... |
Migrating from Elasticsearch to Opensearch: Overview
Why do we migrate?
Due to a change in licensing, we cannot provide Elasticsearch versions >= 8.0.
Version 7.17.x will reach EOL status with the release of Elasticsearch version 9.
We have decided to use OpenSearch as a replacement, since it is a fork of Elasticsearch version 7.10.2, still running under the previous licensing model and wire-compatible.
A more detailed reasoning can be found on their [website](https://opensearch.o...
Using rack-mini-profiler (with Unpoly)
Debugging performance issues in your Rails app can be a tough challenge.
To get more detailed insights consider using the rack-mini-profiler gem.
Setup with Unpoly
Add the following gems:
group :development do
gem 'memory_profiler'
gem 'rack-mini-profiler'
gem 'stackprof'
end
Unpoly will interfere with the rack-mini-profiler widget, but configuring the following works okayish:
// rack-mini-profiler + unpoly
if (process...
Caution: rem in @media query definitions ignore your font-size
Note
Using rem only ever makes sense when the root font size is dynamic, i.e. you leave control to the user. Either by a) respecting their user agent defaults, or by b) offering multiple root font sizes in your application.
By defining @media queries in rem, they will accommodate to the root font size of your page. At a larger root font, breakpoints will be at larger widths, scaling with the font. However, there is a catch in case b) mentioned in the note above.
Relative length units in media queries are based on the initial value,...
Debug SAML in development using a local keycloak server
Developing or debugging SAML functionality can be a hassle, especially when you need to go back and forth with someone external who is managing the identity provider (IDP).
But you can setup a local keycloak server to act as your IDP to play around with. This might seam intimidating, but is actually quite simple when using docker and turning off some verification steps.
1. Start a keycloak instance using docker
`mkdir -p keycloak_data && docker run --network=host -e KEYCLOAK_ADMIN=admin -e KEYCLOAK_ADMIN...