mirror of
https://github.com/jbranchaud/til
synced 2026-01-16 21:48:02 +00:00
Compare commits
90 Commits
4cabfc303f
...
d09e046b91
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d09e046b91 | ||
|
|
86972f41cc | ||
|
|
93a663cc9c | ||
|
|
0c1dd29d8d | ||
|
|
b492a9d765 | ||
|
|
543a82730d | ||
|
|
877537228f | ||
|
|
1513611857 | ||
|
|
74514b462d | ||
|
|
484dec8e24 | ||
|
|
8574113dc6 | ||
|
|
1c4e37ed8a | ||
|
|
581aa1decb | ||
|
|
0c4795c1d2 | ||
|
|
9bbde247a5 | ||
|
|
36ca71bfb1 | ||
|
|
8a682e3a89 | ||
|
|
c7a38c8267 | ||
|
|
71d3e56b3d | ||
|
|
af3974d3fe | ||
|
|
adc6b2e903 | ||
|
|
9a6ebd4c6b | ||
|
|
6df0693804 | ||
|
|
507602ef0c | ||
|
|
18bdcc88b8 | ||
|
|
95115c7ebc | ||
|
|
4e859b93d2 | ||
|
|
23c20e99bf | ||
|
|
63bb627716 | ||
|
|
21385f4491 | ||
|
|
5b47326ab3 | ||
|
|
c16d80fd94 | ||
|
|
edf38308da | ||
|
|
dc7159c16c | ||
|
|
33f780a69f | ||
|
|
dfe9c002ee | ||
|
|
3e34636d80 | ||
|
|
dcef57d344 | ||
|
|
6580393b7a | ||
|
|
17d7f0933b | ||
|
|
e4abc56f4c | ||
|
|
43ea7acd74 | ||
|
|
d7d331b688 | ||
|
|
b743dc2ac0 | ||
|
|
4a72c63e42 | ||
|
|
431507fd0e | ||
|
|
fb153f35bf | ||
|
|
f4ba6a9ef7 | ||
|
|
0dee39d3c5 | ||
|
|
5ebdd9a1a9 | ||
|
|
33c5cd748f | ||
|
|
ff515c8d6a | ||
|
|
fad36e0691 | ||
|
|
4d1d8e7134 | ||
|
|
567637497c | ||
|
|
028b76ba6b | ||
|
|
1934c8f63e | ||
|
|
e5a003dbaf | ||
|
|
ab9d2b5bf6 | ||
|
|
aa00c55b06 | ||
|
|
cc858382d8 | ||
|
|
dbb8c585c1 | ||
|
|
f25064031f | ||
|
|
cfbe640eb0 | ||
|
|
bf04dfcca5 | ||
|
|
24b1b02d52 | ||
|
|
8ef2cfdc69 | ||
|
|
02086e7115 | ||
|
|
0ecc41bd29 | ||
|
|
569220e734 | ||
|
|
594ec08636 | ||
|
|
475f125f4b | ||
|
|
ba6492d46e | ||
|
|
3ac3014659 | ||
|
|
4b6833c437 | ||
|
|
fc93ecfed4 | ||
|
|
a07a19e6d9 | ||
|
|
951a2f04ad | ||
|
|
6b9ec224b8 | ||
|
|
c33be14ec2 | ||
|
|
2922bbdd6a | ||
|
|
2e72ec1160 | ||
|
|
baab5738e7 | ||
|
|
191c9d6d9d | ||
|
|
25b5677260 | ||
|
|
df3492d4ef | ||
|
|
bd49b31bb0 | ||
|
|
4ff1a381d1 | ||
|
|
0bfeb0e236 | ||
|
|
b4080eb9ee |
12
.vimrc
12
.vimrc
@@ -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',
|
||||
" })
|
||||
|
||||
96
README.md
96
README.md
@@ -10,7 +10,7 @@ pairing with smart people at Hashrocket.
|
||||
|
||||
For a steady stream of TILs, [sign up for my newsletter](https://crafty-builder-6996.ck.page/e169c61186).
|
||||
|
||||
_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
|
||||
|
||||
56
astro/generate-types-for-a-content-collection.md
Normal file
56
astro/generate-types-for-a-content-collection.md
Normal 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`.
|
||||
53
astro/markdown-files-are-of-type-markdown-instance.md
Normal file
53
astro/markdown-files-are-of-type-markdown-instance.md
Normal 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");
|
||||
```
|
||||
48
brew/export-list-of-everything-installed-by-brew.md
Normal file
48
brew/export-list-of-everything-installed-by-brew.md
Normal 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/)
|
||||
51
css/add-line-numbers-to-a-code-block-with-counter.md
Normal file
51
css/add-line-numbers-to-a-code-block-with-counter.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# Add Line Numbers To A Code Block With Counter
|
||||
|
||||
The
|
||||
[`counter`](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_counter_styles/Using_CSS_counters)
|
||||
feature in CSS is a stateful feature that allows you to increment and display a
|
||||
number based on elements' locations in the document. This feature is useful for
|
||||
adding numbers to headings and lists, but it can also be used to add line
|
||||
numbers to a code block.
|
||||
|
||||
We need to initialize the counter to start using it. This will give it a name
|
||||
and default it to the value 0. We'll tie this to a `pre` tag which wraps our
|
||||
lines of code.
|
||||
|
||||
```css {{ title: 'globals.css' }}
|
||||
pre.shiki {
|
||||
counter-reset: line-number;
|
||||
}
|
||||
```
|
||||
|
||||
Then we need to increment the counter for every line of code that appears in
|
||||
the code block
|
||||
|
||||
```css {{ title: 'globals.css' }}
|
||||
pre.shiki .line {
|
||||
counter-increment: line-number;
|
||||
}
|
||||
```
|
||||
|
||||
Last, we need to display these incrementing `line-number` values _before_ each
|
||||
line.
|
||||
|
||||
```css {{ title: 'globals.css }}
|
||||
pre.shiki .line:not(:last-of-type)::before {
|
||||
content: counter(line-number);
|
||||
/*
|
||||
* plus any styling and spacing of the numbers
|
||||
*/
|
||||
}
|
||||
```
|
||||
|
||||
This essentially attaches an element to the front (`::before`) of the line
|
||||
whose content is the current value of `line-number`. It is applied to all but
|
||||
the last `.line` because [shiki](https://shiki.matsu.io/) includes an empty
|
||||
`.line` at the end.
|
||||
|
||||
Here is [the real-world example of
|
||||
this](https://github.com/pingdotgg/uploadthing/blob/4954c9956c141a25a5405991c34cc5ce8d990085/docs/src/styles/tailwind.css#L13-L37)
|
||||
that I referenced for this post.
|
||||
|
||||
Note: the counter can be incremented, decremented, or even explicitly set to a
|
||||
specific value.
|
||||
22
docker/list-running-docker-containers.md
Normal file
22
docker/list-running-docker-containers.md
Normal 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.
|
||||
48
drizzle/create-bigint-identity-column-for-primary-key.md
Normal file
48
drizzle/create-bigint-identity-column-for-primary-key.md
Normal 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")
|
||||
);
|
||||
```
|
||||
39
drizzle/drizzle-tracks-migrations-in-a-log-table.md
Normal file
39
drizzle/drizzle-tracks-migrations-in-a-log-table.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# Drizzle Tracks Migrations In A Log Table
|
||||
|
||||
When I generate (`npx drizzle-kit generate`) and apply (`npx drizzle-kit
|
||||
migrate`) schema migrations against my database with Drizzle, there are SQL
|
||||
files that get created and run.
|
||||
|
||||
How does Drizzle know which SQL files have been run and which haven't?
|
||||
|
||||
Like many SQL schema migration tools, it uses a table in the database to record
|
||||
this metadata. Drizzle defaults to calling this table `__drizzle_migrations`
|
||||
and puts it in the `drizzle` schema (which is like a database namespace).
|
||||
|
||||
Let's take a look at this table for a project with two migrations:
|
||||
|
||||
```sql
|
||||
postgres> \d drizzle.__drizzle_migrations
|
||||
Table "drizzle.__drizzle_migrations"
|
||||
Column | Type | Collation | Nullable | Default
|
||||
------------+---------+-----------+----------+----------------------------------------------------------
|
||||
id | integer | | not null | nextval('drizzle.__drizzle_migrations_id_seq'::regclass)
|
||||
hash | text | | not null |
|
||||
created_at | bigint | | |
|
||||
Indexes:
|
||||
"__drizzle_migrations_pkey" PRIMARY KEY, btree (id)
|
||||
|
||||
postgres> select * from drizzle.__drizzle_migrations;
|
||||
id | hash | created_at
|
||||
----+------------------------------------------------------------------+---------------
|
||||
1 | 8961353bf66f9b3fe1a715f6ea9d9ef2bc65697bb8a5c2569df939a61e72a318 | 1730219291288
|
||||
2 | b75e61451e2ce37d831608b1bc9231bf3af09e0ab54bf169be117de9d4ff6805 | 1730224013018
|
||||
(2 rows)
|
||||
```
|
||||
|
||||
Notice that Drizzle stores each migration record as [a SHA256 hash of the
|
||||
migration
|
||||
file](https://github.com/drizzle-team/drizzle-orm/blob/526996bd2ea20d5b1a0d65e743b47e23329d441c/drizzle-orm/src/migrator.ts#L52)
|
||||
and a timestamp of when the migration was run.
|
||||
|
||||
[source](https://orm.drizzle.team/docs/drizzle-kit-migrate#applied-migrations-log-in-the-database)
|
||||
56
drizzle/get-fields-for-inserted-row.md
Normal file
56
drizzle/get-fields-for-inserted-row.md
Normal 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 })
|
||||
```
|
||||
28
git/check-how-a-file-is-being-ignored.md
Normal file
28
git/check-how-a-file-is-being-ignored.md
Normal 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.
|
||||
27
git/count-all-files-of-specific-type-tracked-by-git.md
Normal file
27
git/count-all-files-of-specific-type-tracked-by-git.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# Count All Files Of Specific Type Tracked By Git
|
||||
|
||||
I want to get a count of all the markdown files in my [TIL
|
||||
repo](https://github.com/jbranchaud/til). Since all the files I care about are
|
||||
tracked by `git`, I can use `git ls-files` to get a listing of all files. That
|
||||
command on its own lists all files tracked by your git repository. Though there
|
||||
are many other flags we can apply, that will do for my purposes.
|
||||
|
||||
By giving `git ls-files` a pattern to match against, I can turn up just, for
|
||||
instance, markdown files (`*.md`). I can pipe that to `wc -l` to get a count
|
||||
rather than exploding my terminal with a list of file names.
|
||||
|
||||
```bash
|
||||
❯ git ls-files '*.md' | wc -l
|
||||
1503
|
||||
```
|
||||
|
||||
That command includes `README.md` and `CONTRIBUTING.md`, but really I only want
|
||||
to count the markdown files that constitute a TIL. Those all happen to be
|
||||
nested under a single directory. So I can tweak the glob pattern like so:
|
||||
|
||||
```bash
|
||||
❯ git ls-files '*/*.md' | wc -l
|
||||
1501
|
||||
```
|
||||
|
||||
See `man git-ls-files` for more details.
|
||||
33
git/override-the-global-git-ignore-file.md
Normal file
33
git/override-the-global-git-ignore-file.md
Normal 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)
|
||||
@@ -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)
|
||||
|
||||
56
go/do-something-n-times.md
Normal file
56
go/do-something-n-times.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# Do Something N Times
|
||||
|
||||
With Go 1.23 there is a new for-range syntax that makes looping a bit easier
|
||||
and more compact.
|
||||
|
||||
Instead of needing to set up our 3-part for-loop syntax, we can say we want to
|
||||
do something `N` times with `for range N`.
|
||||
|
||||
```go
|
||||
for range n {
|
||||
// do something
|
||||
}
|
||||
```
|
||||
|
||||
Let's look at an actual, runnable example:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
import "math/rand"
|
||||
import "time"
|
||||
|
||||
func main() {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
|
||||
food := []string{"taco", "burrito", "torta", "enchilada", "tostada"}
|
||||
|
||||
for range 5 {
|
||||
randomIndex := rand.Intn(len(food))
|
||||
fmt.Println(food[randomIndex])
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The output is random and might look something like this:
|
||||
|
||||
```bash
|
||||
$ go run loop.go
|
||||
taco
|
||||
burrito
|
||||
tostada
|
||||
taco
|
||||
enchilada
|
||||
```
|
||||
|
||||
I appreciate this syntax addition because it feels very akin to Ruby's `#times`
|
||||
method:
|
||||
|
||||
```ruby
|
||||
5.times do
|
||||
# do something
|
||||
end
|
||||
```
|
||||
|
||||
[source](https://eli.thegreenplace.net/2024/ranging-over-functions-in-go-123/)
|
||||
26
go/find-executables-installed-by-go.md
Normal file
26
go/find-executables-installed-by-go.md
Normal 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
|
||||
```
|
||||
29
heroku/connect-to-a-database-by-color.md
Normal file
29
heroku/connect-to-a-database-by-color.md
Normal 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)
|
||||
17
heroku/open-dashboard-for-specific-add-on.md
Normal file
17
heroku/open-dashboard-for-specific-add-on.md
Normal 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.
|
||||
40
internet/add-styled-alerts-to-github-markdown-documents.md
Normal file
40
internet/add-styled-alerts-to-github-markdown-documents.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# Add Styled Alerts To GitHub Markdown Documents
|
||||
|
||||
The GFM (GitHub Flavored Markdown) variant of markdown adds some nice features
|
||||
to our GitHub-rendered markdown documents.
|
||||
|
||||
One such feature that has been around for a couple years, but which I only just
|
||||
learned about, are these styled alerts. There are five of them each with a
|
||||
different color and icon to help convey meaning.
|
||||
|
||||
```
|
||||
> [!NOTE]
|
||||
> Useful information that users should know, even when skimming content.
|
||||
|
||||
> [!TIP]
|
||||
> Helpful advice for doing things better or more easily.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Key information users need to know to achieve their goal.
|
||||
|
||||
> [!WARNING]
|
||||
> Urgent info that needs immediate user attention to avoid problems.
|
||||
|
||||
> [!CAUTION]
|
||||
> Advises about risks or negative outcomes of certain actions.
|
||||
```
|
||||
|
||||
I just added the following to the top of one of my project's READMEs to help me
|
||||
remember that it is not under active development.
|
||||
|
||||
```
|
||||
> [!WARNING]
|
||||
> This repo is not under active development, you might be looking for
|
||||
> [til-visualmode-dev](https://github.com/jbranchaud/til-visualmode-dev).
|
||||
```
|
||||
|
||||
Visit the GitHub docs for
|
||||
[Alerts](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#alerts)
|
||||
to see examples of how these render.
|
||||
|
||||
[source](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#alerts)
|
||||
21
internet/analyze-your-website-performance.md
Normal file
21
internet/analyze-your-website-performance.md
Normal 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.
|
||||
29
internet/digraph-unicode-characters-have-a-titlecase.md
Normal file
29
internet/digraph-unicode-characters-have-a-titlecase.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# Digraph Unicode Characters Have a Titlecase
|
||||
|
||||
Coming from primarily being exposed to the US American alphabet, I'm familiar
|
||||
with characters that I type into the computer having one of two cases. Either
|
||||
it is lowercase by default (`c`) or I can hit the shift key to produce the
|
||||
uppercase version (`C`).
|
||||
|
||||
Unicode, which has broad support for character encoding across most languages,
|
||||
has a couple characters that are called _digraphs_. These are single code
|
||||
points, but look like they are made up of two characters.
|
||||
|
||||
A good example of this is `dž`. And if that character were to appear in an all
|
||||
uppercase word, then it would display as `DŽ`.
|
||||
|
||||
But what if it appears at the beginning of a capitalized word?
|
||||
|
||||
That's where _titlecase_ comes into the picture -- `Dž`.
|
||||
|
||||
From [wikipedia](https://en.wikipedia.org/wiki/D%C5%BE):
|
||||
|
||||
> Note that when the letter is the initial of a capitalised word (like Džungla
|
||||
> or Džemper, or personal names like Džemal or Džamonja), the ž is not
|
||||
> uppercase. Only when the whole word is written in uppercase, is the Ž
|
||||
> capitalised.
|
||||
|
||||
(I find it odd that wikipedia's article on this digraph code point is using
|
||||
separate characters instead of the digraph.)
|
||||
|
||||
[source](https://devblogs.microsoft.com/oldnewthing/20241031-00/?p=110443)
|
||||
14
internet/exclude-ai-overview-from-google-search.md
Normal file
14
internet/exclude-ai-overview-from-google-search.md
Normal 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)
|
||||
55
java/ensure-resources-always-get-closed.md
Normal file
55
java/ensure-resources-always-get-closed.md
Normal 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)
|
||||
47
java/install-java-on-mac-with-brew.md
Normal file
47
java/install-java-on-mac-with-brew.md
Normal 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 couldn’t 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)
|
||||
27
java/run-a-hello-world-program-in-eclipse.md
Normal file
27
java/run-a-hello-world-program-in-eclipse.md
Normal 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.
|
||||
28
javascript/check-media-queries-from-javascript.md
Normal file
28
javascript/check-media-queries-from-javascript.md
Normal 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.
|
||||
61
javascript/make-truly-deep-clone-with-structured-clone.md
Normal file
61
javascript/make-truly-deep-clone-with-structured-clone.md
Normal 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)
|
||||
55
javascript/prevent-hidden-element-from-flickering-on-load.md
Normal file
55
javascript/prevent-hidden-element-from-flickering-on-load.md
Normal file
@@ -0,0 +1,55 @@
|
||||
# Prevent Hidden Element From Flickering On Load
|
||||
|
||||
Here is what it might look like to use [Alpine.js](https://alpinejs.dev/) to
|
||||
sprinkle in some JavaScript for controlling a dropdown menu.
|
||||
|
||||
```html
|
||||
<div x-data="{ profileDropdownOpen: false }">
|
||||
<button
|
||||
type="button"
|
||||
@click="profileDropdownOpen = !profileDropdownOpen"
|
||||
>
|
||||
<!-- some inner html -->
|
||||
</button>
|
||||
<div x-show="profileDropdownOpen" role="menu">
|
||||
<a href="/profile" role="menuitem">Your Profile</a>
|
||||
<a href="/sign-out" role="menuitem">Sign Out</a>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
Functionally that will work. You can click the button to toggle the menu open
|
||||
and closed.
|
||||
|
||||
What you might notice, however, when you refresh the page is that the menu
|
||||
flickers open as the page first loads and then disappears. This is a quirk of
|
||||
the element being rendered before Alpine.js is loaded and the
|
||||
[`x-show`](https://alpinejs.dev/directives/show) directive has a chance to take
|
||||
effect.
|
||||
|
||||
To get around this, we can _cloak_ any element with an `x-show` directive that
|
||||
should be hidden by default.
|
||||
|
||||
```html
|
||||
<div x-data="{ profileDropdownOpen: false }">
|
||||
<button
|
||||
type="button"
|
||||
@click="profileDropdownOpen = !profileDropdownOpen"
|
||||
>
|
||||
<!-- some inner html -->
|
||||
</button>
|
||||
<div x-cloak x-show="profileDropdownOpen" role="menu">
|
||||
<a href="/profile" role="menuitem">Your Profile</a>
|
||||
<a href="/sign-out" role="menuitem">Sign Out</a>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
This addition needs to be paired with some custom CSS to hide any _cloaked_
|
||||
elements.
|
||||
|
||||
```css
|
||||
[x-cloak] { display: none !important; }
|
||||
```
|
||||
|
||||
[source](https://alpinejs.dev/directives/cloak)
|
||||
44
mac/check-network-quality-stats-from-the-command-line.md
Normal file
44
mac/check-network-quality-stats-from-the-command-line.md
Normal 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/)
|
||||
24
mac/keyboard-shortcuts-for-interacting-with-text-areas.md
Normal file
24
mac/keyboard-shortcuts-for-interacting-with-text-areas.md
Normal 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).
|
||||
36
nextjs/avoid-conflicting-files.md
Normal file
36
nextjs/avoid-conflicting-files.md
Normal 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.
|
||||
25
postgres/add-unique-constraint-using-existing-index.md
Normal file
25
postgres/add-unique-constraint-using-existing-index.md
Normal 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)
|
||||
@@ -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.)
|
||||
|
||||
53
postgres/concatenate-strings-with-a-separator.md
Normal file
53
postgres/concatenate-strings-with-a-separator.md
Normal 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 |
|
||||
+---------------+
|
||||
```
|
||||
35
postgres/enforce-uniqueness-on-column-expression.md
Normal file
35
postgres/enforce-uniqueness-on-column-expression.md
Normal 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.
|
||||
43
postgres/generate-modern-primary-key-columns.md
Normal file
43
postgres/generate-modern-primary-key-columns.md
Normal 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).
|
||||
56
postgres/generate-random-alphanumeric-identifier.md
Normal file
56
postgres/generate-random-alphanumeric-identifier.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# Generate Random Alphanumeric Identifier
|
||||
|
||||
Here is a PostgreSQL query that uses
|
||||
[`pgcrypto`](https://www.postgresql.org/docs/current/pgcrypto.html) (for
|
||||
[`get_random_bytes`](https://www.postgresql.org/docs/current/pgcrypto.html#PGCRYPTO-RANDOM-DATA-FUNCS))
|
||||
and a CTE to generate a cryptographically-random 8-character alphanumeric
|
||||
identifier.
|
||||
|
||||
```sql
|
||||
-- First ensure pgcrypto is installed
|
||||
create extension if not exists pgcrypto;
|
||||
|
||||
-- Generates a single 8-character identifier
|
||||
with chars as (
|
||||
-- excludes some look-alike characters
|
||||
select '23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz' as charset
|
||||
),
|
||||
random_bytes as (
|
||||
select gen_random_bytes(8) as bytes
|
||||
),
|
||||
positions as (
|
||||
select generate_series(0, 7) as pos
|
||||
)
|
||||
select string_agg(
|
||||
substr(
|
||||
charset,
|
||||
(get_byte(bytes, pos) % length(charset)) + 1,
|
||||
1
|
||||
),
|
||||
'' order by pos
|
||||
) as short_id
|
||||
from positions, random_bytes, chars;
|
||||
```
|
||||
|
||||
Here is an example of the output:
|
||||
|
||||
```sql
|
||||
+----------+
|
||||
| short_id |
|
||||
|----------|
|
||||
| NXdu9AnV |
|
||||
+----------+
|
||||
```
|
||||
|
||||
The
|
||||
[`generate_series`](https://www.postgresql.org/docs/current/functions-srf.html)
|
||||
gives us an 8-row table from 0 to 7 that we can use as indexes into the byte
|
||||
positions of the value we get from `gen_random_bytes`. Those random bytes get
|
||||
mapped to individual alphanumeric characters from `chars`. That then gets
|
||||
squeezed together with `string_agg`.
|
||||
|
||||
Note: the character set excludes some characters that can be mistaken for one
|
||||
another like `0` and `O` or `1` and `l`.
|
||||
|
||||
Note: you could change the right-bound of the `generate_series` to generate a
|
||||
different length identifier.
|
||||
30
postgres/postgres-does-not-support-unsigned-integers.md
Normal file
30
postgres/postgres-does-not-support-unsigned-integers.md
Normal 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.
|
||||
46
postgres/put-unique-constraint-on-generated-column.md
Normal file
46
postgres/put-unique-constraint-on-generated-column.md
Normal 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.
|
||||
80
postgres/table-names-are-treated-as-lower-case-by-default.md
Normal file
80
postgres/table-names-are-treated-as-lower-case-by-default.md
Normal file
@@ -0,0 +1,80 @@
|
||||
# Table Names Are Treated As Lower-Case By Default
|
||||
|
||||
This one is a bit unintuitive and can cause some real confusion -- when you
|
||||
create a table in PostgreSQL, any casing is ignored, it is treated as
|
||||
lower-case. Let's see it to believe it:
|
||||
|
||||
```sql
|
||||
> create table BookMarks (
|
||||
id integer generated always as identity primary key,
|
||||
location text not null
|
||||
);
|
||||
|
||||
> \d
|
||||
+--------+--------------------+----------+----------+
|
||||
| Schema | Name | Type | Owner |
|
||||
|--------+--------------------+----------+----------|
|
||||
| public | bookmarks | table | postgres |
|
||||
| public | bookmarks_id_seq | sequence | postgres |
|
||||
+--------+--------------------+----------+----------+
|
||||
```
|
||||
|
||||
Notice that when we list our tables, the uppercase `M` and `B` are gone. That's
|
||||
because Postgres folds away the casing when processing the table name
|
||||
identifier.
|
||||
|
||||
It doesn't matter how we refer to it for queries:
|
||||
|
||||
```sql
|
||||
> select * from BookMarks;
|
||||
+----+----------+
|
||||
| id | location |
|
||||
|----+----------|
|
||||
+----+----------+
|
||||
|
||||
> select * from bookmarks;
|
||||
+----+----------+
|
||||
| id | location |
|
||||
|----+----------|
|
||||
+----+----------+
|
||||
```
|
||||
|
||||
You can force Postgres to respect the casing by wrapping the table name in
|
||||
quotes.
|
||||
|
||||
```sql
|
||||
> create table "BookMarks" (
|
||||
id integer generated always as identity primary key,
|
||||
location text not null
|
||||
);
|
||||
|
||||
> \d
|
||||
+--------+--------------------+----------+----------+
|
||||
| Schema | Name | Type | Owner |
|
||||
|--------+--------------------+----------+----------|
|
||||
| public | BookMarks | table | postgres |
|
||||
| public | BookMarks_id_seq | sequence | postgres |
|
||||
+--------+--------------------+----------+----------+
|
||||
|
||||
> select * from "BookMarks";
|
||||
+----+----------+
|
||||
| id | location |
|
||||
|----+----------|
|
||||
+----+----------+
|
||||
|
||||
> select * from "bookmarks";
|
||||
relation "bookmarks" does not exist
|
||||
LINE 1: select * from "bookmarks"
|
||||
^
|
||||
|
||||
> select * from BookMarks;
|
||||
relation "bookmarks" does not exist
|
||||
LINE 1: select * from BookMarks
|
||||
^
|
||||
```
|
||||
|
||||
That then means you have to quote your table name anytime you want to refer to
|
||||
it in a query. It's not worth it. It is better to always keep your table names
|
||||
lower-case using snake case.
|
||||
|
||||
[source](https://weiyen.net/articles/avoid-capital-letters-in-postgres-names)
|
||||
22
python/dunder-methods.md
Normal file
22
python/dunder-methods.md
Normal 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.
|
||||
86
python/override-the-boolean-context-of-a-class.md
Normal file
86
python/override-the-boolean-context-of-a-class.md
Normal 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
|
||||
```
|
||||
49
python/store-and-access-immutable-data-in-a-tuple.md
Normal file
49
python/store-and-access-immutable-data-in-a-tuple.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# Store And Access Immutable Data In A Tuple
|
||||
|
||||
You can store heterogeneous data (of varying types) as a _tuple_ which is a
|
||||
light-weight immutable data structure.
|
||||
|
||||
You can be explicit about the tuple by wrapping the items in parentheses:
|
||||
|
||||
```python
|
||||
>>> book = ('An Immense World', 'Ed Yong', 2022)
|
||||
```
|
||||
|
||||
Though it is also possible to comma-separate the items and forego the
|
||||
parentheses.
|
||||
|
||||
```python
|
||||
>>> book2 = 'The Shining', 'Stephen King', 1977
|
||||
>>> book2
|
||||
('The Shining', 'Stephen King', 1977)
|
||||
```
|
||||
|
||||
Once we have our tuple, we can access any item from it positionally. We can
|
||||
also use _sequence unpacking_ to assign the values to a series of variables:
|
||||
|
||||
```python
|
||||
>>> book[0]
|
||||
'An Immense World'
|
||||
>>> book[1]
|
||||
'Ed Yong'
|
||||
>>> book[2]
|
||||
2022
|
||||
>>> title, author, publication_year = book
|
||||
>>> title
|
||||
'An Immense World'
|
||||
>>> author
|
||||
'Ed Yong'
|
||||
>>> publication_year
|
||||
2022
|
||||
```
|
||||
|
||||
And, as promised, it is immutable (unlike lists):
|
||||
|
||||
```python
|
||||
>>> book[1] = 'Agatha Christie'
|
||||
Traceback (most recent call last):
|
||||
File "<stdin>", line 1, in <module>
|
||||
TypeError: 'tuple' object does not support item assignment
|
||||
```
|
||||
|
||||
[source](https://docs.python.org/3/tutorial/datastructures.html#tuples-and-sequences)
|
||||
30
rails/create-table-with-bigint-id-as-primary-key.md
Normal file
30
rails/create-table-with-bigint-id-as-primary-key.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# Create Table With bigint ID As Primary Key
|
||||
|
||||
When creating a new table with an ActiveRecord migration, we specify all the
|
||||
fields _except_ the `id`. The `id`, which is the primary key, is implicit. We
|
||||
get it by default.
|
||||
|
||||
The type of that `id` defaults to `int` which is a 32-bit signed integer.
|
||||
|
||||
We can override the type of `id` in a variety of ways. The one I prefer in most
|
||||
cases is to make the `id` of type `bigint`. This is a 64-bit signed integer. It
|
||||
offers quite a bit more headroom for the number of unique identifies in our
|
||||
table.
|
||||
|
||||
This can be specified by including `id: :bigint` as an option to the
|
||||
`create_table` method.
|
||||
|
||||
```ruby
|
||||
class CreatePosts < ActiveRecord::Migration[8.0]
|
||||
def change
|
||||
create_table :posts, id: :bigint do |t|
|
||||
t.string :title, null: false
|
||||
t.string :body, null: false
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
[source](https://api.rubyonrails.org/v7.1/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html#method-i-create_table)
|
||||
37
rails/define-the-root-path-for-the-app.md
Normal file
37
rails/define-the-root-path-for-the-app.md
Normal 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)
|
||||
49
rails/empty-find-by-returns-first-record.md
Normal file
49
rails/empty-find-by-returns-first-record.md
Normal 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
26
rails/generate-a-model.md
Normal 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!
|
||||
27
rails/generate-a-rails-app-from-the-main-branch.md
Normal file
27
rails/generate-a-rails-app-from-the-main-branch.md
Normal 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)
|
||||
63
rails/prefer-select-all-over-execute-for-read-queries.md
Normal file
63
rails/prefer-select-all-over-execute-for-read-queries.md
Normal file
@@ -0,0 +1,63 @@
|
||||
# Prefer select_all Over execute For Read Queries
|
||||
|
||||
Though the `#execute` function provided by ActiveRecord technically works as a
|
||||
general-purpose query runner for strings of raw SQL, it has some downsides.
|
||||
|
||||
First, let's say we have a large semi-complex (better in SQL than ActiveRecord
|
||||
DSL) SQL query defined in a heredoc.
|
||||
|
||||
```ruby
|
||||
books_by_status_query = <<-SQL
|
||||
select
|
||||
books.*,
|
||||
latest_statuses.status as current_status,
|
||||
array_to_json(array_agg(...)) as reading_statuses
|
||||
from books
|
||||
-- plus several left joins
|
||||
-- where clause, group by, and order by
|
||||
SQL
|
||||
```
|
||||
|
||||
I reflexively reach for
|
||||
[`#execute`](https://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/DatabaseStatements.html#method-i-execute)
|
||||
in a situation like that:
|
||||
|
||||
```ruby
|
||||
result = ActiveRecord::Base.connection.execute(books_by_status_query)
|
||||
```
|
||||
|
||||
However, if we're doing a read-only query and we are expecting multiple rows in
|
||||
the result, then we are better off reaching for
|
||||
[`#select_all`](https://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/DatabaseStatements.html#method-i-select_all).
|
||||
|
||||
```ruby
|
||||
result = ActiveRecord::Base.connection.select_all(books_by_status_query)
|
||||
```
|
||||
|
||||
It has the advantage of semantically communicating that it's just a read and
|
||||
won't have any side-effects. It also avoids an unnecessary clear of the query
|
||||
cache.
|
||||
|
||||
> Note: [when execute is used] the query is assumed to have side effects and
|
||||
> the query cache will be cleared. If the query is read-only, consider using
|
||||
> select_all instead.
|
||||
|
||||
The `#execute` method also has been known to leak memory with some database
|
||||
connectors.
|
||||
|
||||
> Note: depending on your database connector, the result returned by this
|
||||
> method may be manually memory managed. Consider using exec_query wrapper
|
||||
> instead.
|
||||
|
||||
We can then iterate through and transform the results just as we would have
|
||||
done with `#execute`.
|
||||
|
||||
```ruby
|
||||
result.map do |row|
|
||||
row.tap do |hash|
|
||||
hash["reading_statuses"] = JSON.parse(hash["reading_statuses"])
|
||||
end
|
||||
|
||||
OpenStruct.new(row)
|
||||
end
|
||||
```
|
||||
25
rails/rollback-a-couple-migrations.md
Normal file
25
rails/rollback-a-couple-migrations.md
Normal 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
|
||||
```
|
||||
45
rails/set-datetime-to-include-time-zone-in-migrations.md
Normal file
45
rails/set-datetime-to-include-time-zone-in-migrations.md
Normal 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.
|
||||
52
rails/set-default-as-sql-function-in-migration.md
Normal file
52
rails/set-default-as-sql-function-in-migration.md
Normal 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)
|
||||
33
rails/temporarily-turn-off-pending-migrations-error.md
Normal file
33
rails/temporarily-turn-off-pending-migrations-error.md
Normal 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)
|
||||
32
rails/validate-column-data-with-check-constraints.md
Normal file
32
rails/validate-column-data-with-check-constraints.md
Normal 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.
|
||||
32
rspec/avoid-accidentally-disabling-pry.md
Normal file
32
rspec/avoid-accidentally-disabling-pry.md
Normal 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`.
|
||||
33
rspec/configure-tests-to-run-in-random-order.md
Normal file
33
rspec/configure-tests-to-run-in-random-order.md
Normal 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
|
||||
```
|
||||
38
ruby/execute-several-commands-with-backtick-heredoc.md
Normal file
38
ruby/execute-several-commands-with-backtick-heredoc.md
Normal 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)
|
||||
45
ruby/forward-all-arguments-to-another-method.md
Normal file
45
ruby/forward-all-arguments-to-another-method.md
Normal 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)
|
||||
46
ruby/multi-line-comments.md
Normal file
46
ruby/multi-line-comments.md
Normal 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)
|
||||
42
ruby/output-bytecode-for-a-ruby-program.md
Normal file
42
ruby/output-bytecode-for-a-ruby-program.md
Normal 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)
|
||||
45
ruby/prevent-erb-lint-from-removing-opening-tags.md
Normal file
45
ruby/prevent-erb-lint-from-removing-opening-tags.md
Normal 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)
|
||||
44
ruby/skip-the-front-of-an-array-with-drop.md
Normal file
44
ruby/skip-the-front-of-an-array-with-drop.md
Normal 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.
|
||||
37
tmux/open-new-splits-to-the-current-directory.md
Normal file
37
tmux/open-new-splits-to-the-current-directory.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# Open New Splits To The Current Directory
|
||||
|
||||
I typically work in one project per tmux session. When I create a given tmux
|
||||
session, the default directory is for that project. All new windows and pane
|
||||
splits will open at that default directory. This generally is the default
|
||||
behavior I want.
|
||||
|
||||
One caveat: I often open a new window within an existing session that I want
|
||||
anchored to another directory. This could be because I'm working in a monorepo
|
||||
and I need to work from a subdirectory for a specific package or app. Or it
|
||||
could be that I'm temporarily digging into another project and it isn't worth
|
||||
create a whole new session.
|
||||
|
||||
Regardless of the reason, I run into a bit of friction with tmux's defaults.
|
||||
|
||||
First I open the new window and `cd` to another project. After some working, I
|
||||
need to open a split pane, maybe to run a project command like a build or dev
|
||||
server. Hitting `prefix-"` (horizontal split) or `prefix-%` (vertical split)
|
||||
opens a pane with the shell defaulting back to the original directory, not the
|
||||
current directory.
|
||||
|
||||
The trick to fixing this bit of friction is overriding the directory of pane
|
||||
splits. I can do that by adding the following to my `~/.tmux.conf`:
|
||||
|
||||
```
|
||||
# Pane splits should open to the same path as the current pane
|
||||
bind '"' split-window -v -c "#{pane_current_path}"
|
||||
bind % split-window -h -c "#{pane_current_path}"
|
||||
```
|
||||
|
||||
Make sure to run `tmux source-file ~/.tmux.conf` to apply these config changes.
|
||||
|
||||
The `pane_current_path` is called a "Format" in tmux parlance. It resolves to
|
||||
the absolute path of the current pane's current directory. You can find all the
|
||||
formats in the manpage with this command: `man tmux | less +'/^FORMATS'`. You
|
||||
can also show yourself that this format resolves to what you expect by running
|
||||
`tmux display-message -p '#{pane_current_path}'`.
|
||||
35
typescript/ignore-all-errors-in-a-typescript-file.md
Normal file
35
typescript/ignore-all-errors-in-a-typescript-file.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# Ignore All Errors In A TypeScript File
|
||||
|
||||
As of TypeScript 3.7, we can mark an entire TypeScript file to be ignored by
|
||||
the TypeScript compiler when it is doing static type checking.
|
||||
|
||||
We can do this by adding the `@ts-nocheck` directive at the top of the file:
|
||||
|
||||
```typescript
|
||||
// @ts-nocheck
|
||||
|
||||
type User = {
|
||||
id: string;
|
||||
name: string;
|
||||
email: string;
|
||||
}
|
||||
|
||||
const user: User = {
|
||||
id: 123,
|
||||
name: "Liz Lemon",
|
||||
email: "liz.lemon@nbc.com",
|
||||
};
|
||||
```
|
||||
|
||||
Notice that `id` is typed as a `string`, but we are using a `number` with `id`
|
||||
for `user`. That is a type error. But with the `@ts-nocheck` directive at the
|
||||
top, the type checker doesn't run on the file and we see no type errors.
|
||||
|
||||
I'd generally suggest to avoid doing this. It can hide real type errors that
|
||||
you should be addressing. That said, in special circumstances, you may need it,
|
||||
even if just temporarily, like if an imported package doesn't have types. Here
|
||||
is an example of that in [uploadthing's
|
||||
`rehype.js`](https://github.com/pingdotgg/uploadthing/blob/d98fbefedddf64d183cc5a00b3fd707e8d8f2f6c/docs/src/mdx/rehype.js#L1)
|
||||
which is missing types from `mdx-annotations`.
|
||||
|
||||
[source](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#-ts-nocheck-in-typescript-files)
|
||||
20
unix/convert-jpeg-to-png-with-ffmpeg.md
Normal file
20
unix/convert-jpeg-to-png-with-ffmpeg.md
Normal 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.
|
||||
28
unix/convert-svg-to-favicon.md
Normal file
28
unix/convert-svg-to-favicon.md
Normal 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)
|
||||
23
unix/download-a-file-with-curl.md
Normal file
23
unix/download-a-file-with-curl.md
Normal 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)
|
||||
37
unix/find-any-dotfiles-that-modify-path-env-var.md
Normal file
37
unix/find-any-dotfiles-that-modify-path-env-var.md
Normal 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.
|
||||
34
unix/get-the-sha256-hash-for-a-file.md
Normal file
34
unix/get-the-sha256-hash-for-a-file.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# Get The SHA256 Hash For A File
|
||||
|
||||
Unix systems come with a `sha256sum` utility that we can use to compute the
|
||||
SHA256 hash of a file. This means the contents of file are compressed into a
|
||||
256-bit digest.
|
||||
|
||||
Here I use it on a SQL migration file that I've generated.
|
||||
|
||||
```bash
|
||||
$ sha256sum migrations/0001_large_doctor_spectrum.sql
|
||||
b75e61451e2ce37d831608b1bc9231bf3af09e0ab54bf169be117de9d4ff6805 migrations/0001_large_doctor_spectrum.sql
|
||||
```
|
||||
|
||||
Each file passed to this utility gets output to a separate line which is why we
|
||||
see the filename next to the hash. Since I am only running it on a single file
|
||||
and I may want to pipe the output to some other program, I can clip off just
|
||||
the part I need.
|
||||
|
||||
```bash
|
||||
sha256sum migrations/0001_large_doctor_spectrum.sql | cut -d ' ' -f 1
|
||||
b75e61451e2ce37d831608b1bc9231bf3af09e0ab54bf169be117de9d4ff6805
|
||||
```
|
||||
|
||||
We can also produce these digests with `openssl`:
|
||||
|
||||
```bash
|
||||
$ openssl dgst -sha256 migrations/0001_large_doctor_spectrum.sql
|
||||
SHA2-256(migrations/0001_large_doctor_spectrum.sql)= b75e61451e2ce37d831608b1bc9231bf3af09e0ab54bf169be117de9d4ff6805
|
||||
|
||||
$ openssl dgst -sha256 migrations/0001_large_doctor_spectrum.sql | cut -d ' ' -f 2
|
||||
b75e61451e2ce37d831608b1bc9231bf3af09e0ab54bf169be117de9d4ff6805
|
||||
```
|
||||
|
||||
See `sha256sum --help` or `openssl dgst --help` for more details.
|
||||
29
unix/gracefully-exit-a-script-with-trap.md
Normal file
29
unix/gracefully-exit-a-script-with-trap.md
Normal 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)
|
||||
32
unix/make-direnv-less-noisy.md
Normal file
32
unix/make-direnv-less-noisy.md
Normal 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)
|
||||
20
unix/undo-changes-made-to-current-terminal-prompt.md
Normal file
20
unix/undo-changes-made-to-current-terminal-prompt.md
Normal file
@@ -0,0 +1,20 @@
|
||||
# Undo Change Made to Current Terminal Prompt
|
||||
|
||||
I frequently use a variety of ASCII command characters like `ctrl-u` to delete
|
||||
the entire line or `ctrl-a` to jump to the front of a long line so I can make
|
||||
some edits toward that side of the command or `ctrl-e` to jump to the end of
|
||||
the command for the same reason. I sometimes even use `ctrl-k` to delete
|
||||
everything after the cursor to the end of the line.
|
||||
|
||||
What I didn't realize until now is that any of those commands the modify the
|
||||
current line of the termianl prompt plus regular typing and hitting the
|
||||
backspace are all _undoable_.
|
||||
|
||||
So, if I just wiped out half the line (with `ctrl-k`) and I immediately regret
|
||||
it, I can restore it with `ctrl-_`. The system keeps of history of the actions
|
||||
you've taken, so you can keep hitting `ctrl-_` to undo even further.
|
||||
|
||||
The `ctrl-/` command does the same, per GNU's [Undo Changes in the Emacs
|
||||
docs](https://www.gnu.org/software/emacs/manual/html_node/emacs/Basic-Undo.html).
|
||||
|
||||
[source](https://jvns.ca/ascii)
|
||||
17
vim/switch-moving-end-of-visual-selection.md
Normal file
17
vim/switch-moving-end-of-visual-selection.md
Normal 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.
|
||||
18
vscode/jump-to-problems-in-the-current-file.md
Normal file
18
vscode/jump-to-problems-in-the-current-file.md
Normal 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.
|
||||
19
workflow/add-hotkeys-for-specific-raycast-extensions.md
Normal file
19
workflow/add-hotkeys-for-specific-raycast-extensions.md
Normal 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.
|
||||
64
workflow/add-subscriber-to-kit-form-via-api.md
Normal file
64
workflow/add-subscriber-to-kit-form-via-api.md
Normal 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.
|
||||
23
workflow/configure-email-redirect-with-cloudflare.md
Normal file
23
workflow/configure-email-redirect-with-cloudflare.md
Normal 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)
|
||||
27
workflow/get-url-for-github-user-profile-photo.md
Normal file
27
workflow/get-url-for-github-user-profile-photo.md
Normal 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)
|
||||
@@ -13,6 +13,6 @@ $ yarn global add fkill-cli
|
||||
|
||||
Then run it with no arguments to trigger the interactive mode.
|
||||
|
||||

|
||||

|
||||
|
||||
gif credit: [`fkill-cli` repo](https://github.com/sindresorhus/fkill-cli)
|
||||
|
||||
64
workflow/send-a-message-to-a-discord-channel.md
Normal file
64
workflow/send-a-message-to-a-discord-channel.md
Normal 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.
|
||||
25
zsh/add-to-the-path-via-path-array.md
Normal file
25
zsh/add-to-the-path-via-path-array.md
Normal 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)
|
||||
38
zsh/link-a-scalar-to-an-array.md
Normal file
38
zsh/link-a-scalar-to-an-array.md
Normal 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)
|
||||
37
zsh/use-a-space-to-exclude-command-from-history.md
Normal file
37
zsh/use-a-space-to-exclude-command-from-history.md
Normal 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)
|
||||
Reference in New Issue
Block a user