Notes
Welcome to my digital garden 🌱🌿🌾
All Thoughts are my own, and not always fully formed 🧠
Switching to PrismJS
Awhile back, I posted about using HighlightJS to add some syntax highlighting. I saw that PrismJS has a very small core size, which means faster load times. I also thought it was easier to use than HighlightJS at least for my simple purposes. The downside is that it's not as good at language autodetection, but when writing code snippets I've found that needing to provide the language upfront isn't that big of a deal. Let's see how it goes!
import Prism from 'prismjs'
// When using Turbo with hotwire, page navigation happens without full reloads,
// so we need to re-run Prism's syntax highlighting on every Turbo page load.
// This ensures code blocks are properly highlighted even after AJAX page swaps.
document.addEventListener("turbo:load", () => Prism.highlightAll());
Threads inside transaction blocks
Beware of using threads that execute database queries inside active record transaction blocks. And yes this includes using gems like parallel with threads too! Modifying the same record in a transaction and within a thread within that transaction can cause deadlocks 🔒This is because the inner thread checks out a different database connection from the pool, which isn't aware of the outer transaction's context as it hasn't been committed yet. This can easily be simulated by running the code below in a Rails console. The 2nd operation times out because it's started from a different DB connection in a new thread, and that 2nd DB connection is waiting for the outer transaction to commit. But the outer transaction never commits because it's waiting on the code within the thread to complete.
ActiveRecord::Base.transaction do
user = User.find_by(email: 'test@example.com').update!(name: 'Jon')
Thread.new do
ActiveRecord::Base.connection_pool.with_connection do
User.find_by(email: 'test@example.com').update!(name: 'Kevin')
end
end.join
end
The digital garden 🌱
While searching for interesting personal sites on the web, I came across the concept of a digital garden. I have a goal this year to write more, including longer posts. But with longer posts, there's an expectation (even if internal) that the content is more thought out and polished like a traditional "blog." That's where I find the concept of the digital garden inspiring.
Rather than presenting a set of polished articles, displayed in reverse chronological order, these personal sites act more like free form, work-in-progress wikis
I remember enjoying the old web from the early 2000s, where content was less curated and more freeform. Individuals posted on the web for fun instead of views/follows! I didn't realize it when originally spinning up this site, but this notes section is my own version of the digital garden 🌿
Writing Notes in HTML
On this site I've made a potentially unpopular choice to store notes I've written in HTML instead of markdown (though I do also plan to support markdown, and also potentially MDX). Why you ask? I really miss the days of the old web, where sites felt creative and free. Everyone's myspace page was completely random, and full of easter eggs. HTML allows for more interactivity!
Sure HTML is more verbose, and not as friendly for note taking. But its more fun no :) ?
RSpec bisect
I've been using rspec for over a decade to write tests in ruby, and I just learned about the --bisect option. When executed with this option, rspec repeatedly runs subsets of a suite in order to isolate the minimal set of examples that reproduce the same failures. This can be incredibly useful when working with a particularly flakey spec with a failure case that is difficult to reproduce.
Remember the HTML marquee tag?
Brings me back to the days of myspace!
Querying for invalid postgres indexes
Indexes can fail to build concurrently in Postgres in a number of reasons. I've used the following query several times to find indexes that have failed to successfully build. This query returns the name of the index which can then be used to drop the index manually if needed.
SELECT
*
FROM
pg_class,
pg_index
WHERE
pg_index.indisvalid = FALSE
AND pg_index.indexrelid = pg_class.oid
Docker running low on space?
Docker images failing to run because you're seeing the following message?
E: You don't have enough free space in /var/cache/apt/archives/Turns out you reclaim tons of space by running docker system prune. A reminder for myself 🗑️!
Total reclaimed space: 22.78GB
On being right
If you want to be right, don't be right about someone else's shit. Be right about your own shit.- Day9
Rails devs vs Typescript devs
Rails devs - dislike using types, but use a SQL database.
Typescript devs - must use types everywhere, but then use firebase.
Ironic no?Rails Delegated Types
Today I stumbled upon Rails delegated types, a feature added in Rails 7. The paradigm can be used as an alternative to single table inheritance and purely abstract ActiveRecord classes. Keeping this one in my back pocket to use next time I come across a problem where I want the superclass view to be separately "queryable" in its own table without having to store all of unrelated subclass attributes in that same table.
With this approach, the “superclass” is a concrete class that is represented by its own table, where all the superclass attributes that are shared amongst all the “subclasses” are stored. And then each of the subclasses have their own individual tables for additional attributes that are particular to their implementation.
Snood is still around?
Snood was awesome, was it not? And what the heck, it's still being sold and it's $19.99 for the original version!
Using Faraday
I've seen the faraday ruby gem used in a number of ruby projects over the years, but I hadn't used it directly myself until recently.
There are a LOT of HTTP clients in ruby land, but faraday is consistently on the top of the list as being the most popular. I decided to try it out for a project I was leading at work. I needed to build a generalized HTTP client that could be used to interact with a number of different websites that accepted and responded with different content types. I also wanted to add some defaults such as a proxy uri, basic error handling, and a timeout. I really liked faraday's approach to mixing in ad-hoc request/response middleware to handle making requests, so I decided to give it a go. I built a little utility that wraps a faraday connection and its response. Nothing too fancy, but it made working with the connection and response objects more flexible for my specific use case. The example below is simplified, but it looked something like the following:
require "faraday"
require "http-cookie"
# Wraps a Faraday::Connection
#
# - Uses default configuration options for a proxy uri, timeout, and error handling
# - Accepts all configuration options for a faraday connection
# - Accepts a block to configure additional middleware
# - Returns a custom custom Response object
class HTTPConnection
attr_reader :connection
def initialize(url:, **kwargs, &block)
@connection = Faraday.new(
url:,
proxy: { uri: "https://someproxy.com" },
request: { timeout: 15 }, # seconds
**kwargs
) do |builder|
builder.response(:raise_error)
block&.call(builder)
end
end
%w[get post put delete patch].each do |request_method|
define_method(request_method) do |path, *args|
Response.new(connection.public_send(request_method, path, *args))
end
end
# Wraps a Faraday::Response
#
# - Adds helper to get the request_method
# - Adds helper to get the url
# - Adds helper to easily retrieve cookies from the response
class Response
extend Forwardable
attr_reader :response, :env
def_delegators :response, :status, :body, :headers
def_delegators :env, :request_body, :request_headers
def url
env.url.to_s
end
def request_method
env.method.to_s
end
def cookies
Cookies.new(
cookie_header: headers['Set-Cookie'],
url:
)
end
def initialize(response)
@response = response
@env = response.env
end
end
class Cookies
def initialize(cookie_header:, url:)
@jar = HTTP::CookieJar.new
@url = url
@jar.parse(cookie_header.to_s, @url)
end
def to_a
@jar.cookies(@url)
end
def to_header
{ 'Cookie' => HTTP::Cookie.cookie_value(to_a) }
end
end
end
conn = HTTPConnection.new(
url: "https://example.com",
headers: { "Content-Type" => "application/json" }
) do |builder|
# Can add any additional middleware here, depending on the requirements of the endpoint
builder.request(:url_encoded)
builder.response(:json)
end
# Works just like a normal faraday request
response = conn.post('/endpoint', { 'name' => 'Jason' })
More Stack Overflow
My previous note was about stack overflow so I continued the trend by answering another couple questions:
jsonb columns in postgres if I don't have to, I've overall had a pretty good experience using the jsonb_accessor gem. Despite some of its quirks, it does make using jsonb columns in Rails more user friendly. And at the end of the day that's a good thing.Entering Stack Overflow
While working on this site, I encountered an issue where content displayed using the the flash (a special tool used in conjunction with rails sessions to display temporary content between requests) was lingering for a brief second when revisiting a page that had previously displayed something from the flash. After searching through some documentation, I found that the underlying cause was due to the way turbo caches content between page reloads. A few minutes later, I happened to stumble upon this stack overflow thread related to the issue. I thought that I could provide a useful answer, and so I responded 🎉
In the age of LLMs and notably ChatGPT, stack overflow is becoming less relevant for day to day usage, but I am happy that fiddling around with this site inspired me to contribute a response 🖥️Remembering Gall’s Law
Gall’s law has greatly influenced the way I’ve operated as a software developer over the last decade. It is as follows:
A complex system that works is invariably found to have evolved from a simple system that worked. A complex system designed from scratch never works and cannot be patched up to make it work. You have to start over with a working simple system
HTTP Basic Auth and the Browser
Today I remembered that it's not possible to traditionally log out of a website if HTTP basic authentication is used to login.
Basic auth credentials are transmitted with each and every request by the browser. Hence why you cannot traditionally log out without closing the browserAs soon as the browser prompts for credentials and the user logs in, those credentials are cached sent with every subsequent request until the browser is closed or restarted. The more you know ✨
On Execution
I reminded someone of a quote I've heard today, which I still take to heart
Good strategy is nothing without good execution