mirror of
https://github.com/jbranchaud/til
synced 2026-01-15 13:08:02 +00:00
Compare commits
59 Commits
cbfb7e7b96
...
2a3d1448c4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2a3d1448c4 | ||
|
|
86972f41cc | ||
|
|
93a663cc9c | ||
|
|
0c1dd29d8d | ||
|
|
b492a9d765 | ||
|
|
543a82730d | ||
|
|
877537228f | ||
|
|
1513611857 | ||
|
|
74514b462d | ||
|
|
484dec8e24 | ||
|
|
8574113dc6 | ||
|
|
1c4e37ed8a | ||
|
|
581aa1decb | ||
|
|
0c4795c1d2 | ||
|
|
9bbde247a5 | ||
|
|
36ca71bfb1 | ||
|
|
8a682e3a89 | ||
|
|
c7a38c8267 | ||
|
|
71d3e56b3d | ||
|
|
af3974d3fe | ||
|
|
adc6b2e903 | ||
|
|
9a6ebd4c6b | ||
|
|
6df0693804 | ||
|
|
507602ef0c | ||
|
|
18bdcc88b8 | ||
|
|
95115c7ebc | ||
|
|
4e859b93d2 | ||
|
|
23c20e99bf | ||
|
|
63bb627716 | ||
|
|
21385f4491 | ||
|
|
5b47326ab3 | ||
|
|
c16d80fd94 | ||
|
|
edf38308da | ||
|
|
dc7159c16c | ||
|
|
33f780a69f | ||
|
|
dfe9c002ee | ||
|
|
3e34636d80 | ||
|
|
dcef57d344 | ||
|
|
6580393b7a | ||
|
|
17d7f0933b | ||
|
|
e4abc56f4c | ||
|
|
43ea7acd74 | ||
|
|
d7d331b688 | ||
|
|
b743dc2ac0 | ||
|
|
4a72c63e42 | ||
|
|
431507fd0e | ||
|
|
fb153f35bf | ||
|
|
f4ba6a9ef7 | ||
|
|
0dee39d3c5 | ||
|
|
5ebdd9a1a9 | ||
|
|
33c5cd748f | ||
|
|
ff515c8d6a | ||
|
|
fad36e0691 | ||
|
|
4d1d8e7134 | ||
|
|
567637497c | ||
|
|
028b76ba6b | ||
|
|
1934c8f63e | ||
|
|
e5a003dbaf | ||
|
|
15337dfd71 |
12
.vimrc
12
.vimrc
@@ -9,3 +9,15 @@ function! CountTILs()
|
||||
endfunction
|
||||
|
||||
nnoremap <leader>c :call CountTILs()<cr>
|
||||
|
||||
augroup DisableMarkdownFormattingForTILReadme
|
||||
autocmd!
|
||||
autocmd BufRead ~/code/til/README.md autocmd! Format
|
||||
augroup END
|
||||
|
||||
" local til_readme_group = vim.api.nvim_create_augroup('DisableMarkdownFormattingForTILReadme', { clear = true })
|
||||
" vim.api.nvim_create_autocmd('BufRead', {
|
||||
" command = 'autocmd! Format',
|
||||
" group = til_readme_group,
|
||||
" pattern = vim.fn.expand '~/code/til/README.md',
|
||||
" })
|
||||
|
||||
59
README.md
59
README.md
@@ -10,7 +10,7 @@ pairing with smart people at Hashrocket.
|
||||
|
||||
For a steady stream of TILs, [sign up for my newsletter](https://crafty-builder-6996.ck.page/e169c61186).
|
||||
|
||||
_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
|
||||
|
||||
|
||||
56
astro/generate-types-for-a-content-collection.md
Normal file
56
astro/generate-types-for-a-content-collection.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# Generate Types For A Content Collection
|
||||
|
||||
Let's say I'm using Astro to publish posts via markdown. One of the best ways
|
||||
to do that is as a _Content Collection_. The posts will live in `src/content`
|
||||
probably under a `posts` directory. Plus a config file will define the
|
||||
collection and specify validations for the frontmatter.
|
||||
|
||||
```typescript
|
||||
// src/content/config.ts
|
||||
import { defineCollection, z } from 'astro:content';
|
||||
|
||||
const postsCollection = defineCollection({
|
||||
schema: z.object({
|
||||
title: z.string(),
|
||||
description: z.string(),
|
||||
tags: z.array(z.string())
|
||||
})
|
||||
});
|
||||
|
||||
export const collections = {
|
||||
'posts': postsCollection,
|
||||
};
|
||||
```
|
||||
|
||||
When I first add this to my project and get the collection, it won't know what
|
||||
the types are.
|
||||
|
||||
```astro
|
||||
---
|
||||
import { getCollection } from "astro:content";
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const blogEntries = await getCollection("posts");
|
||||
// ^^^ any
|
||||
|
||||
return blogEntries.map((entry) => ({
|
||||
params: { slug: entry.slug },
|
||||
props: { entry },
|
||||
}));
|
||||
}
|
||||
---
|
||||
```
|
||||
|
||||
I can tell Astro to generate a fresh set of types for things like content
|
||||
collections by running the [`astro sync`
|
||||
command](https://docs.astro.build/en/reference/cli-reference/#astro-sync).
|
||||
|
||||
```bash
|
||||
$ npm run astro sync
|
||||
```
|
||||
|
||||
This updates auto-generated files under the `.astro` directory which get pulled
|
||||
in to your project's `env.d.ts` file.
|
||||
|
||||
All of these types will also be synced anytime I run `astro dev`, `astro
|
||||
build`, or `astro check`.
|
||||
53
astro/markdown-files-are-of-type-markdown-instance.md
Normal file
53
astro/markdown-files-are-of-type-markdown-instance.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# Markdown Files Are Of Type MarkdownInstance
|
||||
|
||||
One of the things Astro excels at is rendering markdown files as HTML pages in
|
||||
your site. And at some point we'll want to access a listing of those markdown
|
||||
files in order to do something like display a list of them on an index page.
|
||||
For that, we'll use
|
||||
[`Astro.glob()`](https://docs.astro.build/en/reference/api-reference/#astroglob).
|
||||
|
||||
```typescript
|
||||
---
|
||||
const allPosts = await Astro.glob("../posts/*.md");
|
||||
---
|
||||
|
||||
<ul>
|
||||
{allPosts.map(post => {
|
||||
return <Post title={post.frontmatter.title} slug={post.frontmatter.slug} />
|
||||
})}
|
||||
</ul>
|
||||
```
|
||||
|
||||
This looks great, but we'll run into a type error on that first line:
|
||||
`'allPosts' implicitly has type 'any'`. We need to declare the type
|
||||
of these post instances that are being read-in by Astro.
|
||||
|
||||
These are of [type
|
||||
`MarkdownInstance`](https://docs.astro.build/en/reference/api-reference/#markdown-files).
|
||||
That's a generic though, so we need to tell it a bit more about the shape of a
|
||||
post.
|
||||
|
||||
```typescript
|
||||
import type { MarkdownInstance } from "astro";
|
||||
|
||||
export type BarePost = {
|
||||
layout: string;
|
||||
title: string;
|
||||
slug: string;
|
||||
tags: string[];
|
||||
};
|
||||
|
||||
export type Post = MarkdownInstance<BarePost>;
|
||||
```
|
||||
|
||||
We can then update that first line:
|
||||
|
||||
```typescript
|
||||
const allPosts: Post[] = await Astro.glob("../posts/*.md");
|
||||
```
|
||||
|
||||
Alternatively, you can specify the generic on `glob`:
|
||||
|
||||
```typescript
|
||||
const allPosts = await Astro.glob<BarePost>("../posts/*.md");
|
||||
```
|
||||
51
css/add-line-numbers-to-a-code-block-with-counter.md
Normal file
51
css/add-line-numbers-to-a-code-block-with-counter.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# Add Line Numbers To A Code Block With Counter
|
||||
|
||||
The
|
||||
[`counter`](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_counter_styles/Using_CSS_counters)
|
||||
feature in CSS is a stateful feature that allows you to increment and display a
|
||||
number based on elements' locations in the document. This feature is useful for
|
||||
adding numbers to headings and lists, but it can also be used to add line
|
||||
numbers to a code block.
|
||||
|
||||
We need to initialize the counter to start using it. This will give it a name
|
||||
and default it to the value 0. We'll tie this to a `pre` tag which wraps our
|
||||
lines of code.
|
||||
|
||||
```css {{ title: 'globals.css' }}
|
||||
pre.shiki {
|
||||
counter-reset: line-number;
|
||||
}
|
||||
```
|
||||
|
||||
Then we need to increment the counter for every line of code that appears in
|
||||
the code block
|
||||
|
||||
```css {{ title: 'globals.css' }}
|
||||
pre.shiki .line {
|
||||
counter-increment: line-number;
|
||||
}
|
||||
```
|
||||
|
||||
Last, we need to display these incrementing `line-number` values _before_ each
|
||||
line.
|
||||
|
||||
```css {{ title: 'globals.css }}
|
||||
pre.shiki .line:not(:last-of-type)::before {
|
||||
content: counter(line-number);
|
||||
/*
|
||||
* plus any styling and spacing of the numbers
|
||||
*/
|
||||
}
|
||||
```
|
||||
|
||||
This essentially attaches an element to the front (`::before`) of the line
|
||||
whose content is the current value of `line-number`. It is applied to all but
|
||||
the last `.line` because [shiki](https://shiki.matsu.io/) includes an empty
|
||||
`.line` at the end.
|
||||
|
||||
Here is [the real-world example of
|
||||
this](https://github.com/pingdotgg/uploadthing/blob/4954c9956c141a25a5405991c34cc5ce8d990085/docs/src/styles/tailwind.css#L13-L37)
|
||||
that I referenced for this post.
|
||||
|
||||
Note: the counter can be incremented, decremented, or even explicitly set to a
|
||||
specific value.
|
||||
48
drizzle/create-bigint-identity-column-for-primary-key.md
Normal file
48
drizzle/create-bigint-identity-column-for-primary-key.md
Normal file
@@ -0,0 +1,48 @@
|
||||
# Create bigint Identity Column For Primary Key
|
||||
|
||||
Using the Drizzle ORM with Postgres, here is how we can create a table that
|
||||
uses a [`bigint` data
|
||||
type](https://orm.drizzle.team/docs/column-types/pg#bigint) as a primary key
|
||||
[identity
|
||||
column](https://www.postgresql.org/docs/current/ddl-identity-columns.html).
|
||||
|
||||
```typescript
|
||||
import {
|
||||
pgTable,
|
||||
bigint,
|
||||
text,
|
||||
timestamp,
|
||||
} from "drizzle-orm/pg-core";
|
||||
|
||||
// Users table
|
||||
export const users = pgTable("users", {
|
||||
id: bigint({ mode: 'bigint' }).primaryKey().generatedAlwaysAsIdentity(),
|
||||
email: text("email").unique().notNull(),
|
||||
name: text("name").notNull(),
|
||||
createdAt: timestamp("created_at").defaultNow().notNull(),
|
||||
});
|
||||
```
|
||||
|
||||
There are a couple key pieces here:
|
||||
|
||||
1. We import `bigint` so that we can declare a column of that type.
|
||||
2. We specify that it is a primary key with `.primaryKey()`.
|
||||
3. We declare its default value as `generated always as identity` via
|
||||
`.generatedAlwaysAsIdentity()`.
|
||||
|
||||
Note: you need to specify the `mode` for `bigint` or else you will see a
|
||||
`TypeError: Cannot read properties of undefined (reading 'mode')` error.
|
||||
|
||||
If we run `npx drizzle-kit generate` the SQL migration file that gets
|
||||
generated will contain something like this:
|
||||
|
||||
```sql
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "users" (
|
||||
"id" bigint PRIMARY KEY GENERATED ALWAYS AS IDENTITY (sequence name "users_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 9223372036854775807 START WITH 1 CACHE 1),
|
||||
"email" text NOT NULL,
|
||||
"name" text NOT NULL,
|
||||
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||
CONSTRAINT "users_email_unique" UNIQUE("email")
|
||||
);
|
||||
```
|
||||
39
drizzle/drizzle-tracks-migrations-in-a-log-table.md
Normal file
39
drizzle/drizzle-tracks-migrations-in-a-log-table.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# Drizzle Tracks Migrations In A Log Table
|
||||
|
||||
When I generate (`npx drizzle-kit generate`) and apply (`npx drizzle-kit
|
||||
migrate`) schema migrations against my database with Drizzle, there are SQL
|
||||
files that get created and run.
|
||||
|
||||
How does Drizzle know which SQL files have been run and which haven't?
|
||||
|
||||
Like many SQL schema migration tools, it uses a table in the database to record
|
||||
this metadata. Drizzle defaults to calling this table `__drizzle_migrations`
|
||||
and puts it in the `drizzle` schema (which is like a database namespace).
|
||||
|
||||
Let's take a look at this table for a project with two migrations:
|
||||
|
||||
```sql
|
||||
postgres> \d drizzle.__drizzle_migrations
|
||||
Table "drizzle.__drizzle_migrations"
|
||||
Column | Type | Collation | Nullable | Default
|
||||
------------+---------+-----------+----------+----------------------------------------------------------
|
||||
id | integer | | not null | nextval('drizzle.__drizzle_migrations_id_seq'::regclass)
|
||||
hash | text | | not null |
|
||||
created_at | bigint | | |
|
||||
Indexes:
|
||||
"__drizzle_migrations_pkey" PRIMARY KEY, btree (id)
|
||||
|
||||
postgres> select * from drizzle.__drizzle_migrations;
|
||||
id | hash | created_at
|
||||
----+------------------------------------------------------------------+---------------
|
||||
1 | 8961353bf66f9b3fe1a715f6ea9d9ef2bc65697bb8a5c2569df939a61e72a318 | 1730219291288
|
||||
2 | b75e61451e2ce37d831608b1bc9231bf3af09e0ab54bf169be117de9d4ff6805 | 1730224013018
|
||||
(2 rows)
|
||||
```
|
||||
|
||||
Notice that Drizzle stores each migration record as [a SHA256 hash of the
|
||||
migration
|
||||
file](https://github.com/drizzle-team/drizzle-orm/blob/526996bd2ea20d5b1a0d65e743b47e23329d441c/drizzle-orm/src/migrator.ts#L52)
|
||||
and a timestamp of when the migration was run.
|
||||
|
||||
[source](https://orm.drizzle.team/docs/drizzle-kit-migrate#applied-migrations-log-in-the-database)
|
||||
56
drizzle/get-fields-for-inserted-row.md
Normal file
56
drizzle/get-fields-for-inserted-row.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# Get Fields For Inserted Row
|
||||
|
||||
With Drizzle, we can insert a row with a set of values like so:
|
||||
|
||||
```typescript
|
||||
await db
|
||||
.insert(todoItems)
|
||||
.values({
|
||||
title,
|
||||
userId,
|
||||
description,
|
||||
})
|
||||
```
|
||||
|
||||
The result of this is `QueryResult<never>`. In other words, nothing useful is
|
||||
coming back to us from the database.
|
||||
|
||||
Sometimes an insert is treated as a fire-and-forget (as long as it succeeds) or
|
||||
since we know what data we are inserting, we don't need the database to
|
||||
response. But what about values that are generated or computed by the database
|
||||
-- such as an id from a sequence, timestamp columns that default to `now()`, or
|
||||
generated columns.
|
||||
|
||||
To get all the fields of a freshly inserted row, we can tack on [the
|
||||
`returning()` function](https://orm.drizzle.team/docs/insert#insert-returning)
|
||||
(which likely adds something like [`returning
|
||||
*`](https://www.postgresql.org/docs/current/dml-returning.html)) to the insert
|
||||
query under the hood).
|
||||
|
||||
```typescript
|
||||
await db
|
||||
.insert(todoItems)
|
||||
.values({
|
||||
title,
|
||||
userId,
|
||||
description,
|
||||
})
|
||||
.returning()
|
||||
```
|
||||
|
||||
This will have a return type of `Array<type todoItems>` which means that for
|
||||
each inserted row we'll have all the fields (columns) for that row.
|
||||
|
||||
Alternatively, if we just need the generated ID for the new row(s), we can use
|
||||
a partial return like so:
|
||||
|
||||
```typescript
|
||||
await db
|
||||
.insert(todoItems)
|
||||
.values({
|
||||
title,
|
||||
userId,
|
||||
description,
|
||||
})
|
||||
.returning({ id: todoItems.id })
|
||||
```
|
||||
28
git/check-how-a-file-is-being-ignored.md
Normal file
28
git/check-how-a-file-is-being-ignored.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# Check How A File Is Being Ignored
|
||||
|
||||
There are a few places on your machine where you can specify the files that git
|
||||
should ignore. The most common is a repository's `.gitignore` file. The other
|
||||
places those excludes are specified can be more obscure. Fortunately, `git
|
||||
check-ignore` is a command that can show you specifically where.
|
||||
|
||||
For instance, let's check why my `notes.md` file is being ignored.
|
||||
|
||||
```bash
|
||||
$ git check-ignore -v .DS_Store
|
||||
.git/info/exclude:7:notes.md notes.md
|
||||
```
|
||||
|
||||
At some point I added it to my repo's `.git/info/exclude` file. The `-v` flag
|
||||
(_verbose_) when included with `check-ignore` tells me the file location.
|
||||
|
||||
How about these pesky `.DS_Store` directories? How are those being ignored?
|
||||
|
||||
```bash
|
||||
$ git check-ignore -v .DS_Store
|
||||
/Users/jbranchaud/.gitignore:3:.DS_Store .DS_Store
|
||||
```
|
||||
|
||||
Ah yes, I had added it to my _global exclude file_ which I've configured in
|
||||
`~/.gitconfig` to be the `~/.gitignore` file.
|
||||
|
||||
See `man git-check-ignore` for more details.
|
||||
27
git/count-all-files-of-specific-type-tracked-by-git.md
Normal file
27
git/count-all-files-of-specific-type-tracked-by-git.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# Count All Files Of Specific Type Tracked By Git
|
||||
|
||||
I want to get a count of all the markdown files in my [TIL
|
||||
repo](https://github.com/jbranchaud/til). Since all the files I care about are
|
||||
tracked by `git`, I can use `git ls-files` to get a listing of all files. That
|
||||
command on its own lists all files tracked by your git repository. Though there
|
||||
are many other flags we can apply, that will do for my purposes.
|
||||
|
||||
By giving `git ls-files` a pattern to match against, I can turn up just, for
|
||||
instance, markdown files (`*.md`). I can pipe that to `wc -l` to get a count
|
||||
rather than exploding my terminal with a list of file names.
|
||||
|
||||
```bash
|
||||
❯ git ls-files '*.md' | wc -l
|
||||
1503
|
||||
```
|
||||
|
||||
That command includes `README.md` and `CONTRIBUTING.md`, but really I only want
|
||||
to count the markdown files that constitute a TIL. Those all happen to be
|
||||
nested under a single directory. So I can tweak the glob pattern like so:
|
||||
|
||||
```bash
|
||||
❯ git ls-files '*/*.md' | wc -l
|
||||
1501
|
||||
```
|
||||
|
||||
See `man git-ls-files` for more details.
|
||||
33
git/override-the-global-git-ignore-file.md
Normal file
33
git/override-the-global-git-ignore-file.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# Override The Global Git Ignore File
|
||||
|
||||
One of the places that `git` looks when deciding whether to pay attention to or
|
||||
ignore a file is in your global _ignore_ file. By default, `git` will look for
|
||||
this file at `$XDG_CONFIG_HOME/git/ignore` or `$HOME/.config/git/ignore`.
|
||||
|
||||
I don't have `$XDG_CONFIG_HOME` set on my machine, so it will fall back to the
|
||||
config directory under `$HOME`.
|
||||
|
||||
I may have to create the `git` directory and `ignore` file.
|
||||
|
||||
```bash
|
||||
$ mkdir $HOME/.config/git
|
||||
$ touch $HOME/.config/git/ignore
|
||||
```
|
||||
|
||||
Then I can add file and directories to exclude to that `ignore` file just like
|
||||
I would any other `.gitignore` file.
|
||||
|
||||
If I'd prefer for the global _ignore_ file to live somewhere else, I can
|
||||
specify that location and filename in my `$HOME/.gitconfig` file.
|
||||
|
||||
```
|
||||
[core]
|
||||
excludesFile = ~/.gitignore
|
||||
```
|
||||
|
||||
Setting this will override the default, meaning the default file mentioned
|
||||
above will be ignored ("now you know how it feels, ignore file!"). In this
|
||||
case, I'll need to create the `.gitignore` file in my home directory and add
|
||||
any of my ignore rules.
|
||||
|
||||
[source](https://git-scm.com/docs/gitignore)
|
||||
@@ -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)
|
||||
|
||||
56
go/do-something-n-times.md
Normal file
56
go/do-something-n-times.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# Do Something N Times
|
||||
|
||||
With Go 1.23 there is a new for-range syntax that makes looping a bit easier
|
||||
and more compact.
|
||||
|
||||
Instead of needing to set up our 3-part for-loop syntax, we can say we want to
|
||||
do something `N` times with `for range N`.
|
||||
|
||||
```go
|
||||
for range n {
|
||||
// do something
|
||||
}
|
||||
```
|
||||
|
||||
Let's look at an actual, runnable example:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
import "math/rand"
|
||||
import "time"
|
||||
|
||||
func main() {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
|
||||
food := []string{"taco", "burrito", "torta", "enchilada", "tostada"}
|
||||
|
||||
for range 5 {
|
||||
randomIndex := rand.Intn(len(food))
|
||||
fmt.Println(food[randomIndex])
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The output is random and might look something like this:
|
||||
|
||||
```bash
|
||||
$ go run loop.go
|
||||
taco
|
||||
burrito
|
||||
tostada
|
||||
taco
|
||||
enchilada
|
||||
```
|
||||
|
||||
I appreciate this syntax addition because it feels very akin to Ruby's `#times`
|
||||
method:
|
||||
|
||||
```ruby
|
||||
5.times do
|
||||
# do something
|
||||
end
|
||||
```
|
||||
|
||||
[source](https://eli.thegreenplace.net/2024/ranging-over-functions-in-go-123/)
|
||||
40
internet/add-styled-alerts-to-github-markdown-documents.md
Normal file
40
internet/add-styled-alerts-to-github-markdown-documents.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# Add Styled Alerts To GitHub Markdown Documents
|
||||
|
||||
The GFM (GitHub Flavored Markdown) variant of markdown adds some nice features
|
||||
to our GitHub-rendered markdown documents.
|
||||
|
||||
One such feature that has been around for a couple years, but which I only just
|
||||
learned about, are these styled alerts. There are five of them each with a
|
||||
different color and icon to help convey meaning.
|
||||
|
||||
```
|
||||
> [!NOTE]
|
||||
> Useful information that users should know, even when skimming content.
|
||||
|
||||
> [!TIP]
|
||||
> Helpful advice for doing things better or more easily.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Key information users need to know to achieve their goal.
|
||||
|
||||
> [!WARNING]
|
||||
> Urgent info that needs immediate user attention to avoid problems.
|
||||
|
||||
> [!CAUTION]
|
||||
> Advises about risks or negative outcomes of certain actions.
|
||||
```
|
||||
|
||||
I just added the following to the top of one of my project's READMEs to help me
|
||||
remember that it is not under active development.
|
||||
|
||||
```
|
||||
> [!WARNING]
|
||||
> This repo is not under active development, you might be looking for
|
||||
> [til-visualmode-dev](https://github.com/jbranchaud/til-visualmode-dev).
|
||||
```
|
||||
|
||||
Visit the GitHub docs for
|
||||
[Alerts](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#alerts)
|
||||
to see examples of how these render.
|
||||
|
||||
[source](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#alerts)
|
||||
21
internet/analyze-your-website-performance.md
Normal file
21
internet/analyze-your-website-performance.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# Analyze Your Website Performance
|
||||
|
||||
The [PageSpeed Insights](https://pagespeed.web.dev/) tool from Google is a
|
||||
great way to quickly get actionable insights about where to improve your
|
||||
website and app's _Performance_, _Accessibility_, and _SEO_.
|
||||
|
||||
To see how your public site or app does, grab its URL and analyze it at
|
||||
[PageSpeed Insights](https://pagespeed.web.dev/).
|
||||
|
||||
It will take a minute to run on either Mobile or Desktop (make sure to check
|
||||
both) and then will output four headline numbers (out of 100) for each of the
|
||||
categories.
|
||||
|
||||
You can then dig in to each category to see what recommendations they make for
|
||||
improving your score.
|
||||
|
||||
This can also be run directly from Chrome devtools which is useful if you want
|
||||
to see how a locally running site is doing. You can run the analysis from the
|
||||
_Lighthouse_ tab of devtools. Note: if the _Performance_ score looks bad, it
|
||||
might be that you are running a non-optimized dev server that isn't reflective
|
||||
of how your site would do in production.
|
||||
29
internet/digraph-unicode-characters-have-a-titlecase.md
Normal file
29
internet/digraph-unicode-characters-have-a-titlecase.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# Digraph Unicode Characters Have a Titlecase
|
||||
|
||||
Coming from primarily being exposed to the US American alphabet, I'm familiar
|
||||
with characters that I type into the computer having one of two cases. Either
|
||||
it is lowercase by default (`c`) or I can hit the shift key to produce the
|
||||
uppercase version (`C`).
|
||||
|
||||
Unicode, which has broad support for character encoding across most languages,
|
||||
has a couple characters that are called _digraphs_. These are single code
|
||||
points, but look like they are made up of two characters.
|
||||
|
||||
A good example of this is `dž`. And if that character were to appear in an all
|
||||
uppercase word, then it would display as `DŽ`.
|
||||
|
||||
But what if it appears at the beginning of a capitalized word?
|
||||
|
||||
That's where _titlecase_ comes into the picture -- `Dž`.
|
||||
|
||||
From [wikipedia](https://en.wikipedia.org/wiki/D%C5%BE):
|
||||
|
||||
> Note that when the letter is the initial of a capitalised word (like Džungla
|
||||
> or Džemper, or personal names like Džemal or Džamonja), the ž is not
|
||||
> uppercase. Only when the whole word is written in uppercase, is the Ž
|
||||
> capitalised.
|
||||
|
||||
(I find it odd that wikipedia's article on this digraph code point is using
|
||||
separate characters instead of the digraph.)
|
||||
|
||||
[source](https://devblogs.microsoft.com/oldnewthing/20241031-00/?p=110443)
|
||||
14
internet/exclude-ai-overview-from-google-search.md
Normal file
14
internet/exclude-ai-overview-from-google-search.md
Normal file
@@ -0,0 +1,14 @@
|
||||
# Exclude AI Overview From Google Search
|
||||
|
||||
At the top of most Google searches these days is a section of text that takes a
|
||||
moment to appear, presumably because it is being generated in the moment. This
|
||||
is Google's _AI Overview_. These are sometimes useful summaries of the article
|
||||
you are about to click on anyway. Other times the overview is no good, it takes
|
||||
up a bunch of screen real estate, and may even [10x the energy consumed by a
|
||||
regular
|
||||
search](https://www.reddit.com/r/technology/comments/1dsvefb/googles_ai_search_summaries_use_10x_more_energy/).
|
||||
|
||||
If you want to exclude the _AI Overview_, tack on a `-ai` when writing out your
|
||||
search query.
|
||||
|
||||
[source](https://www.yahoo.com/tech/turn-off-ai-overview-results-170014202.html)
|
||||
55
java/ensure-resources-always-get-closed.md
Normal file
55
java/ensure-resources-always-get-closed.md
Normal file
@@ -0,0 +1,55 @@
|
||||
# Ensure Resources Always Get Closed
|
||||
|
||||
Java has a construct known as _try-with-resource_ that allows us to always
|
||||
ensure opened resources get closed. This is safer than similar cleanup in the
|
||||
`finally` block which could still leave a memory leak if an error occurs in
|
||||
that block.
|
||||
|
||||
To use the _try-with-resource_ construct, instantiate your opened resource in
|
||||
parentheses with the `try`.
|
||||
|
||||
```java
|
||||
try (BufferedReader reader = new BufferedReader(new FileReader(filename))) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
The resource will be automatically closed when the try/catch block completes.
|
||||
|
||||
Here is a full example:
|
||||
|
||||
```java
|
||||
import java.io.BufferedReader;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
|
||||
public class FileReaderExample {
|
||||
public static void main(String[] args) {
|
||||
String fileName = "example.txt";
|
||||
|
||||
try (BufferedReader reader = new BufferedReader(new FileReader(fileName))) {
|
||||
String line;
|
||||
int lineCount = 0;
|
||||
|
||||
while ((line = reader.readLine()) != null && lineCount < 5) {
|
||||
System.out.println(line);
|
||||
lineCount++;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
System.out.println("An error occurred while reading the file: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You can even specify multiple resources in one `try`. The above does that, but
|
||||
this will make it more obvious:
|
||||
|
||||
```java
|
||||
try (FileReader fr = new FileReader(filename);
|
||||
BufferedReader br = new BufferedReader(fr)) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
[source](https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html)
|
||||
61
javascript/make-truly-deep-clone-with-structured-clone.md
Normal file
61
javascript/make-truly-deep-clone-with-structured-clone.md
Normal file
@@ -0,0 +1,61 @@
|
||||
# Make Truly Deep Clone With Structured Clone
|
||||
|
||||
There are a lot of ways to make a copy of an object. They are all hacks and
|
||||
they all fail in certain circumstances. Using the spread trick only gives you a
|
||||
shallow copy where references to nested objects and arrays can still be
|
||||
updated. The `JSON.stringify` trick has to make things like dates into strings,
|
||||
so it is lossy.
|
||||
|
||||
There is however now a dedicated method for deep copies with broad support
|
||||
called
|
||||
[`structuredClone`](https://developer.mozilla.org/en-US/docs/Web/API/Window/structuredClone).
|
||||
It is available on `window`. Let's take a look at it and see how it comparse to
|
||||
the spread operator trick.
|
||||
|
||||
```javascript
|
||||
> // some data setup
|
||||
|
||||
> const data = { one: 1, two: 2, rest: [3,4,5] }
|
||||
|
||||
> const obj = { hello: 'world', taco: 'bell', data }
|
||||
|
||||
> const shallowObj = { ...obj }
|
||||
|
||||
> const deepObj = structuredClone(obj)
|
||||
|
||||
> // let's modify the original `data.rest` array
|
||||
|
||||
> data.rest.push(6)
|
||||
4
|
||||
> data
|
||||
{ one: 1, two: 2, rest: [ 3, 4, 5, 6 ] }
|
||||
|
||||
> // now let's see who was impacted by that mutation
|
||||
|
||||
> obj
|
||||
{
|
||||
hello: 'world',
|
||||
taco: 'bell',
|
||||
data: { one: 1, two: 2, rest: [ 3, 4, 5, 6 ] }
|
||||
}
|
||||
|
||||
> shallowObj
|
||||
{
|
||||
hello: 'world',
|
||||
taco: 'bell',
|
||||
data: { one: 1, two: 2, rest: [ 3, 4, 5, 6 ] }
|
||||
}
|
||||
|
||||
> deepObj
|
||||
{
|
||||
hello: 'world',
|
||||
taco: 'bell',
|
||||
data: { one: 1, two: 2, rest: [ 3, 4, 5 ] }
|
||||
}
|
||||
```
|
||||
|
||||
The `shallowObj` from the spread operator copy was mutated even though we
|
||||
didn't intend for that. The `deepObj` from `structuredClone` was a true deep
|
||||
copy and was unaffected.
|
||||
|
||||
[source](https://www.builder.io/blog/structured-clone)
|
||||
55
javascript/prevent-hidden-element-from-flickering-on-load.md
Normal file
55
javascript/prevent-hidden-element-from-flickering-on-load.md
Normal file
@@ -0,0 +1,55 @@
|
||||
# Prevent Hidden Element From Flickering On Load
|
||||
|
||||
Here is what it might look like to use [Alpine.js](https://alpinejs.dev/) to
|
||||
sprinkle in some JavaScript for controlling a dropdown menu.
|
||||
|
||||
```html
|
||||
<div x-data="{ profileDropdownOpen: false }">
|
||||
<button
|
||||
type="button"
|
||||
@click="profileDropdownOpen = !profileDropdownOpen"
|
||||
>
|
||||
<!-- some inner html -->
|
||||
</button>
|
||||
<div x-show="profileDropdownOpen" role="menu">
|
||||
<a href="/profile" role="menuitem">Your Profile</a>
|
||||
<a href="/sign-out" role="menuitem">Sign Out</a>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
Functionally that will work. You can click the button to toggle the menu open
|
||||
and closed.
|
||||
|
||||
What you might notice, however, when you refresh the page is that the menu
|
||||
flickers open as the page first loads and then disappears. This is a quirk of
|
||||
the element being rendered before Alpine.js is loaded and the
|
||||
[`x-show`](https://alpinejs.dev/directives/show) directive has a chance to take
|
||||
effect.
|
||||
|
||||
To get around this, we can _cloak_ any element with an `x-show` directive that
|
||||
should be hidden by default.
|
||||
|
||||
```html
|
||||
<div x-data="{ profileDropdownOpen: false }">
|
||||
<button
|
||||
type="button"
|
||||
@click="profileDropdownOpen = !profileDropdownOpen"
|
||||
>
|
||||
<!-- some inner html -->
|
||||
</button>
|
||||
<div x-cloak x-show="profileDropdownOpen" role="menu">
|
||||
<a href="/profile" role="menuitem">Your Profile</a>
|
||||
<a href="/sign-out" role="menuitem">Sign Out</a>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
This addition needs to be paired with some custom CSS to hide any _cloaked_
|
||||
elements.
|
||||
|
||||
```css
|
||||
[x-cloak] { display: none !important; }
|
||||
```
|
||||
|
||||
[source](https://alpinejs.dev/directives/cloak)
|
||||
25
postgres/add-unique-constraint-using-existing-index.md
Normal file
25
postgres/add-unique-constraint-using-existing-index.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# Add Unique Constraint Using Existing Index
|
||||
|
||||
Adding a unique constraint to an existing column on a production table can
|
||||
block updates. If we need to avoid this kind of locking for the duration of
|
||||
index creation, then we can first create the index concurrently and then use
|
||||
that existing index to back the unique constraint.
|
||||
|
||||
```sql
|
||||
create index concurrently users_email_idx on users (email);
|
||||
|
||||
-- wait for that to complete
|
||||
|
||||
alter table users
|
||||
add constraint unique_users_email unique using index users_email_idx;
|
||||
```
|
||||
|
||||
First, we concurrently create the index. The time this takes will depend on how
|
||||
large the table is. That's the blocking time we are avoiding with this
|
||||
approach. Then once that completes we can apply a unique constraint using that
|
||||
preexisting index.
|
||||
|
||||
Note: if a non-unique value exists in the table for that column, adding the
|
||||
constraint will fail. You'll need to deal with that _duplicate_ value first.
|
||||
|
||||
[source](https://dba.stackexchange.com/questions/81627/postgresql-9-3-add-unique-constraint-using-an-existing-unique-index)
|
||||
@@ -4,7 +4,9 @@ PostgreSQL's `between` construct allows you to make a comparison _between_
|
||||
two values (numbers, timestamps, etc.).
|
||||
|
||||
```sql
|
||||
> select * from generate_series(1,10) as numbers(a) where numbers.a between 3 and 6;
|
||||
> select *
|
||||
from generate_series(1,10) as numbers(a)
|
||||
where numbers.a between 3 and 6;
|
||||
a
|
||||
---
|
||||
3
|
||||
@@ -17,7 +19,9 @@ If you supply an empty range by using the larger of the two values first, an
|
||||
empty set will result.
|
||||
|
||||
```sql
|
||||
> select * from generate_series(1,10) as numbers(a) where numbers.a between 6 and 3;
|
||||
> select *
|
||||
from generate_series(1,10) as numbers(a)
|
||||
where numbers.a between 6 and 3;
|
||||
a
|
||||
---
|
||||
```
|
||||
@@ -26,7 +30,9 @@ Tacking `symmetric` onto the `between` construct is one way to avoid this
|
||||
issue.
|
||||
|
||||
```sql
|
||||
> select * from generate_series(1,10) as numbers(a) where numbers.a between symmetric 6 and 3;
|
||||
> select *
|
||||
from generate_series(1,10) as numbers(a)
|
||||
where numbers.a between symmetric 6 and 3;
|
||||
a
|
||||
---
|
||||
3
|
||||
@@ -39,3 +45,5 @@ issue.
|
||||
> that the argument to the left of AND be less than or equal to the argument
|
||||
> on the right. If it is not, those two arguments are automatically swapped,
|
||||
> so that a nonempty range is always implied.
|
||||
|
||||
[source](https://www.postgresql.org/docs/current/functions-comparison.html#:~:text=BETWEEN%20SYMMETRIC%20is%20like%20BETWEEN,nonempty%20range%20is%20always%20implied.)
|
||||
|
||||
53
postgres/concatenate-strings-with-a-separator.md
Normal file
53
postgres/concatenate-strings-with-a-separator.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# Concatenate Strings With A Separator
|
||||
|
||||
I was putting together an example of using a generated column that concatenates
|
||||
string values from a few other columns. I used manual concatenation with the
|
||||
`||` operator like so:
|
||||
|
||||
```sql
|
||||
create table folders (
|
||||
id integer generated always as identity primary key,
|
||||
user_id integer not null,
|
||||
name text not null,
|
||||
parent_folder_id integer references folders(id),
|
||||
path text generated always as (
|
||||
user_id::text || ':' || lower(name) || ':' || coalesce(parent_folder_id::text, '0')
|
||||
) stored
|
||||
);
|
||||
```
|
||||
|
||||
Instead of doing that manual concatenation for the `path` generated column, I
|
||||
can use
|
||||
[`concat_ws`](https://www.postgresql.org/docs/current/functions-string.html).
|
||||
|
||||
```sql
|
||||
create table folders (
|
||||
id integer generated always as identity primary key,
|
||||
user_id integer not null,
|
||||
name text not null,
|
||||
parent_folder_id integer references folders(id),
|
||||
path text generated always as (
|
||||
concat_ws(
|
||||
':',
|
||||
user_id::text,
|
||||
lower(name),
|
||||
coalesce(parent_folder_id::text, '0')
|
||||
)
|
||||
) stored
|
||||
);
|
||||
```
|
||||
|
||||
The first argument to `concat_ws` is the separator I want to use. The remaining
|
||||
arguments are the strings that should be concatenated with that separator.
|
||||
|
||||
One other things that is nice about `concat_ws` is that it will ignore `null`
|
||||
values that it receives.
|
||||
|
||||
```sql
|
||||
> select concat_ws(':', 'one', 'two', null, 'three');
|
||||
+---------------+
|
||||
| concat_ws |
|
||||
|---------------|
|
||||
| one:two:three |
|
||||
+---------------+
|
||||
```
|
||||
35
postgres/enforce-uniqueness-on-column-expression.md
Normal file
35
postgres/enforce-uniqueness-on-column-expression.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# Enforce Uniqueness On Column Expression
|
||||
|
||||
When creating a table for, say `users`, where you will store `email` addresses,
|
||||
you'll likely want to enforce uniqueness on the that `email` field. And because
|
||||
people have all sorts of ways of entering their emails, in terms of casing, you
|
||||
may be tempted to try to enforce uniqueness on a lowercased version of `email`.
|
||||
|
||||
```sql
|
||||
create table users (
|
||||
id integer generated always as identity primary key,
|
||||
email text not null,
|
||||
unique ( lower(email) ) -- !! this won't work
|
||||
);
|
||||
```
|
||||
|
||||
A unique _constraint_ must be on one or more columns. You cannot include an
|
||||
_expression_ when defining the unique constraint.
|
||||
|
||||
You can however accomplish similar aims with [a _unique index_ on the
|
||||
expression](https://www.postgresql.org/docs/current/indexes-expressional.html).
|
||||
That is because the index is able to store the result of the expression in
|
||||
itself.
|
||||
|
||||
```sql
|
||||
create table users (
|
||||
id integer generated always as identity primary key,
|
||||
email text not null
|
||||
);
|
||||
|
||||
create unique index unq_lower_email on users ( lower(email) );
|
||||
```
|
||||
|
||||
This is likely what you want for this example anyway because it will probably
|
||||
be a common query to look up the user by `lower(email)` and the index will
|
||||
speed up those queries.
|
||||
56
postgres/generate-random-alphanumeric-identifier.md
Normal file
56
postgres/generate-random-alphanumeric-identifier.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# Generate Random Alphanumeric Identifier
|
||||
|
||||
Here is a PostgreSQL query that uses
|
||||
[`pgcrypto`](https://www.postgresql.org/docs/current/pgcrypto.html) (for
|
||||
[`get_random_bytes`](https://www.postgresql.org/docs/current/pgcrypto.html#PGCRYPTO-RANDOM-DATA-FUNCS))
|
||||
and a CTE to generate a cryptographically-random 8-character alphanumeric
|
||||
identifier.
|
||||
|
||||
```sql
|
||||
-- First ensure pgcrypto is installed
|
||||
create extension if not exists pgcrypto;
|
||||
|
||||
-- Generates a single 8-character identifier
|
||||
with chars as (
|
||||
-- excludes some look-alike characters
|
||||
select '23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz' as charset
|
||||
),
|
||||
random_bytes as (
|
||||
select gen_random_bytes(8) as bytes
|
||||
),
|
||||
positions as (
|
||||
select generate_series(0, 7) as pos
|
||||
)
|
||||
select string_agg(
|
||||
substr(
|
||||
charset,
|
||||
(get_byte(bytes, pos) % length(charset)) + 1,
|
||||
1
|
||||
),
|
||||
'' order by pos
|
||||
) as short_id
|
||||
from positions, random_bytes, chars;
|
||||
```
|
||||
|
||||
Here is an example of the output:
|
||||
|
||||
```sql
|
||||
+----------+
|
||||
| short_id |
|
||||
|----------|
|
||||
| NXdu9AnV |
|
||||
+----------+
|
||||
```
|
||||
|
||||
The
|
||||
[`generate_series`](https://www.postgresql.org/docs/current/functions-srf.html)
|
||||
gives us an 8-row table from 0 to 7 that we can use as indexes into the byte
|
||||
positions of the value we get from `gen_random_bytes`. Those random bytes get
|
||||
mapped to individual alphanumeric characters from `chars`. That then gets
|
||||
squeezed together with `string_agg`.
|
||||
|
||||
Note: the character set excludes some characters that can be mistaken for one
|
||||
another like `0` and `O` or `1` and `l`.
|
||||
|
||||
Note: you could change the right-bound of the `generate_series` to generate a
|
||||
different length identifier.
|
||||
30
postgres/postgres-does-not-support-unsigned-integers.md
Normal file
30
postgres/postgres-does-not-support-unsigned-integers.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# Postgres Does Not Support Unsigned Integers
|
||||
|
||||
PostgreSQL has a variety of sizes of integer types, from `smallint` (2 bytes)
|
||||
to `integer` (4 bytes) to `bigint` (8 bytes), as well as [other numeric
|
||||
types](https://www.postgresql.org/docs/current/datatype-numeric.html).
|
||||
|
||||
It does _not_ however support unsigned versions of these numeric types.
|
||||
|
||||
That means, with an `integer` for instance, we can store numbers between
|
||||
`-2147483648` and `+2147483647`. That's everything that can fit into 4 bytes.
|
||||
In a system that supported 4 byte unsigned integers we'd be able to represent
|
||||
from `0` all the way up to `4294967295`.
|
||||
|
||||
In PostgreSQL, we're limited to these _signed_ numeric types.
|
||||
|
||||
That means if we were hoping that the data type could essentially enforce a
|
||||
non-negative restriction on the data in one of our columns, we're going to have
|
||||
to be more creative. The obvious choice to me is to consider adding a [check
|
||||
constraint](https://www.postgresql.org/docs/current/ddl-constraints.html#DDL-CONSTRAINTS-CHECK-CONSTRAINTS)
|
||||
(e.g. `quantity integer check (quantity > 0)`).
|
||||
|
||||
Another option, as pointed out by [this StackOverflow
|
||||
answer](https://stackoverflow.com/a/31833279/535590), is to create [a
|
||||
user-defined _domain
|
||||
type_](https://www.postgresql.org/docs/current/domains.html) that restricts
|
||||
valid values. To me, the ergonomics of using a domain type are a bit awkward
|
||||
and not worth the effort.
|
||||
|
||||
With either of these solutions, we are only approximating an unsigned integer
|
||||
and do not actually have the same range of values available.
|
||||
46
postgres/put-unique-constraint-on-generated-column.md
Normal file
46
postgres/put-unique-constraint-on-generated-column.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# Put Unique Constraint On Generated Column
|
||||
|
||||
You cannot apply a _unique constraint_ to an expression over a column, e.g.
|
||||
`lower(email)`. You can, however, create a [generated
|
||||
column](https://www.postgresql.org/docs/current/ddl-generated-columns.html) for
|
||||
that expression and then apply the unique constraint to that generated column.
|
||||
|
||||
Here is what that could look like:
|
||||
|
||||
```sql
|
||||
> create table users (
|
||||
id integer generated always as identity primary key,
|
||||
name text not null,
|
||||
email text not null,
|
||||
email_lower text generated always as (lower(email)) stored,
|
||||
unique ( email_lower )
|
||||
);
|
||||
|
||||
> \d users
|
||||
+-------------+---------+-----------------------------------------------------------------+
|
||||
| Column | Type | Modifiers |
|
||||
|-------------+---------+-----------------------------------------------------------------|
|
||||
| id | integer | not null generated always as identity |
|
||||
| name | text | not null |
|
||||
| email | text | not null |
|
||||
| email_lower | text | default lower(email) generated always as (lower(email)) stored |
|
||||
+-------------+---------+-----------------------------------------------------------------+
|
||||
Indexes:
|
||||
"users_pkey" PRIMARY KEY, btree (id)
|
||||
"users_email_lower_key" UNIQUE CONSTRAINT, btree (email_lower)
|
||||
```
|
||||
|
||||
And then an demonstration of violating that constraint:
|
||||
|
||||
```sql
|
||||
|
||||
> insert into users (name, email) values ('Bob', 'bob@email.com');
|
||||
INSERT 0 1
|
||||
|
||||
> insert into users (name, email) values ('Bobby', 'BOB@email.com');
|
||||
duplicate key value violates unique constraint "users_email_lower_key"
|
||||
DETAIL: Key (email_lower)=(bob@email.com) already exists.
|
||||
```
|
||||
|
||||
The main tradeoff here is that you are doubling the amount of storage you need
|
||||
for that column. Unless it is a massive table, that is likely not an issue.
|
||||
80
postgres/table-names-are-treated-as-lower-case-by-default.md
Normal file
80
postgres/table-names-are-treated-as-lower-case-by-default.md
Normal file
@@ -0,0 +1,80 @@
|
||||
# Table Names Are Treated As Lower-Case By Default
|
||||
|
||||
This one is a bit unintuitive and can cause some real confusion -- when you
|
||||
create a table in PostgreSQL, any casing is ignored, it is treated as
|
||||
lower-case. Let's see it to believe it:
|
||||
|
||||
```sql
|
||||
> create table BookMarks (
|
||||
id integer generated always as identity primary key,
|
||||
location text not null
|
||||
);
|
||||
|
||||
> \d
|
||||
+--------+--------------------+----------+----------+
|
||||
| Schema | Name | Type | Owner |
|
||||
|--------+--------------------+----------+----------|
|
||||
| public | bookmarks | table | postgres |
|
||||
| public | bookmarks_id_seq | sequence | postgres |
|
||||
+--------+--------------------+----------+----------+
|
||||
```
|
||||
|
||||
Notice that when we list our tables, the uppercase `M` and `B` are gone. That's
|
||||
because Postgres folds away the casing when processing the table name
|
||||
identifier.
|
||||
|
||||
It doesn't matter how we refer to it for queries:
|
||||
|
||||
```sql
|
||||
> select * from BookMarks;
|
||||
+----+----------+
|
||||
| id | location |
|
||||
|----+----------|
|
||||
+----+----------+
|
||||
|
||||
> select * from bookmarks;
|
||||
+----+----------+
|
||||
| id | location |
|
||||
|----+----------|
|
||||
+----+----------+
|
||||
```
|
||||
|
||||
You can force Postgres to respect the casing by wrapping the table name in
|
||||
quotes.
|
||||
|
||||
```sql
|
||||
> create table "BookMarks" (
|
||||
id integer generated always as identity primary key,
|
||||
location text not null
|
||||
);
|
||||
|
||||
> \d
|
||||
+--------+--------------------+----------+----------+
|
||||
| Schema | Name | Type | Owner |
|
||||
|--------+--------------------+----------+----------|
|
||||
| public | BookMarks | table | postgres |
|
||||
| public | BookMarks_id_seq | sequence | postgres |
|
||||
+--------+--------------------+----------+----------+
|
||||
|
||||
> select * from "BookMarks";
|
||||
+----+----------+
|
||||
| id | location |
|
||||
|----+----------|
|
||||
+----+----------+
|
||||
|
||||
> select * from "bookmarks";
|
||||
relation "bookmarks" does not exist
|
||||
LINE 1: select * from "bookmarks"
|
||||
^
|
||||
|
||||
> select * from BookMarks;
|
||||
relation "bookmarks" does not exist
|
||||
LINE 1: select * from BookMarks
|
||||
^
|
||||
```
|
||||
|
||||
That then means you have to quote your table name anytime you want to refer to
|
||||
it in a query. It's not worth it. It is better to always keep your table names
|
||||
lower-case using snake case.
|
||||
|
||||
[source](https://weiyen.net/articles/avoid-capital-letters-in-postgres-names)
|
||||
22
python/dunder-methods.md
Normal file
22
python/dunder-methods.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# Dunder Methods
|
||||
|
||||
Python has all kinds of special, or rather, _magic_ methods that allow for
|
||||
customizing all kinds of class behavior. There is `__init__()`, `__bool__()`,
|
||||
and so many others.
|
||||
|
||||
The thing they all have in common is that their names are wrapped in _double
|
||||
underscores_. This is why they are called _dunder methods_.
|
||||
|
||||
Some of these are used every single day, like the `__init__()` method for
|
||||
defining how a class should create an object. Others, used from time to time,
|
||||
are for overriding how comparisons or conversions happen. E.g. you may want to
|
||||
override `__bool__()` or `__len__()` to customize the truthiness of a custom
|
||||
class.
|
||||
|
||||
There are so many others, ones you probably haven't even heard of. To see a
|
||||
full listing, check out this [cheat sheet of every dunder
|
||||
method](https://www.pythonmorsels.com/every-dunder-method/#cheat-sheet).
|
||||
|
||||
Note: these are not to be confused with _dunder attributes_ which are things
|
||||
like `__name__`, `__file__`, and `__version__` which correspond to a value that
|
||||
you can access in a specific context rather than behavior to override.
|
||||
86
python/override-the-boolean-context-of-a-class.md
Normal file
86
python/override-the-boolean-context-of-a-class.md
Normal file
@@ -0,0 +1,86 @@
|
||||
# Override The Boolean Context Of A Class
|
||||
|
||||
Everything in Python has a truthiness that can be checked with `bool()`. An
|
||||
empty list (`[]`) is falsy. A non-empty list (`[1,2,3]`) is truthy. Similar
|
||||
with numbers:
|
||||
|
||||
```python
|
||||
>>> bool(0)
|
||||
False
|
||||
>>> bool(1)
|
||||
True
|
||||
```
|
||||
|
||||
Any instance of an object is going to be truthy by default. If you want to
|
||||
control in what context an instance is considered truthy or falsy, you can
|
||||
override
|
||||
[`__bool__()`](https://docs.python.org/3/reference/datamodel.html#object.__bool__).
|
||||
If that's not implemented, but
|
||||
[`__len__()`](https://docs.python.org/3/reference/datamodel.html#object.__len__)
|
||||
is, then it will fallback to that.
|
||||
|
||||
Let's look at a few example classes:
|
||||
|
||||
```python
|
||||
class CartZero:
|
||||
def __init__(self, items=[]):
|
||||
self.items = items or []
|
||||
|
||||
class CartBool:
|
||||
def __init__(self, items=[]):
|
||||
self.items = items or []
|
||||
|
||||
def __bool__(self):
|
||||
print("__bool__() override")
|
||||
return bool(self.items)
|
||||
|
||||
class CartLen:
|
||||
def __init__(self, items=[]):
|
||||
self.items = items or []
|
||||
|
||||
def __len__(self):
|
||||
print("__len__() override")
|
||||
return len(self.items)
|
||||
|
||||
class CartBoolAndLen:
|
||||
def __init__(self, items=[]):
|
||||
self.items = items or []
|
||||
|
||||
def __len__(self):
|
||||
print("__len__() override")
|
||||
return len(self.items)
|
||||
|
||||
def __bool__(self):
|
||||
print("__bool__() override")
|
||||
return bool(self.items)
|
||||
|
||||
cart1 = CartZero()
|
||||
cart2 = CartBool()
|
||||
cart3 = CartLen()
|
||||
cart4 = CartBoolAndLen()
|
||||
|
||||
print("CartZero() -> %s" %(bool(cart1)))
|
||||
print('')
|
||||
print("CartBool() -> %s" %(bool(cart2)))
|
||||
print('')
|
||||
print("CartLen() -> %s" %(bool(cart3)))
|
||||
print('')
|
||||
print("CartBoolAndLen() -> %s" %(bool(cart4)))
|
||||
```
|
||||
|
||||
An 'empty' `Cart` be default is truthy. However, we can override some
|
||||
combination of `__bool__()` or `__len__()` to give it a boolean context that
|
||||
goes `false` when "empty".
|
||||
|
||||
```
|
||||
CartZero() -> True
|
||||
|
||||
__bool__() override
|
||||
CartBool() -> False
|
||||
|
||||
__len__() override
|
||||
CartLen() -> False
|
||||
|
||||
__bool__() override
|
||||
CartBoolAndLen() -> False
|
||||
```
|
||||
49
python/store-and-access-immutable-data-in-a-tuple.md
Normal file
49
python/store-and-access-immutable-data-in-a-tuple.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# Store And Access Immutable Data In A Tuple
|
||||
|
||||
You can store heterogeneous data (of varying types) as a _tuple_ which is a
|
||||
light-weight immutable data structure.
|
||||
|
||||
You can be explicit about the tuple by wrapping the items in parentheses:
|
||||
|
||||
```python
|
||||
>>> book = ('An Immense World', 'Ed Yong', 2022)
|
||||
```
|
||||
|
||||
Though it is also possible to comma-separate the items and forego the
|
||||
parentheses.
|
||||
|
||||
```python
|
||||
>>> book2 = 'The Shining', 'Stephen King', 1977
|
||||
>>> book2
|
||||
('The Shining', 'Stephen King', 1977)
|
||||
```
|
||||
|
||||
Once we have our tuple, we can access any item from it positionally. We can
|
||||
also use _sequence unpacking_ to assign the values to a series of variables:
|
||||
|
||||
```python
|
||||
>>> book[0]
|
||||
'An Immense World'
|
||||
>>> book[1]
|
||||
'Ed Yong'
|
||||
>>> book[2]
|
||||
2022
|
||||
>>> title, author, publication_year = book
|
||||
>>> title
|
||||
'An Immense World'
|
||||
>>> author
|
||||
'Ed Yong'
|
||||
>>> publication_year
|
||||
2022
|
||||
```
|
||||
|
||||
And, as promised, it is immutable (unlike lists):
|
||||
|
||||
```python
|
||||
>>> book[1] = 'Agatha Christie'
|
||||
Traceback (most recent call last):
|
||||
File "<stdin>", line 1, in <module>
|
||||
TypeError: 'tuple' object does not support item assignment
|
||||
```
|
||||
|
||||
[source](https://docs.python.org/3/tutorial/datastructures.html#tuples-and-sequences)
|
||||
30
rails/create-table-with-bigint-id-as-primary-key.md
Normal file
30
rails/create-table-with-bigint-id-as-primary-key.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# Create Table With bigint ID As Primary Key
|
||||
|
||||
When creating a new table with an ActiveRecord migration, we specify all the
|
||||
fields _except_ the `id`. The `id`, which is the primary key, is implicit. We
|
||||
get it by default.
|
||||
|
||||
The type of that `id` defaults to `int` which is a 32-bit signed integer.
|
||||
|
||||
We can override the type of `id` in a variety of ways. The one I prefer in most
|
||||
cases is to make the `id` of type `bigint`. This is a 64-bit signed integer. It
|
||||
offers quite a bit more headroom for the number of unique identifies in our
|
||||
table.
|
||||
|
||||
This can be specified by including `id: :bigint` as an option to the
|
||||
`create_table` method.
|
||||
|
||||
```ruby
|
||||
class CreatePosts < ActiveRecord::Migration[8.0]
|
||||
def change
|
||||
create_table :posts, id: :bigint do |t|
|
||||
t.string :title, null: false
|
||||
t.string :body, null: false
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
[source](https://api.rubyonrails.org/v7.1/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html#method-i-create_table)
|
||||
49
rails/empty-find-by-returns-first-record.md
Normal file
49
rails/empty-find-by-returns-first-record.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# Empty find_by Returns First Record
|
||||
|
||||
During a RubyConf 2024 talk, a speaker mentioned that if you pass `nil` to
|
||||
[ActiveRecord's `#find_by`
|
||||
method](https://api.rubyonrails.org/classes/ActiveRecord/FinderMethods.html#method-i-find_by),
|
||||
it will return the first record from the database. This is a bit unintuitive,
|
||||
so lets look at an example and then I'll show you why.
|
||||
|
||||
```ruby
|
||||
> Book.first
|
||||
#=> #<Book:0x00000001142e4c48 id: 13, title: "The Secret History", ... >
|
||||
|
||||
> Book.find_by(nil)
|
||||
#=> #<Book:0x00000001142ca3c0 id: 13, title: "The Secret History", ... >
|
||||
```
|
||||
|
||||
So, that is the same object in both cases, but why?
|
||||
|
||||
Our first hint is in the SQL that gets constructed when making that method
|
||||
call.
|
||||
|
||||
```ruby
|
||||
Book Load (2.5ms) SELECT "books".* FROM "books" LIMIT $1 [["LIMIT", 1]]
|
||||
```
|
||||
|
||||
It's grabbing all books and limiting to _one_ result.
|
||||
|
||||
Lets look at the underlying implementation of the `#find_by` method.
|
||||
|
||||
```ruby
|
||||
# File activerecord/lib/active_record/relation/finder_methods.rb, line 111
|
||||
def find_by(arg, *args)
|
||||
where(arg, *args).take
|
||||
end
|
||||
```
|
||||
|
||||
Sure enough, the implementation is a `#where` followed by a `#take`. Since the
|
||||
`#where` is receiving `nil` as its `arg`, there are no conditions _filtering_
|
||||
the query. And the `#take` corresponds to the `limit 1`.
|
||||
|
||||
Knowing that, we can understand that we will also get the first record from the
|
||||
database if we call `#find_by` with `{}`. Again, no conditions to filter on, so
|
||||
give me all books limited to one.
|
||||
|
||||
One small caveat: notice how there is no `order by` clause in the above SQL
|
||||
output. This differs from `Books.first` which implicitly does an order on the
|
||||
`id` column. Though these method are likely to return the same result, the
|
||||
ordering of `#find_by` is not guaranteed to be the same without an `order by`
|
||||
clause.
|
||||
63
rails/prefer-select-all-over-execute-for-read-queries.md
Normal file
63
rails/prefer-select-all-over-execute-for-read-queries.md
Normal file
@@ -0,0 +1,63 @@
|
||||
# Prefer select_all Over execute For Read Queries
|
||||
|
||||
Though the `#execute` function provided by ActiveRecord technically works as a
|
||||
general-purpose query runner for strings of raw SQL, it has some downsides.
|
||||
|
||||
First, let's say we have a large semi-complex (better in SQL than ActiveRecord
|
||||
DSL) SQL query defined in a heredoc.
|
||||
|
||||
```ruby
|
||||
books_by_status_query = <<-SQL
|
||||
select
|
||||
books.*,
|
||||
latest_statuses.status as current_status,
|
||||
array_to_json(array_agg(...)) as reading_statuses
|
||||
from books
|
||||
-- plus several left joins
|
||||
-- where clause, group by, and order by
|
||||
SQL
|
||||
```
|
||||
|
||||
I reflexively reach for
|
||||
[`#execute`](https://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/DatabaseStatements.html#method-i-execute)
|
||||
in a situation like that:
|
||||
|
||||
```ruby
|
||||
result = ActiveRecord::Base.connection.execute(books_by_status_query)
|
||||
```
|
||||
|
||||
However, if we're doing a read-only query and we are expecting multiple rows in
|
||||
the result, then we are better off reaching for
|
||||
[`#select_all`](https://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/DatabaseStatements.html#method-i-select_all).
|
||||
|
||||
```ruby
|
||||
result = ActiveRecord::Base.connection.select_all(books_by_status_query)
|
||||
```
|
||||
|
||||
It has the advantage of semantically communicating that it's just a read and
|
||||
won't have any side-effects. It also avoids an unnecessary clear of the query
|
||||
cache.
|
||||
|
||||
> Note: [when execute is used] the query is assumed to have side effects and
|
||||
> the query cache will be cleared. If the query is read-only, consider using
|
||||
> select_all instead.
|
||||
|
||||
The `#execute` method also has been known to leak memory with some database
|
||||
connectors.
|
||||
|
||||
> Note: depending on your database connector, the result returned by this
|
||||
> method may be manually memory managed. Consider using exec_query wrapper
|
||||
> instead.
|
||||
|
||||
We can then iterate through and transform the results just as we would have
|
||||
done with `#execute`.
|
||||
|
||||
```ruby
|
||||
result.map do |row|
|
||||
row.tap do |hash|
|
||||
hash["reading_statuses"] = JSON.parse(hash["reading_statuses"])
|
||||
end
|
||||
|
||||
OpenStruct.new(row)
|
||||
end
|
||||
```
|
||||
45
rails/set-datetime-to-include-time-zone-in-migrations.md
Normal file
45
rails/set-datetime-to-include-time-zone-in-migrations.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# Set Datetime To Include Time Zone In Migrations
|
||||
|
||||
When using Rails and PostgreSQL, your migrations will contain DSL syntax like
|
||||
`t.datetime` and `t.timestamps` which will produce columns using the
|
||||
`timestamp` (`without time zone`) Postgres data type.
|
||||
|
||||
While reading [A Simple Explanation of Postgres' <code>Timestamp with Time
|
||||
Zone</code>](https://naildrivin5.com/blog/2024/10/10/a-simple-explanation-of-postgres-timestamp-with-time-zone.html),
|
||||
I learned that there is a way to configure your app to instead use
|
||||
`timestamptz` by default. This data type is widely recommended as a good
|
||||
default, so it is nice that we can configure Rails to use it.
|
||||
|
||||
First, add these lines to a new initializer (`config/initializers/postgres.rb`)
|
||||
file.
|
||||
|
||||
```ruby
|
||||
require "active_record/connection_adapters/postgresql_adapter"
|
||||
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.datetime_type = :timestamptz
|
||||
```
|
||||
|
||||
Alternatively, you can configure this via `config/application.rb` per the
|
||||
[Configuring ActiveRecord
|
||||
docs](https://guides.rubyonrails.org/configuring.html#activerecord-connectionadapters-postgresqladapter-datetime-type).
|
||||
|
||||
Then, if you have a new migration like the following:
|
||||
|
||||
```ruby
|
||||
class AddEventsTable < ActiveRecord::Migration[7.2]
|
||||
def change
|
||||
create_table :events do |t|
|
||||
t.string :title
|
||||
t.text :description
|
||||
t.datetime :start_time
|
||||
t.datetime :end_time
|
||||
t.timestamps
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
you can expect to have four `timestamptz` columns, namely `start_time`,
|
||||
`end_time`, `created_at`, and `updated_at`.
|
||||
|
||||
Here is the [Rails PR](https://github.com/rails/rails/pull/41084) that adds
|
||||
this config option.
|
||||
52
rails/set-default-as-sql-function-in-migration.md
Normal file
52
rails/set-default-as-sql-function-in-migration.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# Set Default As SQL Function In Migration
|
||||
|
||||
With static default values, like `0`, `true`, or `'pending'`, we can set them
|
||||
directly as the value of `default`.
|
||||
|
||||
```ruby
|
||||
class CreateActionsTable < ActiveRecord::Migration[7.2]
|
||||
def change
|
||||
create_table :actions do |t|
|
||||
t.string :status, default: 'pending'
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
However, if we want our default value to be a SQL function like `now()`, we
|
||||
have to use a lambda.
|
||||
|
||||
Let's extend the above example to see what that looks like:
|
||||
|
||||
```ruby
|
||||
class CreateActionsTable < ActiveRecord::Migration[7.2]
|
||||
def change
|
||||
create_table :actions do |t|
|
||||
t.string :status, default: 'pending'
|
||||
|
||||
t.column :created_at, :timestamptz, default: -> { 'now()' }, null: false
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
If we need to alter the default of an existing table's column, we can do
|
||||
something like this:
|
||||
|
||||
```ruby
|
||||
class AddDefaultTimestampsToActions < ActiveRecord::Migration[7.2]
|
||||
def up
|
||||
change_column_default :actions, :created_at, -> { "now()" }
|
||||
change_column_default :actions, :updated_at, -> { "now()" }
|
||||
end
|
||||
|
||||
def down
|
||||
change_column_default :actions, :created_at, nil
|
||||
change_column_default :actions, :updated_at, nil
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
I believe this functionality is available to Rails 5.0 and later.
|
||||
|
||||
[source](https://github.com/rails/rails/issues/27077#issuecomment-261155826)
|
||||
38
ruby/execute-several-commands-with-backtick-heredoc.md
Normal file
38
ruby/execute-several-commands-with-backtick-heredoc.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# Execute Several Commands With Backtick Heredoc
|
||||
|
||||
A fun feature of Ruby is that we can execute a command in a subprocess just by
|
||||
wrapping it in backticks.
|
||||
|
||||
For instance, we might shell out to `git` to check if a file is tracked:
|
||||
|
||||
```ruby
|
||||
`git ls-files --error-unmatch #{file_path} 2>/dev/null`
|
||||
$?.success?
|
||||
```
|
||||
|
||||
But what if we need to execute several commands? Perhaps they depend on one
|
||||
another. We want them to run in the same subprocess.
|
||||
|
||||
For this, we can use the backtick version of a heredoc. That is a special
|
||||
version of a heredoc where the delimiter is wrapped in backticks.
|
||||
|
||||
```ruby
|
||||
puts <<`SHELL`
|
||||
# Set up trap
|
||||
trap 'echo "Cleaning up temp files"; rm -f *.tmp' EXIT
|
||||
|
||||
# Create temporary file
|
||||
echo "test data" > work.tmp
|
||||
|
||||
# Do some work
|
||||
cat work.tmp
|
||||
|
||||
# Trap will clean up on exit
|
||||
SHELL
|
||||
```
|
||||
|
||||
Here we set up a `trap` for file cleanup on exit, then create a file, then do
|
||||
something with the file, and that's it, the process exits (triggering the
|
||||
trap).
|
||||
|
||||
[source](https://ruby-doc.org/3.3.6/syntax/literals_rdoc.html#label-Here+Document+Literals)
|
||||
45
ruby/forward-all-arguments-to-another-method.md
Normal file
45
ruby/forward-all-arguments-to-another-method.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# Forward All Arguments To Another Method
|
||||
|
||||
There are three types of arguments that a Ruby method can receive. Positional
|
||||
arguments, keyword arguments, and a block argument.
|
||||
|
||||
A method that deals with all three might be defined like this:
|
||||
|
||||
```ruby
|
||||
def forwarding_method(*args, **kwargs, &block)
|
||||
# implementation
|
||||
end
|
||||
```
|
||||
|
||||
Now lets say we have some concrete method that we want to forward these
|
||||
arguments to:
|
||||
|
||||
```ruby
|
||||
def concrete_method(*args, **kwargs)
|
||||
x = args.first || 1
|
||||
key, y = kwargs.first || [:a, 2]
|
||||
|
||||
puts "Dealing with #{x} and key #{key}: #{y}"
|
||||
|
||||
yield(x, y)
|
||||
end
|
||||
```
|
||||
|
||||
We could forward arguments the longhand way like this:
|
||||
|
||||
```ruby
|
||||
def forwarding_method(*args, **kwargs, &block)
|
||||
concrete_method(*args, **kwargs, &block)
|
||||
end
|
||||
```
|
||||
|
||||
However, since Ruby 2.7 we have access to a shorthand "triple-dot" syntax for
|
||||
forwarding all arguments.
|
||||
|
||||
```ruby
|
||||
def forwarding_method(...)
|
||||
concrete_method(...)
|
||||
end
|
||||
```
|
||||
|
||||
[source](https://ruby-doc.org/3.3.6/syntax/methods_rdoc.html#label-Argument+Forwarding)
|
||||
42
ruby/output-bytecode-for-a-ruby-program.md
Normal file
42
ruby/output-bytecode-for-a-ruby-program.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# Output Bytecode For A Ruby Program
|
||||
|
||||
The `ruby` CLI comes with a flag to dump the disassembled YARV bytecode for the
|
||||
given Ruby program. This can be a fun way to explore how a Ruby program is
|
||||
interpreted under the hood.
|
||||
|
||||
Aaron Patterson demoed this behavior during his RubyConf 2024 talk.
|
||||
|
||||
Pass the `--dump` flag with `insns` along with either the path to your file or
|
||||
an inline bit of Ruby.
|
||||
|
||||
Here is a really basic example:
|
||||
|
||||
```bash
|
||||
❯ ruby --dump=insns -e '2 + 3'
|
||||
== disasm: #<ISeq:<main>@-e:1 (1,0)-(1,5)> (catch: false)
|
||||
0000 putobject 2 ( 1)[Li]
|
||||
0002 putobject 3
|
||||
0004 opt_plus <calldata!mid:+, argc:1, ARGS_SIMPLE>[CcCr]
|
||||
0006 leave
|
||||
```
|
||||
|
||||
And another quite basic example, but with local variables this time:
|
||||
|
||||
```bash
|
||||
❯ ruby --dump=insns -e 'x = 2; y = 3; x + y'
|
||||
== disasm: #<ISeq:<main>@-e:1 (1,0)-(1,19)> (catch: false)
|
||||
local table (size: 2, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
|
||||
[ 2] x@0 [ 1] y@1
|
||||
0000 putobject 2 ( 1)[Li]
|
||||
0002 setlocal_WC_0 x@0
|
||||
0004 putobject 3
|
||||
0006 setlocal_WC_0 y@1
|
||||
0008 getlocal_WC_0 x@0
|
||||
0010 getlocal_WC_0 y@1
|
||||
0012 opt_plus <calldata!mid:+, argc:1, ARGS_SIMPLE>[CcCr]
|
||||
0014 leave
|
||||
```
|
||||
|
||||
If you want to dig in to how to read everything that is going on in these
|
||||
outputs, I'd recommend checking out this [Advent of YARV
|
||||
series](https://kddnewton.com/2022/11/30/advent-of-yarv-part-0.html)
|
||||
37
tmux/open-new-splits-to-the-current-directory.md
Normal file
37
tmux/open-new-splits-to-the-current-directory.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# Open New Splits To The Current Directory
|
||||
|
||||
I typically work in one project per tmux session. When I create a given tmux
|
||||
session, the default directory is for that project. All new windows and pane
|
||||
splits will open at that default directory. This generally is the default
|
||||
behavior I want.
|
||||
|
||||
One caveat: I often open a new window within an existing session that I want
|
||||
anchored to another directory. This could be because I'm working in a monorepo
|
||||
and I need to work from a subdirectory for a specific package or app. Or it
|
||||
could be that I'm temporarily digging into another project and it isn't worth
|
||||
create a whole new session.
|
||||
|
||||
Regardless of the reason, I run into a bit of friction with tmux's defaults.
|
||||
|
||||
First I open the new window and `cd` to another project. After some working, I
|
||||
need to open a split pane, maybe to run a project command like a build or dev
|
||||
server. Hitting `prefix-"` (horizontal split) or `prefix-%` (vertical split)
|
||||
opens a pane with the shell defaulting back to the original directory, not the
|
||||
current directory.
|
||||
|
||||
The trick to fixing this bit of friction is overriding the directory of pane
|
||||
splits. I can do that by adding the following to my `~/.tmux.conf`:
|
||||
|
||||
```
|
||||
# Pane splits should open to the same path as the current pane
|
||||
bind '"' split-window -v -c "#{pane_current_path}"
|
||||
bind % split-window -h -c "#{pane_current_path}"
|
||||
```
|
||||
|
||||
Make sure to run `tmux source-file ~/.tmux.conf` to apply these config changes.
|
||||
|
||||
The `pane_current_path` is called a "Format" in tmux parlance. It resolves to
|
||||
the absolute path of the current pane's current directory. You can find all the
|
||||
formats in the manpage with this command: `man tmux | less +'/^FORMATS'`. You
|
||||
can also show yourself that this format resolves to what you expect by running
|
||||
`tmux display-message -p '#{pane_current_path}'`.
|
||||
35
typescript/ignore-all-errors-in-a-typescript-file.md
Normal file
35
typescript/ignore-all-errors-in-a-typescript-file.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# Ignore All Errors In A TypeScript File
|
||||
|
||||
As of TypeScript 3.7, we can mark an entire TypeScript file to be ignored by
|
||||
the TypeScript compiler when it is doing static type checking.
|
||||
|
||||
We can do this by adding the `@ts-nocheck` directive at the top of the file:
|
||||
|
||||
```typescript
|
||||
// @ts-nocheck
|
||||
|
||||
type User = {
|
||||
id: string;
|
||||
name: string;
|
||||
email: string;
|
||||
}
|
||||
|
||||
const user: User = {
|
||||
id: 123,
|
||||
name: "Liz Lemon",
|
||||
email: "liz.lemon@nbc.com",
|
||||
};
|
||||
```
|
||||
|
||||
Notice that `id` is typed as a `string`, but we are using a `number` with `id`
|
||||
for `user`. That is a type error. But with the `@ts-nocheck` directive at the
|
||||
top, the type checker doesn't run on the file and we see no type errors.
|
||||
|
||||
I'd generally suggest to avoid doing this. It can hide real type errors that
|
||||
you should be addressing. That said, in special circumstances, you may need it,
|
||||
even if just temporarily, like if an imported package doesn't have types. Here
|
||||
is an example of that in [uploadthing's
|
||||
`rehype.js`](https://github.com/pingdotgg/uploadthing/blob/d98fbefedddf64d183cc5a00b3fd707e8d8f2f6c/docs/src/mdx/rehype.js#L1)
|
||||
which is missing types from `mdx-annotations`.
|
||||
|
||||
[source](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#-ts-nocheck-in-typescript-files)
|
||||
20
unix/convert-jpeg-to-png-with-ffmpeg.md
Normal file
20
unix/convert-jpeg-to-png-with-ffmpeg.md
Normal file
@@ -0,0 +1,20 @@
|
||||
# Convert JPEG To PNG With ffmpeg
|
||||
|
||||
The `ffmpeg` utility "is a universal media converter." That means we can use it
|
||||
to convert, for instance, a JPEG file to a PNG file.
|
||||
|
||||
There is not a lot to a conversion like this. We use `-i` to specify the
|
||||
existing input file (a JPEG) and then the other argument is the name and
|
||||
extension of the output file.
|
||||
|
||||
```bash
|
||||
$ ls
|
||||
profile.jpg
|
||||
|
||||
$ ffmpeg -i profile.jpg profile.png
|
||||
|
||||
$ ls
|
||||
profile.jpg profile.png
|
||||
```
|
||||
|
||||
See `man ffmpeg` for more details.
|
||||
34
unix/get-the-sha256-hash-for-a-file.md
Normal file
34
unix/get-the-sha256-hash-for-a-file.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# Get The SHA256 Hash For A File
|
||||
|
||||
Unix systems come with a `sha256sum` utility that we can use to compute the
|
||||
SHA256 hash of a file. This means the contents of file are compressed into a
|
||||
256-bit digest.
|
||||
|
||||
Here I use it on a SQL migration file that I've generated.
|
||||
|
||||
```bash
|
||||
$ sha256sum migrations/0001_large_doctor_spectrum.sql
|
||||
b75e61451e2ce37d831608b1bc9231bf3af09e0ab54bf169be117de9d4ff6805 migrations/0001_large_doctor_spectrum.sql
|
||||
```
|
||||
|
||||
Each file passed to this utility gets output to a separate line which is why we
|
||||
see the filename next to the hash. Since I am only running it on a single file
|
||||
and I may want to pipe the output to some other program, I can clip off just
|
||||
the part I need.
|
||||
|
||||
```bash
|
||||
sha256sum migrations/0001_large_doctor_spectrum.sql | cut -d ' ' -f 1
|
||||
b75e61451e2ce37d831608b1bc9231bf3af09e0ab54bf169be117de9d4ff6805
|
||||
```
|
||||
|
||||
We can also produce these digests with `openssl`:
|
||||
|
||||
```bash
|
||||
$ openssl dgst -sha256 migrations/0001_large_doctor_spectrum.sql
|
||||
SHA2-256(migrations/0001_large_doctor_spectrum.sql)= b75e61451e2ce37d831608b1bc9231bf3af09e0ab54bf169be117de9d4ff6805
|
||||
|
||||
$ openssl dgst -sha256 migrations/0001_large_doctor_spectrum.sql | cut -d ' ' -f 2
|
||||
b75e61451e2ce37d831608b1bc9231bf3af09e0ab54bf169be117de9d4ff6805
|
||||
```
|
||||
|
||||
See `sha256sum --help` or `openssl dgst --help` for more details.
|
||||
29
unix/gracefully-exit-a-script-with-trap.md
Normal file
29
unix/gracefully-exit-a-script-with-trap.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# Gracefully Exit A Script With Trap
|
||||
|
||||
With `trap` you can intercept signals that would cause your script to exit and
|
||||
then inject some additional behavior. Perhaps you want to make sure the script
|
||||
cleans up after itself before it exists.
|
||||
|
||||
During this script's execution, it creates a file in the filesystem. It would
|
||||
be nice to make sure that no matter path this script ends up down that it will
|
||||
clean up after itself as it exits. We set up a `trap` that looks for the `EXIT`
|
||||
signal to do this.
|
||||
|
||||
```bash
|
||||
# Set up trap
|
||||
trap 'echo "Cleaning up temp files"; rm -f *.tmp' EXIT
|
||||
|
||||
# Create temporary file
|
||||
echo "test data" > work.tmp
|
||||
|
||||
# Do some work
|
||||
cat work.tmp
|
||||
|
||||
# Trap will clean up on exit
|
||||
```
|
||||
|
||||
Whatever is in quotes is what the trap will execute when it is triggered. The
|
||||
following one or more signals are what the trap listens for, in this case
|
||||
`EXIT`.
|
||||
|
||||
[source](https://tldp.org/LDP/Bash-Beginners-Guide/html/sect_12_02.html)
|
||||
32
unix/make-direnv-less-noisy.md
Normal file
32
unix/make-direnv-less-noisy.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# Make Direnv Less Noisy
|
||||
|
||||
I've been using [`direnv`](https://direnv.net/) to manage project and folder
|
||||
specific environment variables for a bit now. I've found it to be pretty
|
||||
seamless. It can feel like it is littering my shell with too much output when I
|
||||
change directories though.
|
||||
|
||||
There are two levers to control its output.
|
||||
|
||||
First, the direnv logs (e.g. `direnv: loading ~/.../.envrc`) can be controlled
|
||||
with the `DIRENV_LOG_FORMAT` env var. Add this to the
|
||||
`~/.config/direnv/direnvrc` file (add that directory and file if necessary).
|
||||
You can leave it blank to altogether hide log messages or you can gray-out the
|
||||
log messages like this:
|
||||
|
||||
```
|
||||
export DIRENV_LOG_FORMAT=$'\033[2mdirenv: %s\033[0m'
|
||||
```
|
||||
|
||||
Second, you can hide the env var diff with a separate config. This diff is not
|
||||
covered under the umbrella of logs controlled by the above setting. Set
|
||||
[`hide_env_diff` in the `~/.config/direnv/direnv.toml`
|
||||
file](https://direnv.net/man/direnv.toml.1.html#codehideenvdiffcode):
|
||||
|
||||
```toml
|
||||
[global]
|
||||
hide_env_diff = true
|
||||
```
|
||||
|
||||
This second config was only added as of `v2.34.0`.
|
||||
|
||||
[source](https://esham.io/2023/10/direnv)
|
||||
20
unix/undo-changes-made-to-current-terminal-prompt.md
Normal file
20
unix/undo-changes-made-to-current-terminal-prompt.md
Normal file
@@ -0,0 +1,20 @@
|
||||
# Undo Change Made to Current Terminal Prompt
|
||||
|
||||
I frequently use a variety of ASCII command characters like `ctrl-u` to delete
|
||||
the entire line or `ctrl-a` to jump to the front of a long line so I can make
|
||||
some edits toward that side of the command or `ctrl-e` to jump to the end of
|
||||
the command for the same reason. I sometimes even use `ctrl-k` to delete
|
||||
everything after the cursor to the end of the line.
|
||||
|
||||
What I didn't realize until now is that any of those commands the modify the
|
||||
current line of the termianl prompt plus regular typing and hitting the
|
||||
backspace are all _undoable_.
|
||||
|
||||
So, if I just wiped out half the line (with `ctrl-k`) and I immediately regret
|
||||
it, I can restore it with `ctrl-_`. The system keeps of history of the actions
|
||||
you've taken, so you can keep hitting `ctrl-_` to undo even further.
|
||||
|
||||
The `ctrl-/` command does the same, per GNU's [Undo Changes in the Emacs
|
||||
docs](https://www.gnu.org/software/emacs/manual/html_node/emacs/Basic-Undo.html).
|
||||
|
||||
[source](https://jvns.ca/ascii)
|
||||
17
vim/switch-moving-end-of-visual-selection.md
Normal file
17
vim/switch-moving-end-of-visual-selection.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# Switch Moving End Of Visual Selection
|
||||
|
||||
When I go into character-wise visual selection mode, one end of the visual
|
||||
selection is fixed while I move my cursor around to define the other end of it.
|
||||
|
||||
Let's say I've arranged a visual selection that encompasses several lines of my
|
||||
file. And then I realize that the fixed front-end of my visual selection is off
|
||||
by a bit. Maybe I've selected an entire function definition and I just want to
|
||||
inner part of the function.
|
||||
|
||||
Instead of starting over with my visual selection. I can leave the right-end of
|
||||
the visual selection where it is, hit `o` which will switch the moving end to
|
||||
the other side, and then continue making adjustments from there.
|
||||
|
||||
I can always hit `o` again to switch it back to the original side.
|
||||
|
||||
See `:h v_o` for more details.
|
||||
19
workflow/add-hotkeys-for-specific-raycast-extensions.md
Normal file
19
workflow/add-hotkeys-for-specific-raycast-extensions.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# Add Hotkeys For Specific Raycast Extensions
|
||||
|
||||
One of the main things I use [Raycast](https://www.raycast.com/) for is its
|
||||
built-in clipboard history extension. It's super handy when I've copied a Git
|
||||
SHA or a URL or a bit of text several copies ago and I want to be able to refer
|
||||
back to it. It keeps 7 days of clipboard history by default, so anything that I
|
||||
remember copying is going to be there.
|
||||
|
||||
To get there, I have to trigger Raycast, which I usually open via Alfred (I
|
||||
know, 😅). Then I search for Clipboard History (or find it under recent
|
||||
suggestions).
|
||||
|
||||
Raycast supports arbitrary hotkey bindings for triggering different extensions
|
||||
and their features. So I can get to the Clipboard History in a single step --
|
||||
e.g. with `Ctrl+Shift+V`.
|
||||
|
||||
From Raycast _Settings_, go to the _Extensions_ tab. Find _Clipboard History_
|
||||
in the list and then click the _Record Hotkey_ input for it. I then type
|
||||
`Ctrl+Shift+V` and I'm all set.
|
||||
64
workflow/add-subscriber-to-kit-form-via-api.md
Normal file
64
workflow/add-subscriber-to-kit-form-via-api.md
Normal file
@@ -0,0 +1,64 @@
|
||||
# Add Subscriber To Kit Form Via API
|
||||
|
||||
Because the Kit API is a bit sparse in words, I found it difficult to figure
|
||||
out exactly what I needed to do to add a subscriber via a custom form. After
|
||||
some experimenting and reading through [Course
|
||||
Builder](https://github.com/badass-courses/course-builder/blob/main/packages/core/src/providers/convertkit.ts)
|
||||
code, I was able to figure out that with the Kit v4 API there are two separate
|
||||
calls that need to be made.
|
||||
|
||||
First, you need to create an `inactive` subscriber with the user's email (and
|
||||
first name if given) via the [Create a subscriber
|
||||
endpoint](https://developers.kit.com/v4#create-a-subscriber).
|
||||
|
||||
```typescript
|
||||
async function createSubscriber(
|
||||
data: SubscriberData,
|
||||
apiKey: string,
|
||||
): Promise<Response> {
|
||||
return fetch('https://api.convertkit.com/v4/subscribers', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Kit-Api-Key': apiKey,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email_address: data.email,
|
||||
first_name: data.name,
|
||||
state: 'inactive',
|
||||
}),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
Since this subscriber is `inactive`, they won't show up in the Kit dashboard,
|
||||
but a subscriber record of them has been created.
|
||||
|
||||
Second, you need to add the subscriber to something, like a form or a sequence.
|
||||
In my case, I had already created a form via the Kit dashboard, so I grab that
|
||||
_Form ID_ and stick it in my `.env`. That way I am able to [add the subscriber
|
||||
to the form by email
|
||||
address](https://developers.kit.com/v4#add-subscriber-to-form-by-email-address).
|
||||
|
||||
```typescript
|
||||
async function addSubscriberToForm(
|
||||
email: string,
|
||||
apiKey: string,
|
||||
formId: string,
|
||||
): Promise<Response> {
|
||||
const url = `https://api.convertkit.com/v4/forms/${formId}/subscribers`
|
||||
return fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Kit-Api-Key': apiKey,
|
||||
},
|
||||
body: JSON.stringify({ email_address: email }),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
This will send a confirmation email to the email address. Once the person has
|
||||
opened the email and hit 'Confirm', their corresponding `subscriber` record
|
||||
will be marked as `active` and they will be added to the list of subscribers
|
||||
for that form.
|
||||
23
workflow/configure-email-redirect-with-cloudflare.md
Normal file
23
workflow/configure-email-redirect-with-cloudflare.md
Normal file
@@ -0,0 +1,23 @@
|
||||
# Configure Email Redirect With Cloudflare
|
||||
|
||||
I have a domain registered with Cloudflare --
|
||||
[visualmode.dev](https://www.visualmode.dev). I want to be able to sign up for
|
||||
services and receive emails as `josh@visualmode.dev`. I don't want a separate
|
||||
inbox that I have to check though.
|
||||
|
||||
The solution, for me, is to have Cloudflare redirect incoming emails to an
|
||||
email address/inbox that I'm already regularly checking.
|
||||
|
||||
On the _Email_ dashboard there is a _Routing Rules_ tab. In the _Custom
|
||||
Addresses_ section, I can click _Create Address_. There I specify the custom
|
||||
address (what will appear before the `@`), the action to make ("Send to an
|
||||
email"), and the destination (the existing email address for an inbox I
|
||||
regularly check).
|
||||
|
||||
Finally, I hit _Save_. If they don't already exist, I'll be prompted to confirm
|
||||
the setup of MX records for the domain. After confirming that, I should be able
|
||||
to receive emails via that new address.
|
||||
|
||||
The Email Routing dashboard will even show me a summary of all rerouted emails.
|
||||
|
||||
[source](https://blog.cloudflare.com/introducing-email-routing/#cloudflare-email-routing)
|
||||
27
workflow/get-url-for-github-user-profile-photo.md
Normal file
27
workflow/get-url-for-github-user-profile-photo.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# Get URL For GitHub User Profile Photo
|
||||
|
||||
You can access your (or really any user's) profile photo by visiting the GitHub
|
||||
profile URL with `.png` added on to the end.
|
||||
|
||||
So, my GitHub profile URL is:
|
||||
|
||||
```
|
||||
https://github.com/jbranchaud
|
||||
```
|
||||
|
||||
If I were to add `.png` on to that and visit it in my browser:
|
||||
|
||||
```
|
||||
https://github.com/jbranchaud.png
|
||||
```
|
||||
|
||||
I'd be redirected to the following URL:
|
||||
|
||||
```
|
||||
https://avatars.githubusercontent.com/u/694063?v=4
|
||||
```
|
||||
|
||||
This is the stable URL for my GitHub avatar. In the browser I will see the full
|
||||
resolution image which I can download as needed.
|
||||
|
||||
[source](https://dev.to/10xlearner/how-to-get-the-profile-picture-of-a-github-account-1d82)
|
||||
64
workflow/send-a-message-to-a-discord-channel.md
Normal file
64
workflow/send-a-message-to-a-discord-channel.md
Normal file
@@ -0,0 +1,64 @@
|
||||
# Send A Message To A Discord Channel
|
||||
|
||||
I recently added a form to [visualmode.dev](https://www.visualmode.dev) that
|
||||
when submitted should send the details to an internal channel in my discord
|
||||
server.
|
||||
|
||||
I didn't need to set up an _App_ or a _Bot_ to do this. It is much simpler than
|
||||
that. All I needed was a valid [_webhook_
|
||||
endpoint](https://discord.com/developers/docs/resources/webhook) for my channel
|
||||
that I can `POST` to.
|
||||
|
||||
From Discord, I can select _Edit Channel_ for a specific channel, go to the
|
||||
_Integrations_ tab, go to _Webhooks_, and then create a _New Webhook_. I can
|
||||
name it, save it, and then copy the webhook URL.
|
||||
|
||||
As a demonstration, I can `POST` to that webhook URL using `cURL` like so:
|
||||
|
||||
```bash
|
||||
curl -H "Content-Type: application/json" -X POST -d '{"content":"Hello from cURL!"}' <YOUR_WEBHOOK_URL>
|
||||
```
|
||||
|
||||
Similarly, in some non-public-facing code like a Next.js serverless function, I
|
||||
can `POST` to that webhook URL with the `content` that I want to appear in my
|
||||
channel.
|
||||
|
||||
```
|
||||
import { NextResponse } from 'next/server'
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const data = await request.json()
|
||||
|
||||
const discordWebhookUrl = process.env.DISCORD_WEBHOOK_URL
|
||||
if (discordWebhookUrl) {
|
||||
try {
|
||||
const response = await fetch(discordWebhookUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
content: `New contact form submission:\nName: ${data.name}\nEmail: ${data.email}\nCompany: ${data.company}\nPhone: ${data.phone}\nMessage: ${data.message}`,
|
||||
}),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to send Discord message')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error sending to Discord:', error)
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to process form submission' },
|
||||
{ status: 500 },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return NextResponse.json({ message: 'Form submitted successfully' })
|
||||
}
|
||||
```
|
||||
|
||||
This [Structure of Webhook
|
||||
guide](https://birdie0.github.io/discord-webhooks-guide/discord_webhook.html)
|
||||
has more details on how to specifically structure and format a more complex
|
||||
message.
|
||||
37
zsh/use-a-space-to-exclude-command-from-history.md
Normal file
37
zsh/use-a-space-to-exclude-command-from-history.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# Use A Space To Exclude Command From History
|
||||
|
||||
When using a shell like `zsh`, you get the benefit of it keeping track of the
|
||||
history of the commands you've entered into the shell. This means you can
|
||||
quickly traverse pack to a previous command that you want to run again. It also
|
||||
means [a tool like `fzf` can hook into your history
|
||||
file](https://github.com/junegunn/fzf?tab=readme-ov-file#key-bindings-for-command-line)
|
||||
so that you can fuzzy-search for a command you may have executed weeks ago.
|
||||
|
||||
The history is stored on your machine in a plaintext file. Not every command
|
||||
should be stored in a plaintext file. For instance, you don't want `zsh` to
|
||||
persist a command that includes a password.
|
||||
|
||||
With the `histignorespace` option enabled in `zsh`, we can put a leading space
|
||||
in front of our command and it will be excluded from the history file.
|
||||
|
||||
Try it yourself:
|
||||
|
||||
```bash
|
||||
$ echo 'this command will be remembered'
|
||||
this command will be remembered
|
||||
|
||||
$ echo 'this command will be forgotten'
|
||||
this command will be forgotten
|
||||
```
|
||||
|
||||
Notice the leading space in the second command. Trying pressing your _up_ arrow
|
||||
and notice only that first `echo` is remembered.
|
||||
|
||||
Make sure `histignorespace` is included in the list when you run `setopt`. If
|
||||
it isn't, then add it:
|
||||
|
||||
```bash
|
||||
$ setopt histignorespace
|
||||
```
|
||||
|
||||
[source](https://stackoverflow.com/questions/8473121/execute-a-command-without-keeping-it-in-history/49643320#49643320)
|
||||
Reference in New Issue
Block a user