routing-filter is broken with Rails 7.1
If you are using the routing-filter gem in your Rails 7.1 app for managing URL segments for locales or suffixes, you will notice that the filters do no longer apply, routes are broken and the necessary parameters are no longer extracted. That is because routing-filter patches Rails' find_routes
-method to get the current path and apply its defined filters on it. These filters then modify the params that are handed over to your controller action. This way you receive a locale
parameter from a ...
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...
Rails: Using require and permit for attributes
Raising errors for required and permitted attributes makes it easier to find errors in your application during development and in tests. Consider this approach if you want to strengthen the params handling in your application.
Example
# config/application.rb
config.action_controller.action_on_unpermitted_parameters = :raise
def user_params
params.require(:user).permit(:full_name)
end
Effects
- This raises an error `Ac...
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.
Ruby: Following redirects with the http gem ("httprb")
When making requests using the http
gem you might want to automatically follow redirects to get the desired response. This is supported but not the default behavior.
You can do so by using the .follow
method.
This follows redirects for the following status codes: 300, 301, 302, 303, 307, 308
response = HTTP.get('https://www.example.com/redirect')
response.status # => 302
response.uri.to_s # => "https://www.example.com/redirect"
response = HTTP.follow.get('https://www.example.com/redirect')
response.status # => 200
response....
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...
SAML Single Logout (SLO)
There are two ways a logout in SAML can happen: Service Provider (SP) initiated and Identity Provider (IDP) initiated logout. I'll explain how to implement both flows with devise_saml_authenticatable
.
Note
SAML also supports a
SOAP
and anArtifact
binding to do this. This guide only refers toPOST
andRedirect
bindings.devise_saml_authenticatable
does not supportSOAP
andArtifact
bindings.
SP initiated logout (using the Redirect Binding)
When the user clicks on Logout
within the app, the app can trigger...
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...
Ruby: How to connect to a host with expired SSL certificate
If you need to make an HTTPS connection to a host which uses an expired certificate, do not disable certificate verifications entirely. Doing that enables e.g. man in the middle attacks.
If you accept only a single expired and known certificate, you are much less in trouble.
Setup
All the solutions described below use a verify_callback
for the request's OpenSSL::X509::Store
where you can specify a lambda to adjust its verification response.
Your callback must return either true
or false
and OpenSSL's verification result is...
Rails: Testing file downloads with request specs
tl;dr
Prefer request specs over end-to-end tests (Capybara) to joyfully test file downloads!
Why?
Testing file downloads via Capybara is not easy and results in slow and fragile tests. We tried different approaches and the best one is just okay.
Tests for file downloads via Capybara ...
- ... are slow,
- ... are fragile (breaks CI, breaks if Selenium driver changes, ...),
- ... need workarounds for your specia...
List of handy Ruby scripts to transcode different file types (often by using GPT)
It's 2024 and we have tools like ffmpeg, imagemagick and GPT readily available. With them, it's easy to convert texts, images, audio and video clips into each other.
For the everyday use without any parameter tweaking I'm using a collection of tiny scripts in my ~/bin
folder that can then be used as bash functions. And: It's faster to use the CLI than interacting with a website and cheaper to use the API than buying GPT plus.. :-)
Usage
text-to-image "parmiggiano cheese wedding cake, digital art"
- `text-to-audio "Yesterday I ate ...
Virtual scrolling: A solution for scrolling wide content on desktops
I recently built a screen with a very high and wide table in the center. This posed some challenges:
- Giving the table a horizontal scroll bar is very unergonomic, since the scrollbar might be far off screen.
- Making the whole page scrollable looks bad, since I don't want the rest of the UI to scroll.
- Giving the table its own vertical scrollbar and a limited height would have solved it, but felt weird, since the table was 90% of the page.
What I ended up doing is reusing the horizontal page scrollbar (which is naturally fixed at t...
Be careful when checking scopes for blankness
Today I stumbled across a pretty harmless-looking query in our application which turned out to be pretty harmful and caused huge memory usage as well as downing our passenger workers by letting requests take up to 60 seconds. We had a method that received a scope and then checked, if the scope parameter was blank?
and aborted the method execution in this case.
def foo(scope)
return if scope.blank?
# Use scope, e.g.
scope.find(...)
end
We then called this method with an all
scope: foo(Media::Document::Base.all)
. *...
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...
Bash: How to grep logs for a pattern and expand it to the full request
Example
I, [2024-01-21T06:22:17.484221 #2698200] INFO -- : [4cdad7a4-8617-4bc9-84e9-c40364eea2e4] test
I, [2024-01-21T06:22:17.484221 #2698200] INFO -- : [4cdad7a4-8617-4bc9-84e9-c40364eea2e4] more
I, [2024-01-21T06:22:17.484221 #2698200] INFO -- : [6e047fb3-05df-4df7-808e-efa9fcd05f87] test
I, [2024-01-21T06:22:17.484221 #2698200] INFO -- : [6e047fb3-05df-4df7-808e-efa9fcd05f87] more
I, [2024-01-21T06:22:17.484221 #2698200] INFO -- : [53a240c1-489e-4936-bbeb-d6f77284cf38] nope
I, [2024-01-21T06:22:17.484221 #2698200] INFO -- ...
Opening a zipped coverage report with one click
Tested on Ubunut 22.04
1. Opener script
- Create a file
~/.local/bin/coverage_zip_opener
with:
#!/bin/bash
tmp_folder="/tmp/coverage-report-opener"
if [ -z "$1" ]
then
echo "Usage: coverage_zip_opener [filename]"
exit -1
fi
if ! [[ "$1" =~ ^.*Pipeline.*Coverage.*\.zip$ || "$1" =~ ^.*merged_coverage_report.*\.zip$ ]]; then
file-roller "$1"
exit 0
fi
rm -Rf $tmp_folder
unzip -qq "$1" -d $tmp_folder
index_filename=$(find /tmp/coverage-report-opener -name "index.html" | ...
Unpoly 3.7.1, 3.7.2 and 3.7.3 released
Version 3.7.0 broke some things in complex forms. Sorry for that. Concurrent user input is hard.
3.7.1
This change fixes two regressions for form field watchers, introduced by 3.7.0:
- When a change is detected while waiting for an async callback, prevent the new callback from crashing with
Cannot destructure property { disable } of null
. - When a change is detected while waiting for an async callback, the full debounce delay of that new change is honored.
...
Browser debugging tricks
A list of clever debugging tricks. TOC:
- Advanced Conditional Breakpoints
- monitor() class Calls
- Call and Debug a Function
- Pause Execution on URL Change
- Debugging Property Reads
- Use copy()
- Debugging HTML/CSS
open-next-failure: An alias to speed up test debugging
Getting an entire test suite green can be a tedious task which involves frequent switches between the CLI that is running tests back to the IDE where its cause can be fixed.
The following bash aliases helped me speed up that process:
alias show-next-failure="bundle exec rspec --next-failure"
alias open-next-failure="show-next-failure || show-next-failure --format json | jq -r '.examples[0]' | jq '\"--line \" + (.line_number|tostring) + \" \" + .file_path' | xargs echo | xargs rubymine"
There is a lot going on above but the gist...
Livereload + esbuild
Getting CSS (and JS) live reloading to work in a esbuild / Rails project is a bit of a hassle, but the following seems to work decently well.
We assume that you already use a standard "esbuild in Rails" setup, and have an esbuild watcher running that picks up your source code in app/assets
and compiles to public/assets
; if not change the paths below accordingly.
Basic idea
We will
- use the
guard-livereload
gem as the livereload server (which send updates to the browser), - use the
livereload-js
npm package in the browser to con...
How to (and how not to) design REST APIs ยท stickfigure/blog Wiki
In my career, I have consumed hundreds of REST APIs and produced dozens. Since I often see the same mistakes repeated in API design, I thought it might be nice to write down a set of best practices. And poke fun at a couple widely-used APIs.
Much of this may be "duh", but there might be a few rules you haven't considered yet.
A reasonable default CSP for Rails projects
Every modern Rails app should have a Content Security Policy enabled.
Very compatible default
The following "default" is a minimal policy that should
- "just work" for almost all applications
- give you most of the benefits of a CSP
In your config/initializers/content_security_policy.rb
, set
Rails.application.config.content_security_policy do |policy|
policy.object_src :none
policy.script_src :unsafe_eval, :strict_dynamic, :https # Browsers with support for "'strict-dynamic'" will ignore "https:"
po...