mirror of
https://github.com/jbranchaud/til
synced 2026-01-17 05:58:01 +00:00
Compare commits
22 Commits
1fce1af3d7
...
ad0c2b9d84
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ad0c2b9d84 | ||
|
|
1c4e37ed8a | ||
|
|
581aa1decb | ||
|
|
0c4795c1d2 | ||
|
|
9bbde247a5 | ||
|
|
36ca71bfb1 | ||
|
|
8a682e3a89 | ||
|
|
c7a38c8267 | ||
|
|
71d3e56b3d | ||
|
|
af3974d3fe | ||
|
|
adc6b2e903 | ||
|
|
9a6ebd4c6b | ||
|
|
6df0693804 | ||
|
|
507602ef0c | ||
|
|
18bdcc88b8 | ||
|
|
95115c7ebc | ||
|
|
4e859b93d2 | ||
|
|
23c20e99bf | ||
|
|
63bb627716 | ||
|
|
21385f4491 | ||
|
|
5b47326ab3 | ||
|
|
bc767a0ad3 |
18
README.md
18
README.md
@@ -10,7 +10,7 @@ pairing with smart people at Hashrocket.
|
||||
|
||||
For a steady stream of TILs, [sign up for my newsletter](https://crafty-builder-6996.ck.page/e169c61186).
|
||||
|
||||
_1488 TILs and counting..._
|
||||
_1504 TILs and counting..._
|
||||
|
||||
---
|
||||
|
||||
@@ -157,6 +157,7 @@ _1488 TILs and counting..._
|
||||
### CSS
|
||||
|
||||
- [Add Fab Icons To Your Site With FontAwesome 5](css/add-fab-icons-to-your-site-with-fontawesome-5.md)
|
||||
- [Add Line Numbers To A Code Block With Counter](css/add-line-numbers-to-a-code-block-with-counter.md)
|
||||
- [Animate Smoothly Between Two Background Colors](css/animate-smoothly-between-two-background-colors.md)
|
||||
- [Apply Multiple Box Shadows To Single Element](css/apply-multiple-box-shadows-to-single-element.md)
|
||||
- [Apply Styles Based On Dark-Mode Preferences](css/apply-styles-based-on-dark-mode-preferences.md)
|
||||
@@ -212,6 +213,7 @@ _1488 TILs and counting..._
|
||||
### Drizzle
|
||||
|
||||
- [Create bigint Identity Column For Primary Key](drizzle/create-bigint-identity-column-for-primary-key.md)
|
||||
- [Drizzle Tracks Migrations In A Log Table](drizzle/drizzle-tracks-migrations-in-a-log-table.md)
|
||||
- [Get Fields For Inserted Row](drizzle/get-fields-for-inserted-row.md)
|
||||
|
||||
### Elixir
|
||||
@@ -295,6 +297,7 @@ _1488 TILs and counting..._
|
||||
- [Configure Global gitignore File](git/configure-global-gitignore-file.md)
|
||||
- [Configuring The Pager](git/configuring-the-pager.md)
|
||||
- [Copy A File From Another Branch](git/copy-a-file-from-another-branch.md)
|
||||
- [Count All Files Of Specific Type Tracked By Git](git/count-all-files-of-specific-type-tracked-by-git.md)
|
||||
- [Create A New Branch With Git Switch](git/create-a-new-branch-with-git-switch.md)
|
||||
- [Delete All Untracked Files](git/delete-all-untracked-files.md)
|
||||
- [Determine The Hash Id For A Blob](git/determine-the-hash-id-for-a-blob.md)
|
||||
@@ -394,6 +397,7 @@ _1488 TILs and counting..._
|
||||
|
||||
- [Access Go Docs Offline](go/access-go-docs-offline.md)
|
||||
- [Build For A Specific OS And Architecture](go/build-for-a-specific-os-and-architecture.md)
|
||||
- [Do Something N Times](go/do-something-n-times.md)
|
||||
- [Find Executables Installed By Go](go/find-executables-installed-by-go.md)
|
||||
- [Not So Random](go/not-so-random.md)
|
||||
- [Replace The Current Process With An External Command](go/replace-the-current-process-with-an-external-command.md)
|
||||
@@ -439,8 +443,10 @@ _1488 TILs and counting..._
|
||||
### Internet
|
||||
|
||||
- [Add Emoji To GitHub Repository Description](internet/add-emoji-to-github-repository-description.md)
|
||||
- [Add Styled Alerts To GitHub Markdown Documents](internet/add-styled-alerts-to-github-markdown-documents.md)
|
||||
- [Analyze Your Website Performance](internet/analyze-your-website-performance.md)
|
||||
- [Check Your Public IP Address](internet/check-your-public-ip-address.md)
|
||||
- [Digraph Unicode Characters Have A Titlecase](internet/digraph-unicode-characters-have-a-titlecase.md)
|
||||
- [Enable Keyboard Shortcuts In Gmail](internet/enable-keyboard-shortcuts-in-gmail.md)
|
||||
- [Exclude AI Overview From Google Search](internet/exclude-ai-overview-from-google-search.md)
|
||||
- [Exclude Whitespace Changes From GitHub Diffs](internet/exclude-whitespace-changes-from-github-diffs.md)
|
||||
@@ -523,6 +529,7 @@ _1488 TILs and counting..._
|
||||
- [Open Global npm Config File](javascript/open-global-npm-config-file.md)
|
||||
- [Parse A Date From A Timestamp](javascript/parse-a-date-from-a-timestamp.md)
|
||||
- [Pre And Post Hooks For Yarn Scripts](javascript/pre-and-post-hooks-for-yarn-scripts.md)
|
||||
- [Prevent Hidden Element From Flickering On Load](javascript/prevent-hidden-element-from-flickering-on-load.md)
|
||||
- [Purge Null And Undefined Values From Object](javascript/purge-null-and-undefined-values-from-object.md)
|
||||
- [Random Cannot Be Seeded](javascript/random-cannot-be-seeded.md)
|
||||
- [Reach Into An Object For Nested Data With Get](javascript/reach-into-an-object-for-nested-data-with-get.md)
|
||||
@@ -769,6 +776,7 @@ _1488 TILs and counting..._
|
||||
- [Force SSL When Making A psql Connection](postgres/force-ssl-when-making-a-psql-connection.md)
|
||||
- [Generate A UUID](postgres/generate-a-uuid.md)
|
||||
- [Generate Modern Primary Key Columns](postgres/generate-modern-primary-key-columns.md)
|
||||
- [Generate Random Alphanumeric Identifier](postgres/generate-random-alphanumeric-identifier.md)
|
||||
- [Generate Random UUIDs Without An Extension](postgres/generate-random-uuids-without-an-extension.md)
|
||||
- [Generate Series Of Numbers](postgres/generate-series-of-numbers.md)
|
||||
- [Generating UUIDs With pgcrypto](postgres/generating-uuids-with-pgcrypto.md)
|
||||
@@ -836,6 +844,7 @@ _1488 TILs and counting..._
|
||||
- [Survey Of User-Defined Ordering Of Records](postgres/survey-of-user-defined-ordering-of-records.md)
|
||||
- [Switch Non-Castable Column Type With Using Clause](postgres/switch-non-castable-column-type-with-using-clause.md)
|
||||
- [Switch The Running Postgres Server Version](postgres/switch-the-running-postgres-server-version.md)
|
||||
- [Table Names Are Treated As Lower-Case By Default](postgres/table-names-are-treated-as-lower-case-by-default.md)
|
||||
- [Temporarily Disable Triggers](postgres/temporarily-disable-triggers.md)
|
||||
- [Temporary Tables](postgres/temporary-tables.md)
|
||||
- [Terminating A Connection](postgres/terminating-a-connection.md)
|
||||
@@ -878,6 +887,7 @@ _1488 TILs and counting..._
|
||||
|
||||
- [Access Instance Variables](python/access-instance-variables.md)
|
||||
- [Create A Dummy DataFrame In Pandas](python/create-a-dummy-dataframe-in-pandas.md)
|
||||
- [Store And Access Immutable Data In A Tuple](python/store-and-access-immutable-data-in-a-tuple.md)
|
||||
- [Test A Function With Pytest](python/test-a-function-with-pytest.md)
|
||||
- [Use pipx To Install End User Apps](python/use-pipx-to-install-end-user-apps.md)
|
||||
|
||||
@@ -921,6 +931,7 @@ _1488 TILs and counting..._
|
||||
- [Count The Number Of Records By Attribute](rails/count-the-number-of-records-by-attribute.md)
|
||||
- [Create A Custom Named References Column](rails/create-a-custom-named-references-column.md)
|
||||
- [Create A Join Table With The Migration DSL](rails/create-a-join-table-with-the-migration-dsl.md)
|
||||
- [Create Table With bigint Id As Primary Key](rails/create-table-with-bigint-id-as-primary-key.md)
|
||||
- [Creating Records of Has_One Associations](rails/creating-records-of-has-one-associations.md)
|
||||
- [Custom Validation Message](rails/custom-validation-message.md)
|
||||
- [Customize Paths And Helpers For Devise Routes](rails/customize-paths-and-helpers-for-devise-routes.md)
|
||||
@@ -977,6 +988,7 @@ _1488 TILs and counting..._
|
||||
- [Parse Request Params In Rack::Attack Block](rails/parse-request-params-in-rack-attack-block.md)
|
||||
- [Perform SQL Explain With ActiveRecord](rails/perform-sql-explain-with-activerecord.md)
|
||||
- [Polymorphic Path Helpers](rails/polymorphic-path-helpers.md)
|
||||
- [Prefer select_all Over execute For Read Queries](rails/prefer-select-all-over-execute-for-read-queries.md)
|
||||
- [Pretend Generations](rails/pretend-generations.md)
|
||||
- [Prevent Writes With A Sandboxed Rails Console](rails/prevent-writes-with-a-sandboxed-rails-console.md)
|
||||
- [Query A Single Value From The Database](rails/query-a-single-value-from-the-database.md)
|
||||
@@ -1346,6 +1358,7 @@ _1488 TILs and counting..._
|
||||
- [Kill The Current Session](tmux/kill-the-current-session.md)
|
||||
- [List All Key Bindings](tmux/list-all-key-bindings.md)
|
||||
- [List Sessions](tmux/list-sessions.md)
|
||||
- [Open New Splits To The Current Directory](tmux/open-new-splits-to-the-current-directory.md)
|
||||
- [Open New Window With A Specific Directory](tmux/open-new-window-with-a-specific-directory.md)
|
||||
- [Organizing Windows](tmux/organizing-windows.md)
|
||||
- [Paging Up And Down](tmux/paging-up-and-down.md)
|
||||
@@ -1375,6 +1388,7 @@ _1488 TILs and counting..._
|
||||
- [Generate An Initial tsconfig File](typescript/generate-an-initial-tsconfig-file.md)
|
||||
- [Generate Inferred Type From Zod Schema](typescript/generate-inferred-type-from-zod-schema.md)
|
||||
- [Get The Return Type Of An Async Function](typescript/get-the-return-type-of-an-async-function.md)
|
||||
- [Ignore All Errors In A TypeScript File](typescript/ignore-all-errors-in-a-typescript-file.md)
|
||||
- [Interfaces With The Same Name Are Merged](typescript/interfaces-with-the-same-name-are-merged.md)
|
||||
- [Narrow The Type Of An Array To Its Values](typescript/narrow-the-type-of-an-array-to-its-values.md)
|
||||
- [Re-Export An Imported Type](typescript/re-export-an-imported-type.md)
|
||||
@@ -1443,6 +1457,7 @@ _1488 TILs and counting..._
|
||||
- [Generate Random 20-Character Hex String](unix/generate-random-20-character-hex-string.md)
|
||||
- [Get A List Of Locales On Your System](unix/get-a-list-of-locales-on-your-system.md)
|
||||
- [Get Matching Filenames As Output From Grep](unix/get-matching-filenames-as-output-from-grep.md)
|
||||
- [Get The SHA256 Hash For A File](unix/get-the-sha256-hash-for-a-file.md)
|
||||
- [Get The Unix Timestamp](unix/get-the-unix-timestamp.md)
|
||||
- [Global Substitution On The Previous Command](unix/global-substitution-on-the-previous-command.md)
|
||||
- [Globbing For All Directories In Zsh](unix/globbing-for-all-directories-in-zsh.md)
|
||||
@@ -1513,6 +1528,7 @@ _1488 TILs and counting..._
|
||||
- [Switch Versions of a Brew Formula](unix/switch-versions-of-a-brew-formula.md)
|
||||
- [Tell direnv To Load The Env File](unix/tell-direnv-to-load-the-env-file.md)
|
||||
- [Touch Access And Modify Times Individually](unix/touch-access-and-modify-times-individually.md)
|
||||
- [Undo Changes Made To Current Terminal Prompt](unix/undo-changes-made-to-current-terminal-prompt.md)
|
||||
- [Undo Some Command Line Editing](unix/undo-some-command-line-editing.md)
|
||||
- [Unrestrict Where ripgrep Searches](unix/unrestrict-where-ripgrep-searches.md)
|
||||
- [Update Package Versions Known By asdf Plugin](unix/update-package-versions-known-by-asdf-plugin.md)
|
||||
|
||||
51
css/add-line-numbers-to-a-code-block-with-counter.md
Normal file
51
css/add-line-numbers-to-a-code-block-with-counter.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# Add Line Numbers To A Code Block With Counter
|
||||
|
||||
The
|
||||
[`counter`](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_counter_styles/Using_CSS_counters)
|
||||
feature in CSS is a stateful feature that allows you to increment and display a
|
||||
number based on elements' locations in the document. This feature is useful for
|
||||
adding numbers to headings and lists, but it can also be used to add line
|
||||
numbers to a code block.
|
||||
|
||||
We need to initialize the counter to start using it. This will give it a name
|
||||
and default it to the value 0. We'll tie this to a `pre` tag which wraps our
|
||||
lines of code.
|
||||
|
||||
```css {{ title: 'globals.css' }}
|
||||
pre.shiki {
|
||||
counter-reset: line-number;
|
||||
}
|
||||
```
|
||||
|
||||
Then we need to increment the counter for every line of code that appears in
|
||||
the code block
|
||||
|
||||
```css {{ title: 'globals.css' }}
|
||||
pre.shiki .line {
|
||||
counter-increment: line-number;
|
||||
}
|
||||
```
|
||||
|
||||
Last, we need to display these incrementing `line-number` values _before_ each
|
||||
line.
|
||||
|
||||
```css {{ title: 'globals.css }}
|
||||
pre.shiki .line:not(:last-of-type)::before {
|
||||
content: counter(line-number);
|
||||
/*
|
||||
* plus any styling and spacing of the numbers
|
||||
*/
|
||||
}
|
||||
```
|
||||
|
||||
This essentially attaches an element to the front (`::before`) of the line
|
||||
whose content is the current value of `line-number`. It is applied to all but
|
||||
the last `.line` because [shiki](https://shiki.matsu.io/) includes an empty
|
||||
`.line` at the end.
|
||||
|
||||
Here is [the real-world example of
|
||||
this](https://github.com/pingdotgg/uploadthing/blob/4954c9956c141a25a5405991c34cc5ce8d990085/docs/src/styles/tailwind.css#L13-L37)
|
||||
that I referenced for this post.
|
||||
|
||||
Note: the counter can be incremented, decremented, or even explicitly set to a
|
||||
specific value.
|
||||
39
drizzle/drizzle-tracks-migrations-in-a-log-table.md
Normal file
39
drizzle/drizzle-tracks-migrations-in-a-log-table.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# Drizzle Tracks Migrations In A Log Table
|
||||
|
||||
When I generate (`npx drizzle-kit generate`) and apply (`npx drizzle-kit
|
||||
migrate`) schema migrations against my database with Drizzle, there are SQL
|
||||
files that get created and run.
|
||||
|
||||
How does Drizzle know which SQL files have been run and which haven't?
|
||||
|
||||
Like many SQL schema migration tools, it uses a table in the database to record
|
||||
this metadata. Drizzle defaults to calling this table `__drizzle_migrations`
|
||||
and puts it in the `drizzle` schema (which is like a database namespace).
|
||||
|
||||
Let's take a look at this table for a project with two migrations:
|
||||
|
||||
```sql
|
||||
postgres> \d drizzle.__drizzle_migrations
|
||||
Table "drizzle.__drizzle_migrations"
|
||||
Column | Type | Collation | Nullable | Default
|
||||
------------+---------+-----------+----------+----------------------------------------------------------
|
||||
id | integer | | not null | nextval('drizzle.__drizzle_migrations_id_seq'::regclass)
|
||||
hash | text | | not null |
|
||||
created_at | bigint | | |
|
||||
Indexes:
|
||||
"__drizzle_migrations_pkey" PRIMARY KEY, btree (id)
|
||||
|
||||
postgres> select * from drizzle.__drizzle_migrations;
|
||||
id | hash | created_at
|
||||
----+------------------------------------------------------------------+---------------
|
||||
1 | 8961353bf66f9b3fe1a715f6ea9d9ef2bc65697bb8a5c2569df939a61e72a318 | 1730219291288
|
||||
2 | b75e61451e2ce37d831608b1bc9231bf3af09e0ab54bf169be117de9d4ff6805 | 1730224013018
|
||||
(2 rows)
|
||||
```
|
||||
|
||||
Notice that Drizzle stores each migration record as [a SHA256 hash of the
|
||||
migration
|
||||
file](https://github.com/drizzle-team/drizzle-orm/blob/526996bd2ea20d5b1a0d65e743b47e23329d441c/drizzle-orm/src/migrator.ts#L52)
|
||||
and a timestamp of when the migration was run.
|
||||
|
||||
[source](https://orm.drizzle.team/docs/drizzle-kit-migrate#applied-migrations-log-in-the-database)
|
||||
27
git/count-all-files-of-specific-type-tracked-by-git.md
Normal file
27
git/count-all-files-of-specific-type-tracked-by-git.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# Count All Files Of Specific Type Tracked By Git
|
||||
|
||||
I want to get a count of all the markdown files in my [TIL
|
||||
repo](https://github.com/jbranchaud/til). Since all the files I care about are
|
||||
tracked by `git`, I can use `git ls-files` to get a listing of all files. That
|
||||
command on its own lists all files tracked by your git repository. Though there
|
||||
are many other flags we can apply, that will do for my purposes.
|
||||
|
||||
By giving `git ls-files` a pattern to match against, I can turn up just, for
|
||||
instance, markdown files (`*.md`). I can pipe that to `wc -l` to get a count
|
||||
rather than exploding my terminal with a list of file names.
|
||||
|
||||
```bash
|
||||
❯ git ls-files '*.md' | wc -l
|
||||
1503
|
||||
```
|
||||
|
||||
That command includes `README.md` and `CONTRIBUTING.md`, but really I only want
|
||||
to count the markdown files that constitute a TIL. Those all happen to be
|
||||
nested under a single directory. So I can tweak the glob pattern like so:
|
||||
|
||||
```bash
|
||||
❯ git ls-files '*/*.md' | wc -l
|
||||
1501
|
||||
```
|
||||
|
||||
See `man git-ls-files` for more details.
|
||||
56
go/do-something-n-times.md
Normal file
56
go/do-something-n-times.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# Do Something N Times
|
||||
|
||||
With Go 1.23 there is a new for-range syntax that makes looping a bit easier
|
||||
and more compact.
|
||||
|
||||
Instead of needing to set up our 3-part for-loop syntax, we can say we want to
|
||||
do something `N` times with `for range N`.
|
||||
|
||||
```go
|
||||
for range n {
|
||||
// do something
|
||||
}
|
||||
```
|
||||
|
||||
Let's look at an actual, runnable example:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
import "math/rand"
|
||||
import "time"
|
||||
|
||||
func main() {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
|
||||
food := []string{"taco", "burrito", "torta", "enchilada", "tostada"}
|
||||
|
||||
for range 5 {
|
||||
randomIndex := rand.Intn(len(food))
|
||||
fmt.Println(food[randomIndex])
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The output is random and might look something like this:
|
||||
|
||||
```bash
|
||||
$ go run loop.go
|
||||
taco
|
||||
burrito
|
||||
tostada
|
||||
taco
|
||||
enchilada
|
||||
```
|
||||
|
||||
I appreciate this syntax addition because it feels very akin to Ruby's `#times`
|
||||
method:
|
||||
|
||||
```ruby
|
||||
5.times do
|
||||
# do something
|
||||
end
|
||||
```
|
||||
|
||||
[source](https://eli.thegreenplace.net/2024/ranging-over-functions-in-go-123/)
|
||||
40
internet/add-styled-alerts-to-github-markdown-documents.md
Normal file
40
internet/add-styled-alerts-to-github-markdown-documents.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# Add Styled Alerts To GitHub Markdown Documents
|
||||
|
||||
The GFM (GitHub Flavored Markdown) variant of markdown adds some nice features
|
||||
to our GitHub-rendered markdown documents.
|
||||
|
||||
One such feature that has been around for a couple years, but which I only just
|
||||
learned about, are these styled alerts. There are five of them each with a
|
||||
different color and icon to help convey meaning.
|
||||
|
||||
```
|
||||
> [!NOTE]
|
||||
> Useful information that users should know, even when skimming content.
|
||||
|
||||
> [!TIP]
|
||||
> Helpful advice for doing things better or more easily.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Key information users need to know to achieve their goal.
|
||||
|
||||
> [!WARNING]
|
||||
> Urgent info that needs immediate user attention to avoid problems.
|
||||
|
||||
> [!CAUTION]
|
||||
> Advises about risks or negative outcomes of certain actions.
|
||||
```
|
||||
|
||||
I just added the following to the top of one of my project's READMEs to help me
|
||||
remember that it is not under active development.
|
||||
|
||||
```
|
||||
> [!WARNING]
|
||||
> This repo is not under active development, you might be looking for
|
||||
> [til-visualmode-dev](https://github.com/jbranchaud/til-visualmode-dev).
|
||||
```
|
||||
|
||||
Visit the GitHub docs for
|
||||
[Alerts](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#alerts)
|
||||
to see examples of how these render.
|
||||
|
||||
[source](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#alerts)
|
||||
29
internet/digraph-unicode-characters-have-a-titlecase.md
Normal file
29
internet/digraph-unicode-characters-have-a-titlecase.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# Digraph Unicode Characters Have a Titlecase
|
||||
|
||||
Coming from primarily being exposed to the US American alphabet, I'm familiar
|
||||
with characters that I type into the computer having one of two cases. Either
|
||||
it is lowercase by default (`c`) or I can hit the shift key to produce the
|
||||
uppercase version (`C`).
|
||||
|
||||
Unicode, which has broad support for character encoding across most languages,
|
||||
has a couple characters that are called _digraphs_. These are single code
|
||||
points, but look like they are made up of two characters.
|
||||
|
||||
A good example of this is `dž`. And if that character were to appear in an all
|
||||
uppercase word, then it would display as `DŽ`.
|
||||
|
||||
But what if it appears at the beginning of a capitalized word?
|
||||
|
||||
That's where _titlecase_ comes into the picture -- `Dž`.
|
||||
|
||||
From [wikipedia](https://en.wikipedia.org/wiki/D%C5%BE):
|
||||
|
||||
> Note that when the letter is the initial of a capitalised word (like Džungla
|
||||
> or Džemper, or personal names like Džemal or Džamonja), the ž is not
|
||||
> uppercase. Only when the whole word is written in uppercase, is the Ž
|
||||
> capitalised.
|
||||
|
||||
(I find it odd that wikipedia's article on this digraph code point is using
|
||||
separate characters instead of the digraph.)
|
||||
|
||||
[source](https://devblogs.microsoft.com/oldnewthing/20241031-00/?p=110443)
|
||||
55
javascript/prevent-hidden-element-from-flickering-on-load.md
Normal file
55
javascript/prevent-hidden-element-from-flickering-on-load.md
Normal file
@@ -0,0 +1,55 @@
|
||||
# Prevent Hidden Element From Flickering On Load
|
||||
|
||||
Here is what it might look like to use [Alpine.js](https://alpinejs.dev/) to
|
||||
sprinkle in some JavaScript for controlling a dropdown menu.
|
||||
|
||||
```html
|
||||
<div x-data="{ profileDropdownOpen: false }">
|
||||
<button
|
||||
type="button"
|
||||
@click="profileDropdownOpen = !profileDropdownOpen"
|
||||
>
|
||||
<!-- some inner html -->
|
||||
</button>
|
||||
<div x-show="profileDropdownOpen" role="menu">
|
||||
<a href="/profile" role="menuitem">Your Profile</a>
|
||||
<a href="/sign-out" role="menuitem">Sign Out</a>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
Functionally that will work. You can click the button to toggle the menu open
|
||||
and closed.
|
||||
|
||||
What you might notice, however, when you refresh the page is that the menu
|
||||
flickers open as the page first loads and then disappears. This is a quirk of
|
||||
the element being rendered before Alpine.js is loaded and the
|
||||
[`x-show`](https://alpinejs.dev/directives/show) directive has a chance to take
|
||||
effect.
|
||||
|
||||
To get around this, we can _cloak_ any element with an `x-show` directive that
|
||||
should be hidden by default.
|
||||
|
||||
```html
|
||||
<div x-data="{ profileDropdownOpen: false }">
|
||||
<button
|
||||
type="button"
|
||||
@click="profileDropdownOpen = !profileDropdownOpen"
|
||||
>
|
||||
<!-- some inner html -->
|
||||
</button>
|
||||
<div x-cloak x-show="profileDropdownOpen" role="menu">
|
||||
<a href="/profile" role="menuitem">Your Profile</a>
|
||||
<a href="/sign-out" role="menuitem">Sign Out</a>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
This addition needs to be paired with some custom CSS to hide any _cloaked_
|
||||
elements.
|
||||
|
||||
```css
|
||||
[x-cloak] { display: none !important; }
|
||||
```
|
||||
|
||||
[source](https://alpinejs.dev/directives/cloak)
|
||||
56
postgres/generate-random-alphanumeric-identifier.md
Normal file
56
postgres/generate-random-alphanumeric-identifier.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# Generate Random Alphanumeric Identifier
|
||||
|
||||
Here is a PostgreSQL query that uses
|
||||
[`pgcrypto`](https://www.postgresql.org/docs/current/pgcrypto.html) (for
|
||||
[`get_random_bytes`](https://www.postgresql.org/docs/current/pgcrypto.html#PGCRYPTO-RANDOM-DATA-FUNCS))
|
||||
and a CTE to generate a cryptographically-random 8-character alphanumeric
|
||||
identifier.
|
||||
|
||||
```sql
|
||||
-- First ensure pgcrypto is installed
|
||||
create extension if not exists pgcrypto;
|
||||
|
||||
-- Generates a single 8-character identifier
|
||||
with chars as (
|
||||
-- excludes some look-alike characters
|
||||
select '23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz' as charset
|
||||
),
|
||||
random_bytes as (
|
||||
select gen_random_bytes(8) as bytes
|
||||
),
|
||||
positions as (
|
||||
select generate_series(0, 7) as pos
|
||||
)
|
||||
select string_agg(
|
||||
substr(
|
||||
charset,
|
||||
(get_byte(bytes, pos) % length(charset)) + 1,
|
||||
1
|
||||
),
|
||||
'' order by pos
|
||||
) as short_id
|
||||
from positions, random_bytes, chars;
|
||||
```
|
||||
|
||||
Here is an example of the output:
|
||||
|
||||
```sql
|
||||
+----------+
|
||||
| short_id |
|
||||
|----------|
|
||||
| NXdu9AnV |
|
||||
+----------+
|
||||
```
|
||||
|
||||
The
|
||||
[`generate_series`](https://www.postgresql.org/docs/current/functions-srf.html)
|
||||
gives us an 8-row table from 0 to 7 that we can use as indexes into the byte
|
||||
positions of the value we get from `gen_random_bytes`. Those random bytes get
|
||||
mapped to individual alphanumeric characters from `chars`. That then gets
|
||||
squeezed together with `string_agg`.
|
||||
|
||||
Note: the character set excludes some characters that can be mistaken for one
|
||||
another like `0` and `O` or `1` and `l`.
|
||||
|
||||
Note: you could change the right-bound of the `generate_series` to generate a
|
||||
different length identifier.
|
||||
80
postgres/table-names-are-treated-as-lower-case-by-default.md
Normal file
80
postgres/table-names-are-treated-as-lower-case-by-default.md
Normal file
@@ -0,0 +1,80 @@
|
||||
# Table Names Are Treated As Lower-Case By Default
|
||||
|
||||
This one is a bit unintuitive and can cause some real confusion -- when you
|
||||
create a table in PostgreSQL, any casing is ignored, it is treated as
|
||||
lower-case. Let's see it to believe it:
|
||||
|
||||
```sql
|
||||
> create table BookMarks (
|
||||
id integer generated always as identity primary key,
|
||||
location text not null
|
||||
);
|
||||
|
||||
> \d
|
||||
+--------+--------------------+----------+----------+
|
||||
| Schema | Name | Type | Owner |
|
||||
|--------+--------------------+----------+----------|
|
||||
| public | bookmarks | table | postgres |
|
||||
| public | bookmarks_id_seq | sequence | postgres |
|
||||
+--------+--------------------+----------+----------+
|
||||
```
|
||||
|
||||
Notice that when we list our tables, the uppercase `M` and `B` are gone. That's
|
||||
because Postgres folds away the casing when processing the table name
|
||||
identifier.
|
||||
|
||||
It doesn't matter how we refer to it for queries:
|
||||
|
||||
```sql
|
||||
> select * from BookMarks;
|
||||
+----+----------+
|
||||
| id | location |
|
||||
|----+----------|
|
||||
+----+----------+
|
||||
|
||||
> select * from bookmarks;
|
||||
+----+----------+
|
||||
| id | location |
|
||||
|----+----------|
|
||||
+----+----------+
|
||||
```
|
||||
|
||||
You can force Postgres to respect the casing by wrapping the table name in
|
||||
quotes.
|
||||
|
||||
```sql
|
||||
> create table "BookMarks" (
|
||||
id integer generated always as identity primary key,
|
||||
location text not null
|
||||
);
|
||||
|
||||
> \d
|
||||
+--------+--------------------+----------+----------+
|
||||
| Schema | Name | Type | Owner |
|
||||
|--------+--------------------+----------+----------|
|
||||
| public | BookMarks | table | postgres |
|
||||
| public | BookMarks_id_seq | sequence | postgres |
|
||||
+--------+--------------------+----------+----------+
|
||||
|
||||
> select * from "BookMarks";
|
||||
+----+----------+
|
||||
| id | location |
|
||||
|----+----------|
|
||||
+----+----------+
|
||||
|
||||
> select * from "bookmarks";
|
||||
relation "bookmarks" does not exist
|
||||
LINE 1: select * from "bookmarks"
|
||||
^
|
||||
|
||||
> select * from BookMarks;
|
||||
relation "bookmarks" does not exist
|
||||
LINE 1: select * from BookMarks
|
||||
^
|
||||
```
|
||||
|
||||
That then means you have to quote your table name anytime you want to refer to
|
||||
it in a query. It's not worth it. It is better to always keep your table names
|
||||
lower-case using snake case.
|
||||
|
||||
[source](https://weiyen.net/articles/avoid-capital-letters-in-postgres-names)
|
||||
49
python/store-and-access-immutable-data-in-a-tuple.md
Normal file
49
python/store-and-access-immutable-data-in-a-tuple.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# Store And Access Immutable Data In A Tuple
|
||||
|
||||
You can store heterogeneous data (of varying types) as a _tuple_ which is a
|
||||
light-weight immutable data structure.
|
||||
|
||||
You can be explicit about the tuple by wrapping the items in parentheses:
|
||||
|
||||
```python
|
||||
>>> book = ('An Immense World', 'Ed Yong', 2022)
|
||||
```
|
||||
|
||||
Though it is also possible to comma-separate the items and forego the
|
||||
parentheses.
|
||||
|
||||
```python
|
||||
>>> book2 = 'The Shining', 'Stephen King', 1977
|
||||
>>> book2
|
||||
('The Shining', 'Stephen King', 1977)
|
||||
```
|
||||
|
||||
Once we have our tuple, we can access any item from it positionally. We can
|
||||
also use _sequence unpacking_ to assign the values to a series of variables:
|
||||
|
||||
```python
|
||||
>>> book[0]
|
||||
'An Immense World'
|
||||
>>> book[1]
|
||||
'Ed Yong'
|
||||
>>> book[2]
|
||||
2022
|
||||
>>> title, author, publication_year = book
|
||||
>>> title
|
||||
'An Immense World'
|
||||
>>> author
|
||||
'Ed Yong'
|
||||
>>> publication_year
|
||||
2022
|
||||
```
|
||||
|
||||
And, as promised, it is immutable (unlike lists):
|
||||
|
||||
```python
|
||||
>>> book[1] = 'Agatha Christie'
|
||||
Traceback (most recent call last):
|
||||
File "<stdin>", line 1, in <module>
|
||||
TypeError: 'tuple' object does not support item assignment
|
||||
```
|
||||
|
||||
[source](https://docs.python.org/3/tutorial/datastructures.html#tuples-and-sequences)
|
||||
30
rails/create-table-with-bigint-id-as-primary-key.md
Normal file
30
rails/create-table-with-bigint-id-as-primary-key.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# Create Table With bigint ID As Primary Key
|
||||
|
||||
When creating a new table with an ActiveRecord migration, we specify all the
|
||||
fields _except_ the `id`. The `id`, which is the primary key, is implicit. We
|
||||
get it by default.
|
||||
|
||||
The type of that `id` defaults to `int` which is a 32-bit signed integer.
|
||||
|
||||
We can override the type of `id` in a variety of ways. The one I prefer in most
|
||||
cases is to make the `id` of type `bigint`. This is a 64-bit signed integer. It
|
||||
offers quite a bit more headroom for the number of unique identifies in our
|
||||
table.
|
||||
|
||||
This can be specified by including `id: :bigint` as an option to the
|
||||
`create_table` method.
|
||||
|
||||
```ruby
|
||||
class CreatePosts < ActiveRecord::Migration[8.0]
|
||||
def change
|
||||
create_table :posts, id: :bigint do |t|
|
||||
t.string :title, null: false
|
||||
t.string :body, null: false
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
[source](https://api.rubyonrails.org/v7.1/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html#method-i-create_table)
|
||||
63
rails/prefer-select-all-over-execute-for-read-queries.md
Normal file
63
rails/prefer-select-all-over-execute-for-read-queries.md
Normal file
@@ -0,0 +1,63 @@
|
||||
# Prefer select_all Over execute For Read Queries
|
||||
|
||||
Though the `#execute` function provided by ActiveRecord technically works as a
|
||||
general-purpose query runner for strings of raw SQL, it has some downsides.
|
||||
|
||||
First, let's say we have a large semi-complex (better in SQL than ActiveRecord
|
||||
DSL) SQL query defined in a heredoc.
|
||||
|
||||
```ruby
|
||||
books_by_status_query = <<-SQL
|
||||
select
|
||||
books.*,
|
||||
latest_statuses.status as current_status,
|
||||
array_to_json(array_agg(...)) as reading_statuses
|
||||
from books
|
||||
-- plus several left joins
|
||||
-- where clause, group by, and order by
|
||||
SQL
|
||||
```
|
||||
|
||||
I reflexively reach for
|
||||
[`#execute`](https://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/DatabaseStatements.html#method-i-execute)
|
||||
in a situation like that:
|
||||
|
||||
```ruby
|
||||
result = ActiveRecord::Base.connection.execute(books_by_status_query)
|
||||
```
|
||||
|
||||
However, if we're doing a read-only query and we are expecting multiple rows in
|
||||
the result, then we are better off reaching for
|
||||
[`#select_all`](https://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/DatabaseStatements.html#method-i-select_all).
|
||||
|
||||
```ruby
|
||||
result = ActiveRecord::Base.connection.select_all(books_by_status_query)
|
||||
```
|
||||
|
||||
It has the advantage of semantically communicating that it's just a read and
|
||||
won't have any side-effects. It also avoids an unnecessary clear of the query
|
||||
cache.
|
||||
|
||||
> Note: [when execute is used] the query is assumed to have side effects and
|
||||
> the query cache will be cleared. If the query is read-only, consider using
|
||||
> select_all instead.
|
||||
|
||||
The `#execute` method also has been known to leak memory with some database
|
||||
connectors.
|
||||
|
||||
> Note: depending on your database connector, the result returned by this
|
||||
> method may be manually memory managed. Consider using exec_query wrapper
|
||||
> instead.
|
||||
|
||||
We can then iterate through and transform the results just as we would have
|
||||
done with `#execute`.
|
||||
|
||||
```ruby
|
||||
result.map do |row|
|
||||
row.tap do |hash|
|
||||
hash["reading_statuses"] = JSON.parse(hash["reading_statuses"])
|
||||
end
|
||||
|
||||
OpenStruct.new(row)
|
||||
end
|
||||
```
|
||||
37
tmux/open-new-splits-to-the-current-directory.md
Normal file
37
tmux/open-new-splits-to-the-current-directory.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# Open New Splits To The Current Directory
|
||||
|
||||
I typically work in one project per tmux session. When I create a given tmux
|
||||
session, the default directory is for that project. All new windows and pane
|
||||
splits will open at that default directory. This generally is the default
|
||||
behavior I want.
|
||||
|
||||
One caveat: I often open a new window within an existing session that I want
|
||||
anchored to another directory. This could be because I'm working in a monorepo
|
||||
and I need to work from a subdirectory for a specific package or app. Or it
|
||||
could be that I'm temporarily digging into another project and it isn't worth
|
||||
create a whole new session.
|
||||
|
||||
Regardless of the reason, I run into a bit of friction with tmux's defaults.
|
||||
|
||||
First I open the new window and `cd` to another project. After some working, I
|
||||
need to open a split pane, maybe to run a project command like a build or dev
|
||||
server. Hitting `prefix-"` (horizontal split) or `prefix-%` (vertical split)
|
||||
opens a pane with the shell defaulting back to the original directory, not the
|
||||
current directory.
|
||||
|
||||
The trick to fixing this bit of friction is overriding the directory of pane
|
||||
splits. I can do that by adding the following to my `~/.tmux.conf`:
|
||||
|
||||
```
|
||||
# Pane splits should open to the same path as the current pane
|
||||
bind '"' split-window -v -c "#{pane_current_path}"
|
||||
bind % split-window -h -c "#{pane_current_path}"
|
||||
```
|
||||
|
||||
Make sure to run `tmux source-file ~/.tmux.conf` to apply these config changes.
|
||||
|
||||
The `pane_current_path` is called a "Format" in tmux parlance. It resolves to
|
||||
the absolute path of the current pane's current directory. You can find all the
|
||||
formats in the manpage with this command: `man tmux | less +'/^FORMATS'`. You
|
||||
can also show yourself that this format resolves to what you expect by running
|
||||
`tmux display-message -p '#{pane_current_path}'`.
|
||||
35
typescript/ignore-all-errors-in-a-typescript-file.md
Normal file
35
typescript/ignore-all-errors-in-a-typescript-file.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# Ignore All Errors In A TypeScript File
|
||||
|
||||
As of TypeScript 3.7, we can mark an entire TypeScript file to be ignored by
|
||||
the TypeScript compiler when it is doing static type checking.
|
||||
|
||||
We can do this by adding the `@ts-nocheck` directive at the top of the file:
|
||||
|
||||
```typescript
|
||||
// @ts-nocheck
|
||||
|
||||
type User = {
|
||||
id: string;
|
||||
name: string;
|
||||
email: string;
|
||||
}
|
||||
|
||||
const user: User = {
|
||||
id: 123,
|
||||
name: "Liz Lemon",
|
||||
email: "liz.lemon@nbc.com",
|
||||
};
|
||||
```
|
||||
|
||||
Notice that `id` is typed as a `string`, but we are using a `number` with `id`
|
||||
for `user`. That is a type error. But with the `@ts-nocheck` directive at the
|
||||
top, the type checker doesn't run on the file and we see no type errors.
|
||||
|
||||
I'd generally suggest to avoid doing this. It can hide real type errors that
|
||||
you should be addressing. That said, in special circumstances, you may need it,
|
||||
even if just temporarily, like if an imported package doesn't have types. Here
|
||||
is an example of that in [uploadthing's
|
||||
`rehype.js`](https://github.com/pingdotgg/uploadthing/blob/d98fbefedddf64d183cc5a00b3fd707e8d8f2f6c/docs/src/mdx/rehype.js#L1)
|
||||
which is missing types from `mdx-annotations`.
|
||||
|
||||
[source](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#-ts-nocheck-in-typescript-files)
|
||||
34
unix/get-the-sha256-hash-for-a-file.md
Normal file
34
unix/get-the-sha256-hash-for-a-file.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# Get The SHA256 Hash For A File
|
||||
|
||||
Unix systems come with a `sha256sum` utility that we can use to compute the
|
||||
SHA256 hash of a file. This means the contents of file are compressed into a
|
||||
256-bit digest.
|
||||
|
||||
Here I use it on a SQL migration file that I've generated.
|
||||
|
||||
```bash
|
||||
$ sha256sum migrations/0001_large_doctor_spectrum.sql
|
||||
b75e61451e2ce37d831608b1bc9231bf3af09e0ab54bf169be117de9d4ff6805 migrations/0001_large_doctor_spectrum.sql
|
||||
```
|
||||
|
||||
Each file passed to this utility gets output to a separate line which is why we
|
||||
see the filename next to the hash. Since I am only running it on a single file
|
||||
and I may want to pipe the output to some other program, I can clip off just
|
||||
the part I need.
|
||||
|
||||
```bash
|
||||
sha256sum migrations/0001_large_doctor_spectrum.sql | cut -d ' ' -f 1
|
||||
b75e61451e2ce37d831608b1bc9231bf3af09e0ab54bf169be117de9d4ff6805
|
||||
```
|
||||
|
||||
We can also produce these digests with `openssl`:
|
||||
|
||||
```bash
|
||||
$ openssl dgst -sha256 migrations/0001_large_doctor_spectrum.sql
|
||||
SHA2-256(migrations/0001_large_doctor_spectrum.sql)= b75e61451e2ce37d831608b1bc9231bf3af09e0ab54bf169be117de9d4ff6805
|
||||
|
||||
$ openssl dgst -sha256 migrations/0001_large_doctor_spectrum.sql | cut -d ' ' -f 2
|
||||
b75e61451e2ce37d831608b1bc9231bf3af09e0ab54bf169be117de9d4ff6805
|
||||
```
|
||||
|
||||
See `sha256sum --help` or `openssl dgst --help` for more details.
|
||||
20
unix/undo-changes-made-to-current-terminal-prompt.md
Normal file
20
unix/undo-changes-made-to-current-terminal-prompt.md
Normal file
@@ -0,0 +1,20 @@
|
||||
# Undo Change Made to Current Terminal Prompt
|
||||
|
||||
I frequently use a variety of ASCII command characters like `ctrl-u` to delete
|
||||
the entire line or `ctrl-a` to jump to the front of a long line so I can make
|
||||
some edits toward that side of the command or `ctrl-e` to jump to the end of
|
||||
the command for the same reason. I sometimes even use `ctrl-k` to delete
|
||||
everything after the cursor to the end of the line.
|
||||
|
||||
What I didn't realize until now is that any of those commands the modify the
|
||||
current line of the termianl prompt plus regular typing and hitting the
|
||||
backspace are all _undoable_.
|
||||
|
||||
So, if I just wiped out half the line (with `ctrl-k`) and I immediately regret
|
||||
it, I can restore it with `ctrl-_`. The system keeps of history of the actions
|
||||
you've taken, so you can keep hitting `ctrl-_` to undo even further.
|
||||
|
||||
The `ctrl-/` command does the same, per GNU's [Undo Changes in the Emacs
|
||||
docs](https://www.gnu.org/software/emacs/manual/html_node/emacs/Basic-Undo.html).
|
||||
|
||||
[source](https://jvns.ca/ascii)
|
||||
@@ -6,7 +6,7 @@ convert it using the `ebook-convert` binary from `Calibre`.
|
||||
First, install `Calibre`:
|
||||
|
||||
```bash
|
||||
$ brew cask install calibre
|
||||
$ brew install --cask calibre
|
||||
```
|
||||
|
||||
Then convert your ePub using `ebook-convert`:
|
||||
|
||||
Reference in New Issue
Block a user