Jasmine: using async/await to write nice asynchronous specs
Jasmine has long standing support for writing asynchronous specs. In days gone by we used the done callback to achieve this, but these days it is possible to write much more readable specs.
Async specs
As a first example, say we want to check that some form disables the submit button while working.
// bad (how we used to do it)
beforeEach(() => {
this.form = setupMyForm()
this.submitButton = findTheSubmitButton()
})
it('disables the submit button while working', (done) => {
expect(this.submitButton.disabled).toBe(false)
...
Project management best practices: User stories & Issues
We organize our daily work with issues in our Linear workspace.
Issue format
A good issue needs to be precise. It should be very clear what is part of an issue, and what is not. If there are different expectations between the person who writes and who implements an issue, there will be rejects.
To this end, we use a consistent format for issues that looks like this:
Issue: Autocomplete
As a journalist, I want to have an autocomplete in the search bar, to have a more efficient way to find articles.
Acceptance criteri...
PSA: Chrome and Firefox do not always clear session cookies on exit
Cookies without an expiration timestamp are called "session cookies". [1] They should only be kept until the end of the browsing session.
However, when Chrome or Firefox are configured to reopen tabs from last time upon start, they will keep session cookies when closing the browser. This even applies to tabs that were closed before shutting down the browser.
This is by design in Chrome and [Firefox](https://bugzilla.mozilla.org/buglist.cgi?bug_id=337551,345830,358042,362212,36...
ActiveRecord: count vs size vs length on associations
TL;DR: You should generally use #size to count associated records.
size
- Counts already loaded elements
- If the association is not loaded, falls back to a
COUNTquery
count
- If a counter cache is set up, returns the cached value
- Issues a
COUNTquery else
Caveats
- If you trigger a
COUNTquery for an association of an an unsaved record, Rails will try to load all children where the foreign keyIS NULL. This is not what you want. To prevent this behavior, you can useunsaved_record.association.to_a.size. - `c...
nvm: Setting a default Node.js version
To set a default Node version for new shells, use nvm alias default <VERSION>:
nvm alias default 1.2.3
I like to use the most recent LTS version as my default:
nvm alias default lts/erbium
Toggling a maintenance page with Capistrano
Note
Maintenance mode is enabled on application server as soon as the file
/public/system/maintenance.htmlis present.Note that the servers must be configured accordingly.
Installation
Add this line to your application's Gemfile:
gem 'capistrano', '~> 3.0'
gem 'capistrano-maintenance', '~> 1.0'
Add this line to you application's Capfile:
require 'capistrano/maintenance'
Enable task
Present a maintenance page to visitors. Disables your application's web interface by writing a `#{maintenanc...
How to get notified when Claude Code needs your input
It's quite frustrating to come back to a coding agent after a while only to see that it needed user input only 10 seconds into the task.
You can set up notification hooks to trigger a Desktop notification (or similar) whenever the Claude Code agent halts.
This is my configuration:
# ~/.claude/notify.sh
# Note: You need to `chmod +x` this
#!/bin/bash
INPUT=$(cat)
TITLE=$(echo "$INPUT" | jq -r '.title // "Claude Code"')
MSG=$(echo "...
Rails: Including HTML in your i18n locales
TL;DR
I18n keys ending with
_htmlare automatically marked as HTML-safe when translating witht('.your_key_html').
When you're localizing a Rails application, some localized texts need to contain HTML. Be it some localized link, or some tags for formatting that you wantto include in your translated text. Example:
# e.g. config/locales/en.yml
en:
page:
text: 'Please visit our <a href="https://www.example.com/">corporate website</a> to learn more about <strong>the corporation</strong>.'
When your view tran...
8 steps for fixing other people's code
Guide how to make fixes in other people's GitHub repositories. It's basically "Open Source Development 101".
Way back in mid-2007, when Rails 1.2 was the new hotness and GitHub was still a year away from crawling out of the primordial internet soup, prolific open source contributor Dr Nic wrote an article titled “8 steps for fixing other people’s code”. (...)
Here in the fantastical future world of 2012, while we still don’t have hoverboards or household nuclear fusion, we do have some great tools that make fixing other people’s code...
CSP: Nonces are propagated to imports
When you load a <script> with a nonce, that script can await import() additional sources from any hostname. The nonce is propagated automatically for the one purpose of importing more scripts.
This is not related to strict-dynamic, which propagates nonces for any propose not limited to imports (e.g. inserting <script> elements).
Example
We have a restrictive CSP that only allows nonces:
Content-Security-Policy: default-src 'none'; script-src 'nonce-secret123'
Our HTML loads script.js using that nonce:
A restrictive (but still practicable) CSP for Rails projects
Below is a strict, but still workable Content Security Policy for your Ruby on Rails project. Use this CSP if you want to be very explicit about what scripts you allow, while keeping pragmatic defaults for styles, images, etc. This CSP does not use the viral strict-dynamic source (reasoning).
We also have a very compatible CSP which is more liberal. Compatibility might outweigh strictness if you have a lot of scripts you can...
When you want to format only line breaks, you probably do not want `simple_format`
For outputting a given String in HTML, you mostly want to replace line breaks with <br> or <p> tags.
You can use simple_format, but it has side effects like keeping some HTML.
If you only care about line breaks, you might be better off using a small, specialized helper method:
def format_linebreaks(text)
safe_text = h(text)
paragraphs = split_paragraphs(safe_text).map(&:html_safe)
html = ''.html_safe
paragraphs.each do |paragraph|
html << content_tag(:p, paragraph)
end
html
end
Full di...
Caching file properties with ActiveStorage Analyzers
When working with file uploads, we sometimes need to process intrinsic properties like the page count or page dimensions of PDF files. Retrieving those properties requires us to download (from S3 or GlusterFS) and parse the file, which is slow and resource-intensive.
Active Storage provides the metadata column on ActiveStorage::Blob to cache these values. You can either populate this column with ad-hoc metadata caching or with custom Analyzers.
Attachment...
Detect the current Rails environment from JavaScript or CSS
Detecting if a Javascript is running under Selenium WebDriver is super-painful. It's much easier to detect the current Rails environment instead.
You might be better of checking against the name of the current Rails environment. To do this, store the environment name in a data-environment of your <html>. E.g., in your application layout:
<html data-environment=<%= Rails.env %>>
Now you can say in a pi...
Running "bundle update" will update all gems without constraints
Calling bundle update (without arguments) updates all your gems at once. Given that many gems don't care about stable APIs, this might break your application in a million ways.
To stay sane, update your gems using the applicable way below:
Projects in active development
Update the entire bundle regularily (e.g. once a week). This ensures that your libraries are up-to-date while it's easy to spot major version bumps which may break the app.
Projects that have not been updated in a while
- [Update a single gem conservatively](htt...
Best practices: Large data migrations from legacy systems
Migrating data from a legacy into a new system can be a surprisingly large undertaking. We have done this a few times. While there are significant differences from project to project, we do have a list of general suggestions.
Before you start, talk to someone who has done it before, and read the following hints:
Understand the old system
Before any technical considerations, you need to understand the old system as best as possible. If feasible, do not only look at its API, or database, or frontend, but let a user of the old system sho...
How to fix "Could not resolve any esbuild targets" when building assets
I recently encountered this error as I was trying to build assets:
$ node esbuild.config.js
.../node_modules/esbuild-plugin-browserslist/dist/resolveToEsbuildTarget.js:43
throw new Error('Could not resolve any esbuild targets');
^
Error: Could not resolve any esbuild targets
at resolveToEsbuildTarget (.../node_modules/esbuild-plugin-browserslist/dist/resolveToEsbuildTarget.js:43:15)
at Object.resolveToEsbuildTarget (.../node_modules/esbuild-plugin-browserslist/dist/index.js:9:64)
at Object.<anonymous> (.../e...
Sending newsletters via rapidmail with SMTP and one-click unsubscribe
If you need to implement newsletter sending, rapidmail is a solid option.
Support is very fast, friendly and helpful, and the initial setup is simple. Since rapidmail works via SMTP, you can simply ask the Ops team to configure SMTP credentials for your application.
You also do not need to use rapidmail’s built-in newsletter feature. Instead, you can send emails as transactional mails, which allows you to keep the entire newsletter logic inside your application.
One thing to keep an ey...
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...
Rails developers: Have better context in Git diffs
Git diffs show the surrounding contexts for diff hunks. It does so by applying regular expressions to find the beginning of a context. When it comes to Ruby, however, it will not find method heads and travel up to the class definition:
@@ -24,7 +24,7 @@ class TicketPdf # <=== Actually expected here: the method definition
ApplicationController.render(
"tickets/index.html.haml",
layout: "tickets",
- assigns: { tickets: tickets }
+ assigns: { tickets: tickets, event_name: event_name }
)
end
end
```...
Ensure deterministic ordering of records
Creating records in specs can be so fast that two records created instantly after one another might have the same created_at timestamp (especially since those timestamps don't have an indefinitely high resolution). When ordering lists by timestamps, you should therefore always include a final order condition using the primary key of the table.
class Photo < ActiveRecord::Base
scope :by_date, -> { order('created_at DESC, id DESC') }
end
Photo.by_date
Remember to include the id field in the database index.
Postgres Ranges
Postgres supports multiple built-in range datatypes:
int4rangeint8rangenumrange-
tsrange(range with timestamp without timezone) -
tstzrange(range with timestamp with timezone) daterange
They represent a start and endpoint of something in a single column. Image you're building a vacation booking feature:
create_table :events do |t|
t.date :starts_on
t.date :ends_on
end
This is how you would use a range type in a migration:
create_table :vacations do |t|
t.daterange :period
end
See [t...
TypeScript: Enable strict-boolean-expressions for Safe Nullish Checks
You may remember to use the || operator with caution to set defaults. We'll see that && and other conditionals come with the same limitations. However, TypeScript and eslint can make this a lot safer for us.
TL;DR
- Conditionals like using
&&,||,if else,?:can be used for safe nullish checks - Danger: falsey values give false negatives and will bypass checks wrongly
- Remember to always be explicit (like using
??) or enable `strict-boolean-exp...
Ruby: How to use prepend for cleaner monkey patches
Let's say you have a gem which has the following module:
# within the imaginary super gem
module SuperClient
def self.foo
'Foo'
end
def bar
'Bar'
end
end
For reasons you need to override foo and bar.
Keep in mind: Your code quality is getting worse with with each prepend (other developers are not happy to find many library extensions). Try to avoid it if possible.
- Add a
lib/ext/super_client.rbto your project (see How to organize monkey patches in Ruby on Rails projects)
2...