1
0
mirror of https://github.com/jbranchaud/til synced 2026-01-15 13:08:02 +00:00

Compare commits

...

59 Commits

Author SHA1 Message Date
Karim Bouchez
2a3d1448c4 Merge 15337dfd71 into 86972f41cc 2024-11-20 18:48:51 +00:00
jbranchaud
86972f41cc Add Set Default As SQL Function In Migration as a Rails TIL 2024-11-20 10:10:04 -06:00
jbranchaud
93a663cc9c Adjust formatting, add source link to pg TIL 2024-11-20 09:32:45 -06:00
jbranchaud
0c1dd29d8d Add Output Bytecode For A Ruby Program as a Ruby TIL 2024-11-18 11:22:35 -06:00
jbranchaud
b492a9d765 Add Override The Boolean Context Of A Class as a Python TIL 2024-11-17 09:44:55 -06:00
jbranchaud
543a82730d Add Forward All Arguments To Another Method as a Ruby TIL 2024-11-16 12:32:21 -06:00
jbranchaud
877537228f Add Gracefully Exit A Script With Trap as a Unix TIL 2024-11-15 19:55:22 -06:00
jbranchaud
1513611857 Add Execute Several Commands With Backtick Heredoc as a Ruby TIL 2024-11-14 23:47:07 -06:00
jbranchaud
74514b462d Add a caveat to the latest TIL 2024-11-13 20:31:25 -06:00
jbranchaud
484dec8e24 Add Empty find_by Returns First Record as a Rails TIL 2024-11-13 20:24:59 -06:00
jbranchaud
8574113dc6 Add Dunder Methods as a Python TIL 2024-11-12 17:45:53 -06:00
jbranchaud
1c4e37ed8a Add Count All Files Of Specific Type Tracked By Git as a Git TIL 2024-11-11 20:15:43 -06:00
jbranchaud
581aa1decb Add Add Line Numbers To A Code Block With Counter as a CSS TIL 2024-11-10 12:43:41 -06:00
jbranchaud
0c4795c1d2 Change codeblock language to ruby in latest TIL 2024-11-09 17:48:32 -06:00
jbranchaud
9bbde247a5 Add Create Table With bigint Id As Primary Key as a Rails TIL 2024-11-09 17:28:47 -06:00
jbranchaud
36ca71bfb1 Add Store And Access Immutable Data In A Tuple as a Python TIL 2024-11-08 09:16:45 -06:00
jbranchaud
8a682e3a89 Add Open New Splits To The Current Directory as a tmux TIL 2024-11-07 09:43:46 -06:00
jbranchaud
c7a38c8267 Add Ignore All Errors In A TypeScript File as a TypeScript TIL 2024-11-06 10:18:44 -06:00
jbranchaud
71d3e56b3d Add Styled Alerts To GitHub Markdown Documents as an Internet TIL 2024-11-05 12:44:14 -06:00
jbranchaud
af3974d3fe Add Do Something N Times as a Go TIL 2024-11-05 11:53:23 -06:00
jbranchaud
adc6b2e903 Add Digraph Unicode Characters Have A Titlecase as a Internet TIL 2024-11-04 08:18:52 -06:00
jbranchaud
9a6ebd4c6b Add Table Names Are Treated As Lower-Case By Default as a Postgres TIL 2024-11-03 23:19:17 -06:00
jbranchaud
6df0693804 Add a few more notes to the latest TIL 2024-11-02 13:47:16 -05:00
jbranchaud
507602ef0c Add Prefer select_all Over execute For Read Queries as a Rails TIL 2024-11-02 13:29:54 -05:00
jbranchaud
18bdcc88b8 Add example of output to latest TIL 2024-11-01 14:42:07 -05:00
jbranchaud
95115c7ebc Add Generate Random Alphanumeric Identifier as a Postgres TIL 2024-11-01 12:46:18 -05:00
jbranchaud
4e859b93d2 Add a clarification to the latest TIL 2024-10-31 20:01:26 -05:00
jbranchaud
23c20e99bf Add Undo Changes Made To Current Terminal Prompt as a Unix TIL 2024-10-31 19:57:05 -05:00
jbranchaud
63bb627716 Add Prevent Hidden Element From Flickering On Load as a JavaScript TIL 2024-10-30 15:11:41 -05:00
jbranchaud
21385f4491 Add Drizzle Tracks Migrations In A Log Table as a Drizzle TIL 2024-10-29 16:18:55 -05:00
jbranchaud
5b47326ab3 Add Get The SHA256 For A File as a Unix TIL 2024-10-29 16:09:58 -05:00
jbranchaud
c16d80fd94 Add Get Fields For Inserted Row as a Drizzle TIL 2024-10-28 15:40:24 -05:00
jbranchaud
edf38308da Add Set DateTime To Include Time Zone In Migrations as a Rails TIL 2024-10-28 11:34:34 -05:00
jbranchaud
dc7159c16c Add a bit more to the latest TIL 2024-10-27 22:45:13 -05:00
jbranchaud
33f780a69f Add Concatenate Strings With A Separator as a Postgres TIL 2024-10-27 22:38:59 -05:00
jbranchaud
dfe9c002ee Add Add Unique Constraint Using Existing Index as a Postgres TIL 2024-10-26 10:03:23 -05:00
jbranchaud
3e34636d80 Add Analyze Your Website Performance as an Internet TIL 2024-10-25 12:38:46 -05:00
jbranchaud
dcef57d344 Add Make Truly Deep Clone With Structured Clone as a JavaScript TIL 2024-10-25 08:36:46 -05:00
jbranchaud
6580393b7a Add Create bigint Identity Column For Primary Key as a Drizzle TIL 2024-10-24 17:39:55 -05:00
jbranchaud
17d7f0933b Add Add Subscriber To Kit Form Via API as a Workflow TIL 2024-10-23 19:12:52 -05:00
jbranchaud
e4abc56f4c Add Add Hotkeys For Specific Raycast Extensions as a Workflow TIL 2024-10-22 14:30:59 -05:00
jbranchaud
43ea7acd74 Add Use A Space To Exclude Command Fromm History as a Zsh TIL 2024-10-21 11:21:21 -05:00
jbranchaud
d7d331b688 Add Put Unique Constraint On Generated Column as a Postgres TIL 2024-10-21 11:04:04 -05:00
jbranchaud
b743dc2ac0 Add Exclude AI Overview From Google Search as an Internet TIL 2024-10-20 19:38:08 -05:00
jbranchaud
4a72c63e42 Add Enforce Uniqueness On Column Expression as a Postgres TIL 2024-10-19 19:13:30 -05:00
jbranchaud
431507fd0e Add Send A Message To A Discord Channel as a Workflow TIL 2024-10-18 17:59:08 -05:00
jbranchaud
fb153f35bf Add Postgre Does Not Support Unsigned Integers as a PostgreSQL TIL 2024-10-18 17:43:46 -05:00
jbranchaud
f4ba6a9ef7 Add Override The Global Git Ignore File as a Git TIL 2024-10-18 15:55:40 -05:00
jbranchaud
0dee39d3c5 Add Configure Email Redirect With Cloudflare as a Workflow TIL 2024-10-17 11:09:04 -05:00
jbranchaud
5ebdd9a1a9 Add Convert JPEG To PNG With ffmpeg as a Unix TIL 2024-10-16 12:43:18 -05:00
jbranchaud
33c5cd748f Fix codeblock example in latest TIL 2024-10-15 15:07:55 -05:00
jbranchaud
ff515c8d6a Add Get URL For GitHub User Profile Photo as a Workflow TIL 2024-10-15 15:07:19 -05:00
jbranchaud
fad36e0691 Add Check How A File Is Being Ignored as a Git TIL 2024-10-15 12:19:55 -05:00
jbranchaud
4d1d8e7134 Add Switch Moving End Of Visual Selection as a Vim TIL 2024-10-14 20:20:41 -05:00
jbranchaud
567637497c Add Ensure Resources Always Get Closed as a Java TIL 2024-10-14 16:04:07 -05:00
jbranchaud
028b76ba6b Add Generate Types For A Content Collection as an Astro TIL 2024-10-13 17:03:25 -05:00
jbranchaud
1934c8f63e Add Make Direnv Less Noisy as a Unix TIL 2024-10-13 16:25:44 -05:00
jbranchaud
e5a003dbaf Add Markdown Files Are Of Type MarkdownInstance as an Astro TIL 2024-10-13 11:46:36 -05:00
Karim Bouchez
15337dfd71 Update the old way to capture a GitHub Actions output
See [here](https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/) for more explanations:
> We are monitoring telemetry for the usage of these commands and plan to fully disable them on 31st May 2023. Starting 1st June 2023 workflows using save-state or set-output commands via stdout will fail with an error.
2023-02-12 10:36:45 +01:00
53 changed files with 2110 additions and 7 deletions

