Controlling issue grouping in Sentry
When you use Sentry to monitor exceptions, an important feature is Sentry's error grouping mechanism. It will aggregate similar error "events" into one issue, so you can track and monitor it more easily. Grouping is especially important when you try to silence certain errors.
It is worth understanding how Sentry's grouping mechanism works.
The default grouping mechanism
The exact algorithm has changed over time, and Sentry will keep using the algorithm t...
RSpec: automatic creation of VCR cassettes
This RailsCast demonstrated a very convenient method to activate VCR for a spec by simply tagging it with :vcr.
For RSpec3 the code looks almost the same with a few minor changes. If you have the vcr and webmock gems installed, simply include:
# spec/support/vcr.rb
VCR.configure do |c|
c.cassette_library_dir = Rails.root.join("spec", "vcr")
c.hook_into :webmock
end
RSpec.configure do |c|
c.around(:each, :vcr) do |example|
name = example.metadata[:full_descripti...
Keeping web applications fast
Our applications not only need to be functional, they need to be fast.
But, to quote Donald Knuth,
premature optimization is the root of all evil (or at least most of it) in programming
The reasoning is that you should not waste your time optimizing code where it does not even matter. However, I believe there are some kinds of optimizations you should do right away, because
- they are either obvious and easy
- or they are very hard to do optimize later
This is an attempt to list some of those things:
On the server
...
Apache Tika: Performance-optimized configuration for hybrid PDFs (Text + OCR)
I recently ran into this issue when processing a massive backlog of documents. The server completely stalled, sometimes taking up to 30 minutes for a single page. It turned out Tika was trying to calculate skew angles and perform OCR on thousands of microscopic layout artifacts.
When extracting text from hybrid PDFs (documents containing both digital text and embedded images like scanned tables), two common issues occur with Apache Tika:
- Duplicate text: Tika extracts the digital text and subsequently sends a screenshot of the entire...
Small helper to conditionally re-record VCR cassettes
I find it annoying to iterate on a spec that relies on a recorded VCR casette. You constantly have to remove the same YAMLs file before you can re-run that still-red test.
With this little helper, you (or your coding agent) can do the same with RE_RECORD_VCR=true bundle exec rspec spec/path/to/recorded_spec.rb:
# spec/support/vcr.rb
RSpec.configure do |config|
deleted_vcr_cassettes = []
config.around(:each, :vcr) do |example|
name = example.metadata[:full_description].split(/\s+/, 2).join('/...
Rails: Report CSP Violations to a log file
You can report CSP violations to a log file. Note that there will be a lots of noise, that is not related to errors in your application but raised from various browser extensions of your visitors.
Add the endpoint in you CSP config/initializers/content_security_policy.rb:
Rails.application.configure do
config.content_security_policy do |policy|
# Settings for the policy
policy.report_uri '/content_security_policy_report'
end
end
Create a controller app/controllers/content_security_policy_reports_controller.rb ...
Rails: Destroying vs Deleting
Rails offers several ways to remove records. They differ in whether they instantiate records, fire callbacks (including dependent: associations) and how they manage relation state afterward.
destroy_all
This is the definition of destroy_all:
# ActiveRecord::Relation
def destroy_all
records.each(&:destroy).tap { reset }
end
-
recordsevaluates SQL, caches result -
.each(&:destroy)iterates the cached Array, returns it -
.tap { reset }callsRelation#reset, clearsrecordsandloaded, returns the Array un...
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...