31 Mar 2026
TIL has_many associations have life cycle events you can define callbacks on.
Available callbacks:
before_add
after_add
before_remove
after_remove
They are added on the association (like with dependent: :destroy or counter_cache: true).
class Author < ApplicationRecord
has_many :books, before_add: :check_limit
private
def check_limit(_book)
if books.count >= 5
errors.add(:base, "Cannot add more than 5 books for this author")
throw(:abort)
end
end
end
If a before_add callback throws :abort, the object does not get added to the collection.
If a before_remove callback throws :abort, the object does not get removed from the collection.
You can make the value an Array to run multiple:
class Author < ApplicationRecord
has_many :books, before_add: [:check_limit, :calculate_shipping_charges]
# ...
end
Rails passes the object being added or removed to the callback for you to use.
class Author < ApplicationRecord
has_many :books, before_add: [:check_limit, :calculate_shipping_charges]
# book will be the new book being created
def calculate_shipping_charges(book)
weight_in_pounds = book.weight_in_pounds || 1
shipping_charges = weight_in_pounds * 2
shipping_charges
end
end
These callbacks are called only when the associated objects are added or removed through the association collection.
# Triggers `before_add` callback
author.books << book
author.books = [book, book2]
# Does not trigger the `before_add` callback
book.update(author_id: 1)
This makes it more niche than regular model callbacks but it’s still pretty neat.
Rails guide
01 Feb 2026
TIL the meaning of “discard” differs between ActiveJob and GoodJob.
I was trying to use after_discard callback to handle all other exceptions that are not retried. I assumed this would work because the GoodJob Dashboard was showing “Discarded” on jobs that raised errors.

BUT even though unhandled exceptions say “Discarded” in the GoodJob dashboard, they are not considered discarded by ActiveJob’s standard, which means they don’t trigger after_discard callbacks.
the interaction between Active Job and the Backend when exceptions are raised through the backend is very undefined.
“discard” is an explicit action to be taken in Active Job
Active Job doesn’t define what a Backend should do when a job errors
Good Job reuses the term “discard” to cover:
using discard_on (event: discard.activejob)
using retry_on when attempts: are exceeded (event: retry_stopped.active_job)
implicitly when a job error and is not retried
— bensheldon bensheldon/good_job#844 (comment)
more stuff
Anyway, to actually use after_discard, I added
discard_on StandardError
retry_on *RETRYABLE_ERRORS
after_discard do |...|
# cleanup affected records
end
(the order of these is important)
this addressed my use case, since I only want to retry a select number of exceptions and cleanup the affected records when any other exception is raised.
03 Jan 2026
Notes from Subnetting Mastery playlist.
Seven attributes of subnetting
- Network ID - First IP address on each sub-net
- Broadcast IP - Last IP address on each sub-net
- First Host IP - IP address after the Network ID
- Last Host IP - IP address before the Broadcast ID
- Next Network - Network ID of the next Sub-Network
- Number of IP Addresses (available on sub-network)
- CIDR/Subnet Mask
- Network ID and Broadcast IP are reserved and can’t be given to devices
CIDR notation
- identifies size of particular subnet (alternative to subnet mask)
- examples:
/24, /25, …, /27
CIDR - Subnet Mask
/24 - 255.255.255.0
/25 - 255.255.255.128
-
/27 - 255.255.255.224
- Where does the CIDR number come from?
Draw Cheatsheet
- Start with 1, double until 128 (right to left)
- Subtract top from from 256
- Start with
/32, decrement by 1 (right to left)
128 64 32 16 8 4 2 1 (group size)
128 192 224 240 248 252 254 255 (Subnet mask)
/25 /26 /27 /28 /29 /30 /31 /32 (CIDR)
How to use:
- Convert to from CIDR to Subnet
- Use last octet from target IP and group size to find which block the target IP is in
- Start from 0 and increment by group size until you exceed target IP
- Number BEFORE target is Network ID
- Number AFTER target is Next Network
- IP BEFORE Next Network is Broadcast
- IP After Network ID is FIrst Host
- IP BEFOE Broadcast IP is Last Host
- Group size is number of IPs
Example:
find 7 attributes for the following IP:
16 May 2025
Three ways:
1: Use Quotes
title: "How to escape colon: in Front matter YAML"
2: Use HTML entity
title: How to escape colon: in Front matter YAML
3: Use multi-line title
title: >
How to escape colon: in Front matter YAML
Ref: https://uhded.com/escape-colon-front-matter-yaml
27 Apr 2025
I like the font Iosevka but I can only download it as a TTC and often want to use it on the web, which requires a TTF.
You can convert it with Font Forge CLI easily
Install
sudo apt install fontforge
Usage
fontforge -lang=ff -c 'Open($1); Generate($2);' <YOUR_FONT>.ttc <YOUR_FONT>.ttf
It does output a warning, but I haven’t had issues using the font so it’s probably fine ¯\(ツ)/¯