12
.vimrc
View File

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

View File

@@ -10,7 +10,7 @@ pairing with smart people at Hashrocket.
For a steady stream of TILs, [sign up for my newsletter](https://crafty-builder-6996.ck.page/e169c61186).
_1463 TILs and counting..._
_1512 TILs and counting..._
---
@@ -19,6 +19,7 @@ _1463 TILs and counting..._
* [Ack](#ack)
* [Amplify](#amplify)
* [Ansible](#ansible)
* [Astro](#astro)
* [Brew](#brew)
* [Chrome](#chrome)
* [Clojure](#clojure)
@@ -26,6 +27,7 @@ _1463 TILs and counting..._
* [Deno](#deno)
* [Devops](#devops)
* [Docker](#docker)
* [Drizzle](#drizzle)
* [Elixir](#elixir)
* [Gatsby](#gatsby)
* [Git](#git)
@@ -98,6 +100,11 @@ _1463 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)
@@ -150,6 +157,7 @@ _1463 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)
@@ -202,6 +210,12 @@ _1463 TILs and counting..._
- [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)
@@ -270,6 +284,7 @@ _1463 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)
@@ -282,6 +297,7 @@ _1463 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)
@@ -319,6 +335,7 @@ _1463 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)
@@ -380,6 +397,7 @@ _1463 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)
@@ -425,8 +443,12 @@ _1463 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)
@@ -436,6 +458,7 @@ _1463 TILs and counting..._
### 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)
@@ -495,6 +518,7 @@ _1463 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)
@@ -505,6 +529,7 @@ _1463 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)
@@ -686,6 +711,7 @@ _1463 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)
@@ -705,6 +731,7 @@ _1463 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)
@@ -735,6 +762,7 @@ _1463 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)
@@ -748,6 +776,7 @@ _1463 TILs and counting..._
- [Force SSL When Making A psql Connection](postgres/force-ssl-when-making-a-psql-connection.md)
- [Generate A UUID](postgres/generate-a-uuid.md)
- [Generate Modern Primary Key Columns](postgres/generate-modern-primary-key-columns.md)
- [Generate Random Alphanumeric Identifier](postgres/generate-random-alphanumeric-identifier.md)
- [Generate Random UUIDs Without An Extension](postgres/generate-random-uuids-without-an-extension.md)
- [Generate Series Of Numbers](postgres/generate-series-of-numbers.md)
- [Generating UUIDs With pgcrypto](postgres/generating-uuids-with-pgcrypto.md)
@@ -787,11 +816,13 @@ _1463 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)
@@ -813,6 +844,7 @@ _1463 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)
@@ -855,6 +887,9 @@ _1463 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)
@@ -898,6 +933,7 @@ _1463 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)
@@ -907,6 +943,7 @@ _1463 TILs and counting..._
- [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)
@@ -954,6 +991,7 @@ _1463 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)
@@ -981,6 +1019,8 @@ _1463 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)
@@ -1173,6 +1213,7 @@ _1463 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)
@@ -1182,6 +1223,7 @@ _1463 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)
@@ -1208,6 +1250,7 @@ _1463 TILs and counting..._
- [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)
@@ -1322,6 +1365,7 @@ _1463 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)
@@ -1351,6 +1395,7 @@ _1463 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)
@@ -1378,6 +1423,7 @@ _1463 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)
@@ -1418,10 +1464,12 @@ _1463 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)
@@ -1448,6 +1496,7 @@ _1463 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)
@@ -1487,6 +1536,7 @@ _1463 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)
@@ -1648,6 +1698,7 @@ _1463 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)
@@ -1689,14 +1740,18 @@ _1463 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)
@@ -1704,6 +1759,7 @@ _1463 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)
@@ -1739,6 +1795,7 @@ _1463 TILs and counting..._
- [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

View File

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

View File

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

View File

@@ -0,0 +1,51 @@
# Add Line Numbers To A Code Block With Counter
The
[`counter`](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_counter_styles/Using_CSS_counters)
feature in CSS is a stateful feature that allows you to increment and display a
number based on elements' locations in the document. This feature is useful for
adding numbers to headings and lists, but it can also be used to add line
numbers to a code block.
We need to initialize the counter to start using it. This will give it a name
and default it to the value 0. We'll tie this to a `pre` tag which wraps our
lines of code.
```css {{ title: 'globals.css' }}
pre.shiki {
counter-reset: line-number;
}
```
Then we need to increment the counter for every line of code that appears in
the code block
```css {{ title: 'globals.css' }}
pre.shiki .line {
counter-increment: line-number;
}
```
Last, we need to display these incrementing `line-number` values _before_ each
line.
```css {{ title: 'globals.css }}
pre.shiki .line:not(:last-of-type)::before {
content: counter(line-number);
/*
* plus any styling and spacing of the numbers
*/
}
```
This essentially attaches an element to the front (`::before`) of the line
whose content is the current value of `line-number`. It is applied to all but
the last `.line` because [shiki](https://shiki.matsu.io/) includes an empty
`.line` at the end.
Here is [the real-world example of
this](https://github.com/pingdotgg/uploadthing/blob/4954c9956c141a25a5405991c34cc5ce8d990085/docs/src/styles/tailwind.css#L13-L37)
that I referenced for this post.
Note: the counter can be incremented, decremented, or even explicitly set to a
specific value.

View File

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

View File

@@ -0,0 +1,39 @@
# Drizzle Tracks Migrations In A Log Table
When I generate (`npx drizzle-kit generate`) and apply (`npx drizzle-kit
migrate`) schema migrations against my database with Drizzle, there are SQL
files that get created and run.
How does Drizzle know which SQL files have been run and which haven't?
Like many SQL schema migration tools, it uses a table in the database to record
this metadata. Drizzle defaults to calling this table `__drizzle_migrations`
and puts it in the `drizzle` schema (which is like a database namespace).
Let's take a look at this table for a project with two migrations:
```sql
postgres> \d drizzle.__drizzle_migrations
Table "drizzle.__drizzle_migrations"
Column | Type | Collation | Nullable | Default
------------+---------+-----------+----------+----------------------------------------------------------
id | integer | | not null | nextval('drizzle.__drizzle_migrations_id_seq'::regclass)
hash | text | | not null |
created_at | bigint | | |
Indexes:
"__drizzle_migrations_pkey" PRIMARY KEY, btree (id)
postgres> select * from drizzle.__drizzle_migrations;
id | hash | created_at
----+------------------------------------------------------------------+---------------
1 | 8961353bf66f9b3fe1a715f6ea9d9ef2bc65697bb8a5c2569df939a61e72a318 | 1730219291288
2 | b75e61451e2ce37d831608b1bc9231bf3af09e0ab54bf169be117de9d4ff6805 | 1730224013018
(2 rows)
```
Notice that Drizzle stores each migration record as [a SHA256 hash of the
migration
file](https://github.com/drizzle-team/drizzle-orm/blob/526996bd2ea20d5b1a0d65e743b47e23329d441c/drizzle-orm/src/migrator.ts#L52)
and a timestamp of when the migration was run.
[source](https://orm.drizzle.team/docs/drizzle-kit-migrate#applied-migrations-log-in-the-database)

View File

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

View File

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

View File

@@ -0,0 +1,27 @@
# Count All Files Of Specific Type Tracked By Git
I want to get a count of all the markdown files in my [TIL
repo](https://github.com/jbranchaud/til). Since all the files I care about are
tracked by `git`, I can use `git ls-files` to get a listing of all files. That
command on its own lists all files tracked by your git repository. Though there
are many other flags we can apply, that will do for my purposes.
By giving `git ls-files` a pattern to match against, I can turn up just, for
instance, markdown files (`*.md`). I can pipe that to `wc -l` to get a count
rather than exploding my terminal with a list of file names.
```bash
git ls-files '*.md' | wc -l
1503
```
That command includes `README.md` and `CONTRIBUTING.md`, but really I only want
to count the markdown files that constitute a TIL. Those all happen to be
nested under a single directory. So I can tweak the glob pattern like so:
```bash
git ls-files '*/*.md' | wc -l
1501
```
See `man git-ls-files` for more details.

View File

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

View File

@@ -23,11 +23,11 @@ version from my `.tool-versions` file with a step that uses `set-output`.
- name: Read Node.js version to install from `.tool-versions`
id: nodejs
run: >-
echo "::set-output name=NODE_VERSION::$(
echo "NODE_VERSION=$(
cat .tool-versions |
grep nodejs |
sed 's/nodejs \(.*\)$/\1/'
)"
)" >> $GITHUB_OUTPUT
```
`echo` runs the command in the string which sets `NODE_VERSION` as an output
@@ -45,4 +45,4 @@ This output value can be referenced in a later step.
`steps` has a reference to the `nodejs` step (note the `id` above) which then
has `outputs` like the `NODE_VERSION`.
[source](https://docs.github.com/en/actions/reference/workflow-commands-for-github-actions#using-workflow-commands-to-access-toolkit-functions)
[source](https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#setting-an-output-parameter)

View File

@@ -0,0 +1,56 @@
# Do Something N Times
With Go 1.23 there is a new for-range syntax that makes looping a bit easier
and more compact.
Instead of needing to set up our 3-part for-loop syntax, we can say we want to
do something `N` times with `for range N`.
```go
for range n {
// do something
}
```
Let's look at an actual, runnable example:
```go
package main
import "fmt"
import "math/rand"
import "time"
func main() {
rand.Seed(time.Now().UnixNano())
food := []string{"taco", "burrito", "torta", "enchilada", "tostada"}
for range 5 {
randomIndex := rand.Intn(len(food))
fmt.Println(food[randomIndex])
}
}
```
The output is random and might look something like this:
```bash
$ go run loop.go
taco
burrito
tostada
taco
enchilada
```
I appreciate this syntax addition because it feels very akin to Ruby's `#times`
method:
```ruby
5.times do
# do something
end
```
[source](https://eli.thegreenplace.net/2024/ranging-over-functions-in-go-123/)

View File

@@ -0,0 +1,40 @@
# Add Styled Alerts To GitHub Markdown Documents
The GFM (GitHub Flavored Markdown) variant of markdown adds some nice features
to our GitHub-rendered markdown documents.
One such feature that has been around for a couple years, but which I only just
learned about, are these styled alerts. There are five of them each with a
different color and icon to help convey meaning.
```
> [!NOTE]
> Useful information that users should know, even when skimming content.
> [!TIP]
> Helpful advice for doing things better or more easily.
> [!IMPORTANT]
> Key information users need to know to achieve their goal.
> [!WARNING]
> Urgent info that needs immediate user attention to avoid problems.
> [!CAUTION]
> Advises about risks or negative outcomes of certain actions.
```
I just added the following to the top of one of my project's READMEs to help me
remember that it is not under active development.
```
> [!WARNING]
> This repo is not under active development, you might be looking for
> [til-visualmode-dev](https://github.com/jbranchaud/til-visualmode-dev).
```
Visit the GitHub docs for
[Alerts](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#alerts)
to see examples of how these render.
[source](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#alerts)

View File

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

View File

@@ -0,0 +1,29 @@
# Digraph Unicode Characters Have a Titlecase
Coming from primarily being exposed to the US American alphabet, I'm familiar
with characters that I type into the computer having one of two cases. Either
it is lowercase by default (`c`) or I can hit the shift key to produce the
uppercase version (`C`).
Unicode, which has broad support for character encoding across most languages,
has a couple characters that are called _digraphs_. These are single code
points, but look like they are made up of two characters.
A good example of this is `dž`. And if that character were to appear in an all
uppercase word, then it would display as `DŽ`.
But what if it appears at the beginning of a capitalized word?
That's where _titlecase_ comes into the picture -- `Dž`.
From [wikipedia](https://en.wikipedia.org/wiki/D%C5%BE):
> Note that when the letter is the initial of a capitalised word (like Džungla
> or Džemper, or personal names like Džemal or Džamonja), the ž is not
> uppercase. Only when the whole word is written in uppercase, is the Ž
> capitalised.
(I find it odd that wikipedia's article on this digraph code point is using
separate characters instead of the digraph.)
[source](https://devblogs.microsoft.com/oldnewthing/20241031-00/?p=110443)

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,55 @@
# Prevent Hidden Element From Flickering On Load
Here is what it might look like to use [Alpine.js](https://alpinejs.dev/) to
sprinkle in some JavaScript for controlling a dropdown menu.
```html
<div x-data="{ profileDropdownOpen: false }">
<button
type="button"
@click="profileDropdownOpen = !profileDropdownOpen"
>
<!-- some inner html -->
</button>
<div x-show="profileDropdownOpen" role="menu">
<a href="/profile" role="menuitem">Your Profile</a>
<a href="/sign-out" role="menuitem">Sign Out</a>
</div>
</div>
```
Functionally that will work. You can click the button to toggle the menu open
and closed.
What you might notice, however, when you refresh the page is that the menu
flickers open as the page first loads and then disappears. This is a quirk of
the element being rendered before Alpine.js is loaded and the
[`x-show`](https://alpinejs.dev/directives/show) directive has a chance to take
effect.
To get around this, we can _cloak_ any element with an `x-show` directive that
should be hidden by default.
```html
<div x-data="{ profileDropdownOpen: false }">
<button
type="button"
@click="profileDropdownOpen = !profileDropdownOpen"
>
<!-- some inner html -->
</button>
<div x-cloak x-show="profileDropdownOpen" role="menu">
<a href="/profile" role="menuitem">Your Profile</a>
<a href="/sign-out" role="menuitem">Sign Out</a>
</div>
</div>
```
This addition needs to be paired with some custom CSS to hide any _cloaked_
elements.
```css
[x-cloak] { display: none !important; }
```
[source](https://alpinejs.dev/directives/cloak)

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,56 @@
# Generate Random Alphanumeric Identifier
Here is a PostgreSQL query that uses
[`pgcrypto`](https://www.postgresql.org/docs/current/pgcrypto.html) (for
[`get_random_bytes`](https://www.postgresql.org/docs/current/pgcrypto.html#PGCRYPTO-RANDOM-DATA-FUNCS))
and a CTE to generate a cryptographically-random 8-character alphanumeric
identifier.
```sql
-- First ensure pgcrypto is installed
create extension if not exists pgcrypto;
-- Generates a single 8-character identifier
with chars as (
-- excludes some look-alike characters
select '23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz' as charset
),
random_bytes as (
select gen_random_bytes(8) as bytes
),
positions as (
select generate_series(0, 7) as pos
)
select string_agg(
substr(
charset,
(get_byte(bytes, pos) % length(charset)) + 1,
1
),
'' order by pos
) as short_id
from positions, random_bytes, chars;
```
Here is an example of the output:
```sql
+----------+
| short_id |
|----------|
| NXdu9AnV |
+----------+
```
The
[`generate_series`](https://www.postgresql.org/docs/current/functions-srf.html)
gives us an 8-row table from 0 to 7 that we can use as indexes into the byte
positions of the value we get from `gen_random_bytes`. Those random bytes get
mapped to individual alphanumeric characters from `chars`. That then gets
squeezed together with `string_agg`.
Note: the character set excludes some characters that can be mistaken for one
another like `0` and `O` or `1` and `l`.
Note: you could change the right-bound of the `generate_series` to generate a
different length identifier.

View File

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

View File

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

View File

@@ -0,0 +1,80 @@
# Table Names Are Treated As Lower-Case By Default
This one is a bit unintuitive and can cause some real confusion -- when you
create a table in PostgreSQL, any casing is ignored, it is treated as
lower-case. Let's see it to believe it:
```sql
> create table BookMarks (
id integer generated always as identity primary key,
location text not null
);
> \d
+--------+--------------------+----------+----------+
| Schema | Name | Type | Owner |
|--------+--------------------+----------+----------|
| public | bookmarks | table | postgres |
| public | bookmarks_id_seq | sequence | postgres |
+--------+--------------------+----------+----------+
```
Notice that when we list our tables, the uppercase `M` and `B` are gone. That's
because Postgres folds away the casing when processing the table name
identifier.
It doesn't matter how we refer to it for queries:
```sql
> select * from BookMarks;
+----+----------+
| id | location |
|----+----------|
+----+----------+
> select * from bookmarks;
+----+----------+
| id | location |
|----+----------|
+----+----------+
```
You can force Postgres to respect the casing by wrapping the table name in
quotes.
```sql
> create table "BookMarks" (
id integer generated always as identity primary key,
location text not null
);
> \d
+--------+--------------------+----------+----------+
| Schema | Name | Type | Owner |
|--------+--------------------+----------+----------|
| public | BookMarks | table | postgres |
| public | BookMarks_id_seq | sequence | postgres |
+--------+--------------------+----------+----------+
> select * from "BookMarks";
+----+----------+
| id | location |
|----+----------|
+----+----------+
> select * from "bookmarks";
relation "bookmarks" does not exist
LINE 1: select * from "bookmarks"
^
> select * from BookMarks;
relation "bookmarks" does not exist
LINE 1: select * from BookMarks
^
```
That then means you have to quote your table name anytime you want to refer to
it in a query. It's not worth it. It is better to always keep your table names
lower-case using snake case.
[source](https://weiyen.net/articles/avoid-capital-letters-in-postgres-names)

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

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

View File

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

View File

@@ -0,0 +1,49 @@
# Store And Access Immutable Data In A Tuple
You can store heterogeneous data (of varying types) as a _tuple_ which is a
light-weight immutable data structure.
You can be explicit about the tuple by wrapping the items in parentheses:
```python
>>> book = ('An Immense World', 'Ed Yong', 2022)
```
Though it is also possible to comma-separate the items and forego the
parentheses.
```python
>>> book2 = 'The Shining', 'Stephen King', 1977
>>> book2
('The Shining', 'Stephen King', 1977)
```
Once we have our tuple, we can access any item from it positionally. We can
also use _sequence unpacking_ to assign the values to a series of variables:
```python
>>> book[0]
'An Immense World'
>>> book[1]
'Ed Yong'
>>> book[2]
2022
>>> title, author, publication_year = book
>>> title
'An Immense World'
>>> author
'Ed Yong'
>>> publication_year
2022
```
And, as promised, it is immutable (unlike lists):
```python
>>> book[1] = 'Agatha Christie'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
```
[source](https://docs.python.org/3/tutorial/datastructures.html#tuples-and-sequences)

View File

@@ -0,0 +1,30 @@
# Create Table With bigint ID As Primary Key
When creating a new table with an ActiveRecord migration, we specify all the
fields _except_ the `id`. The `id`, which is the primary key, is implicit. We
get it by default.
The type of that `id` defaults to `int` which is a 32-bit signed integer.
We can override the type of `id` in a variety of ways. The one I prefer in most
cases is to make the `id` of type `bigint`. This is a 64-bit signed integer. It
offers quite a bit more headroom for the number of unique identifies in our
table.
This can be specified by including `id: :bigint` as an option to the
`create_table` method.
```ruby
class CreatePosts < ActiveRecord::Migration[8.0]
def change
create_table :posts, id: :bigint do |t|
t.string :title, null: false
t.string :body, null: false
t.timestamps
end
end
end
```
[source](https://api.rubyonrails.org/v7.1/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html#method-i-create_table)

View File

@@ -0,0 +1,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.

View File

@@ -0,0 +1,63 @@
# Prefer select_all Over execute For Read Queries
Though the `#execute` function provided by ActiveRecord technically works as a
general-purpose query runner for strings of raw SQL, it has some downsides.
First, let's say we have a large semi-complex (better in SQL than ActiveRecord
DSL) SQL query defined in a heredoc.
```ruby
books_by_status_query = <<-SQL
select
books.*,
latest_statuses.status as current_status,
array_to_json(array_agg(...)) as reading_statuses
from books
-- plus several left joins
-- where clause, group by, and order by
SQL
```
I reflexively reach for
[`#execute`](https://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/DatabaseStatements.html#method-i-execute)
in a situation like that:
```ruby
result = ActiveRecord::Base.connection.execute(books_by_status_query)
```
However, if we're doing a read-only query and we are expecting multiple rows in
the result, then we are better off reaching for
[`#select_all`](https://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/DatabaseStatements.html#method-i-select_all).
```ruby
result = ActiveRecord::Base.connection.select_all(books_by_status_query)
```
It has the advantage of semantically communicating that it's just a read and
won't have any side-effects. It also avoids an unnecessary clear of the query
cache.
> Note: [when execute is used] the query is assumed to have side effects and
> the query cache will be cleared. If the query is read-only, consider using
> select_all instead.
The `#execute` method also has been known to leak memory with some database
connectors.
> Note: depending on your database connector, the result returned by this
> method may be manually memory managed. Consider using exec_query wrapper
> instead.
We can then iterate through and transform the results just as we would have
done with `#execute`.
```ruby
result.map do |row|
row.tap do |hash|
hash["reading_statuses"] = JSON.parse(hash["reading_statuses"])
end
OpenStruct.new(row)
end
```

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,37 @@
# Open New Splits To The Current Directory
I typically work in one project per tmux session. When I create a given tmux
session, the default directory is for that project. All new windows and pane
splits will open at that default directory. This generally is the default
behavior I want.
One caveat: I often open a new window within an existing session that I want
anchored to another directory. This could be because I'm working in a monorepo
and I need to work from a subdirectory for a specific package or app. Or it
could be that I'm temporarily digging into another project and it isn't worth
create a whole new session.
Regardless of the reason, I run into a bit of friction with tmux's defaults.
First I open the new window and `cd` to another project. After some working, I
need to open a split pane, maybe to run a project command like a build or dev
server. Hitting `prefix-"` (horizontal split) or `prefix-%` (vertical split)
opens a pane with the shell defaulting back to the original directory, not the
current directory.
The trick to fixing this bit of friction is overriding the directory of pane
splits. I can do that by adding the following to my `~/.tmux.conf`:
```
# Pane splits should open to the same path as the current pane
bind '"' split-window -v -c "#{pane_current_path}"
bind % split-window -h -c "#{pane_current_path}"
```
Make sure to run `tmux source-file ~/.tmux.conf` to apply these config changes.
The `pane_current_path` is called a "Format" in tmux parlance. It resolves to
the absolute path of the current pane's current directory. You can find all the
formats in the manpage with this command: `man tmux | less +'/^FORMATS'`. You
can also show yourself that this format resolves to what you expect by running
`tmux display-message -p '#{pane_current_path}'`.

View File

@@ -0,0 +1,35 @@
# Ignore All Errors In A TypeScript File
As of TypeScript 3.7, we can mark an entire TypeScript file to be ignored by
the TypeScript compiler when it is doing static type checking.
We can do this by adding the `@ts-nocheck` directive at the top of the file:
```typescript
// @ts-nocheck
type User = {
id: string;
name: string;
email: string;
}
const user: User = {
id: 123,
name: "Liz Lemon",
email: "liz.lemon@nbc.com",
};
```
Notice that `id` is typed as a `string`, but we are using a `number` with `id`
for `user`. That is a type error. But with the `@ts-nocheck` directive at the
top, the type checker doesn't run on the file and we see no type errors.
I'd generally suggest to avoid doing this. It can hide real type errors that
you should be addressing. That said, in special circumstances, you may need it,
even if just temporarily, like if an imported package doesn't have types. Here
is an example of that in [uploadthing's
`rehype.js`](https://github.com/pingdotgg/uploadthing/blob/d98fbefedddf64d183cc5a00b3fd707e8d8f2f6c/docs/src/mdx/rehype.js#L1)
which is missing types from `mdx-annotations`.
[source](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#-ts-nocheck-in-typescript-files)

View File

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

View File

@@ -0,0 +1,34 @@
# Get The SHA256 Hash For A File
Unix systems come with a `sha256sum` utility that we can use to compute the
SHA256 hash of a file. This means the contents of file are compressed into a
256-bit digest.
Here I use it on a SQL migration file that I've generated.
```bash
$ sha256sum migrations/0001_large_doctor_spectrum.sql
b75e61451e2ce37d831608b1bc9231bf3af09e0ab54bf169be117de9d4ff6805 migrations/0001_large_doctor_spectrum.sql
```
Each file passed to this utility gets output to a separate line which is why we
see the filename next to the hash. Since I am only running it on a single file
and I may want to pipe the output to some other program, I can clip off just
the part I need.
```bash
sha256sum migrations/0001_large_doctor_spectrum.sql | cut -d ' ' -f 1
b75e61451e2ce37d831608b1bc9231bf3af09e0ab54bf169be117de9d4ff6805
```
We can also produce these digests with `openssl`:
```bash
$ openssl dgst -sha256 migrations/0001_large_doctor_spectrum.sql
SHA2-256(migrations/0001_large_doctor_spectrum.sql)= b75e61451e2ce37d831608b1bc9231bf3af09e0ab54bf169be117de9d4ff6805
$ openssl dgst -sha256 migrations/0001_large_doctor_spectrum.sql | cut -d ' ' -f 2
b75e61451e2ce37d831608b1bc9231bf3af09e0ab54bf169be117de9d4ff6805
```
See `sha256sum --help` or `openssl dgst --help` for more details.

View File

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

View File

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

View File

@@ -0,0 +1,20 @@
# Undo Change Made to Current Terminal Prompt
I frequently use a variety of ASCII command characters like `ctrl-u` to delete
the entire line or `ctrl-a` to jump to the front of a long line so I can make
some edits toward that side of the command or `ctrl-e` to jump to the end of
the command for the same reason. I sometimes even use `ctrl-k` to delete
everything after the cursor to the end of the line.
What I didn't realize until now is that any of those commands the modify the
current line of the termianl prompt plus regular typing and hitting the
backspace are all _undoable_.
So, if I just wiped out half the line (with `ctrl-k`) and I immediately regret
it, I can restore it with `ctrl-_`. The system keeps of history of the actions
you've taken, so you can keep hitting `ctrl-_` to undo even further.
The `ctrl-/` command does the same, per GNU's [Undo Changes in the Emacs
docs](https://www.gnu.org/software/emacs/manual/html_node/emacs/Basic-Undo.html).
[source](https://jvns.ca/ascii)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,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)