1
0
mirror of https://github.com/jbranchaud/til synced 2026-01-17 14:08:01 +00:00

Compare commits

..

89 Commits

Author SHA1 Message Date
edieblu
d09e046b91 Merge b4080eb9ee into 86972f41cc 2024-11-20 18:48:50 +00:00
jbranchaud
86972f41cc Add Set Default As SQL Function In Migration as a Rails TIL 2024-11-20 10:10:04 -06:00
jbranchaud
93a663cc9c Adjust formatting, add source link to pg TIL 2024-11-20 09:32:45 -06:00
jbranchaud
0c1dd29d8d Add Output Bytecode For A Ruby Program as a Ruby TIL 2024-11-18 11:22:35 -06:00
jbranchaud
b492a9d765 Add Override The Boolean Context Of A Class as a Python TIL 2024-11-17 09:44:55 -06:00
jbranchaud
543a82730d Add Forward All Arguments To Another Method as a Ruby TIL 2024-11-16 12:32:21 -06:00
jbranchaud
877537228f Add Gracefully Exit A Script With Trap as a Unix TIL 2024-11-15 19:55:22 -06:00
jbranchaud
1513611857 Add Execute Several Commands With Backtick Heredoc as a Ruby TIL 2024-11-14 23:47:07 -06:00
jbranchaud
74514b462d Add a caveat to the latest TIL 2024-11-13 20:31:25 -06:00
jbranchaud
484dec8e24 Add Empty find_by Returns First Record as a Rails TIL 2024-11-13 20:24:59 -06:00
jbranchaud
8574113dc6 Add Dunder Methods as a Python TIL 2024-11-12 17:45:53 -06:00
jbranchaud
1c4e37ed8a Add Count All Files Of Specific Type Tracked By Git as a Git TIL 2024-11-11 20:15:43 -06:00
jbranchaud
581aa1decb Add Add Line Numbers To A Code Block With Counter as a CSS TIL 2024-11-10 12:43:41 -06:00
jbranchaud
0c4795c1d2 Change codeblock language to ruby in latest TIL 2024-11-09 17:48:32 -06:00
jbranchaud
9bbde247a5 Add Create Table With bigint Id As Primary Key as a Rails TIL 2024-11-09 17:28:47 -06:00
jbranchaud
36ca71bfb1 Add Store And Access Immutable Data In A Tuple as a Python TIL 2024-11-08 09:16:45 -06:00
jbranchaud
8a682e3a89 Add Open New Splits To The Current Directory as a tmux TIL 2024-11-07 09:43:46 -06:00
jbranchaud
c7a38c8267 Add Ignore All Errors In A TypeScript File as a TypeScript TIL 2024-11-06 10:18:44 -06:00
jbranchaud
71d3e56b3d Add Styled Alerts To GitHub Markdown Documents as an Internet TIL 2024-11-05 12:44:14 -06:00
jbranchaud
af3974d3fe Add Do Something N Times as a Go TIL 2024-11-05 11:53:23 -06:00
jbranchaud
adc6b2e903 Add Digraph Unicode Characters Have A Titlecase as a Internet TIL 2024-11-04 08:18:52 -06:00
jbranchaud
9a6ebd4c6b Add Table Names Are Treated As Lower-Case By Default as a Postgres TIL 2024-11-03 23:19:17 -06:00
jbranchaud
6df0693804 Add a few more notes to the latest TIL 2024-11-02 13:47:16 -05:00
jbranchaud
507602ef0c Add Prefer select_all Over execute For Read Queries as a Rails TIL 2024-11-02 13:29:54 -05:00
jbranchaud
18bdcc88b8 Add example of output to latest TIL 2024-11-01 14:42:07 -05:00
jbranchaud
95115c7ebc Add Generate Random Alphanumeric Identifier as a Postgres TIL 2024-11-01 12:46:18 -05:00
jbranchaud
4e859b93d2 Add a clarification to the latest TIL 2024-10-31 20:01:26 -05:00
jbranchaud
23c20e99bf Add Undo Changes Made To Current Terminal Prompt as a Unix TIL 2024-10-31 19:57:05 -05:00
jbranchaud
63bb627716 Add Prevent Hidden Element From Flickering On Load as a JavaScript TIL 2024-10-30 15:11:41 -05:00
jbranchaud
21385f4491 Add Drizzle Tracks Migrations In A Log Table as a Drizzle TIL 2024-10-29 16:18:55 -05:00
jbranchaud
5b47326ab3 Add Get The SHA256 For A File as a Unix TIL 2024-10-29 16:09:58 -05:00
jbranchaud
c16d80fd94 Add Get Fields For Inserted Row as a Drizzle TIL 2024-10-28 15:40:24 -05:00
jbranchaud
edf38308da Add Set DateTime To Include Time Zone In Migrations as a Rails TIL 2024-10-28 11:34:34 -05:00
jbranchaud
dc7159c16c Add a bit more to the latest TIL 2024-10-27 22:45:13 -05:00
jbranchaud
33f780a69f Add Concatenate Strings With A Separator as a Postgres TIL 2024-10-27 22:38:59 -05:00
jbranchaud
dfe9c002ee Add Add Unique Constraint Using Existing Index as a Postgres TIL 2024-10-26 10:03:23 -05:00
jbranchaud
3e34636d80 Add Analyze Your Website Performance as an Internet TIL 2024-10-25 12:38:46 -05:00
jbranchaud
dcef57d344 Add Make Truly Deep Clone With Structured Clone as a JavaScript TIL 2024-10-25 08:36:46 -05:00
jbranchaud
6580393b7a Add Create bigint Identity Column For Primary Key as a Drizzle TIL 2024-10-24 17:39:55 -05:00
jbranchaud
17d7f0933b Add Add Subscriber To Kit Form Via API as a Workflow TIL 2024-10-23 19:12:52 -05:00
jbranchaud
e4abc56f4c Add Add Hotkeys For Specific Raycast Extensions as a Workflow TIL 2024-10-22 14:30:59 -05:00
jbranchaud
43ea7acd74 Add Use A Space To Exclude Command Fromm History as a Zsh TIL 2024-10-21 11:21:21 -05:00
jbranchaud
d7d331b688 Add Put Unique Constraint On Generated Column as a Postgres TIL 2024-10-21 11:04:04 -05:00
jbranchaud
b743dc2ac0 Add Exclude AI Overview From Google Search as an Internet TIL 2024-10-20 19:38:08 -05:00
jbranchaud
4a72c63e42 Add Enforce Uniqueness On Column Expression as a Postgres TIL 2024-10-19 19:13:30 -05:00
jbranchaud
431507fd0e Add Send A Message To A Discord Channel as a Workflow TIL 2024-10-18 17:59:08 -05:00
jbranchaud
fb153f35bf Add Postgre Does Not Support Unsigned Integers as a PostgreSQL TIL 2024-10-18 17:43:46 -05:00
jbranchaud
f4ba6a9ef7 Add Override The Global Git Ignore File as a Git TIL 2024-10-18 15:55:40 -05:00
jbranchaud
0dee39d3c5 Add Configure Email Redirect With Cloudflare as a Workflow TIL 2024-10-17 11:09:04 -05:00
jbranchaud
5ebdd9a1a9 Add Convert JPEG To PNG With ffmpeg as a Unix TIL 2024-10-16 12:43:18 -05:00
jbranchaud
33c5cd748f Fix codeblock example in latest TIL 2024-10-15 15:07:55 -05:00
jbranchaud
ff515c8d6a Add Get URL For GitHub User Profile Photo as a Workflow TIL 2024-10-15 15:07:19 -05:00
jbranchaud
fad36e0691 Add Check How A File Is Being Ignored as a Git TIL 2024-10-15 12:19:55 -05:00
jbranchaud
4d1d8e7134 Add Switch Moving End Of Visual Selection as a Vim TIL 2024-10-14 20:20:41 -05:00
jbranchaud
567637497c Add Ensure Resources Always Get Closed as a Java TIL 2024-10-14 16:04:07 -05:00
jbranchaud
028b76ba6b Add Generate Types For A Content Collection as an Astro TIL 2024-10-13 17:03:25 -05:00
jbranchaud
1934c8f63e Add Make Direnv Less Noisy as a Unix TIL 2024-10-13 16:25:44 -05:00
jbranchaud
e5a003dbaf Add Markdown Files Are Of Type MarkdownInstance as an Astro TIL 2024-10-13 11:46:36 -05:00
jbranchaud
ab9d2b5bf6 Add Check Media Queries From JavaScript as a JavaScript TIL 2024-10-12 12:30:07 -05:00
jbranchaud
aa00c55b06 Add Jump To Problems In The Current File as a VSCode TIL 2024-10-12 10:35:52 -05:00
jbranchaud
cc858382d8 Add Temporarily Turn Off Pending Migrations Error as a Rails TIL 2024-10-11 15:05:31 -05:00
jbranchaud
dbb8c585c1 Add updated note to TIL about godoc 2024-10-11 13:57:47 -05:00
jbranchaud
f25064031f Add Export List Of Everything Installed By Brew as a Brew TIL 2024-10-11 10:57:42 -05:00
jbranchaud
cfbe640eb0 Add Keyboard Shortcuts For Interacting With Text Areas as a Mac TIL 2024-10-10 11:52:22 -05:00
jbranchaud
bf04dfcca5 Add Rollback A Couple Migrations as a Rails TIL 2024-10-09 17:44:45 -05:00
jbranchaud
24b1b02d52 Add Define The Root Path For The App as a Rails TIL 2024-10-09 13:23:09 -05:00
jbranchaud
8ef2cfdc69 Add Prevent erb_lint From Removing Opening Tags as a Ruby TIL 2024-10-09 10:53:39 -05:00
jbranchaud
02086e7115 Add Link A Scalar To An Array as a Zsh TIL 2024-10-07 18:51:57 -05:00
jbranchaud
0ecc41bd29 Add Add To The Path Via Path Array as a Zsh TIL 2024-10-07 18:37:23 -05:00
jbranchaud
569220e734 Add Find Any Dotfiles That Modify Path Env Var as a Unix TIL 2024-10-07 13:44:21 -05:00
jbranchaud
594ec08636 Add Download A File With Curl as a Unix TIL 2024-10-04 12:46:21 -05:00
jbranchaud
475f125f4b Add List Running Docker Containers as a Docker TIL 2024-10-03 11:38:13 -05:00
jbranchaud
ba6492d46e Add Run A Hello World Program In Eclipse as a Java TIL 2024-10-02 21:22:43 -05:00
jbranchaud
3ac3014659 Add table of contents entry for Java 2024-10-02 21:22:23 -05:00
jbranchaud
4b6833c437 Add Install Java On Mac With Brew as a Java TIL 2024-10-01 19:21:14 -05:00
jbranchaud
fc93ecfed4 Add Validate Column Data With Check Constraints as a Rails TIL 2024-09-30 19:50:52 -05:00
jbranchaud
a07a19e6d9 Add Generate Modern Primary Key Columns as a Postgres TIL 2024-09-27 09:57:33 -05:00
jbranchaud
951a2f04ad Add Find Executables Installed By Go as a Go TIL 2024-09-26 20:54:41 -05:00
jbranchaud
6b9ec224b8 Add Connect To A Database By Color as a Heroku TIL 2024-09-26 08:47:31 -05:00
jbranchaud
c33be14ec2 Add Skip The Front Of An Array With Drop as a Ruby TIL 2024-09-25 22:43:04 -05:00
jbranchaud
2922bbdd6a Add Check Network Quality Stats From The Command Line as a Mac TIL 2024-09-24 11:08:38 -05:00
jbranchaud
2e72ec1160 Add Generate A Model as a Rails TIL 2024-09-20 20:54:52 -05:00
jbranchaud
baab5738e7 Add Convert SVG To Favicon as a Unix TIL 2024-09-15 12:24:18 -05:00
jbranchaud
191c9d6d9d Add Generate A Rails App From The Main Branch as a Rails TIL 2024-09-09 11:57:10 -05:00
jbranchaud
25b5677260 Add Open Dashboard For Specific Add-On as Heroku TIL 2024-08-23 13:29:47 -05:00
jbranchaud
df3492d4ef Add Configure Tests To Run In Random Order as an RSpec TIL 2024-08-22 11:19:50 -05:00
jbranchaud
bd49b31bb0 Add Multi-Line Comments as a Ruby TIL 2024-08-22 11:12:28 -05:00
jbranchaud
4ff1a381d1 Add Avoid Accidentally Disabling Pry as an rspec TIL 2024-08-14 10:09:07 -05:00
jbranchaud
0bfeb0e236 Add Avoid Conflicting Files as a Next.js TIL 2024-07-16 10:43:03 -05:00
82 changed files with 3090 additions and 4 deletions

12
.vimrc
View File

@@ -9,3 +9,15 @@ function! CountTILs()
endfunction
nnoremap <leader>c :call CountTILs()<cr>
augroup DisableMarkdownFormattingForTILReadme
autocmd!
autocmd BufRead ~/code/til/README.md autocmd! Format
augroup END
" local til_readme_group = vim.api.nvim_create_augroup('DisableMarkdownFormattingForTILReadme', { clear = true })
" vim.api.nvim_create_autocmd('BufRead', {
" command = 'autocmd! Format',
" group = til_readme_group,
" pattern = vim.fn.expand '~/code/til/README.md',
" })

View File

@@ -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).
_1434 TILs and counting..._
_1512 TILs and counting..._
---
@@ -19,6 +19,7 @@ _1434 TILs and counting..._
* [Ack](#ack)
* [Amplify](#amplify)
* [Ansible](#ansible)
* [Astro](#astro)
* [Brew](#brew)
* [Chrome](#chrome)
* [Clojure](#clojure)
@@ -26,6 +27,7 @@ _1434 TILs and counting..._
* [Deno](#deno)
* [Devops](#devops)
* [Docker](#docker)
* [Drizzle](#drizzle)
* [Elixir](#elixir)
* [Gatsby](#gatsby)
* [Git](#git)
@@ -37,6 +39,7 @@ _1434 TILs and counting..._
* [HTTP](#http)
* [Inngest](#inngest)
* [Internet](#internet)
* [Java](#java)
* [JavaScript](#javascript)
* [jq](#jq)
* [Kitty](#kitty)
@@ -79,6 +82,7 @@ _1434 TILs and counting..._
* [XState](#xstate)
* [YAML](#yaml)
* [Zod](#zod)
* [Zsh](#zsh)
---
@@ -96,9 +100,15 @@ _1434 TILs and counting..._
- [Loop Over A List Of Dictionaries](ansible/loop-over-a-list-of-dictionaries.md)
### Astro
- [Generate Types For A Content Collection](astro/generate-types-for-a-content-collection.md)
- [Markdown Files Are Of Type MarkdownInstance](astro/markdown-files-are-of-type-markdown-instance.md)
### Brew
- [Configure Brew Environment Variables](brew/configure-brew-environment-variables.md)
- [Export List Of Everything Installed By Brew](brew/export-list-of-everything-installed-by-brew.md)
- [List All Services Managed By Brew](brew/list-all-services-managed-by-brew.md)
### Chrome
@@ -147,6 +157,7 @@ _1434 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)
@@ -196,8 +207,15 @@ _1434 TILs and counting..._
### Docker
- [Configure Different Host And Container Ports](docker/configure-different-host-and-container-ports.md)
- [List Running Docker Containers](docker/list-running-docker-containers.md)
- [Run A Basic PostgreSQL Server In Docker](docker/run-a-basic-postgresql-server-in-docker.md)
### 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
- [All Values For A Key In A Keyword List](elixir/all-values-for-a-key-in-a-keyword-list.md)
@@ -266,6 +284,7 @@ _1434 TILs and counting..._
- [Auto-Squash Those Fixup Commits](git/auto-squash-those-fixup-commits.md)
- [Caching Credentials](git/caching-credentials.md)
- [Change The Start Point Of A Branch](git/change-the-start-point-of-a-branch.md)
- [Check How A File Is Being Ignored](git/check-how-a-file-is-being-ignored.md)
- [Checking Commit Ancestry](git/checking-commit-ancestry.md)
- [Checkout Old Version Of A File](git/checkout-old-version-of-a-file.md)
- [Checkout Previous Branch](git/checkout-previous-branch.md)
@@ -278,6 +297,7 @@ _1434 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)
@@ -315,6 +335,7 @@ _1434 TILs and counting..._
- [List Untracked Files](git/list-untracked-files.md)
- [List Untracked Files For Scripting](git/list-untracked-files-for-scripting.md)
- [Move The Latest Commit To A New Branch](git/move-the-latest-commit-to-a-new-branch.md)
- [Override The Global Git Ignore File](git/override-the-global-git-ignore-file.md)
- [Pick Specific Changes To Stash](git/pick-specific-changes-to-stash.md)
- [Pulling In Changes During An Interactive Rebase](git/pulling-in-changes-during-an-interactive-rebase.md)
- [Push To A Branch On Another Remote](git/push-to-a-branch-on-another-remote.md)
@@ -376,6 +397,8 @@ _1434 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)
- [Sleep For A Duration](go/sleep-for-a-duration.md)
@@ -390,8 +413,10 @@ _1434 TILs and counting..._
### Heroku
- [Connect To A Database By Color](heroku/connect-to-a-database-by-color.md)
- [Deploy A Review App To A Different Stack](heroku/deploy-a-review-app-to-a-different-stack.md)
- [Diagnose Problems In A Heroku Postgres Database](heroku/diagnose-problems-in-a-heroku-postgres-database.md)
- [Open Dashboard For Specific Add-On](heroku/open-dashboard-for-specific-add-on.md)
- [Run SQL Against Remote Postgres Database](heroku/run-sql-against-remote-postgres-database.md)
- [Set And Show Heroku Env Variables](heroku/set-and-show-heroku-env-variables.md)
- [SSH Into Heroku Server Hosting App](heroku/ssh-into-heroku-server-hosting-app.md)
@@ -418,8 +443,12 @@ _1434 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)
- [Figure Out Your Public IP Address](internet/figure-out-your-public-ip-address.md)
- [Focus The URL Bar](internet/focus-the-url-bar.md)
@@ -427,6 +456,12 @@ _1434 TILs and counting..._
- [Search Tweets By Author](internet/search-tweets-by-author.md)
- [Show All Pivotal Stories With Blockers](internet/show-all-pivotal-stories-with-blockers.md)
### Java
- [Ensure Resources Always Get Closed](java/ensure-resources-always-get-closed.md)
- [Install Java On Mac With Brew](java/install-java-on-mac-with-brew.md)
- [Run A Hello World Program In Eclipse](java/run-a-hello-world-program-in-eclipse.md)
### JavaScript
- [Accessing Arguments To A Function](javascript/accessing-arguments-to-a-function.md)
@@ -437,6 +472,7 @@ _1434 TILs and counting..._
- [Check If A Number Is Positive Or Negative](javascript/check-if-a-number-is-positive-or-negative.md)
- [Check If File Exists Before Reading It](javascript/check-if-file-exists-before-reading-it.md)
- [Check If Something Is An Array](javascript/check-if-something-is-an-array.md)
- [Check Media Queries From JavaScript](javascript/check-media-queries-from-javascript.md)
- [Check The Password Confirmation With Yup](javascript/check-the-password-confirmation-with-yup.md)
- [Compare The Equality Of Two Date Objects](javascript/compare-the-equality-of-two-date-objects.md)
- [Computed Property Names In ES6](javascript/computed-property-names-in-es6.md)
@@ -482,6 +518,7 @@ _1434 TILs and counting..._
- [List Top-Level NPM Dependencies](javascript/list-top-level-npm-dependencies.md)
- [Load And Use Env Var In Node Script](javascript/load-and-use-env-var-in-node-script.md)
- [Make The Browser Editable With Design Mode](javascript/make-the-browser-editable-with-design-mode.md)
- [Make Truly Deep Clone With Structured Clone](javascript/make-truly-deep-clone-with-structured-clone.md)
- [Matching A Computed Property In Function Args](javascript/matching-a-computed-property-in-function-args.md)
- [Matching Multiple Values In A Switch Statement](javascript/matching-multiple-values-in-a-switch-statement.md)
- [Mock A Function With Return Values Using Jest](javascript/mock-a-function-with-return-values-using-jest.md)
@@ -492,6 +529,7 @@ _1434 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)
@@ -570,6 +608,7 @@ _1434 TILs and counting..._
- [Access All Screen And Video Capture Options](mac/access-all-screen-and-video-capture-options.md)
- [Access System Information On OS X](mac/access-system-information-on-osx.md)
- [Access Unsupported Screen Resolutions With RDM](mac/access-unsupported-screen-resolutions-with-rdm.md)
- [Check Network Quality Stats From The Command Line](mac/check-network-quality-stats-from-the-command-line.md)
- [Clean Up Old Homebrew Files](mac/clean-up-old-homebrew-files.md)
- [Convert An HEIC Image File To JPG](mac/convert-an-heic-image-file-to-jpg.md)
- [Default Screenshot Location](mac/default-screenshot-location.md)
@@ -578,6 +617,7 @@ _1434 TILs and counting..._
- [Find The Process Using A Specific Port](mac/find-the-process-using-a-specific-port.md)
- [Gesture For Viewing All Windows Of Current App](mac/gesture-for-viewing-all-windows-of-current-app.md)
- [Insert A Non-Breaking Space Character](mac/insert-a-non-breaking-space-character.md)
- [Keyboard Shortcuts For Interesting With Text Areas](mac/keyboard-shortcuts-for-interacting-with-text-areas.md)
- [List All The Say Voices](mac/list-all-the-say-voices.md)
- [Open Finder.app To Specific Directory](mac/open-finder-app-to-specific-directory.md)
- [Quickly Type En Dashes And Em Dashes](mac/quickly-type-en-dashes-and-em-dashes.md)
@@ -634,6 +674,7 @@ _1434 TILs and counting..._
### Next.js
- [Avoid Conflicting Files](nextjs/avoid-conflicting-files.md)
- [Create Files And Directories For Dynamic Routes](nextjs/create-files-and-directories-for-dynamic-routes.md)
- [Define URL Redirects In The Next Config](nextjs/define-url-redirects-in-the-next-config.md)
- [Fetch Does Not Work In API Serverless Function](nextjs/fetch-does-not-work-in-api-serverless-function.md)
@@ -670,6 +711,7 @@ _1434 TILs and counting..._
- [A Better Null Display Character](postgres/a-better-null-display-character.md)
- [Add Foreign Key Constraint Without A Full Lock](postgres/add-foreign-key-constraint-without-a-full-lock.md)
- [Add ON DELETE CASCADE To Foreign Key Constraint](postgres/add-on-delete-cascade-to-foreign-key-constraint.md)
- [Add Unique Constraint Using Existing Index](postgres/add-unique-constraint-using-existing-index.md)
- [Adding Composite Uniqueness Constraints](postgres/adding-composite-uniqueness-constraints.md)
- [Aggregate A Column Into An Array](postgres/aggregate-a-column-into-an-array.md)
- [Assumed Radius Of The Earth](postgres/assumed-radius-of-the-earth.md)
@@ -689,6 +731,7 @@ _1434 TILs and counting..._
- [Compute Hashes With pgcrypto](postgres/compute-hashes-with-pgcrypto.md)
- [Compute The Levenshtein Distance Of Two Strings](postgres/compute-the-levenshtein-distance-of-two-strings.md)
- [Compute The md5 Hash Of A String](postgres/compute-the-md5-hash-of-a-string.md)
- [Concatenate Strings With A Separator](postgres/concatenate-strings-with-a-separator.md)
- [Configure The Timezone](postgres/configure-the-timezone.md)
- [Constructing A Range Of Dates](postgres/constructing-a-range-of-dates.md)
- [Convert A String To A Timestamp](postgres/convert-a-string-to-a-timestamp.md)
@@ -719,6 +762,7 @@ _1434 TILs and counting..._
- [Duplicate A Local Database](postgres/duplicate-a-local-database.md)
- [Edit Existing Functions](postgres/edit-existing-functions.md)
- [Enable Logging Of Database Activity](postgres/enable-logging-of-database-activity.md)
- [Enforce Uniqueness On Column Expression](postgres/enforce-uniqueness-on-column-expression.md)
- [Escaping A Quote In A String](postgres/escaping-a-quote-in-a-string.md)
- [Escaping String Literals With Dollar Quoting](postgres/escaping-string-literals-with-dollar-quoting.md)
- [Export Query Results To A CSV](postgres/export-query-results-to-a-csv.md)
@@ -731,6 +775,8 @@ _1434 TILs and counting..._
- [Fizzbuzz With Common Table Expressions](postgres/fizzbuzz-with-common-table-expressions.md)
- [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)
@@ -770,11 +816,13 @@ _1434 TILs and counting..._
- [Max Identifier Length Is 63 Bytes](postgres/max-identifier-length-is-63-bytes.md)
- [Open Heroku Database In Postico From Terminal](postgres/open-heroku-database-in-postico-from-terminal.md)
- [pg Prefix Is Reserved For System Schemas](postgres/pg-prefix-is-reserved-for-system-schemas.md)
- [Postgres Does Not Support Unsigned Integers](postgres/postgres-does-not-support-unsigned-integers.md)
- [Prepare, Execute, And Deallocate Statements](postgres/prepare-execute-and-deallocate-statements.md)
- [Pretty Print Data Sizes](postgres/pretty-print-data-sizes.md)
- [Pretty Printing JSONB Rows](postgres/pretty-printing-jsonb-rows.md)
- [Prevent A Query From Running Too Long](postgres/prevent-a-query-from-running-too-long.md)
- [Print The Query Buffer In psql](postgres/print-the-query-buffer-in-psql.md)
- [Put Unique Constraint On Generated Column](postgres/put-unique-constraint-on-generated-column.md)
- [Remove Not Null Constraint From A Column](postgres/remove-not-null-constraint-from-a-column.md)
- [Renaming A Sequence](postgres/renaming-a-sequence.md)
- [Renaming A Table](postgres/renaming-a-table.md)
@@ -796,6 +844,7 @@ _1434 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)
@@ -838,6 +887,9 @@ _1434 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)
- [Dunder Methods](python/dunder-methods.md)
- [Override The Boolean Context Of A Class](python/override-the-boolean-context-of-a-class.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)
@@ -881,20 +933,25 @@ _1434 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)
- [Customize The Path Of A Resource Route](rails/customize-the-path-of-a-resource-route.md)
- [Define The Root Path For The App](rails/define-the-root-path-for-the-app.md)
- [Delete Paranoid Records](rails/delete-paranoid-records.md)
- [Demodulize A Class Name](rails/demodulize-a-class-name.md)
- [Different Ways To Add A Foreign Key Reference](rails/different-ways-to-add-a-foreign-key-reference.md)
- [Disambiguate Where In A Joined Relation](rails/disambiguate-where-in-a-joined-relation.md)
- [Empty find_by Returns First Record](rails/empty-find-by-returns-first-record.md)
- [Ensure A Rake Task Cannot Write Data](rails/ensure-a-rake-task-cannot-write-data.md)
- [Ensure Migrations Use The Latest Schema](rails/ensure-migrations-use-the-latest-schema.md)
- [Ensure Record Saved With after_commit Callback](rails/ensure-record-saved-with-after-commit-callback.md)
- [Find Or Create A Record With FactoryBot](rails/find-or-create-a-record-with-factory-bot.md)
- [Find Records With Multiple Associated Records](rails/find-records-with-multiple-associated-records.md)
- [Force All Users To Sign Out](rails/force-all-users-to-sign-out.md)
- [Generate A Model](rails/generate-a-model.md)
- [Generate A Rails App From The Main Branch](rails/generate-a-rails-app-from-the-main-branch.md)
- [Generating And Executing SQL](rails/generating-and-executing-sql.md)
- [Get A Quick Approximate Count Of A Large Table](rails/get-a-quick-approximate-count-of-a-large-table.md)
- [Get ActiveRecord Attribute Directly From Database](rails/get-active-record-attribute-directly-from-database.md)
@@ -934,6 +991,7 @@ _1434 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)
@@ -948,6 +1006,7 @@ _1434 TILs and counting..._
- [Rescue From With A Separate Method](rails/rescue-from-with-a-separate-method.md)
- [Respond With JSON Regardless of Content Type](rails/respond-with-json-regardless-of-content-type.md)
- [Retrieve An Object If It Exists](rails/retrieve-an-object-if-it-exists.md)
- [Rollback A Couple Migrations](rails/rollback-a-couple-migrations.md)
- [Rollback A Specific Migration Out Of Order](rails/rollback-a-specific-migration-out-of-order.md)
- [Rounding Numbers With Precision](rails/rounding-numbers-with-precision.md)
- [Run A Rake Task Programmatically](rails/run-a-rake-task-programmatically.md)
@@ -960,6 +1019,8 @@ _1434 TILs and counting..._
- [Select Value For SQL Counts](rails/select-value-for-sql-counts.md)
- [Serialize With fast_jsonapi In A Rails App](rails/serialize-with-fast-jsonapi-in-a-rails-app.md)
- [Set A Timestamp Field To The Current Time](rails/set-a-timestamp-field-to-the-current-time.md)
- [Set DateTime To Include Time Zone In Migrations](rails/set-datetime-to-include-time-zone-in-migrations.md)
- [Set Default As SQL Function In Migration](rails/set-default-as-sql-function-in-migration.md)
- [Set default_url_options For Entire Application](rails/set-default-url-options-for-entire-application.md)
- [Set Schema Search Path](rails/set-schema-search-path.md)
- [Set Statement Timeout For All Postgres Connections](rails/set-statement-timeout-for-all-postgres-connections.md)
@@ -970,6 +1031,7 @@ _1434 TILs and counting..._
- [Skip Validations When Creating A Record](rails/skip-validations-when-creating-a-record.md)
- [Specify New Attributes For #find_or_create_by](rails/specify-new-attributes-for-find-or-create-by.md)
- [Temporarily Disable strong_params](rails/temporarily-disable-strong-params.md)
- [Temporarily Turn Off Pending Migrations Error](rails/temporarily-turn-off-pending-migrations-error.md)
- [Test For A Subset Of Attributes On A Model](rails/test-for-a-subset-of-attributes-on-a-model.md)
- [Test If An Instance Variable Was Assigned](rails/test-if-an-instance-variable-was-assigned.md)
- [Test If deliver_later Is Called For A Mailer](rails/test-if-deliver-later-is-called-for-a-mailer.md)
@@ -978,6 +1040,7 @@ _1434 TILs and counting..._
- [Update Column Versus Update Attribute](rails/update-column-versus-update-attribute.md)
- [Upgrading Your Manifest For Sprocket's 4](rails/upgrading-your-manifest-for-sprockets-4.md)
- [Use IRB And Ruby Flags With Rails Console](rails/use-irb-and-ruby-flags-with-rails-console.md)
- [Validate Column Data With Check Constraints](rails/validate-column-data-with-check-constraints.md)
- [Verify And Read A Signed Cookie Value](rails/verify-and-read-a-signed-cookie-value.md)
- [Where Am I In The Partial Iteration?](rails/where-am-i-in-the-partial-iteration.md)
- [Why Redirect And Return In Controllers](rails/why-redirect-and-return-in-controllers.md)
@@ -1098,7 +1161,9 @@ _1434 TILs and counting..._
### RSpec
- [Avoid Accidentally Disabling Pry](rspec/avoid-accidentally-disabling-pry.md)
- [Check Specific Arguments To Received Method](rspec/check-specific-arguments-to-received-method.md)
- [Configure Tests To Run In Random Order](rspec/configure-tests-to-run-in-random-order.md)
- [Find Minimal Set Of Tests Causing A Flicker](rspec/find-minimal-set-of-tests-causing-a-flicker.md)
- [Format Test Results As A JSON File](rspec/format-test-results-as-a-json-file.md)
- [Run Tests With Documentation Formatting](rspec/run-tests-with-documentation-formatting.md)
@@ -1148,6 +1213,7 @@ _1434 TILs and counting..._
- [Enumerate A Pairing Of Every Two Sequential Items](ruby/enumerate-a-pairing-of-every-two-sequential-items.md)
- [Evaluating One-Off Commands](ruby/evaluating-one-off-commands.md)
- [Exclude Values From An Array](ruby/exclude-values-from-an-array.md)
- [Execute Several Commands With Backtick Heredoc](ruby/execute-several-commands-with-backtick-heredoc.md)
- [Exit A Process With An Error Message](ruby/exit-a-process-with-an-error-message.md)
- [Expect A Method To Be Called And Actually Call It](ruby/expect-a-method-to-be-called-and-actually-call-it.md)
- [Extract A Column Of Data From A CSV File](ruby/extract-a-column-of-data-from-a-csv-file.md)
@@ -1157,6 +1223,7 @@ _1434 TILs and counting..._
- [Find The Min And Max With A Single Call](ruby/find-the-min-and-max-with-a-single-call.md)
- [Finding The Source of Ruby Methods](ruby/finding-the-source-of-ruby-methods.md)
- [Format A Hash Into A String Template](ruby/format-a-hash-into-a-string-template.md)
- [Forward All Arguments To Another Method](ruby/forward-all-arguments-to-another-method.md)
- [Generate A Signed JWT Token](ruby/generate-a-signed-jwt-token.md)
- [Generate Ruby Version And Gemset Files With RVM](ruby/generate-ruby-version-and-gemset-files-with-rvm.md)
- [Get Info About Your RubyGems Environment](ruby/get-info-about-your-ruby-gems-environment.md)
@@ -1178,10 +1245,12 @@ _1434 TILs and counting..._
- [Map With Index Over An Array](ruby/map-with-index-over-an-array.md)
- [Mock Method Chain Calls With RSpec](ruby/mock-method-chain-calls-with-rspec.md)
- [Mocking Requests With Partial URIs Using Regex](ruby/mocking-requests-with-partial-uris-using-regex.md)
- [Multi-Line Comments](ruby/multi-line-comments.md)
- [Named Regex Captures Are Assigned To Variables](ruby/named-regex-captures-are-assigned-to-variables.md)
- [Navigate Back In The Browser With Capybara](ruby/navigate-back-in-the-browser-with-capybara.md)
- [Next And Previous Floats](ruby/next-and-previous-floats.md)
- [Or Operator Precedence](ruby/or-operator-precedence.md)
- [Output Bytecode For A Ruby Program](ruby/output-bytecode-for-a-ruby-program.md)
- [Override The Initial Sequence Value](ruby/override-the-initial-sequence-value.md)
- [Parallel Bundle Install](ruby/parallel-bundle-install.md)
- [Parse JSON Into An OpenStruct](ruby/parse-json-into-an-open-struct.md)
@@ -1192,6 +1261,7 @@ _1434 TILs and counting..._
- [Pattern Match Values From A Hash](ruby/pattern-match-values-from-a-hash.md)
- [Percent Notation](ruby/percent-notation.md)
- [Precedence Of Logical Operators](ruby/precedence-of-logical-operators.md)
- [Prevent erb_lint From Removing Opening Tags](ruby/prevent-erb-lint-from-removing-opening-tags.md)
- [Print Data To Formatted Table](ruby/print-data-to-formatted-table.md)
- [Question Mark Operator](ruby/question-mark-operator.md)
- [Rake Only Lists Tasks With Descriptions](ruby/rake-only-lists-tasks-with-descriptions.md)
@@ -1216,6 +1286,7 @@ _1434 TILs and counting..._
- [Silence The Output Of A Ruby Statement In Pry](ruby/silence-the-output-of-a-ruby-statement-in-pry.md)
- [Single And Double Quoted String Notation](ruby/single-and-double-quoted-string-notation.md)
- [Skip Specific CVEs When Auditing Your Bundle](ruby/skip-specific-cves-when-auditing-your-bundle.md)
- [Skip The Front Of An Array With Drop](ruby/skip-the-front-of-an-array-with-drop.md)
- [Specify Dependencies For A Rake Task](ruby/specify-dependencies-for-a-rake-task.md)
- [Specify How Random Array#sample Is](ruby/specify-how-random-array-sample-is.md)
- [Split A Float Into Its Integer And Decimal](ruby/split-a-float-into-its-integer-and-decimal.md)
@@ -1294,6 +1365,7 @@ _1434 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)
@@ -1323,6 +1395,7 @@ _1434 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)
@@ -1350,6 +1423,8 @@ _1434 TILs and counting..._
- [Command Line Length Limitations](unix/command-line-length-limitations.md)
- [Compare Two Variables In A Bash Script](unix/compare-two-variables-in-a-bash-script.md)
- [Configure cd To Behave Like pushd In Zsh](unix/configure-cd-to-behave-like-pushd-in-zsh.md)
- [Convert JPEG To PNG With ffmpeg](unix/convert-jpeg-to-png-with-ffmpeg.md)
- [Convert SVG To Favicon](unix/convert-svg-to-favicon.md)
- [Copying File Contents To System Paste Buffer](unix/copying-file-contents-to-system-paste-buffer.md)
- [Copying Nested Directories With Ditto](unix/copying-nested-directories-with-ditto.md)
- [Count The Lines In A CSV Where A Column Is Empty](unix/count-the-lines-in-a-csv-where-a-column-is-empty.md)
@@ -1366,6 +1441,7 @@ _1434 TILs and counting..._
- [Display The Contents Of A Directory As A Tree](unix/display-the-contents-of-a-directory-as-a-tree.md)
- [Do A Dry Run Of An rsync](unix/do-a-dry-run-of-an-rsync.md)
- [Do Not Overwrite Existing Files](unix/do-not-overwrite-existing-files.md)
- [Download A File With Curl](unix/download-a-file-with-curl.md)
- [Enable Multi-Select Of Results With fzf](unix/enable-multi-select-of-results-with-fzf.md)
- [Exclude A Command From The ZSH History File](unix/exclude-a-command-from-the-zsh-history-file.md)
- [Exclude A Directory With Find](unix/exclude-a-directory-with-find.md)
@@ -1374,6 +1450,7 @@ _1434 TILs and counting..._
- [File Type Info With File](unix/file-type-info-with-file.md)
- [Find All Files Matching A Name With fd](unix/find-all-files-matching-a-name-with-fd.md)
- [Find All Files With A Specific Extension With fd](unix/find-all-files-with-a-specific-extension-with-fd.md)
- [Find Any Dotfiles That Modify Path Env Var](unix/find-any-dotfiles-that-modify-path-env-var.md)
- [Find A File Installed By Brew](unix/find-a-file-installed-by-brew.md)
- [Find Duplicate Lines In A File](unix/find-duplicate-lines-in-a-file.md)
- [Find Files With fd](unix/find-files-with-fd.md)
@@ -1387,10 +1464,12 @@ _1434 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)
- [Globbing For Filenames In Zsh](unix/globbing-for-filenames-in-zsh.md)
- [Gracefully Exit A Script With Trap](unix/gracefully-exit-a-script-with-trap.md)
- [Grep For Files Without A Match](unix/grep-for-files-without-a-match.md)
- [Grep For Files With Multiple Matches](unix/grep-for-files-with-multiple-matches.md)
- [Grep For Multiple Patterns](unix/grep-for-multiple-patterns.md)
@@ -1417,6 +1496,7 @@ _1434 TILs and counting..._
- [List The Stack Of Remembered Directories](unix/list-the-stack-of-remembered-directories.md)
- [Load Env Vars In Bash Script](unix/load-env-vars-in-bash-script.md)
- [Look Through All Files That Have Been Git Stashed](unix/look-through-all-files-that-have-been-git-stashed.md)
- [Make Direnv Less Noisy](unix/make-direnv-less-noisy.md)
- [Map A Domain To localhost](unix/map-a-domain-to-localhost.md)
- [Negative Look-Ahead Search With ripgrep](unix/negative-look-ahead-search-with-ripgrep.md)
- [Occupy A Local Port With Netcat](unix/occupy-a-local-port-with-netcat.md)
@@ -1456,6 +1536,7 @@ _1434 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)
@@ -1617,6 +1698,7 @@ _1434 TILs and counting..._
- [Swap Occurrences Of Two Words](vim/swap-occurrences-of-two-words.md)
- [Swapping Split Windows](vim/swapping-split-windows.md)
- [Swap The Position Of Two Split Windows](vim/swap-the-position-of-two-split-windows.md)
- [Switch Moving End Of Visual Selection](vim/switch-moving-end-of-visual-selection.md)
- [Tabs To Spaces](vim/tabs-to-spaces.md)
- [The Vim Info File](vim/the-vim-info-file.md)
- [Toggle Absolute And Relative Paths In BufExplorer](vim/toggle-absolute-and-relative-paths-in-bufexplorer.md)
@@ -1641,6 +1723,7 @@ _1434 TILs and counting..._
- [Advance Through Search Results](vscode/advance-through-search-results.md)
- [Enable Breadcrumbs For Version 1.26 Release](vscode/enable-breadcrumbs-for-version-126-release.md)
- [Find The Location Of User Settings JSON File](vscode/find-the-location-of-user-settings-json-file.md)
- [Jump To Problems In The Current File](vscode/jump-to-problems-in-the-current-file.md)
- [Open An Integrated Terminal Window](vscode/open-an-integrated-terminal-window.md)
- [Pop Open The Quick Fix Window](vscode/pop-open-the-quick-fix-window.md)
- [Step Through Project-Wide Search Results](vscode/step-through-project-wide-search-results.md)
@@ -1657,14 +1740,18 @@ _1434 TILs and counting..._
### Workflow
- [Add Hotkeys For Specific Raycast Extensions](workflow/add-hotkeys-for-specific-raycast-extensions.md)
- [Add Subscriber To Kit Form Via API](workflow/add-subscriber-to-kit-form-via-api.md)
- [Add Subtitles To Existing Mux Video Asset](workflow/add-subtitles-to-existing-mux-video-asset.md)
- [Access 1Password Credential From CLI](workflow/access-1password-credential-from-cli.md)
- [Change Window Name In iTerm](workflow/change-window-name-in-iterm.md)
- [Configure Email Redirect With Cloudflare](workflow/configure-email-redirect-with-cloudflare.md)
- [Convert An ePub Document To PDF On Mac](workflow/convert-an-epub-document-to-pdf-on-mac.md)
- [Create A Local Sanity Dataset Backup](workflow/create-a-local-sanity-dataset-backup.md)
- [Create A Public URL For A Local Server](workflow/create-a-public-url-for-a-local-server.md)
- [Enable Dev Tools For Safari](workflow/enable-dev-tools-for-safari.md)
- [Forward Stripe Events To Local Server](workflow/forward-stripe-events-to-local-server.md)
- [Get URL For GitHub User Profile Photo](workflow/get-url-for-github-user-profile-photo.md)
- [Get Your Public IP Address](workflow/get-your-public-ip-address.md)
- [Import A Github Project Into CodeSandbox](workflow/import-a-github-project-into-codesandbox.md)
- [Interactively Kill A Process With fkill](workflow/interactively-kill-a-process-with-fkill.md)
@@ -1672,6 +1759,7 @@ _1434 TILs and counting..._
- [Prune The Excess From node_modules](workflow/prune-the-excess-from-node-modules.md)
- [Rotate An Image To Be Oriented Upright](workflow/rotate-an-image-to-be-oriented-upright.md)
- [See Overlaps For A Set Of Time Zones](workflow/see-overlaps-for-a-set-of-time-zones.md)
- [Send A Message To A Discord Channel](workflow/send-a-message-to-a-discord-channel.md)
- [Set Recurring Reminders In Slack](workflow/set-recurring-reminders-in-slack.md)
- [Toggle Between Stories In Storybook](workflow/toggle-between-stories-in-storybook.md)
- [Update asdf Plugins With Latest Package Versions](workflow/update-asdf-plugins-with-latest-package-versions.md)
@@ -1703,6 +1791,12 @@ _1434 TILs and counting..._
- [Incorporate Existing Type Into Zod Schema](zod/incorporate-existing-type-into-zod-schema.md)
- [Set Custom Error Message For Nonempty Array](zod/set-custom-error-message-for-nonempty-array.md)
### Zsh
- [Add To The Path Via Path Array](zsh/add-to-the-path-via-path-array.md)
- [Link A Scalar To An Array](zsh/link-a-scalar-to-an-array.md)
- [Use A Space To Exclude Command From History](zsh/use-a-space-to-exclude-command-from-history.md)
## Usage
The `.vimrc` file for this project contains a function `CountTILs` that can

View File

@@ -0,0 +1,56 @@
# Generate Types For A Content Collection
Let's say I'm using Astro to publish posts via markdown. One of the best ways
to do that is as a _Content Collection_. The posts will live in `src/content`
probably under a `posts` directory. Plus a config file will define the
collection and specify validations for the frontmatter.
```typescript
// src/content/config.ts
import { defineCollection, z } from 'astro:content';
const postsCollection = defineCollection({
schema: z.object({
title: z.string(),
description: z.string(),
tags: z.array(z.string())
})
});
export const collections = {
'posts': postsCollection,
};
```
When I first add this to my project and get the collection, it won't know what
the types are.
```astro
---
import { getCollection } from "astro:content";
export async function getStaticPaths() {
const blogEntries = await getCollection("posts");
// ^^^ any
return blogEntries.map((entry) => ({
params: { slug: entry.slug },
props: { entry },
}));
}
---
```
I can tell Astro to generate a fresh set of types for things like content
collections by running the [`astro sync`
command](https://docs.astro.build/en/reference/cli-reference/#astro-sync).
```bash
$ npm run astro sync
```
This updates auto-generated files under the `.astro` directory which get pulled
in to your project's `env.d.ts` file.
All of these types will also be synced anytime I run `astro dev`, `astro
build`, or `astro check`.

View File

@@ -0,0 +1,53 @@
# Markdown Files Are Of Type MarkdownInstance
One of the things Astro excels at is rendering markdown files as HTML pages in
your site. And at some point we'll want to access a listing of those markdown
files in order to do something like display a list of them on an index page.
For that, we'll use
[`Astro.glob()`](https://docs.astro.build/en/reference/api-reference/#astroglob).
```typescript
---
const allPosts = await Astro.glob("../posts/*.md");
---
<ul>
{allPosts.map(post => {
return <Post title={post.frontmatter.title} slug={post.frontmatter.slug} />
})}
</ul>
```
This looks great, but we'll run into a type error on that first line:
`'allPosts' implicitly has type 'any'`. We need to declare the type
of these post instances that are being read-in by Astro.
These are of [type
`MarkdownInstance`](https://docs.astro.build/en/reference/api-reference/#markdown-files).
That's a generic though, so we need to tell it a bit more about the shape of a
post.
```typescript
import type { MarkdownInstance } from "astro";
export type BarePost = {
layout: string;
title: string;
slug: string;
tags: string[];
};
export type Post = MarkdownInstance<BarePost>;
```
We can then update that first line:
```typescript
const allPosts: Post[] = await Astro.glob("../posts/*.md");
```
Alternatively, you can specify the generic on `glob`:
```typescript
const allPosts = await Astro.glob<BarePost>("../posts/*.md");
```

View File

@@ -0,0 +1,48 @@
# Export List Of Everything Installed By Brew
If you're on a Mac using Homebrew to install various tools and utilities, there
may come a time when you want a listing of what is installed.
Run this command:
```bash
$ brew bundle dump
```
It may take 10 or so seconds. When it is done, you'll have a `Brewfile` in your
current directory.
Open it up and you'll see a bunch of lines like the following:
```
tap "heroku/brew"
tap "homebrew/bundle"
tap "homebrew/services"
tap "mongodb/brew"
tap "planetscale/tap"
tap "stripe/stripe-cli"
brew "asdf"
brew "bat"
brew "direnv"
brew "entr"
brew "exa"
brew "fd"
brew "ffmpeg"
brew "fx"
brew "fzf"
brew "gcc"
brew "gh"
brew "planetscale/tap/pscale"
brew "stripe/stripe-cli/stripe"
cask "1password-cli"
vscode "ms-playwright.playwright"
vscode "ms-vsliveshare.vsliveshare"
vscode "prisma.prisma"
```
Notice there are `tap`, `brew`, `cask`, and even `vscode` directives.
This is a file you could export and then run on a 'new' machine to install all
the programs you're used to having available on your current machine.
[source](https://danmunoz.com/setting-up-a-new-computer-with-homebrew/)

View 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.

View File

@@ -0,0 +1,22 @@
# List Running Docker Containers
The `docker` CLI has a `ps` command that will list all running container by
default.
When I run it, I can see that I have a container running a Postgres database
and another running a MySQL database.
```bash
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
ba792e185734 postgres:latest "docker-entrypoint.s…" 12 days ago Up 12 days 0.0.0.0:9876->5432/tcp better_reads-postgres-1
7ca7c1e882e0 mysql:8.0 "docker-entrypoint.s…" 19 months ago Up 8 seconds 33060/tcp, 0.0.0.0:3309->3306/tcp some-app-db-1
```
It lists several pieces of info about the containers: the container id, the
image it is based off, when it was created, the running status, the port
configuration, and the name of the container
If I run `docker ps --help` I can see some additional options. One option is
the `--all` flag which will display all known docker container instead of just
the running ones.

View File

@@ -0,0 +1,48 @@
# Create bigint Identity Column For Primary Key
Using the Drizzle ORM with Postgres, here is how we can create a table that
uses a [`bigint` data
type](https://orm.drizzle.team/docs/column-types/pg#bigint) as a primary key
[identity
column](https://www.postgresql.org/docs/current/ddl-identity-columns.html).
```typescript
import {
pgTable,
bigint,
text,
timestamp,
} from "drizzle-orm/pg-core";
// Users table
export const users = pgTable("users", {
id: bigint({ mode: 'bigint' }).primaryKey().generatedAlwaysAsIdentity(),
email: text("email").unique().notNull(),
name: text("name").notNull(),
createdAt: timestamp("created_at").defaultNow().notNull(),
});
```
There are a couple key pieces here:
1. We import `bigint` so that we can declare a column of that type.
2. We specify that it is a primary key with `.primaryKey()`.
3. We declare its default value as `generated always as identity` via
`.generatedAlwaysAsIdentity()`.
Note: you need to specify the `mode` for `bigint` or else you will see a
`TypeError: Cannot read properties of undefined (reading 'mode')` error.
If we run `npx drizzle-kit generate` the SQL migration file that gets
generated will contain something like this:
```sql
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "users" (
"id" bigint PRIMARY KEY GENERATED ALWAYS AS IDENTITY (sequence name "users_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 9223372036854775807 START WITH 1 CACHE 1),
"email" text NOT NULL,
"name" text NOT NULL,
"created_at" timestamp DEFAULT now() NOT NULL,
CONSTRAINT "users_email_unique" UNIQUE("email")
);
```

View 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)

View File

@@ -0,0 +1,56 @@
# Get Fields For Inserted Row
With Drizzle, we can insert a row with a set of values like so:
```typescript
await db
.insert(todoItems)
.values({
title,
userId,
description,
})
```
The result of this is `QueryResult<never>`. In other words, nothing useful is
coming back to us from the database.
Sometimes an insert is treated as a fire-and-forget (as long as it succeeds) or
since we know what data we are inserting, we don't need the database to
response. But what about values that are generated or computed by the database
-- such as an id from a sequence, timestamp columns that default to `now()`, or
generated columns.
To get all the fields of a freshly inserted row, we can tack on [the
`returning()` function](https://orm.drizzle.team/docs/insert#insert-returning)
(which likely adds something like [`returning
*`](https://www.postgresql.org/docs/current/dml-returning.html)) to the insert
query under the hood).
```typescript
await db
.insert(todoItems)
.values({
title,
userId,
description,
})
.returning()
```
This will have a return type of `Array<type todoItems>` which means that for
each inserted row we'll have all the fields (columns) for that row.
Alternatively, if we just need the generated ID for the new row(s), we can use
a partial return like so:
```typescript
await db
.insert(todoItems)
.values({
title,
userId,
description,
})
.returning({ id: todoItems.id })
```

View File

@@ -0,0 +1,28 @@
# Check How A File Is Being Ignored
There are a few places on your machine where you can specify the files that git
should ignore. The most common is a repository's `.gitignore` file. The other
places those excludes are specified can be more obscure. Fortunately, `git
check-ignore` is a command that can show you specifically where.
For instance, let's check why my `notes.md` file is being ignored.
```bash
$ git check-ignore -v .DS_Store
.git/info/exclude:7:notes.md notes.md
```
At some point I added it to my repo's `.git/info/exclude` file. The `-v` flag
(_verbose_) when included with `check-ignore` tells me the file location.
How about these pesky `.DS_Store` directories? How are those being ignored?
```bash
$ git check-ignore -v .DS_Store
/Users/jbranchaud/.gitignore:3:.DS_Store .DS_Store
```
Ah yes, I had added it to my _global exclude file_ which I've configured in
`~/.gitconfig` to be the `~/.gitignore` file.
See `man git-check-ignore` for more details.

View 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.

View File

@@ -0,0 +1,33 @@
# Override The Global Git Ignore File
One of the places that `git` looks when deciding whether to pay attention to or
ignore a file is in your global _ignore_ file. By default, `git` will look for
this file at `$XDG_CONFIG_HOME/git/ignore` or `$HOME/.config/git/ignore`.
I don't have `$XDG_CONFIG_HOME` set on my machine, so it will fall back to the
config directory under `$HOME`.
I may have to create the `git` directory and `ignore` file.
```bash
$ mkdir $HOME/.config/git
$ touch $HOME/.config/git/ignore
```
Then I can add file and directories to exclude to that `ignore` file just like
I would any other `.gitignore` file.
If I'd prefer for the global _ignore_ file to live somewhere else, I can
specify that location and filename in my `$HOME/.gitconfig` file.
```
[core]
excludesFile = ~/.gitignore
```
Setting this will override the default, meaning the default file mentioned
above will be ignored ("now you know how it feels, ignore file!"). In this
case, I'll need to create the `.gitignore` file in my home directory and add
any of my ignore rules.
[source](https://git-scm.com/docs/gitignore)

View File

@@ -15,4 +15,10 @@ $ godoc -http=:6060
and then visit `localhost:6060`.
Note: if you do not already have `godoc` installed, you can install it with:
```bash
$ go install golang.org/x/tools/cmd/godoc@latest
```
[source](http://www.andybritcliffe.com/post/44610795381/offline-go-lang-documentation)

View 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/)

View File

@@ -0,0 +1,26 @@
# Find Executables Installed By Go
When you install an executable using `go install`, it puts that executable in
the `bin` directory designated by the `GOBIN` environment variable. If that env
var isn't set, then it falls back to one of `$GOPATH/bin` or `$HOME/go/bin`.
When I run `go help install`, it tells me as much:
```
Executables are installed in the directory named by the GOBIN environment
variable, which defaults to $GOPATH/bin or $HOME/go/bin if the GOPATH
environment variable is not set.
```
So, if I am to install something like [`tern`](https://github.com/jackc/tern),
```bash
$ go install github.com/jackc/tern/v2@latest
```
it is going to place that binary in `~/go/bin` for me.
```bash
$ which tern
/Users/jbranchaud/go/bin/tern
```

View File

@@ -0,0 +1,29 @@
# Connect To A Database By Color
All of your PostgreSQL databases in Heroku are given attachment names that use
a random color. This might be _pink_, _brown_, _cobalt_, etc. And the
attachment names then look like `HEROKU_POSTGRESQL_PINK`,
`HEROKU_POSTGRESQL_BROWN`, `HEROKU_POSTGRESQL_COBALT`, etc.
We can connect to a Heroku-managed PostgreSQL instance from the command-line
like so:
```bash
$ heroku pg:psql --app my-app
```
This is going to connect to the _default_ database which is the one with the
`DATABASE_URL` attachment.
There are lots of instances where we may have other databases besides the
primary (e.g. let's say we have a read replica follower). If we want to connect
to that one, we can do so by _color_.
If that database's attachment is `HEROKU_POSTGRESQL_IVORY`, then we'd connect
to it like so:
```bash
$ heroku pg:psql ivory --app my-app
```
[source](https://devcenter.heroku.com/articles/managing-heroku-postgres-using-cli#pg-psql)

View File

@@ -0,0 +1,17 @@
# Open Dashboard For Specific Add-On
The number of times I've needed to check the papertrail logs for my
Heroku-hosted Rails app is a lot. I open a browser tab, go through several
layers of navigation to get to my app's dashboard, and then click the
papertrail link under _Add-ons_.
There is a much quicker way using the Heroku CLI.
```bash
$ heroku addons:open papertrail -a my-app-name
Opening https://addons-sso.heroku.com/apps/abc123/addons/efg456...
```
It sends you to an add-ons SSO link in the browser which authenticates you and
drops you into the dashboard for that specific add-on. You just need to specify
the add-on name and the app name.

View 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)

View File

@@ -0,0 +1,21 @@
# Analyze Your Website Performance
The [PageSpeed Insights](https://pagespeed.web.dev/) tool from Google is a
great way to quickly get actionable insights about where to improve your
website and app's _Performance_, _Accessibility_, and _SEO_.
To see how your public site or app does, grab its URL and analyze it at
[PageSpeed Insights](https://pagespeed.web.dev/).
It will take a minute to run on either Mobile or Desktop (make sure to check
both) and then will output four headline numbers (out of 100) for each of the
categories.
You can then dig in to each category to see what recommendations they make for
improving your score.
This can also be run directly from Chrome devtools which is useful if you want
to see how a locally running site is doing. You can run the analysis from the
_Lighthouse_ tab of devtools. Note: if the _Performance_ score looks bad, it
might be that you are running a non-optimized dev server that isn't reflective
of how your site would do in production.

View 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)

View File

@@ -0,0 +1,14 @@
# Exclude AI Overview From Google Search
At the top of most Google searches these days is a section of text that takes a
moment to appear, presumably because it is being generated in the moment. This
is Google's _AI Overview_. These are sometimes useful summaries of the article
you are about to click on anyway. Other times the overview is no good, it takes
up a bunch of screen real estate, and may even [10x the energy consumed by a
regular
search](https://www.reddit.com/r/technology/comments/1dsvefb/googles_ai_search_summaries_use_10x_more_energy/).
If you want to exclude the _AI Overview_, tack on a `-ai` when writing out your
search query.
[source](https://www.yahoo.com/tech/turn-off-ai-overview-results-170014202.html)

View File

@@ -0,0 +1,55 @@
# Ensure Resources Always Get Closed
Java has a construct known as _try-with-resource_ that allows us to always
ensure opened resources get closed. This is safer than similar cleanup in the
`finally` block which could still leave a memory leak if an error occurs in
that block.
To use the _try-with-resource_ construct, instantiate your opened resource in
parentheses with the `try`.
```java
try (BufferedReader reader = new BufferedReader(new FileReader(filename))) {
// ...
}
```
The resource will be automatically closed when the try/catch block completes.
Here is a full example:
```java
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class FileReaderExample {
public static void main(String[] args) {
String fileName = "example.txt";
try (BufferedReader reader = new BufferedReader(new FileReader(fileName))) {
String line;
int lineCount = 0;
while ((line = reader.readLine()) != null && lineCount < 5) {
System.out.println(line);
lineCount++;
}
} catch (IOException e) {
System.out.println("An error occurred while reading the file: " + e.getMessage());
}
}
}
```
You can even specify multiple resources in one `try`. The above does that, but
this will make it more obvious:
```java
try (FileReader fr = new FileReader(filename);
BufferedReader br = new BufferedReader(fr)) {
// ...
}
```
[source](https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html)

View File

@@ -0,0 +1,47 @@
# Install Java On Mac With Brew
If you don't already have Java installed on your Mac, you can install it with
homebrew.
```bash
$ brew install java
```
This will take a bit to run and when all is complete, you'll go to run
something like a version check and see this:
```bash
$ java -version
The operation couldnt be completed. Unable to locate a Java Runtime.
Please visit http://www.java.com for information on installing Java.
```
This is because [OpenJDK](https://openjdk.org/) the open-source implementation
of the Java Development Kit (Java platform) does not get fully set up by
homebrew.
You'll need to symlink `openjdk` and the exact command with correct paths can
be found from running the following:
```bash
$ brew info openjdk
...
For the system Java wrappers to find this JDK, symlink it with
sudo ln -sfn /usr/local/opt/openjdk/libexec/openjdk.jdk /Library/Java/JavaVirtualMachines/openjdk.jdk
...
```
The paths may look different for you, so copy the exact command and run that.
Once the symlink is set, check the version again.
```bash
$ java -version
openjdk version "23" 2024-09-17
OpenJDK Runtime Environment Homebrew (build 23)
OpenJDK 64-Bit Server VM Homebrew (build 23, mixed mode, sharing)
```
[source](https://stackoverflow.com/a/65601197/535590)

View File

@@ -0,0 +1,27 @@
# Run A Hello World Program In Eclipse
First, you'll need to create a new Java Project if you don't already have one
to work in.
From there, you can add a new _Class_ to the `src` folder of that project. I'll
call mine `Greeting.java` and the only thing it will contain is a `main`
method.
```java
public class Greeting {
public static void main(String[] args) {
String name = args.length > 0 ? args[0] : "World";
System.out.println("Hello, " + name + "!");
}
}
```
This method tries to read a name from the arguments given to the program at
time of execution. If one wasn't provided the ternary falls back to `"World"`
as the default name. It then prints the greeting to stdout.
To run this program, we can either select _Run_ from the _Run_ menu (which will
result in `Hello, World!`) or we can select _Run Configurations..._ from the
same menu and add a custom name to _Program Arguments_ under the _Arguments_
tab.

View File

@@ -0,0 +1,28 @@
# Check Media Queries From JavaScript
I'm usually thinking about and [using media
queries](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_media_queries/Using_media_queries)
from a CSS context. I use them to control what styles are displayed for a
variety of scenarios, such as at different screen widths, when a user prefers
reduced motion, or when the user prefers a dark color scheme.
The current value of various media queries can be checked from a JavaScript
context as well.
For instance, if we want to see if the user prefers a _dark_ color schema, we
can look for a _match_ on that media query with
[`matchMedia`](https://developer.mozilla.org/en-US/docs/Web/API/Window/matchMedia).
```javascript
> window.matchMedia('(prefers-color-scheme: dark)')
MediaQueryList {media: '(prefers-color-scheme: dark)', matches: true, onchange: null}
> window.matchMedia('(prefers-color-scheme: dark)')['matches']
true
```
This queries for the [`prefers-color-scheme` media
feature](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme).
The [Astro.build Blog
Tutorial](https://docs.astro.build/en/tutorial/6-islands/2/#add-client-side-interactivity)
shows an example of using this to wire up a Light/Dark mode toggle.

View File

@@ -0,0 +1,61 @@
# Make Truly Deep Clone With Structured Clone
There are a lot of ways to make a copy of an object. They are all hacks and
they all fail in certain circumstances. Using the spread trick only gives you a
shallow copy where references to nested objects and arrays can still be
updated. The `JSON.stringify` trick has to make things like dates into strings,
so it is lossy.
There is however now a dedicated method for deep copies with broad support
called
[`structuredClone`](https://developer.mozilla.org/en-US/docs/Web/API/Window/structuredClone).
It is available on `window`. Let's take a look at it and see how it comparse to
the spread operator trick.
```javascript
> // some data setup
> const data = { one: 1, two: 2, rest: [3,4,5] }
> const obj = { hello: 'world', taco: 'bell', data }
> const shallowObj = { ...obj }
> const deepObj = structuredClone(obj)
> // let's modify the original `data.rest` array
> data.rest.push(6)
4
> data
{ one: 1, two: 2, rest: [ 3, 4, 5, 6 ] }
> // now let's see who was impacted by that mutation
> obj
{
hello: 'world',
taco: 'bell',
data: { one: 1, two: 2, rest: [ 3, 4, 5, 6 ] }
}
> shallowObj
{
hello: 'world',
taco: 'bell',
data: { one: 1, two: 2, rest: [ 3, 4, 5, 6 ] }
}
> deepObj
{
hello: 'world',
taco: 'bell',
data: { one: 1, two: 2, rest: [ 3, 4, 5 ] }
}
```
The `shallowObj` from the spread operator copy was mutated even though we
didn't intend for that. The `deepObj` from `structuredClone` was a true deep
copy and was unaffected.
[source](https://www.builder.io/blog/structured-clone)

View 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)

View File

@@ -0,0 +1,44 @@
# Check Network Quality Stats From The Command Line
MacOS comes with a little known CLI utility for checking your current network
quality statistics. It is aptly named `networkQuality`.
Here is what a basic run of the tool might output:
```bash
$ networkQuality
...
Downlink: capacity 281.430 Mbps, responsiveness 101 RPM - Uplink: capacity 16.629 Mbps, responsiveness 101 R
Downlink: capacity 285.534 Mbps, responsiveness 101 RPM - Uplink: capacity 16.028 Mbps, responsiveness 101 R
==== SUMMARY ====
Uplink capacity: 22.982 Mbps
Downlink capacity: 288.152 Mbps
Responsiveness: Low (93 RPM)
Idle Latency: 26.375 milliseconds
```
You can get an even more detailed summary with the `-v` option:
```bash
$ networkQuality -v
...
==== SUMMARY ====
Uplink capacity: 18.257 Mbps (Accuracy: High)
Downlink capacity: 469.355 Mbps (Accuracy: High)
Responsiveness: Medium (252 RPM) (Accuracy: High)
Idle Latency: 25.583 milliseconds (Accuracy: High)
Interface: en0
Uplink bytes transferred: 19.750 MB
Downlink bytes transferred: 488.265 MB
Uplink Flow count: 8
Downlink Flow count: 12
Start: 9/24/24, 11:06:20 AM
End: 9/24/24, 11:06:30 AM
OS Version: Version 13.5.2 (Build 22G91)
```
See `man networkQuality` for more details.
[source](https://cyberhost.uk/the-hidden-macos-speedtest-tool-networkquality/)

View File

@@ -0,0 +1,24 @@
# Keyboard Shortcuts For Interacting With Text Areas
When interacting with a document text area on MacOS (such as in the Notes app),
there are a bunch of keyboard shortcuts made available to you via the operating
system.
A couple common ones that I'm used to from Unix environments are:
- `ctrl-a` to move the cursor to the beginning of the line
- `ctrl-e` to move the cursor to the end of the line
- `ctrl-p` to move the cursor up a line (this is a common _previous_ keybinding)
- `ctrl-n` to move the cursor down a line (this is a common _next_ keybinding)
A handy one that I wasn't aware of is `ctrl-l` which will scroll the text area
so that the cursor gets centered in the view area.
A much more niche one I wasn't aware of is `ctrl-t` which swaps the character
before the cursor with the character after the cursor. I can only imagine this
being useful for quickly fixing transposed characters in a misspelled word,
e.g. `baer`.
There are many more *Document Shortcuts* as well as well as keyboard shorcuts
for other apps and situations. The full listing is at [Mac Keyboard
Shortcuts](https://support.apple.com/en-us/102650).

View File

@@ -0,0 +1,36 @@
# Avoid Conflicting Files
When Next.js is bundling and building your project, it will get completely
tripped up by any instance of conflicting project files. What I mean by
conflicting project files are two JavaScript or TypeScript (or flavors of JSX
files) that would resolve to the same thing.
Here is one example where the extensions differ:
```
src/pages/welcome.tsx
src/pages/welcome.jsx
```
Here is another example where the paths differ but the bundled result would
conflict:
```
src/pages/welcome.tsx
src/pages/welcome/index.tsx
```
If you have any instances of these conflicting files, you'll be presented with
a beguiling and cryptic error message when trying to run the dev server.
```
TypeError [ERR_INVALID_ARG_TYPE]: The "to" argument must be of type string. Received undefined
at new NodeError (node:internal/errors:405:5)
at validateString (node:internal/validators:162:11)
at Object.relative (node:path:1191:5)
at Watchpack.<anonymous> (/my_app/node_modules/.pnpm/next@14.2.5_@babel+core@7.24.9_react-dom@18.3.1_react@18.3.1/node_modules/next/dist/server/lib/router-utils/setup-dev-bundler.js:381:55) {
code: 'ERR_INVALID_ARG_TYPE'
}
```
One of those files needs to go. Remove one of them and you'll be good to go.

View File

@@ -0,0 +1,25 @@
# Add Unique Constraint Using Existing Index
Adding a unique constraint to an existing column on a production table can
block updates. If we need to avoid this kind of locking for the duration of
index creation, then we can first create the index concurrently and then use
that existing index to back the unique constraint.
```sql
create index concurrently users_email_idx on users (email);
-- wait for that to complete
alter table users
add constraint unique_users_email unique using index users_email_idx;
```
First, we concurrently create the index. The time this takes will depend on how
large the table is. That's the blocking time we are avoiding with this
approach. Then once that completes we can apply a unique constraint using that
preexisting index.
Note: if a non-unique value exists in the table for that column, adding the
constraint will fail. You'll need to deal with that _duplicate_ value first.
[source](https://dba.stackexchange.com/questions/81627/postgresql-9-3-add-unique-constraint-using-an-existing-unique-index)

View File

@@ -4,7 +4,9 @@ PostgreSQL's `between` construct allows you to make a comparison _between_
two values (numbers, timestamps, etc.).
```sql
> select * from generate_series(1,10) as numbers(a) where numbers.a between 3 and 6;
> select *
from generate_series(1,10) as numbers(a)
where numbers.a between 3 and 6;
a
---
3
@@ -17,7 +19,9 @@ If you supply an empty range by using the larger of the two values first, an
empty set will result.
```sql
> select * from generate_series(1,10) as numbers(a) where numbers.a between 6 and 3;
> select *
from generate_series(1,10) as numbers(a)
where numbers.a between 6 and 3;
a
---
```
@@ -26,7 +30,9 @@ Tacking `symmetric` onto the `between` construct is one way to avoid this
issue.
```sql
> select * from generate_series(1,10) as numbers(a) where numbers.a between symmetric 6 and 3;
> select *
from generate_series(1,10) as numbers(a)
where numbers.a between symmetric 6 and 3;
a
---
3
@@ -39,3 +45,5 @@ issue.
> that the argument to the left of AND be less than or equal to the argument
> on the right. If it is not, those two arguments are automatically swapped,
> so that a nonempty range is always implied.
[source](https://www.postgresql.org/docs/current/functions-comparison.html#:~:text=BETWEEN%20SYMMETRIC%20is%20like%20BETWEEN,nonempty%20range%20is%20always%20implied.)

View File

@@ -0,0 +1,53 @@
# Concatenate Strings With A Separator
I was putting together an example of using a generated column that concatenates
string values from a few other columns. I used manual concatenation with the
`||` operator like so:
```sql
create table folders (
id integer generated always as identity primary key,
user_id integer not null,
name text not null,
parent_folder_id integer references folders(id),
path text generated always as (
user_id::text || ':' || lower(name) || ':' || coalesce(parent_folder_id::text, '0')
) stored
);
```
Instead of doing that manual concatenation for the `path` generated column, I
can use
[`concat_ws`](https://www.postgresql.org/docs/current/functions-string.html).
```sql
create table folders (
id integer generated always as identity primary key,
user_id integer not null,
name text not null,
parent_folder_id integer references folders(id),
path text generated always as (
concat_ws(
':',
user_id::text,
lower(name),
coalesce(parent_folder_id::text, '0')
)
) stored
);
```
The first argument to `concat_ws` is the separator I want to use. The remaining
arguments are the strings that should be concatenated with that separator.
One other things that is nice about `concat_ws` is that it will ignore `null`
values that it receives.
```sql
> select concat_ws(':', 'one', 'two', null, 'three');
+---------------+
| concat_ws |
|---------------|
| one:two:three |
+---------------+
```

View File

@@ -0,0 +1,35 @@
# Enforce Uniqueness On Column Expression
When creating a table for, say `users`, where you will store `email` addresses,
you'll likely want to enforce uniqueness on the that `email` field. And because
people have all sorts of ways of entering their emails, in terms of casing, you
may be tempted to try to enforce uniqueness on a lowercased version of `email`.
```sql
create table users (
id integer generated always as identity primary key,
email text not null,
unique ( lower(email) ) -- !! this won't work
);
```
A unique _constraint_ must be on one or more columns. You cannot include an
_expression_ when defining the unique constraint.
You can however accomplish similar aims with [a _unique index_ on the
expression](https://www.postgresql.org/docs/current/indexes-expressional.html).
That is because the index is able to store the result of the expression in
itself.
```sql
create table users (
id integer generated always as identity primary key,
email text not null
);
create unique index unq_lower_email on users ( lower(email) );
```
This is likely what you want for this example anyway because it will probably
be a common query to look up the user by `lower(email)` and the index will
speed up those queries.

View File

@@ -0,0 +1,43 @@
# Generate Modern Primary Key Columns
Chances are if you have looked at some examples, blog posts, or real-world
instances of a `create table` statement, it defined the primary key with
`serial` (or `bigserial`).
```sql
create table books (
id serial primary key,
title text not null,
author text not null,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now()
);
```
The `serial` syntax is everywhere, but for quite a while now it has not been
the recommended way to define a primary key column for the `int` or `bigint`
data types.
The ["Don't Do This" page of the PostgreSQL
wiki](https://wiki.postgresql.org/wiki/Don%27t_Do_This#Don.27t_use_serial) says
"Don't use serial".
> For new applications, identity columns should be used instead. The serial
> types have some weird behaviors that make schema, dependency, and permission
> management unnecessarily cumbersome.
The modern way to define a primary key column for `int` or `bigint` is with a
generated identity column.
```sql
create table books (
id int primary key generated always as identity,
title text not null,
author text not null,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now()
);
```
Check out the PostgreSQL docs for more about [identity
columns](https://www.postgresql.org/docs/17/ddl-identity-columns.html).

View 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.

View File

@@ -0,0 +1,30 @@
# Postgres Does Not Support Unsigned Integers
PostgreSQL has a variety of sizes of integer types, from `smallint` (2 bytes)
to `integer` (4 bytes) to `bigint` (8 bytes), as well as [other numeric
types](https://www.postgresql.org/docs/current/datatype-numeric.html).
It does _not_ however support unsigned versions of these numeric types.
That means, with an `integer` for instance, we can store numbers between
`-2147483648` and `+2147483647`. That's everything that can fit into 4 bytes.
In a system that supported 4 byte unsigned integers we'd be able to represent
from `0` all the way up to `4294967295`.
In PostgreSQL, we're limited to these _signed_ numeric types.
That means if we were hoping that the data type could essentially enforce a
non-negative restriction on the data in one of our columns, we're going to have
to be more creative. The obvious choice to me is to consider adding a [check
constraint](https://www.postgresql.org/docs/current/ddl-constraints.html#DDL-CONSTRAINTS-CHECK-CONSTRAINTS)
(e.g. `quantity integer check (quantity > 0)`).
Another option, as pointed out by [this StackOverflow
answer](https://stackoverflow.com/a/31833279/535590), is to create [a
user-defined _domain
type_](https://www.postgresql.org/docs/current/domains.html) that restricts
valid values. To me, the ergonomics of using a domain type are a bit awkward
and not worth the effort.
With either of these solutions, we are only approximating an unsigned integer
and do not actually have the same range of values available.

View File

@@ -0,0 +1,46 @@
# Put Unique Constraint On Generated Column
You cannot apply a _unique constraint_ to an expression over a column, e.g.
`lower(email)`. You can, however, create a [generated
column](https://www.postgresql.org/docs/current/ddl-generated-columns.html) for
that expression and then apply the unique constraint to that generated column.
Here is what that could look like:
```sql
> create table users (
id integer generated always as identity primary key,
name text not null,
email text not null,
email_lower text generated always as (lower(email)) stored,
unique ( email_lower )
);
> \d users
+-------------+---------+-----------------------------------------------------------------+
| Column | Type | Modifiers |
|-------------+---------+-----------------------------------------------------------------|
| id | integer | not null generated always as identity |
| name | text | not null |
| email | text | not null |
| email_lower | text | default lower(email) generated always as (lower(email)) stored |
+-------------+---------+-----------------------------------------------------------------+
Indexes:
"users_pkey" PRIMARY KEY, btree (id)
"users_email_lower_key" UNIQUE CONSTRAINT, btree (email_lower)
```
And then an demonstration of violating that constraint:
```sql
> insert into users (name, email) values ('Bob', 'bob@email.com');
INSERT 0 1
> insert into users (name, email) values ('Bobby', 'BOB@email.com');
duplicate key value violates unique constraint "users_email_lower_key"
DETAIL: Key (email_lower)=(bob@email.com) already exists.
```
The main tradeoff here is that you are doubling the amount of storage you need
for that column. Unless it is a massive table, that is likely not an issue.

View 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)

22
python/dunder-methods.md Normal file
View File

@@ -0,0 +1,22 @@
# Dunder Methods
Python has all kinds of special, or rather, _magic_ methods that allow for
customizing all kinds of class behavior. There is `__init__()`, `__bool__()`,
and so many others.
The thing they all have in common is that their names are wrapped in _double
underscores_. This is why they are called _dunder methods_.
Some of these are used every single day, like the `__init__()` method for
defining how a class should create an object. Others, used from time to time,
are for overriding how comparisons or conversions happen. E.g. you may want to
override `__bool__()` or `__len__()` to customize the truthiness of a custom
class.
There are so many others, ones you probably haven't even heard of. To see a
full listing, check out this [cheat sheet of every dunder
method](https://www.pythonmorsels.com/every-dunder-method/#cheat-sheet).
Note: these are not to be confused with _dunder attributes_ which are things
like `__name__`, `__file__`, and `__version__` which correspond to a value that
you can access in a specific context rather than behavior to override.

View File

@@ -0,0 +1,86 @@
# Override The Boolean Context Of A Class
Everything in Python has a truthiness that can be checked with `bool()`. An
empty list (`[]`) is falsy. A non-empty list (`[1,2,3]`) is truthy. Similar
with numbers:
```python
>>> bool(0)
False
>>> bool(1)
True
```
Any instance of an object is going to be truthy by default. If you want to
control in what context an instance is considered truthy or falsy, you can
override
[`__bool__()`](https://docs.python.org/3/reference/datamodel.html#object.__bool__).
If that's not implemented, but
[`__len__()`](https://docs.python.org/3/reference/datamodel.html#object.__len__)
is, then it will fallback to that.
Let's look at a few example classes:
```python
class CartZero:
def __init__(self, items=[]):
self.items = items or []
class CartBool:
def __init__(self, items=[]):
self.items = items or []
def __bool__(self):
print("__bool__() override")
return bool(self.items)
class CartLen:
def __init__(self, items=[]):
self.items = items or []
def __len__(self):
print("__len__() override")
return len(self.items)
class CartBoolAndLen:
def __init__(self, items=[]):
self.items = items or []
def __len__(self):
print("__len__() override")
return len(self.items)
def __bool__(self):
print("__bool__() override")
return bool(self.items)
cart1 = CartZero()
cart2 = CartBool()
cart3 = CartLen()
cart4 = CartBoolAndLen()
print("CartZero() -> %s" %(bool(cart1)))
print('')
print("CartBool() -> %s" %(bool(cart2)))
print('')
print("CartLen() -> %s" %(bool(cart3)))
print('')
print("CartBoolAndLen() -> %s" %(bool(cart4)))
```
An 'empty' `Cart` be default is truthy. However, we can override some
combination of `__bool__()` or `__len__()` to give it a boolean context that
goes `false` when "empty".
```
CartZero() -> True
__bool__() override
CartBool() -> False
__len__() override
CartLen() -> False
__bool__() override
CartBoolAndLen() -> False
```

View 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)

View 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)

View File

@@ -0,0 +1,37 @@
# Define The Root Path For The App
The `root_path` helper that you might want to use in Rails controllers and
views is not available by default.
```ruby
> Rails.application.routes.url_helpers.root_path
ruby/3.2.2/lib/ruby/gems/3.2.0/gems/irb-1.14.0/lib/irb.rb:1285:in `full_message': undefined method `root_path' for #<Module:0x0000000106d11738> (NoMethodError)
Rails.application.routes.url_helpers.root_path
^^^^^^^^^^
Did you mean? logout_path
book_path
```
It needs to be declared in the `config/routes.rb` file with the controller
action that it points to.
```ruby
# config/routes.rb
Rails.application.routes.draw do
root 'home#index'
end
```
Once this is defined the `root_path` will now be available with the rest of
your URL helpers.
```ruby
better-reads(dev)> reload!
Reloading...
better-reads(dev)> Rails.application.routes.url_helpers.root_path
=> "/"
```
[source](https://guides.rubyonrails.org/routing.html#using-root)

View File

@@ -0,0 +1,49 @@
# Empty find_by Returns First Record
During a RubyConf 2024 talk, a speaker mentioned that if you pass `nil` to
[ActiveRecord's `#find_by`
method](https://api.rubyonrails.org/classes/ActiveRecord/FinderMethods.html#method-i-find_by),
it will return the first record from the database. This is a bit unintuitive,
so lets look at an example and then I'll show you why.
```ruby
> Book.first
#=> #<Book:0x00000001142e4c48 id: 13, title: "The Secret History", ... >
> Book.find_by(nil)
#=> #<Book:0x00000001142ca3c0 id: 13, title: "The Secret History", ... >
```
So, that is the same object in both cases, but why?
Our first hint is in the SQL that gets constructed when making that method
call.
```ruby
Book Load (2.5ms) SELECT "books".* FROM "books" LIMIT $1 [["LIMIT", 1]]
```
It's grabbing all books and limiting to _one_ result.
Lets look at the underlying implementation of the `#find_by` method.
```ruby
# File activerecord/lib/active_record/relation/finder_methods.rb, line 111
def find_by(arg, *args)
where(arg, *args).take
end
```
Sure enough, the implementation is a `#where` followed by a `#take`. Since the
`#where` is receiving `nil` as its `arg`, there are no conditions _filtering_
the query. And the `#take` corresponds to the `limit 1`.
Knowing that, we can understand that we will also get the first record from the
database if we call `#find_by` with `{}`. Again, no conditions to filter on, so
give me all books limited to one.
One small caveat: notice how there is no `order by` clause in the above SQL
output. This differs from `Books.first` which implicitly does an order on the
`id` column. Though these method are likely to return the same result, the
ordering of `#find_by` is not guaranteed to be the same without an `order by`
clause.

26
rails/generate-a-model.md Normal file
View File

@@ -0,0 +1,26 @@
# Generate A Model
The `rails` CLI comes with a variety of generators. Perhaps the mostly common
one to use is the _model_ generator.
The model generator will create a migration and a model file for the entity
that you name. In the following example `Book` will result in a
`app/models/book.rb` file as well as a migration file for a `books` table.
These generators know the singular and plural conventions.
At the end of the command is a series of field definitions containing the field
_name_ and field _type_. These are used in the migration file for defining
columns on the new table.
```bash
bin/rails generate model Book title:string publication_date:date author:string
invoke active_record
create db/migrate/20240920223447_create_books.rb
create app/models/book.rb
invoke rspec
create spec/models/book_spec.rb
```
You may also notice that an `rspec` action was invoked as part of this
generator. That is because I have the `rspec-rails` gem in my project. That gem
hooks into the model generator so that a model spec also gets generated. Handy!

View File

@@ -0,0 +1,27 @@
# Generate A Rails App From The Main Branch
Typically you are going to want to generate a Rails app using some officially
released version of the framework. These releases have been thoroughly tested,
have received patches, and can guarantee a certain level of stability.
However, if you are wanting to try out the latest, unreleased features, you may
want to generate a fresh Rails app based off the current state of the `main`
branch of the `rails` repository.
To do this, add the `--main` flag:
```bash
$ rails new rails_app_on_main --main
```
Toward the top of your app's `Gemfile`, you'll see that `rails` is pointed to
the `main` branch of their repo:
```ruby
# Use main development branch of Rails
gem "rails", github: "rails/rails", branch: "main"
```
See `rails new --help` for more details
[source](https://x.com/gregmolnar/status/1832720168264286571)

View 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
```

View File

@@ -0,0 +1,25 @@
# Rollback A Couple Migrations
Let's say we need to rollback a couple Rails migrations that have been applied
to our local environment. We run `rails db:migrate:status` and see that there
are _2_ migrations that we want to _undo_.
We can accomplish this by using the `STEP` env var with the rollback command.
```bash
$ rails db:rollback STEP=2
```
Just set `STEP` to the number of migrations that we need to rollback. If we
then rerun `rails db:migrate:status` we'll now see those latest two migrations
are `down`.
Note: by default Rails doesn't like to operate with pending migrations. If we
want to temporarily disable the pending migration check, we can alter the
migration error config in `config/development.rb`.
```diff
# Raise an error on page load if there are pending migrations.
- # config.active_record.migration_error = :page_load
+ config.active_record.migration_error = false
```

View File

@@ -0,0 +1,45 @@
# Set Datetime To Include Time Zone In Migrations
When using Rails and PostgreSQL, your migrations will contain DSL syntax like
`t.datetime` and `t.timestamps` which will produce columns using the
`timestamp` (`without time zone`) Postgres data type.
While reading [A Simple Explanation of Postgres' <code>Timestamp with Time
Zone</code>](https://naildrivin5.com/blog/2024/10/10/a-simple-explanation-of-postgres-timestamp-with-time-zone.html),
I learned that there is a way to configure your app to instead use
`timestamptz` by default. This data type is widely recommended as a good
default, so it is nice that we can configure Rails to use it.
First, add these lines to a new initializer (`config/initializers/postgres.rb`)
file.
```ruby
require "active_record/connection_adapters/postgresql_adapter"
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.datetime_type = :timestamptz
```
Alternatively, you can configure this via `config/application.rb` per the
[Configuring ActiveRecord
docs](https://guides.rubyonrails.org/configuring.html#activerecord-connectionadapters-postgresqladapter-datetime-type).
Then, if you have a new migration like the following:
```ruby
class AddEventsTable < ActiveRecord::Migration[7.2]
def change
create_table :events do |t|
t.string :title
t.text :description
t.datetime :start_time
t.datetime :end_time
t.timestamps
end
end
end
```
you can expect to have four `timestamptz` columns, namely `start_time`,
`end_time`, `created_at`, and `updated_at`.
Here is the [Rails PR](https://github.com/rails/rails/pull/41084) that adds
this config option.

View File

@@ -0,0 +1,52 @@
# Set Default As SQL Function In Migration
With static default values, like `0`, `true`, or `'pending'`, we can set them
directly as the value of `default`.
```ruby
class CreateActionsTable < ActiveRecord::Migration[7.2]
def change
create_table :actions do |t|
t.string :status, default: 'pending'
end
end
end
```
However, if we want our default value to be a SQL function like `now()`, we
have to use a lambda.
Let's extend the above example to see what that looks like:
```ruby
class CreateActionsTable < ActiveRecord::Migration[7.2]
def change
create_table :actions do |t|
t.string :status, default: 'pending'
t.column :created_at, :timestamptz, default: -> { 'now()' }, null: false
end
end
end
```
If we need to alter the default of an existing table's column, we can do
something like this:
```ruby
class AddDefaultTimestampsToActions < ActiveRecord::Migration[7.2]
def up
change_column_default :actions, :created_at, -> { "now()" }
change_column_default :actions, :updated_at, -> { "now()" }
end
def down
change_column_default :actions, :created_at, nil
change_column_default :actions, :updated_at, nil
end
end
```
I believe this functionality is available to Rails 5.0 and later.
[source](https://github.com/rails/rails/issues/27077#issuecomment-261155826)

View File

@@ -0,0 +1,33 @@
# Temporarily Turn Off Pending Migrations Error
Whenever I'm working on an end-to-end feature in a Rails app, soon or later I
am going to see the _Pending Migrations_ error page. I try to visit one of the
routes in the browser and the Rails app serves this error page instead of my
actual request response.
This is typically what I want. If there are migrations just sitting there that
haven't been run yet, that's probably an issue. Maybe I just pulled down the
latest changes from my teammates. The app isn't going to work properly without
whatever schema changes are prescribed in those pending migrations.
The thing to do is run those migrations.
In some special cases though, I know what I'm doing and I would like to operate
my app locally with specific migrations not yet applied.
To skip the error, I can change this `config/environments/development.rb`
setting from:
```ruby
config.active_record.migration_error = :page_load
```
to:
```ruby
config.active_record.migration_error = false
```
I just need to make sure to switch it back when I'm done.
[source](https://til.hashrocket.com/posts/ujcixh5rwi-rails-ignore-pending-migrations)

View File

@@ -0,0 +1,32 @@
# Validate Column Data With Check Constraints
A check constraint is a feature of database systems like PostgreSQL that allows
you to enforce rules about the data that goes in a table's column. As of Rails
6.1, ActiveRecord provides a way to add a check constraint via the DSL.
In this example, we want to ensure that the value going into the
reading_statuses.status column is one of four values. Nothing else besides
these four values should be allowed.
```ruby
class AddReadingStatusTable < ActiveRecord::Migration[7.2]
def change
create_table :reading_statuses do |t|
t.references :user, null: false, foreign_key: true
t.references :book, null: false, foreign_key: true
t.string :status, null: false
t.timestamps
end
add_check_constraint
:reading_statuses,
"status in ('started', 'completed', 'abandoned', 'already_read')",
name: "reading_statuses_valid_status_check"
end
end
```
The `#add_check_constraint` method takes the name of the table and a SQL clause
that can evaluate to true or false for a given row. We can optionally include
the name of the check constraint (e.g. {table_name}_{column_name}_check) like
we've done above.

View File

@@ -0,0 +1,32 @@
# Avoid Accidentally Disabling Pry
I was recently working on a test that needed to mock an environment variable in
order to observe the behavior under test. I initially ended up with the
following lines in a `before` block.
```ruby
before do
allow(ENV).to receive(:[]).and_return("")
allow(ENV).to receive(:[]).with("API_SERVER_URL").and_return("localhost")
end
```
The idea was to create a "clean" `ENV` for the test and then layer in any
relevant environment variables from there.
This is a misguided approach for a couple reasons. One particular and
hard-to-debug issue is that this mocking of `ENV` accidentally disabled Pry
(i.e. `binding.pry`). So once I was having issues with my test, I couldn't even
inspect the code at runtime. It would skip right past any `binding.pry`
statement I added.
What happened is that [Pry (specifically `pry-rails`) looks for
`ENV['DISABLE_PRY_RAILS']`](https://github.com/pry/pry-rails/blob/d8d0c6d87a5b8a3e570e0c80910fb80068f3553c/lib/pry-rails.rb#L6)
and if that value is _truthy_ then it won't require the various `pry-rails`
modules.
The first `allow` has `ENV` responding with `""` which is truthy and will
result in Pry being disabled.
A more appropriate default would be `nil`. Also consider that there are many
ways to access `ENV`, such as `#fetch`.

View File

@@ -0,0 +1,33 @@
# Configure Tests To Run In Random Order
By default, an RSpec test suite is going to run in a predictable, sequential
order, every time.
When testing the parts of a complex Rails app that have all kinds of test data
that needs to be set up, I prefer to have my tests always run in a random
(repeatable with a seed) order. This way I'm more likely to catch sooner,
rather than later, bugs that are hidden by passing tests due to test data setup
that happens to work in a specific order.
RSpec can be configured to run tests in a random, seedable order in the
`spec_helper.rb` file.
```ruby
RSpec.configure do |config|
config.order = :random
end
```
Whenever you run your test suite, the first thing you'll see is a message like
this:
```
Randomized with seed 7011
```
That seed number can be used to re-run the suite in a repeatable order when you
need to do so to track down an order-dependent failing test.
```bash
$ be rspec --seed 7011
```

View File

@@ -0,0 +1,38 @@
# Execute Several Commands With Backtick Heredoc
A fun feature of Ruby is that we can execute a command in a subprocess just by
wrapping it in backticks.
For instance, we might shell out to `git` to check if a file is tracked:
```ruby
`git ls-files --error-unmatch #{file_path} 2>/dev/null`
$?.success?
```
But what if we need to execute several commands? Perhaps they depend on one
another. We want them to run in the same subprocess.
For this, we can use the backtick version of a heredoc. That is a special
version of a heredoc where the delimiter is wrapped in backticks.
```ruby
puts <<`SHELL`
# Set up trap
trap 'echo "Cleaning up temp files"; rm -f *.tmp' EXIT
# Create temporary file
echo "test data" > work.tmp
# Do some work
cat work.tmp
# Trap will clean up on exit
SHELL
```
Here we set up a `trap` for file cleanup on exit, then create a file, then do
something with the file, and that's it, the process exits (triggering the
trap).
[source](https://ruby-doc.org/3.3.6/syntax/literals_rdoc.html#label-Here+Document+Literals)

View File

@@ -0,0 +1,45 @@
# Forward All Arguments To Another Method
There are three types of arguments that a Ruby method can receive. Positional
arguments, keyword arguments, and a block argument.
A method that deals with all three might be defined like this:
```ruby
def forwarding_method(*args, **kwargs, &block)
# implementation
end
```
Now lets say we have some concrete method that we want to forward these
arguments to:
```ruby
def concrete_method(*args, **kwargs)
x = args.first || 1
key, y = kwargs.first || [:a, 2]
puts "Dealing with #{x} and key #{key}: #{y}"
yield(x, y)
end
```
We could forward arguments the longhand way like this:
```ruby
def forwarding_method(*args, **kwargs, &block)
concrete_method(*args, **kwargs, &block)
end
```
However, since Ruby 2.7 we have access to a shorthand "triple-dot" syntax for
forwarding all arguments.
```ruby
def forwarding_method(...)
concrete_method(...)
end
```
[source](https://ruby-doc.org/3.3.6/syntax/methods_rdoc.html#label-Argument+Forwarding)

View File

@@ -0,0 +1,46 @@
# Multi-Line Comments
Ruby has an obscure syntax for creating multi-line comments.
In many languages, there is a multi-line comment syntax that looks something
like this:
```javascript
/*
* multi-line comment in javascript
*/
```
This gets used often in those languages.
In Ruby, the multi-line comment syntax is not something we see very often. It
is a departure from the single-line comment syntax and it also requires no
indentation.
```ruby
RSpec.configure do |config|
config.order = :random
# The settings below are suggested to provide a good initial experience
# with RSpec, but feel free to customize to your heart's content.
=begin
# These two settings work together to allow you to limit a spec run
# to individual examples or groups you care about by tagging them with
# `:focus` metadata. When nothing is tagged with `:focus`, all examples
# get run.
config.filter_run :focus
config.run_all_when_everything_filtered = true
# ...
=end
end
```
Using the `=begin` and `=end` syntax (no indentation), we make everything
inbetween into a comment.
Though we don't see this too often, I did pull this example directly from the
`spec_helper.rb` file that RSpec generates.
[source](https://docs.ruby-lang.org/en/master/syntax/comments_rdoc.html)

View File

@@ -0,0 +1,42 @@
# Output Bytecode For A Ruby Program
The `ruby` CLI comes with a flag to dump the disassembled YARV bytecode for the
given Ruby program. This can be a fun way to explore how a Ruby program is
interpreted under the hood.
Aaron Patterson demoed this behavior during his RubyConf 2024 talk.
Pass the `--dump` flag with `insns` along with either the path to your file or
an inline bit of Ruby.
Here is a really basic example:
```bash
ruby --dump=insns -e '2 + 3'
== disasm: #<ISeq:<main>@-e:1 (1,0)-(1,5)> (catch: false)
0000 putobject 2 ( 1)[Li]
0002 putobject 3
0004 opt_plus <calldata!mid:+, argc:1, ARGS_SIMPLE>[CcCr]
0006 leave
```
And another quite basic example, but with local variables this time:
```bash
ruby --dump=insns -e 'x = 2; y = 3; x + y'
== disasm: #<ISeq:<main>@-e:1 (1,0)-(1,19)> (catch: false)
local table (size: 2, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
[ 2] x@0 [ 1] y@1
0000 putobject 2 ( 1)[Li]
0002 setlocal_WC_0 x@0
0004 putobject 3
0006 setlocal_WC_0 y@1
0008 getlocal_WC_0 x@0
0010 getlocal_WC_0 y@1
0012 opt_plus <calldata!mid:+, argc:1, ARGS_SIMPLE>[CcCr]
0014 leave
```
If you want to dig in to how to read everything that is going on in these
outputs, I'd recommend checking out this [Advent of YARV
series](https://kddnewton.com/2022/11/30/advent-of-yarv-part-0.html)

View File

@@ -0,0 +1,45 @@
# Prevent erb_lint From Removing Opening Tags
The [`erb_lint` gem](https://github.com/Shopify/erb_lint) is a tool from
shopify for linting and auto-formatting ERB files. When I first set it up in a
Rails codebase with the base `.erb-lint.yml` recommended in the README, I ran
into a pernicious issue. The linter wanted to remove opening tags (i.e. `<%`
and `<%=`) from my ERB files.
So, for a file that looked like this:
```erb
<div>
<%= form_with(url: login_path, scope: :session) do |f| %>
<div>
<%= f.label :email %>
<%= f.email_field :email %>
</div>
</div>
```
It would get formatted to this:
```erb
<div>
form_with(url: login_path, scope: :session) do |f| %>
<div>
f.label :email %>
f.email_field :email %>
</div>
</div>
```
Yikes!
I had to disable a couple rules (under `rubocop_config:`) in the `.erb-lint.yml` file to get it to stop
doing this.
```yaml
Layout/InitialIndentation:
Enabled: false
Layout/TrailingEmptyLines:
Enabled: false
```
[source](https://github.com/Shopify/erb_lint/issues/222)

View File

@@ -0,0 +1,44 @@
# Skip The Front Of An Array With Drop
I've long been familiar with Ruby's
[`Array#take`](https://apidock.com/ruby/Array/take) which is a handy way to
grab the first few elements of an array.
```ruby
> [:a, :b, :c, :d, :e, :f, :g].take(4)
=> [:a, :b, :c, :d]
```
But what if I want to skip those first four elements of the array and get what
remains. That is where [`Array#drop`](https://apidock.com/ruby/Array/drop)
comes in.
```ruby
> [:a, :b, :c, :d, :e, :f, :g].drop(4)
=> [:e, :f, :g]
```
How about a couple practical situations for use in a Rails app.
Here I want to segment out the first 6 books and whatever remains:
```
def index
@books = Book.order(created_at: :desc).limit(10)
@featured_books = @books.take(6)
@remaining_books = @books.drop(6)
end
```
Or, what about cleaning up all but the first of these duplicate records:
```
Book
.where(title: "The Murder of Roger Ackroyd")
.drop(1)
.each { |duplicate_book| duplicate_book.destroy }
```
The first record is ignored (dropped from the array) and the following records
are processed by the `#each` where they get destroyed.

View 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}'`.

View 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)

View File

@@ -0,0 +1,20 @@
# Convert JPEG To PNG With ffmpeg
The `ffmpeg` utility "is a universal media converter." That means we can use it
to convert, for instance, a JPEG file to a PNG file.
There is not a lot to a conversion like this. We use `-i` to specify the
existing input file (a JPEG) and then the other argument is the name and
extension of the output file.
```bash
$ ls
profile.jpg
$ ffmpeg -i profile.jpg profile.png
$ ls
profile.jpg profile.png
```
See `man ffmpeg` for more details.

View File

@@ -0,0 +1,28 @@
# Convert SVG To Favicon
The imagemagick `convert` CLI tool can convert an SVG file into a transparent
favicon (ICO) file with the different standard sizes.
Assuming the background that we want to make transparent is white, then include
`-transparent white` and then to resize the icon include `-define
icon:auto-resize ...`. Point to the `image.svg` to be converted and specify the
name of the output file (`favicon.ico`).
```bash
$ convert image.svg -transparent white -define icon:auto-resize=16,32,48,64,128 favicon.ico
```
We can then use the `identify` CLI to inspect the `favicon.ico` file to see
that the above worked.
```bash
$ identify favicon.ico
favicon.ico[0] ICO 16x16 16x16+0+0 8-bit sRGB 0.000u 0:00.002
favicon.ico[1] ICO 32x32 32x32+0+0 8-bit sRGB 0.000u 0:00.004
favicon.ico[2] ICO 48x48 48x48+0+0 8-bit sRGB 0.000u 0:00.004
favicon.ico[3] ICO 64x64 64x64+0+0 8-bit sRGB 0.000u 0:00.004
favicon.ico[4] ICO 128x128 128x128+0+0 8-bit sRGB 99678B 0.000u 0:00.003
```
[source](https://www.joshmcarthur.com/2024/06/19/Auto-resizing-images-for-.ico-files.html)

View File

@@ -0,0 +1,23 @@
# Download A File With Curl
Though I typically think of cURL as a way of hitting an API endpoint to check
its headers or see what it returns, it can also be used as an alternative to
`wget` to download a file.
With the `-O` option (short for `--remote-name`) we instruct cURL to save the
contents of the remote file to a local file in the current directory _using the
same name as the remote_.
Here is an example:
```bash
$ curl -O https://www.apache.org/dist/db/derby/db-derby-10.17.1.0/db-derby-10.17.1.0-bin.zip.asc
```
This creates a file in my current directory called
`db-derby-10.17.1.0-bin.zip.asc`. Notice it only uses the last part of the path
for the file name.
See `man curl` for more details.
[source](https://stackoverflow.com/questions/4572153/os-x-equivalent-of-linuxs-wget#comment84857090_4572158)

View File

@@ -0,0 +1,37 @@
# Find Any Dotfiles That Modify Path Env Var
Whether you are using `zsh`, `bash`, or some other shell, there are a variety
of dotfiles where you can place statements to update the `PATH` env var. These
files don't all run in the same contexts and it can be tricky to debug if one
is clobbering the path set by another.
One way to audit how your `PATH` gets set and track down any issues is to find
any place where the path may be getting modified in your dotfiles.
I like to use [`rg` (ripgrep)](https://github.com/BurntSushi/ripgrep) for tasks
like this.
First, I want to check where the `PATH` is explicitly modified.
```bash
$ rg 'export PATH' ~/\.* --max-depth 0
```
This looks at all instances of dotfiles in my home directory where `export
PATH` appears. That should catch the majority of ways that it gets updated.
Next, because I am using `zsh` as my shell, I want to look for another way my
path might be set. `zsh` defaults to setting up `path` as proxy for `PATH` that
acts as an array.
I check for any instances of `path=` or `path+=` in my dotfiles:
```bash
$ rg 'path\+?=' ~/\.* --max-depth 0
```
Note that the `--max-depth 0` is really important for both because otherwise a
ton of irrelevant stuff buried in deeply-nested dot-directories will be
surfaced.
If you want just a file name summary of the results, tack on a `-l` flag.

View 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.

View File

@@ -0,0 +1,29 @@
# Gracefully Exit A Script With Trap
With `trap` you can intercept signals that would cause your script to exit and
then inject some additional behavior. Perhaps you want to make sure the script
cleans up after itself before it exists.
During this script's execution, it creates a file in the filesystem. It would
be nice to make sure that no matter path this script ends up down that it will
clean up after itself as it exits. We set up a `trap` that looks for the `EXIT`
signal to do this.
```bash
# Set up trap
trap 'echo "Cleaning up temp files"; rm -f *.tmp' EXIT
# Create temporary file
echo "test data" > work.tmp
# Do some work
cat work.tmp
# Trap will clean up on exit
```
Whatever is in quotes is what the trap will execute when it is triggered. The
following one or more signals are what the trap listens for, in this case
`EXIT`.
[source](https://tldp.org/LDP/Bash-Beginners-Guide/html/sect_12_02.html)

View File

@@ -0,0 +1,32 @@
# Make Direnv Less Noisy
I've been using [`direnv`](https://direnv.net/) to manage project and folder
specific environment variables for a bit now. I've found it to be pretty
seamless. It can feel like it is littering my shell with too much output when I
change directories though.
There are two levers to control its output.
First, the direnv logs (e.g. `direnv: loading ~/.../.envrc`) can be controlled
with the `DIRENV_LOG_FORMAT` env var. Add this to the
`~/.config/direnv/direnvrc` file (add that directory and file if necessary).
You can leave it blank to altogether hide log messages or you can gray-out the
log messages like this:
```
export DIRENV_LOG_FORMAT=$'\033[2mdirenv: %s\033[0m'
```
Second, you can hide the env var diff with a separate config. This diff is not
covered under the umbrella of logs controlled by the above setting. Set
[`hide_env_diff` in the `~/.config/direnv/direnv.toml`
file](https://direnv.net/man/direnv.toml.1.html#codehideenvdiffcode):
```toml
[global]
hide_env_diff = true
```
This second config was only added as of `v2.34.0`.
[source](https://esham.io/2023/10/direnv)

View 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)

View File

@@ -0,0 +1,17 @@
# Switch Moving End Of Visual Selection
When I go into character-wise visual selection mode, one end of the visual
selection is fixed while I move my cursor around to define the other end of it.
Let's say I've arranged a visual selection that encompasses several lines of my
file. And then I realize that the fixed front-end of my visual selection is off
by a bit. Maybe I've selected an entire function definition and I just want to
inner part of the function.
Instead of starting over with my visual selection. I can leave the right-end of
the visual selection where it is, hit `o` which will switch the moving end to
the other side, and then continue making adjustments from there.
I can always hit `o` again to switch it back to the original side.
See `:h v_o` for more details.

View File

@@ -0,0 +1,18 @@
# Jump To Problems In The Current File
VSCode has a system for different extensions to report problems. The editor
will visually flag those problems in the editor with a red squiggly underline.
It will also list them in the _Problems_ tab of the bottom tray (hit `Cmd+j` to
pop that tray open).
If there are some active problems in your current file, you can jump right to
them.
Hit `F8` and you'll jump to the next problem after wherever the cursor is. Hit
`F8` again and it will go to the next one.
Want to track back through the file? Hit `Shift+F8` and you'll jump to the
closest problem _behind_ the current cursor position.
Using these two, you can quickly survey the current problems in your file
before deciding how to proceed.

View File

@@ -0,0 +1,19 @@
# Add Hotkeys For Specific Raycast Extensions
One of the main things I use [Raycast](https://www.raycast.com/) for is its
built-in clipboard history extension. It's super handy when I've copied a Git
SHA or a URL or a bit of text several copies ago and I want to be able to refer
back to it. It keeps 7 days of clipboard history by default, so anything that I
remember copying is going to be there.
To get there, I have to trigger Raycast, which I usually open via Alfred (I
know, 😅). Then I search for Clipboard History (or find it under recent
suggestions).
Raycast supports arbitrary hotkey bindings for triggering different extensions
and their features. So I can get to the Clipboard History in a single step --
e.g. with `Ctrl+Shift+V`.
From Raycast _Settings_, go to the _Extensions_ tab. Find _Clipboard History_
in the list and then click the _Record Hotkey_ input for it. I then type
`Ctrl+Shift+V` and I'm all set.

View File

@@ -0,0 +1,64 @@
# Add Subscriber To Kit Form Via API
Because the Kit API is a bit sparse in words, I found it difficult to figure
out exactly what I needed to do to add a subscriber via a custom form. After
some experimenting and reading through [Course
Builder](https://github.com/badass-courses/course-builder/blob/main/packages/core/src/providers/convertkit.ts)
code, I was able to figure out that with the Kit v4 API there are two separate
calls that need to be made.
First, you need to create an `inactive` subscriber with the user's email (and
first name if given) via the [Create a subscriber
endpoint](https://developers.kit.com/v4#create-a-subscriber).
```typescript
async function createSubscriber(
data: SubscriberData,
apiKey: string,
): Promise<Response> {
return fetch('https://api.convertkit.com/v4/subscribers', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Kit-Api-Key': apiKey,
},
body: JSON.stringify({
email_address: data.email,
first_name: data.name,
state: 'inactive',
}),
})
}
```
Since this subscriber is `inactive`, they won't show up in the Kit dashboard,
but a subscriber record of them has been created.
Second, you need to add the subscriber to something, like a form or a sequence.
In my case, I had already created a form via the Kit dashboard, so I grab that
_Form ID_ and stick it in my `.env`. That way I am able to [add the subscriber
to the form by email
address](https://developers.kit.com/v4#add-subscriber-to-form-by-email-address).
```typescript
async function addSubscriberToForm(
email: string,
apiKey: string,
formId: string,
): Promise<Response> {
const url = `https://api.convertkit.com/v4/forms/${formId}/subscribers`
return fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Kit-Api-Key': apiKey,
},
body: JSON.stringify({ email_address: email }),
})
}
```
This will send a confirmation email to the email address. Once the person has
opened the email and hit 'Confirm', their corresponding `subscriber` record
will be marked as `active` and they will be added to the list of subscribers
for that form.

View File

@@ -0,0 +1,23 @@
# Configure Email Redirect With Cloudflare
I have a domain registered with Cloudflare --
[visualmode.dev](https://www.visualmode.dev). I want to be able to sign up for
services and receive emails as `josh@visualmode.dev`. I don't want a separate
inbox that I have to check though.
The solution, for me, is to have Cloudflare redirect incoming emails to an
email address/inbox that I'm already regularly checking.
On the _Email_ dashboard there is a _Routing Rules_ tab. In the _Custom
Addresses_ section, I can click _Create Address_. There I specify the custom
address (what will appear before the `@`), the action to make ("Send to an
email"), and the destination (the existing email address for an inbox I
regularly check).
Finally, I hit _Save_. If they don't already exist, I'll be prompted to confirm
the setup of MX records for the domain. After confirming that, I should be able
to receive emails via that new address.
The Email Routing dashboard will even show me a summary of all rerouted emails.
[source](https://blog.cloudflare.com/introducing-email-routing/#cloudflare-email-routing)

View File

@@ -0,0 +1,27 @@
# Get URL For GitHub User Profile Photo
You can access your (or really any user's) profile photo by visiting the GitHub
profile URL with `.png` added on to the end.
So, my GitHub profile URL is:
```
https://github.com/jbranchaud
```
If I were to add `.png` on to that and visit it in my browser:
```
https://github.com/jbranchaud.png
```
I'd be redirected to the following URL:
```
https://avatars.githubusercontent.com/u/694063?v=4
```
This is the stable URL for my GitHub avatar. In the browser I will see the full
resolution image which I can download as needed.
[source](https://dev.to/10xlearner/how-to-get-the-profile-picture-of-a-github-account-1d82)

View File

@@ -0,0 +1,64 @@
# Send A Message To A Discord Channel
I recently added a form to [visualmode.dev](https://www.visualmode.dev) that
when submitted should send the details to an internal channel in my discord
server.
I didn't need to set up an _App_ or a _Bot_ to do this. It is much simpler than
that. All I needed was a valid [_webhook_
endpoint](https://discord.com/developers/docs/resources/webhook) for my channel
that I can `POST` to.
From Discord, I can select _Edit Channel_ for a specific channel, go to the
_Integrations_ tab, go to _Webhooks_, and then create a _New Webhook_. I can
name it, save it, and then copy the webhook URL.
As a demonstration, I can `POST` to that webhook URL using `cURL` like so:
```bash
curl -H "Content-Type: application/json" -X POST -d '{"content":"Hello from cURL!"}' <YOUR_WEBHOOK_URL>
```
Similarly, in some non-public-facing code like a Next.js serverless function, I
can `POST` to that webhook URL with the `content` that I want to appear in my
channel.
```
import { NextResponse } from 'next/server'
export async function POST(request: Request) {
const data = await request.json()
const discordWebhookUrl = process.env.DISCORD_WEBHOOK_URL
if (discordWebhookUrl) {
try {
const response = await fetch(discordWebhookUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
content: `New contact form submission:\nName: ${data.name}\nEmail: ${data.email}\nCompany: ${data.company}\nPhone: ${data.phone}\nMessage: ${data.message}`,
}),
})
if (!response.ok) {
throw new Error('Failed to send Discord message')
}
} catch (error) {
console.error('Error sending to Discord:', error)
return NextResponse.json(
{ error: 'Failed to process form submission' },
{ status: 500 },
)
}
}
return NextResponse.json({ message: 'Form submitted successfully' })
}
```
This [Structure of Webhook
guide](https://birdie0.github.io/discord-webhooks-guide/discord_webhook.html)
has more details on how to specifically structure and format a more complex
message.

View File

@@ -0,0 +1,25 @@
# Add To Path Via Path Array
Typically when managing what is on your path in a Unix shell environment, you
override the `PATH` environment variable with `export`. This is usually an
append or prepend to bring along the existing path entries.
```bash
$ export PATH="$PATH:/Users/me/.local/bin"
```
The `zsh` shell environment exposes another way of adding to your path. They
have a `path` array which can be a little easier to work with since you can use
an array operation instead of string interpolation.
Here is how we'd do the same as above:
```bash
$ path+=/Users/me/.local/bin
```
This works because there is an automatic linking in zsh between arrays and
colon-separated strings (_scalars_).
[source](https://www.zsh.org/mla/users//2005/msg01132.html)
[source](https://superuser.com/a/1447959)

View File

@@ -0,0 +1,38 @@
# Link A Scalar To An Array
`Zsh` has a builtin command `typeset` that does a variety of things. When given
the `-T` flag and the names of a scalar and an array, it will link them
together so that a change to one is reflected in the other.
The scalar is a string of values delimited by a colon (`:`). The array is an
array that can be interacted with using array operations like append (`+=`).
```bash
$ typeset -T FOO foo
$ echo $FOO
$ export FOO="one:two"
$ echo $foo
one two
$ foo+=three
$ echo $FOO
one:two:three
```
Notice `FOO` is initially empty. I then `export` it to overwrite it with two
values delimited by a colon. Since `foo` is automatically kept in sync, I can
`echo $foo` and see those values displayed as an array. I can then append a
third value using an array operation on `foo`. The update will be automatically
reflected in `FOO`.
`Zsh` does this under the hood for `PATH` and `path` which is why you can [add
to the path via the path array](add-to-the-path-via-path-array.md).
See `man zshbuiltins` for more details.
[source](http://devlib.symbian.slions.net/s3/GUID-D87C96CE-3F23-552D-927C-B6A1D61691BF.html)

View File

@@ -0,0 +1,37 @@
# Use A Space To Exclude Command From History
When using a shell like `zsh`, you get the benefit of it keeping track of the
history of the commands you've entered into the shell. This means you can
quickly traverse pack to a previous command that you want to run again. It also
means [a tool like `fzf` can hook into your history
file](https://github.com/junegunn/fzf?tab=readme-ov-file#key-bindings-for-command-line)
so that you can fuzzy-search for a command you may have executed weeks ago.
The history is stored on your machine in a plaintext file. Not every command
should be stored in a plaintext file. For instance, you don't want `zsh` to
persist a command that includes a password.
With the `histignorespace` option enabled in `zsh`, we can put a leading space
in front of our command and it will be excluded from the history file.
Try it yourself:
```bash
$ echo 'this command will be remembered'
this command will be remembered
$ echo 'this command will be forgotten'
this command will be forgotten
```
Notice the leading space in the second command. Trying pressing your _up_ arrow
and notice only that first `echo` is remembered.
Make sure `histignorespace` is included in the list when you run `setopt`. If
it isn't, then add it:
```bash
$ setopt histignorespace
```
[source](https://stackoverflow.com/questions/8473121/execute-a-command-without-keeping-it-in-history/49643320#49643320)