1
0
mirror of https://github.com/jbranchaud/til synced 2026-01-14 04:28:02 +00:00

Compare commits

...

41 Commits

Author SHA1 Message Date
Karim Bouchez
ccb32c5dd6 Merge 15337dfd71 into 97c8701a5a 2025-02-02 15:27:33 +00:00
jbranchaud
97c8701a5a Add Use Labels To Block PR Merge as a GitHub Actions PR 2025-02-01 14:10:25 -06:00
jbranchaud
1fd64e478a Clarify some things in the latest TIL 2025-01-31 14:34:53 -06:00
jbranchaud
8ea123369b Add Trim Leading And Trailing Space From String as a Postgres TIL 2025-01-31 14:23:34 -06:00
jbranchaud
43c6e08b34 Add Add A Generated Column To A PostgreSQL Table as a Rails TIL 2025-01-30 23:10:23 -06:00
jbranchaud
61fc021f52 Add Unable To Infer Data Type In Production as a Postgres TIL 2025-01-28 18:40:09 -06:00
jbranchaud
46ad33df7e Add Set Meta Tags In ERB Views as a Rails TIL 2025-01-28 18:30:12 -06:00
jbranchaud
2028f6cb09 Add List All Fonts On Your Machine as a Unix TIL 2025-01-27 23:34:44 -06:00
jbranchaud
1f039a8958 Add Default Rails Deploy Script On Hatchbox as a devops TIL 2025-01-26 21:12:39 -06:00
jbranchaud
c6eefeac98 Add Count The Number Of Items In An Array as a Postgres TIL 2025-01-25 19:02:13 -06:00
jbranchaud
31a0224fb7 Add Send A PDF To Your Kindle as a Workflow TIL 2025-01-24 15:27:25 -06:00
jbranchaud
aa71ff5f8b Add Override Text Displayed By Form Label as a Rails TIL 2025-01-24 12:36:46 -06:00
jbranchaud
48278c4908 Add Set Up Domain For Hatchbox Rails App as a devops TIL 2025-01-23 11:43:37 -06:00
jbranchaud
8b3ef4872c Add Apply Basic HTML Formatting To Block Of Text as a Rails TIL 2025-01-21 15:48:40 -06:00
jbranchaud
c2184a5ecf Add Hatchbox Exports Env Vars With asdf as a Devops TIL 2025-01-21 15:36:44 -06:00
jbranchaud
e2a8e815e9 Add Inspect Configuration Of Database Connection as a Rails TIL 2025-01-20 10:06:13 -06:00
jbranchaud
c61ddcb326 Add Temporarily Hide CleanShot X Capture Previews as a Workflow TIL 2025-01-19 13:56:45 -06:00
jbranchaud
7632664200 Add Scaffold Auth Functionality With Rails 8 Generator as a Rails TIL 2025-01-15 16:54:29 -06:00
jbranchaud
872a1d2a00 Add Count Number Of Commits On A Branch as a git TIL 2025-01-14 17:02:19 -06:00
jbranchaud
d52a126767 Add Pop Videos Out As Picture-in-Picture as a Workflow TIL 2025-01-12 11:40:29 -06:00
jbranchaud
d9080cc583 Add List All Files Added During Span Of Time as a Git TIL 2025-01-11 13:58:07 -06:00
jbranchaud
654c65c8f6 Add missing README changes for latest TIL 2025-01-11 13:57:32 -06:00
jbranchaud
138cab4fdc Add Control Media With Drop Keyboard as a Workflow TIL 2025-01-10 16:16:01 -06:00
jbranchaud
5592d4266d Add Use A Different Font With iTerm2 as a Mac TIL 2025-01-09 11:13:17 -06:00
jbranchaud
daf448c1a5 Add Rebuild Tailwind Bundle For Dev Server as a Rails TIL 2025-01-08 19:39:41 -06:00
jbranchaud
aaddc35fcd Add Disclose Additional Details as an HTML TIL 2025-01-07 13:31:31 -06:00
jbranchaud
b575534d4e Add Difference Between Slice And Pointer To Slice as a Go TIL 2025-01-06 16:43:30 -06:00
jbranchaud
ae3ecbf72c Add Start Amphetamine Session With AppleScript as a Mac TIL 2025-01-05 19:03:26 -06:00
jbranchaud
1cf67b8f1a Add Configure Max String Print Length For Delve as a Go TIL 2025-01-04 13:07:13 -06:00
jbranchaud
f9c0a566eb Add See Where asdf Gets Current Tool Version as a Unix TIL 2025-01-03 12:11:02 -06:00
jbranchaud
527038ca23 Fix TIL count, it was off by 1 2025-01-02 13:58:26 -06:00
jbranchaud
b972673008 Add Simon Willison's TIL to list of other TILs 2025-01-02 13:54:50 -06:00
jbranchaud
cc31aae25a Update copyright date to 2025, time flies 2025-01-02 13:52:57 -06:00
jbranchaud
26f30c3225 Update README with a few learning resource links 2025-01-02 13:52:24 -06:00
jbranchaud
e14da2f207 Add Basic Delve Debugging Session as a Go TIL 2025-01-02 13:45:16 -06:00
jbranchaud
b7d4a62ecb Add Refer To Implicit Block Argument With It as a Ruby TIL 2025-01-01 12:16:53 -06:00
jbranchaud
1ad41b9776 Add Connect To A SQLite Database as a Go TIL 2024-12-31 10:48:01 -06:00
jbranchaud
11716a8fb5 Add Install Latest Version Of Ruby With asdf as a Ruby TIL 2024-12-30 19:20:33 -07:00
jbranchaud
5e19d53382 Add Pass A Struct To A Function as a Go TIL 2024-12-29 10:26:55 -07:00
jbranchaud
c8aa6ee506 Add Break Justfile Into Separate Hidden Steps as a Workflow TIL 2024-12-28 09:20:00 -07:00
Karim Bouchez
15337dfd71 Update the old way to capture a GitHub Actions output
See [here](https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/) for more explanations:
> We are monitoring telemetry for the usage of these commands and plan to fully disable them on 31st May 2023. Starting 1st June 2023 workflows using save-state or set-output commands via stdout will fail with an error.
2023-02-12 10:36:45 +01:00
35 changed files with 1342 additions and 6 deletions

View File

@@ -10,7 +10,11 @@ pairing with smart people at Hashrocket.
For a steady stream of TILs, [sign up for my newsletter](https://crafty-builder-6996.ck.page/e169c61186).
_1550 TILs and counting..._
_1582 TILs and counting..._
See some of the other learning resources I work on:
- [Ruby Operator Lookup](https://www.visualmode.dev/ruby-operators)
- [Vim Un-Alphabet](https://www.youtube.com/playlist?list=PL46-cKSxMYYCMpzXo6p0Cof8hJInYgohU)
---
@@ -195,12 +199,15 @@ _1550 TILs and counting..._
- [Check The Status of All Services](devops/check-the-status-of-all-services.md)
- [Check The Syntax Of nginx Files](devops/check-the-syntax-of-nginx-files.md)
- [Connect To An RDS PostgreSQL Database](devops/connect-to-an-rds-postgresql-database.md)
- [Default Rails Deploy Script On Hatchbox](devops/default-rails-deploy-script-on-hatchbox.md)
- [Determine The IP Address Of A Domain](devops/determine-the-ip-address-of-a-domain.md)
- [Hatchbox Exports Env Vars With asdf](devops/hatchbox-exports-env-vars-with-asdf.md)
- [Path Of The Packets](devops/path-of-the-packets.md)
- [Push Non-master Branch To Heroku](devops/push-non-master-branch-to-heroku.md)
- [Reload The nginx Configuration](devops/reload-the-nginx-configuration.md)
- [Resolve The Public IP Of A URL](devops/resolve-the-public-ip-of-a-url.md)
- [Running Out Of inode Space](devops/running-out-of-inode-space.md)
- [Set Up Domain For Hatchbox Rails App](devops/set-up-domain-for-hatchbox-rails-app.md)
- [SSH Into A Docker Container](devops/ssh-into-a-docker-container.md)
- [SSL Certificates Can Cover Multiple Domains](devops/ssl-certificates-can-cover-multiple-domains.md)
- [Wipe A Heroku Postgres Database](devops/wipe-a-heroku-postgres-database.md)
@@ -300,6 +307,7 @@ _1550 TILs and counting..._
- [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)
- [Count Number Of Commits On A Branch](git/count-number-of-commits-on-a-branch.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)
@@ -330,6 +338,7 @@ _1550 TILs and counting..._
- [Interactively Unstage Changes](git/interactively-unstage-changes.md)
- [Keep File Locally With `git rm`](git/keep-file-locally-with-git-rm.md)
- [Last Commit A File Appeared In](git/last-commit-a-file-appeared-in.md)
- [List All Files Added During Span Of Time](git/list-all-files-added-during-span-of-time.md)
- [List All Files Changed Between Two Branches](git/list-all-files-changed-between-two-branches.md)
- [List Branches That Contain A Commit](git/list-branches-that-contain-a-commit.md)
- [List Commits On A Branch](git/list-commits-on-a-branch.md)
@@ -398,23 +407,29 @@ _1550 TILs and counting..._
- [Disable A Workflow With The gh CLI](github-actions/disable-a-workflow-with-the-gh-cli.md)
- [Reference An Encrypted Secret In An Action](github-actions/reference-an-encrypted-secret-in-an-action.md)
- [Trigger A Workflow Via An API Call](github-actions/trigger-a-workflow-via-an-api-call.md)
- [Use Labels To Block PR Merge](github-actions/use-labels-to-block-pr-merge.md)
### Go
- [Access Go Docs Offline](go/access-go-docs-offline.md)
- [Add A Method To A Struct](go/add-a-method-to-a-struct.md)
- [Basic Delve Debugging Session](go/basic-delve-debugging-session.md)
- [Build For A Specific OS And Architecture](go/build-for-a-specific-os-and-architecture.md)
- [Check If Cobra Flag Was Set](go/check-if-cobra-flag-was-set.md)
- [Combine Two Slices](go/combine-two-slices.md)
- [Configure Max String Print Length For Delve](go/configure-max-string-print-length-for-delve.md)
- [Connect To A SQLite Database](go/connect-to-a-sqlite-database.md)
- [Create A Slice From An Array](go/create-a-slice-from-an-array.md)
- [Detect If Stdin Comes From A Redirect](go/detect-if-stdin-comes-from-a-redirect.md)
- [Deterministically Seed A Random Number Generator](go/deterministically-seed-a-random-number-generator.md)
- [Difference Between Slice And Pointer To Slice](go/difference-between-slice-and-pointer-to-slice.md)
- [Do Something N Times](go/do-something-n-times.md)
- [Find Executables Installed By Go](go/find-executables-installed-by-go.md)
- [Format Date And Time With Time Constants](go/format-date-and-time-with-time-constants.md)
- [Not So Random](go/not-so-random.md)
- [Parse A String Into Individual Fields](go/parse-a-string-into-individual-fields.md)
- [Parse Flags From CLI Arguments](go/parse-flags-from-cli-arguments.md)
- [Pass A Struct To A Function](go/pass-a-struct-to-a-function.md)
- [Produce The Zero Value Of A Generic Type](go/produce-the-zero-value-of-a-generic-type.md)
- [Redirect File To Stdin During Delve Debug](go/redirect-file-to-stdin-during-delve-debug.md)
- [Replace The Current Process With An External Command](go/replace-the-current-process-with-an-external-command.md)
@@ -445,6 +460,7 @@ _1550 TILs and counting..._
- [Adding Alt Text To An Image](html/adding-alt-text-to-an-image.md)
- [Determine Which Button Submitted The Form](html/determine-which-button-submitted-the-form.md)
- [Disable Auto-Completion For A Form Input](html/disable-auto-completion-for-a-form-input.md)
- [Disclose Additional Details](html/disclose-additional-details.md)
- [Make Elements Non-Interactive With Inert](html/make-elements-non-interactive-with-inert.md)
- [Prevent Search Engines From Indexing A Page](html/prevent-search-engines-from-indexing-a-page.md)
- [Render Text As Superscript](html/render-text-as-superscript.md)
@@ -653,6 +669,8 @@ _1550 TILs and counting..._
- [Run AppleScript Commands Inline In The Terminal](mac/run-applescript-commands-inline-in-the-terminal.md)
- [Set A Window To Its Default Zoom Level](mac/set-a-window-to-its-default-zoom-level.md)
- [Specify App When Opening From Command Line](mac/specify-app-when-opening-from-command-line.md)
- [Start Amphetamine Session With AppleScript](mac/start-amphetamine-session-with-applescript.md)
- [Use A Different Font With iTerm2](mac/use-a-different-font-with-iterm2.md)
- [Use Default Screenshot Shortcuts With CleanShot X](mac/use-default-screenshot-shortcuts-with-cleanshot-x.md)
- [View All Windows Of The Current App](mac/view-all-windows-of-the-current-app.md)
- [Write System Clipboard To A File](mac/write-system-clipboard-to-a-file.md)
@@ -764,6 +782,7 @@ _1550 TILs and counting..._
- [Convert A String To A Timestamp](postgres/convert-a-string-to-a-timestamp.md)
- [Count How Many Records There Are Of Each Type](postgres/count-how-many-records-there-are-of-each-type.md)
- [Count Records By Type](postgres/count-records-by-type.md)
- [Count The Number Of Items In An Array](postgres/count-the-number-of-items-in-an-array.md)
- [Count The Number Of Trues In An Aggregate Query](postgres/count-the-number-of-trues-in-an-aggregate-query.md)
- [Create A Cluster In A Specific Data Directory](postgres/create-a-cluster-in-a-specific-data-directory.md)
- [Create A Composite Primary Key](postgres/create-a-composite-primary-key.md)
@@ -881,12 +900,14 @@ _1550 TILs and counting..._
- [Timestamp Functions](postgres/timestamp-functions.md)
- [Toggling The Pager In PSQL](postgres/toggling-the-pager-in-psql.md)
- [Track psql History Separately Per Database](postgres/track-psql-history-separately-per-database.md)
- [Trim Leading And Trailing Space From String](postgres/trim-leading-and-trailing-space-from-string.md)
- [Truncate All Rows](postgres/truncate-all-rows.md)
- [Truncate Tables With Dependents](postgres/truncate-tables-with-dependents.md)
- [Turning Timing On](postgres/turn-timing-on.md)
- [Two Ways To Compute Factorial](postgres/two-ways-to-compute-factorial.md)
- [Two Ways To Escape A Quote In A String](postgres/two-ways-to-escape-a-quote-in-a-string.md)
- [Types By Category](postgres/types-by-category.md)
- [Unable To Infer Data Type In Production](postgres/unable-to-infer-data-type-in-production.md)
- [Union All Rows Including Duplicates](postgres/union-all-rows-including-duplicates.md)
- [Use A psqlrc File For Common Settings](postgres/use-a-psqlrc-file-for-common-settings.md)
- [Use A Trigger To Mirror Inserts To Another Table](postgres/use-a-trigger-to-mirror-inserts-to-another-table.md)
@@ -927,6 +948,7 @@ _1550 TILs and counting..._
- [Add A Check Constraint To A Table](rails/add-a-check-constraint-to-a-table.md)
- [Add A Database Index If It Does Not Already Exist](rails/add-a-database-index-if-it-does-not-already-exist.md)
- [Add A Foreign Key Reference To A Table](rails/add-a-foreign-key-reference-to-a-table.md)
- [Add A Generated Column To A PostgreSQL Table](rails/add-a-generated-column-to-a-postgresql-table.md)
- [Add A Reference Column With An Index](rails/add-a-reference-column-with-an-index.md)
- [Add ActiveRecord Error Not Tied To Any Attribute](rails/add-activerecord-error-not-tied-to-any-attribute.md)
- [Add React With Webpacker To A New Rails App](rails/add-react-with-webpacker-to-a-new-rails-app.md)
@@ -939,6 +961,7 @@ _1550 TILs and counting..._
- [All or Nothing Database Transactions](rails/all-or-nothing-database-transactions.md)
- [Alphabetize Schema Columns To Keep Them Consistent](rails/alphabetize-schema-columns-to-keep-them-consistent.md)
- [Alter The Rails Setup Script](rails/alter-the-rails-setup-script.md)
- [Apply Basic HTML Formatting To Block Of Text](rails/apply-basic-html-formatting-to-block-of-text.md)
- [Assert Two Arrays Have The Same Items With RSpec](rails/assert-two-arrays-have-the-same-items-with-rspec.md)
- [Attach A File With Capybara](rails/attach-a-file-with-capybara.md)
- [Attribute Getter without the Recursion](rails/attribute-getter-without-the-recursion.md)
@@ -995,6 +1018,7 @@ _1550 TILs and counting..._
- [Hash Slicing](rails/hash-slicing.md)
- [Ignore Poltergeist JavaScript Errors](rails/ignore-poltergeist-javascript-errors.md)
- [Include Devise Helpers In Your Controller Tests](rails/include-devise-helpers-in-your-controller-tests.md)
- [Inspect Configuration Of Database Connection](rails/inspect-configuration-of-database-connection.md)
- [Inspect Previous Changes To ActiveRecord Object](rails/inspect-previous-changes-to-activerecord-object.md)
- [Link To The Current Page With Query Params](rails/link-to-the-current-page-with-query-params.md)
- [List All Installable Rails Versions](rails/list-all-installable-rails-versions.md)
@@ -1014,6 +1038,7 @@ _1550 TILs and counting..._
- [Migrating Up Down Up](rails/migrating-up-down-up.md)
- [Mock Rails Environment With An Inquiry Instance](rails/mock-rails-environment-with-an-inquiry-instance.md)
- [Order Matters For `rescue_from` Blocks](rails/order-matters-for-rescue-from-blocks.md)
- [Override Text Displayed By Form Label](rails/override-text-displayed-by-form-label.md)
- [Params Includes Submission Button Info](rails/params-includes-submission-button-info.md)
- [Params Is A Hash With Indifferent Access](rails/params-is-a-hash-with-indifferent-access.md)
- [Parse Query Params From A URL](rails/parse-query-params-from-a-url.md)
@@ -1026,6 +1051,7 @@ _1550 TILs and counting..._
- [Query A Single Value From The Database](rails/query-a-single-value-from-the-database.md)
- [Read In Environment-Specific Config Values](rails/read-in-environment-specific-config-values.md)
- [Read-Only Models](rails/read-only-models.md)
- [Rebuild Tailwind Bundle For Dev Server](rails/rebuild-tailwind-bundle-for-dev-server.md)
- [Remove A Database Column From A Table](rails/remove-a-database-column-from-a-table.md)
- [Remove The Default Value On A Column](rails/remove-the-default-value-on-a-column.md)
- [Render An Alternative ActionMailer Template](rails/render-an-alternative-action-mailer-template.md)
@@ -1041,6 +1067,7 @@ _1550 TILs and counting..._
- [Run A Rake Task Programmatically](rails/run-a-rake-task-programmatically.md)
- [Run Commands With Specific Rails Version](rails/run-commands-with-specific-rails-version.md)
- [Run Some Code Whenever Rails Console Starts](rails/run-some-code-whenever-rails-console-starts.md)
- [Scaffold Auth Functionality With Rails 8 Generator](rails/scaffold-auth-functionality-with-rails-8-generator.md)
- [Schedule Sidekiq Jobs Out Into The Future](rails/schedule-sidekiq-jobs-out-into-the-future.md)
- [Secure Passwords With Rails And Bcrypt](rails/secure-passwords-with-rails-and-bcrypt.md)
- [Select A Select By Selector](rails/select-a-select-by-selector.md)
@@ -1051,6 +1078,7 @@ _1550 TILs and counting..._
- [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 Meta Tags In ERB Views](rails/set-meta-tags-in-erb-views.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)
- [Set The Default Development Port](rails/set-the-default-development-port.md)
@@ -1268,6 +1296,7 @@ _1550 TILs and counting..._
- [Iterate With An Offset Index](ruby/iterate-with-an-offset-index.md)
- [Include Extra Context In A Honeybadger Notify](ruby/include-extra-context-in-a-honeybadger-notify.md)
- [Ins And Outs Of Pry](ruby/ins-and-outs-of-pry.md)
- [Install Latest Version Of Ruby With asdf](ruby/install-latest-version-of-ruby-with-asdf.md)
- [Invoking Rake Tasks Multiple Times](ruby/invoking-rake-tasks-multiple-times.md)
- [IRB Has Built-In Benchmarking With Ruby 3](ruby/irb-has-built-in-benchmarking-with-ruby-3.md)
- [Jump Out Of A Nested Context With Throw/Catch](ruby/jump-out-of-a-nested-context-with-throw-catch.md)
@@ -1300,6 +1329,7 @@ _1550 TILs and counting..._
- [Question Mark Operator](ruby/question-mark-operator.md)
- [Rake Only Lists Tasks With Descriptions](ruby/rake-only-lists-tasks-with-descriptions.md)
- [Read The First Line From A File](ruby/read-the-first-line-from-a-file.md)
- [Refer To Implicit Block Argument With It](ruby/refer-to-implicit-block-argument-with-it.md)
- [Rendering ERB](ruby/rendering-erb.md)
- [Replace The Current Process With An External Command](ruby/replace-the-current-process-with-an-external-command.md)
- [Require Entire Gemfile In Pry Session](ruby/require-entire-gemfile-in-pry-session.md)
@@ -1520,6 +1550,7 @@ _1550 TILs and counting..._
- [Killing A Frozen SSH Session](unix/killing-a-frozen-ssh-session.md)
- [Last Argument Of The Last Command](unix/last-argument-of-the-last-command.md)
- [Less With Style](unix/less-with-style.md)
- [List All Fonts On Your Machine](unix/list-all-fonts-on-your-machine.md)
- [List All The Enabled ZSH Options](unix/list-all-the-enabled-zsh-options.md)
- [List All Users](unix/list-all-users.md)
- [List Files In A Single Column](unix/list-files-in-a-single-column.md)
@@ -1562,6 +1593,7 @@ _1550 TILs and counting..._
- [Search History](unix/search-history.md)
- [Search Man Page Descriptions](unix/search-man-page-descriptions.md)
- [Securely Remove Files](unix/securely-remove-files.md)
- [See Where asdf Gets Current Tool Version](unix/see-where-asdf-gets-current-tool-version.md)
- [Set The asdf Package Version For A Single Shell](unix/set-the-asdf-package-version-for-a-single-shell.md)
- [Show A File Preview When Searching With FZF](unix/show-a-file-preview-when-searching-with-fzf.md)
- [Show Disk Usage For The Current Directory](unix/show-disk-usage-for-the-current-directory.md)
@@ -1785,8 +1817,10 @@ _1550 TILs and counting..._
- [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)
- [Allow Key-Repeating With Cursor](workflow/allow-key-repeating-with-cursor.md)
- [Break Justfile Into Separate Hidden Steps](workflow/break-justfile-into-separate-hidden-steps.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)
- [Control Media With Drop Keyboard](workflow/control-media-with-drop-keyboard.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)
@@ -1797,12 +1831,15 @@ _1550 TILs and counting..._
- [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)
- [Open Slack's Keyboard Shortcuts Reference Panel](workflow/open-slacks-keyboard-shortcuts-reference-panel.md)
- [Pop Videos Out As Picture-in-Picture](workflow/pop-videos-out-as-picture-in-picture.md)
- [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)
- [Send A PDF To Your Kindle](workflow/send-a-pdf-to-your-kindle.md)
- [Set Recurring Reminders In Slack](workflow/set-recurring-reminders-in-slack.md)
- [Show Linting Errors In Zed](workflow/show-linting-errors-in-zed.md)
- [Temporarily Hide CleanShot X Capture Previews](workflow/temporarily-hide-cleanshot-x-capture-previews.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)
- [View The PR For The Current GitHub Branch](workflow/view-the-pr-for-the-current-github-branch.md)
@@ -1854,11 +1891,11 @@ I shamelessly stole this idea from
* [Today I Learned by Hashrocket](https://til.hashrocket.com)
* [jwworth/til](https://github.com/jwworth/til)
* [thoughtbot/til](https://github.com/thoughtbot/til)
* [til.simonwillison.net](https://til.simonwillison.net/)
## License
© 2015-2022 Josh Branchaud
© 2015-2025 Josh Branchaud
This repository is licensed under the MIT license. See `LICENSE` for
details.

View File

@@ -0,0 +1,28 @@
# Default Rails Deploy Script On Hatchbox
I deployed a Rails app to [Hatchbox](https://hatchbox.io) recently. When
following along in the log during a deploy, I can see most of what is happening
as part of the deploy. Though it is too verbose to look through every line. I'd
rather see the contents of the deploy script.
I did quite a bit of digging around while SSH'd into my hatchbox server, but I
couldn't find if or where that file might be stored.
Instead, there is a [_Help Center_
article](https://hatchbox.relationkit.io/articles/55-what-is-the-default-rails-deploy-script)
where Chris Oliver shares what is in the script.
```bash
bundle install -j $(nproc)
yarn install
bundle exec rails assets:precompile
[[ -n "${CRON}" ]] && bundle exec rails db:migrate
```
It does a parallelized `bundle install`, then a `yarn install` (make sure your
project is using `yarn.lock`), Rails asset precompilation, and then if `CRON`
is set (Cron role is available by checking _Cron_ under _Server
Responsibilities_ for your Hatchbox server), it will run Rails migrations.
From app settings, the deploy script can be overridden, or pre- and post-deploy
steps can be added.

View File

@@ -0,0 +1,44 @@
# Hatchbox Exports Env Vars With asdf
When you add env vars through the [Hatchbox](https://hatchbox.io/) UI, they get
exported to the environment of the asdf-shimmed processes. This is handled by
the [`asdf-vars` plugin](https://github.com/excid3/asdf-vars). That plugin
looks for `.asdf-vars` in the current chain of directories.
I can see there are many `.asdf-vars` files:
```bash
$ find . -name ".asdf-vars" -type f
./.asdf-vars
./my-app/.asdf-vars
./my-app/releases/20250120195106/.asdf-vars
./my-app/releases/20250121041054/.asdf-vars
```
And it is the one in my app's directory that contains the env vars that I set
in the UI.
```bash
$ cat my-app/.asdf-vars
BUNDLE_WITHOUT=development:test
DATABASE_URL=postgresql://user_123:123456789012345@10.0.1.1/my_app_db
PORT=9000
RACK_ENV=production
RAILS_ENV=production
RAILS_LOG_TO_STDOUT=true
RAILS_MASTER_KEY=abc123
SECRET_KEY_BASE=abc123efg456
```
When I run a shimmed process like `ruby`, those env vars are loaded into the
process's environment.
```bash
$ cd my-app/current
$ which ruby
/home/deploy/.asdf/shims/ruby
$ ruby -e "puts ENV['DATABASE_URL']"
postgresql://user_123:123456789012345@10.0.1.1/my_app_db
```
[source](https://www.visualmode.dev/hatchbox-manages-env-vars-with-asdf)

View File

@@ -0,0 +1,24 @@
# Set Up Domain For Hatchbox Rails App
When we deploy a Rails app with [Hatchbox](https://hatchbox.io), we are given
an internal URL for publicly accessing our app. It is something like
`https://123abc.hatchboxapp.com`. That's useful as we are getting things up and
running, but eventually we want to point our own domain at the app.
The first step is to tell Hatchbox what domain we are going to use.
From our app's _Domain & SSL_ page we can enter a domain into the _Add A
Domain_ input. For instance, I have the
[visualmode.dev](https://visualmode.dev) domain and I want the
[still.visualmode.dev](https://still.visualmode.dev) subdomain pointing at my
Rails app. I submit the full name `still.visualmode.dev` and I get an _A
Record_ ipv4 address (e.g. `23.12.234.82`).
The second step is to configure a DNS record with our domain registrar.
From the DNS settings of our registrar (e.g. Cloudflare) we can add an _A
Record_ where we specify the name (e.g. `still`) and then include the ipv4
address provided by Hatchbox. We can save this and wait a minute for it to
propagate.
And soon enough we can visit our Rails app at the custom domain.

View File

@@ -0,0 +1,48 @@
# Count Number Of Commits On A Branch
The `git rev-list` command will show all commits that fit the given revision
criteria. By adding in the `--count` flag, we get a count of the number of
commits that would have been displayed. Knowing this, we can get the count of
commits for the current branch like so:
```bash
$ git rev-list --count HEAD
4
```
This finds and counts commits from `HEAD` (usually the top of the current
branch) all the back in reverse chronological order to the beginning of the
branch (typically the beginning of the repository). This works exactly as
expected for a the `main` branch.
What about when we are on a feature branch though?
Let's say we've branched off `main` and made a few commits. And now we want the
count.
```bash
$ git rev-list --count HEAD
7
```
Unfortunately, that is counting up the commits on the feature branch but it
keeps counting all the way back to the beginning of the repo.
If we want a count of just the commits on the current branch, then we can
specify a range: from whatever `main` was when we branched to the `HEAD` of
this branch.
```bash
$ git rev-list --count HEAD
3
```
This is the same as saying, I want all commits on `HEAD`, but exclude (`^`) the
commits on `main`:
```bash
git rev-list --count HEAD ^main
3
```
See `man git-rev-list` for more details.

View File

@@ -0,0 +1,49 @@
# List All Files Added During Span Of Time
I wanted to get an idea of all the TIL posts I wrote during 2024. Every TIL I
write is under version control in a [git repo on
github](https://github.com/jbranchaud/til). That means git has all the info I
need to figure that out.
The `git diff` command is a good way at this problem. With the
`--diff-filter=A` flag I can restrict the results to just files that were
_Added_. And with `--name-only` I can cut all the other diff details out and
get just filenames.
But filenames added to which commits? We need to specify a ref range. There is
a ton of flexibility in how you define a ref, including [a date specification
suffix](https://git-scm.com/docs/gitrevisions#Documentation/gitrevisions.txt-emltrefnamegtltdategtemegemmasteryesterdayememHEAD5minutesagoem)
that points to the value of the ref at an earlier point in time.
So, how about from the beginning of 2024 to the beginning of 2025:
```
HEAD@{2024-01-01}..HEAD@{2025-01-01}
```
Putting that all together, we this command and potentially a big list of files.
```bash
$ git diff --diff-filter=A --name-only HEAD@{2024-01-01}..HEAD@{2025-01-01}
```
I wanted to restrict the results to just markdown files, so I added a filename
pattern.
```bash
$ git diff --diff-filter=A --name-only HEAD@{2024-01-01}..HEAD@{2025-01-01} -- "*.md"
```
I could even go a step further to see only the files added to a specific
directory.
```bash
$ git diff --diff-filter=A --name-only HEAD@{2024-01-01}..HEAD@{2025-01-01} -- "postgres/*.md"
```
As a final bonus, I can spit out the github URLs for all those files with a bit of `awk`.
```bash
$ git diff --diff-filter=A --name-only HEAD@{2024-01-01}..HEAD@{2025-01-01} -- "postgres/*.md" |
awk '{print "https://github.com/jbranchaud/til/blob/master/" $0}'
```

View File

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

View File

@@ -0,0 +1,41 @@
# Use Labels To Block PR Merge
Let's say our GitHub project has custom tags for both `no merge` and `wip`
(_work in progress_). Whenever either of those labels has been applied to a PR,
we want there to be a failed check so as to block the merge. This is useful to
ensure automated tools (as well as someone not looking closely enough) don't
merge a PR that isn't _ready to go_.
This can be achieved with a basic GitHub Actions workflow that requires no
3rd-party actions. We can add the following as
`.github/workflows/block-labeled-prs.yml` in our project.
```yaml
name: Block Labeled PR Merges
on:
pull_request:
types: [labeled, unlabeled, opened, edited, synchronize]
jobs:
prevent-merge:
if: ${{ contains(github.event.*.labels.*.name, 'no merge') || contains(github.event.*.labels.*.name, 'wip') }}
name: Prevent Merging
runs-on: ubuntu-latest
steps:
- name: Check for label
run: |
echo "Pull request label prevents merging."
echo "Labels: ${{ join(github.event.*.labels.*.name, ', ') }}"
echo "Remove the blocking label(s) to skip this check."
exit 1
```
This workflow is run when a pull request is opened, when it is edited or
synchronized, and when a label change is made. The job `prevent-merge` sees if
any of the label names match `no merge` or `wip`. If so, we echo out some
details in the ubuntu container and then `exit 1` to fail the check.
Shoutout to [Jesse Squire's
implementation](https://www.jessesquires.com/blog/2021/08/24/useful-label-based-github-actions-workflows/#updated-21-march-2022)
which I've heavily borrowed from here.

View File

@@ -0,0 +1,63 @@
# Basic Delve Debugging Session
When using [delve](https://github.com/go-delve/delve) to debug a Go program,
these are the series of things I usually find myself doing.
First, I start running the program with `dlv` including any arguments after a `--` (in my case, the `solve` subcommand and a filename).
```bash
$ dlv debug . -- solve samples/001.txt
```
`dlv` starts up and is ready to run my program from the beginning. I'll need to
set a couple breakpoints before continuing. I do this with the `break` command,
specifying the filename and line number.
```
(dlv) break main.go:528
Breakpoint 1 set at 0x10c1a5bea for main.traversePuzzleIterative() ./main.go:528
(dlv) break main.go:599
Breakpoint 2 set at 0x10c1a6dcc for main.traversePuzzleIterative() ./main.go:599
```
Now I can continue which will run the program until hitting a breakpoint.
```
(dlv) continue
> [Breakpoint 2] main.traversePuzzleIterative() ./main.go:599 (hits goroutine(1):1 total:1) (PC: 0x10c1a6dcc)
594: }
595: }
596:
597: topStackFrame := stack[len(stack)-1]
598: // if the current stack frame has more values, try the next
=> 599: if len(topStackFrame.PossibleValues) > 0 {
600: nextValue := topStackFrame.PossibleValues[0]
601: topStackFrame.PossibleValues = topStackFrame.PossibleValues[1:]
602: topStackFrame.CurrValue = nextValue
603:
604: // Undo the last placement and make a new one
```
I can see the context around the line we've stopped on. From here I can dig
into the current state of the program by looking at local variables (`locals`)
or printing out a specific value (`print someVar`). I can continue to step
through the program line by line with `next` or eventually run `continue` to
proceed to the next breakpoint.
```
(dlv) locals
diagnostics = main.Diagnostics {BacktrackCount: 0, NodeVisitCount: 1, ValidityCheckCount: 2,...+2 more}
stack = []main.StackData len: 1, cap: 1, [...]
emptyCellPositions = [][]int len: 3, cap: 4, [...]
emptyCellIndex = 1
status = "Invalid"
topStackFrame = main.StackData {RowIndex: 1, ColumnIndex: 7, PossibleValues: []int len: 8, cap: 8, [...],...+1 more}
(dlv) print topStackFrame
main.StackData {
RowIndex: 1,
ColumnIndex: 7,
PossibleValues: []int len: 8, cap: 8, [2,3,4,5,6,7,8,9],
CurrValue: 1,}
(dlv) next
> main.traversePuzzleIterative() ./main.go:600 (PC: 0x10c1a6dea)
```

View File

@@ -0,0 +1,29 @@
# Configure Max String Print Length For Delve
During a [Delve](https://github.com/go-delve/delve) debugging session, we can
print out the value of a given variable with the `print` command. Similarly, we
can see the values of all local variables with the `locals` command.
Whenever Delve is printing out strings and slices, it will truncate what it
displays to 64 characters (or items) by default.
```go
(dlv) print diagnostics.Solutions[0]
"295743861\n431865972\n876192543\n387459216\n612387495\n549216738\n7635...+25 more"
```
This can be overridden by [changing the `config` of
`max-string-len`](https://github.com/derekparker/delve/blob/237c5026f40e38d2dd6f62a7362de7b25b00c1c7/Documentation/cli/expr.md?plain=1#L59)
to something longer. In my case here, all I need are about 90 characters to
display my full string, so run `config max-string-len 90` from the `dlv`
session.
```go
(dlv) config max-string-len 90
(dlv) print diagnostics.Solutions[0]
"295743861\n431865972\n876192543\n387459216\n612387495\n549216738\n763524189\n928671354\n154938627"
```
Now I can see the entire string instead of the truncated version.
[source](https://stackoverflow.com/a/52416264/535590)

View File

@@ -0,0 +1,50 @@
# Connect To A SQLite Database
Using the `database/sql` module and the `github.com/mattn/go-sqlite3` package,
we can connect to a SQLite database and run some queries. In my case, I have a
SQLite connection string exported to my environment, so I can access that with
`os.Getenv`. It's a local SQLite file, `./test.db`.
Calling `sql.Open`, I'm able to connect with a SQLite3 driver to the database
at that connection string. The `setupDatabase` function returns that database
connection pointer. Things like `Exec` and `QueryRow` can be called on `db`. I
also need to make sure I close the connection to the database with a `defer`.
Here is a full example of connecting to a local SQLite database and inserting a
record:
```go
package main
import (
"database/sql"
"fmt"
"os"
_ "github.com/mattn/go-sqlite3"
)
func setupDatabase() *sql.DB {
databaseString := os.Getenv("GOOSE_DBSTRING")
if len(databaseString) == 0 {
fmt.Println("Error retrieving `GOOSE_DBSTRING` from env")
os.Exit(1)
}
db, err := sql.Open("sqlite3", databaseString)
if err != nil {
fmt.Printf("Error opening database: %v\n", err)
os.Exit(1)
}
return db
}
func main() {
db := setupDatabase()
defer db.Close()
sql := `insert into users (name) values (?);`
db.Exec(sql, "Josh")
}
```

View File

@@ -0,0 +1,55 @@
# Difference Between Slice And Pointer To Slice
Though a slice can be thought of and used as a flexible, variable-length
array-like data structure, it is important to understand that it is also a
special kind of pointer to an underlying array.
This matters when we a function receives a slice versus a pointer to a slice as
an argument, depending on what it is doing with that slice.
If the function is access or updating elements in the slice, there is no
difference. There is no meaningful difference between these two functions and
we might as well use the former.
```go
func replaceAtIndex(slice []string, index int, value string) {
slice[index] = value
}
func replaceAtIndexPtr(slice *[]string, index int, value string) {
(*slice)[index] = value
}
```
On the other hand, if the receiving function needs to append to or replace the
slice, then we need to pass a pointer to the slice. A direct slice argument
will result in only the function-local copy getting replaced.
```go
package main
import (
"fmt"
)
func main() {
s1 := []int{8, 6, 7, 9}
s2 := []int{8, 6, 7, 9}
addItem(s1, 11)
fmt.Printf("s1: %v\n", s1) //=> s1: [8 6 7 9]
addItemPtr(&s2, 11)
fmt.Printf("s2: %v\n", s2) //=> s2: [8 6 7 9 11]
}
func addItem(slice []int, value int) {
slice = append(slice, value)
}
func addItemPtr(slice *[]int, value int) {
(*slice) = append(*slice, value)
}
```
[source](https://go.dev/tour/moretypes/8)

View File

@@ -0,0 +1,65 @@
# Pass A Struct To A Function
Go operates as _pass-by-value_ which means that when we pass a struct to a
function, the receiving function gets a copy of the struct. Two things worth
noticing about that are 1) an extra memory allocation happens when calling the
function and 2) altering the struct does not affect the original in the calling
context.
On the other hand, we can have a function that takes a pointer to a struct.
When we call that function, we have a reference to the memory location of the
struct instead of a copy of the struct. That means no additional allocation and
modifications to the dereferenced struct are modifications to the original in
the calling context.
Here is an example that demonstrates both of these. Notice the printed output
that is included in comments at the end which shows memory locations and
contents of the struct at various points.
```go
package main
import "fmt"
type Order struct {
Item string
Quantity int
DineIn bool
}
func main() {
order := Order{Item: "taco", Quantity: 3, DineIn: true}
fmt.Println("Order:", order)
fmt.Printf("main - Loc: %p\n", &order)
doubledOrder := doubleOrder(order)
fmt.Println("Double Order:", doubledOrder)
fmt.Println("Original Order:", order)
doubleOrderPtr(&order)
fmt.Println("Double Order Ptr:", order)
}
func doubleOrder(order Order) Order {
fmt.Printf("doubleOrder - Loc: %p\n", &order)
order.Quantity *= 2
return order
}
func doubleOrderPtr(order *Order) {
fmt.Printf("doubleOrderPtr - Loc: %p\n", order)
(*order).Quantity *= 2
}
// Order: {taco 3 true}
// main - Loc: 0xc0000b4000
// doubleOrder - Loc: 0xc0000b4040
// Double Order: {taco 6 true}
// Original Order: {taco 3 true}
// doubleOrderPtr - Loc: 0xc0000b4000
// Double Order Ptr: {taco 6 true}
```

View File

@@ -0,0 +1,28 @@
# Disclose Additional Details
You can add extra details to an HTML page that are only disclosed if the user
chooses to disclose them. To do that, we use the `<details>` tag. This tag
needs to have a `<summary>` tag nested within it. Anything else nested within
`<details>` will be what is disclosed when it is toggled open. The `<summary>`
is what is displayed when it is not open.
Here is a `<detail>` block I recently added to [Ruby Operator
Lookup](https://www.visualmode.dev/ruby-operators).
```html
<details className="pt-2 pb-6">
<summary>What is this thing?</summary>
<p className="pl-3 pt-2 text-gray-700 text-sm">
Ruby is an expressive, versatile, and flexible dynamic programming language. That means there are all kinds of syntax features, operators, and symbols we can encounter that might look unfamiliar and are hard to look up. Ruby Operator Lookup is a directory of all these language features.
</p>
<p className="pl-3 pt-2 text-gray-700 text-sm">
Use the search bar to narrow down the results. Then click on a button for the operator or symbol you want to explore further.
</p>
</details>
```
On page load, the only thing we see is "What is this thing?" with a triangle
symbol next to it. If we click the summary, then the entire details block
(those two `<p>` tags) are disclosed.
[source](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/details)

View File

@@ -0,0 +1,37 @@
# Start Amphetamine Session With AppleScript
I use the _Amphetamine_ app on Mac to keep my computer from going to sleep
during the day. It is a menu bar app that can be used to start a _Session_ of
time where it will keep your computer from going to sleep. At the start of my
day, I'll typically start an 8 hour _Session_. This is useful if I have to step
away fo 10 minutes or if I'm doing some writing in my notebook, my computer
won't go to sleep on me.
Though these sessions can be controlled from the menu bar app, I was excited to
learn that I can also programatically start a session with AppleScript.
Here is how to start a _Session_ (overriding an existing session) with options
that specify it is 8 hours long and the display should not be allowed to sleep.
```bash
$ osascript -e 'tell application "Amphetamine" to start new session with options {duration:8, interval:hours, displaySleepAllowed:false}'
```
The `interval` could also be `minutes` and then I could change the duration to
an amount of time that makes sense in minutes, e.g. `90` for 1.5 hours.
Note: the `with options {...}` segement is all or nothing. All three need to be included or don't include the clause at all.
Additionally, a session of indefinite duration can be started by including no options:
```bash
$ osascript -e 'tell application "Amphetamine" to start new session'
```
And any existing session can be ended with:
```bash
$ osascript -e 'tell application "Amphetamine" to end session'
```
[source](https://iffy.freshdesk.com/support/solutions/articles/48000078223-applescript-documentation)

View File

@@ -0,0 +1,25 @@
# Use A Different Font With iTerm2
I wanted to give [`gh-dash`](https://github.com/dlvhdr/gh-dash) a try, but
after installing and opening it up, I was seeing a bunch of `?` characters
where specialized font icons were missing. Their README recommended installing
a [`Nerd Font`](https://github.com/ryanoasis/nerd-fonts) that includes those
icons, such as [`Fira Code`](https://github.com/tonsky/FiraCode).
I was able to install `font-fira-code-nerd-font` with homebrew:
```bash
$ brew install font-fira-code-nerd-font
```
Then to get iTerm2 to start using that font, I had to change the font setting
for my current profile.
Under the _iTerm2_ menu is _Settings..._. From there, I clicked the _Profiles_
section. For the _Default_ profile, I went to the _Text_ tab and under _Font_ I
selected _FireCode Nerd Font Mono_ from the dropdown.
That won't take effect on any current iTerm2 windows. Since I have everything
running through `tmux`, I could close my current window, open a new one
(`Cmd+N`), and reconnect to my existing `tmux` session. Now when I run `gh
dash`, I see all the font icons that were missing before.

View File

@@ -0,0 +1,56 @@
# Count The Number Of Items In An Array
There are two ways to count the number of items in an array with PostgreSQL.
The one that might jump out at you or show up at the top of search results is
[`array_length`](https://www.postgresql.org/docs/current/functions-array.html).
```sql
> select array_length(array[1,2,3], 1);
+--------------+
| array_length |
|--------------|
| 3 |
+--------------+
> select array_length(array[[1,2], [3,4]], 2);
+--------------+
| array_length |
|--------------|
| 2 |
+--------------+
```
This requires specifying the dimension at which you want to check the length.
The first example, checking the 1st dimension of a one-dimensional array, seems
like the more common and useful scenario. In the second example, we are
checking the 2nd dimension.
The other way we can determine the number of items in an array is with the
[`cardinality`](https://www.postgresql.org/docs/current/functions-array.html)
function.
> Returns the total number of elements in the array, or 0 if the array is
> empty.
```sql
> select cardinality(array[1,2,3]);
+-------------+
| cardinality |
|-------------|
| 3 |
+-------------+
> select cardinality(array[[1,2], [3,4]]);
+-------------+
| cardinality |
|-------------|
| 4 |
+-------------+
```
This behaves the same as `array_length` for a one-dimensional array and doesn't
require a second argument. Where it gets more interesting is with
multi-dimensional arrays. It returns the total number of elements in the
arrayregardless of the nesting.
[source](https://mattrighetti.com/2025/01/20/you-dont-need-sql-builders)

View File

@@ -0,0 +1,58 @@
# Trim Leading And Trailing Space From String
PostgreSQL has a bunch of [string
functions](https://www.postgresql.org/docs/current/functions-string.html),
including several for doing various string trimming.
We can use the simplest form of `trim` to remove leading and trailing space
characters from a string.
```sql
> select trim(' Taco Cat ');
+----------+
| btrim |
|----------|
| Taco Cat |
+----------+
```
The syntax for calling `trim` is a bit odd relative to other PostgreSQL
functions and functions in other languages. Here is the "grammar" as described
in the docs:
```
trim ( [ LEADING | TRAILING | BOTH ] [ characters text ] FROM string text ) → text
```
We pick `leading`, `trailing`, or `both`, with `both` being the default. Then
we specify the character(s) we want to remove. This is also optional, the
default being the space character. Then we say `from` what string we want to
trim those characters.
Here we remove all sequential spaces from `both` ends of the given string:
```sql
> select trim(both from ' Taco Cat ');
+----------+
| btrim |
|----------|
| Taco Cat |
+----------+
```
To further demonstrate how `trim` works, here we remove all sequences made up
of any of spaces, uppercase `T`, and lowercase `t` from `both` ends of the
string:
```sql
> select trim(both ' Tt' from ' Taco Cat ');
+--------+
| btrim |
|--------|
| aco Ca |
+--------+
```
Notice that in all the above examples the column name of the result is `btrim`.
That's probably because `btrim` (_trim both ends_) is being called under the
hood for the `both` option.

View File

@@ -0,0 +1,45 @@
# Unable To Infer Data Type In Production
Inspired by [You Probably Don't Need Query
Builders](https://mattrighetti.com/2025/01/20/you-dont-need-sql-builders), I
wrote a query in one of my applications that has filter clauses that get
short-circuited if the filter value hasn't been included.
That query looked something like this:
```ruby
@tags =
Tag.where("? is null or normalized_value ilike ?", normalized_query, "%#{normalized_query}%")
.order(:normalized_value)
.limit(10)
```
The `normalized_value ilike ?` filtering won't be applied if the
`normalized_query` value isn't present (`nil`). This helps me avoid writing
messy ternaries or if-else conditional query building madness.
Unfortunately, when I shipped this query to production, the page started
failing and Postgres was reporting this error in the logs.
```
Caused by: PG::IndeterminateDatatype (ERROR: could not determine data type of parameter $1)
```
The query is prepared as a parameterized statement and Postgres appears to be
unable to determine the datatype of the first parameter (`$1`) —
`normalized_query`.
I was unable to reproduce the issue in development. It was only occuring in
production. Until I can come up with a root cause analysis, I have the
following fix that does a casting to `text`. This helps out with the type
inference and makes the issue go away.
```ruby
@tags =
Tag.where("cast(? as text) is null or normalized_value ilike ?", normalized_query, "%#{normalized_query}%")
.order(:normalized_value)
.limit(10)
```
Interestingly, this person using `pgtyped` [ran into the exact same issue with
the same type of query](https://github.com/adelsz/pgtyped/issues/354).

View File

@@ -0,0 +1,22 @@
# Add A Generated Column To A PostgreSQL Table
As of Rails 7, ActiveRecord supports generated columns for app's backed by a
PostgreSQL database. This is achieved with a `virtual` column.
```ruby
class CreateTags < ActiveRecord::Migration[8.0]
def change
create_table :tags, id: :bigint do |t|
t.string :value
t.virtual :normalized_value, type: :text, as: "lower(value)", stored: true
t.timestamps
end
end
end
```
With a table like this, any time we add a record with a `value`, PostgreSQL
computes and stores the `normalized_value` column based on that.
[source](https://blog.saeloun.com/2022/01/25/rails-7-postgres-support-for-generated-columns/)

View File

@@ -0,0 +1,40 @@
# Apply Basic HTML Formatting To Block Of Text
My Rails app has a form that allows a user to enter in free-form text. I enter
in a couple paragraphs and save the record. It is rendered on a show page with
a couple lines of ERB like so:
```ruby
<div class="max-w-3xl mx-auto">
<div class="space-y-4">
<div class="prose mt-8 text-gray-700">
<%= @record.notes %>
</div>
</div>
</div>
```
When I view the erb-displayed version of that record's text, all those
carefully spaced paragraphs are clumped together. That is because those newline
(`\n` and `\n\n`) characters while understood to be whitespace do not have
formatting implications in the browser like a combination of HTML tags and CSS
do.
I can apply some basic formatting with [the aptly named `simple_format` method
available as an `ActionView`
helper](https://api.rubyonrails.org/classes/ActionView/Helpers/TextHelper.html#method-i-simple_format).
```ruby
<div class="max-w-3xl mx-auto">
<div class="space-y-4">
<div class="prose mt-8 text-gray-700">
<%= simple_format(@record.notes) %>
</div>
</div>
</div>
```
This turns single `\n` characters into a `<br />` tag and double `\n\n` cause
the surrounding paragraphs to be wrapped in `<p>` tags. That simple formatting
combined with my existing TailwindCSS styles makes the formatting of my text
immediately look much better.

View File

@@ -0,0 +1,45 @@
# Inspect Configuration Of Database Connection
There are a lot of factors that can effect the database configuration values.
- What are the settings in each environment in `config/database.yml`?
- Is there any dynamic ERB code in `config/database.yml`?
- Is `DATABASE_URL` set in the current environment?
- Is any other code overriding these settings?
To check the current _configuration hash_ for the database connection at
runtime, we can run the following statement:
```ruby
> ActiveRecord::Base.connection.pool.db_config.configuration_hash
=>
{:adapter=>"postgresql",
:encoding=>"unicode",
:host=>"::1",
:user=>"postgres",
:password=>"postgres",
:pool=>5,
:database=>"still_development",
:port=>9875}
```
In this case, I'm running the statement from the Rails console of my app's
development environment.
I could even access and print these values as part of debugging in a production
environment with a rake task:
```ruby
# In lib/tasks/debug.rake
namespace :debug do
task :db_config => :environment do
puts "==== Database Configuration Debug ===="
puts "DATABASE_URL: #{ENV['DATABASE_URL']}"
puts "Active Record Config: #{ActiveRecord::Base.connection.pool.db_config.configuration_hash}"
puts "Raw ENV dump:"
ENV.sort.each { |k,v| puts "#{k}: #{v}" if k.include?('DB') || k.include?('DATABASE') }
end
end
```
[source](https://api.rubyonrails.org/classes/ActiveRecord/DatabaseConfigurations/HashConfig.html)

View File

@@ -0,0 +1,38 @@
# Override Text Displayed By Form Label
Rails does a good job with the default text displayed by a form label. It takes
the primary symbol value you give it and capitalizes that. And that is often
good enough.
```ruby
<%= form_with(model: post) do |form| %>
<%= form.label :title, class: "text-sm font-medium text-gray-700" %>
<%= form.text_field :title, required: true, class: "..." %>
<% end %>
```
This will yield a label value of _Title_.
Sometimes, however, the casing needs to be different or you need entirely
different text. Take this URL field for example. Rails will convert `:url` into
_Url_ for the label text. Not ideal. I can override the default with a second
positional argument, in this case, `"URL"`.
```ruby
<%= form_with(model: post) do |form| %>
<%= form.label :url, "URL", class: "text-sm font-medium text-gray-700" %>
<%= form.url_field :url, required: true, class: "..." %>
<% end %>
```
The [Rails docs have another good
example](https://guides.rubyonrails.org/form_helpers.html#a-generic-search-form).
A label with a value of `query` that is overridden to display "Search for:".
```ruby
<%= form_with url: "/search", method: :get do |form| %>
<%= form.label :query, "Search for:" %>
<%= form.search_field :query %>
<%= form.submit "Search" %>
<% end %>
```

View File

@@ -0,0 +1,29 @@
# Rebuild Tailwind Bundle For Dev Server
If you're using the TailwindCSS gem in your Rails app:
```ruby
# Use Tailwind CSS [https://github.com/rails/tailwindcss-rails]
gem "tailwindcss-rails"
```
you may find that as you add and adjust styles in your views, refreshing the
page doesn't take any styling effects. That is because the tailwind bundle gets
built with just the style rules that were used at the time it was generated.
In development, as we're working, we expect the styles used by our app to
actively changed. And we don't mind a little performance hit to have the bundle
rebuilt. In that case, we can instruct `puma` to _Live Rebuild_ in
`development` with the `tailwindcss` plugin.
```ruby
# config/puma.rb
# Enable TailwindCSS rebuild in development
plugin :tailwindcss if ENV.fetch("RAILS_ENV", "development") == "development"
```
This has `rails server` run a watch process in the background that live
rebuilds the bundle.
[source](https://github.com/rails/tailwindcss-rails?tab=readme-ov-file#puma-plugin)

View File

@@ -0,0 +1,44 @@
# Scaffold Auth Functionality With Rails 8 Generator
Rails 8 added a built-in generator for authentication that scaffolds the core
models, controllers, views, routes, etc. needed for a basic email/password
authentication flow. It creates a `User` model, if one doesn't already exist,
as the authenticated object. It uses the `bcrypt` gem for password hashing,
etc.
Here is an example of what you get when running the generator on a relatively
new Rails 8 project:
```bash
$ bin/rails generate authentication
invoke tailwindcss
create app/views/passwords/new.html.erb
create app/views/passwords/edit.html.erb
create app/views/sessions/new.html.erb
create app/models/session.rb
create app/models/user.rb
create app/models/current.rb
create app/controllers/sessions_controller.rb
create app/controllers/concerns/authentication.rb
create app/controllers/passwords_controller.rb
create app/channels/application_cable/connection.rb
create app/mailers/passwords_mailer.rb
create app/views/passwords_mailer/reset.html.erb
create app/views/passwords_mailer/reset.text.erb
create test/mailers/previews/passwords_mailer_preview.rb
insert app/controllers/application_controller.rb
route resources :passwords, param: :token
route resource :session
gsub Gemfile
bundle install --quiet
generate migration CreateUsers email_address:string!:uniq password_digest:string! --force
rails generate migration CreateUsers email_address:string!:uniq password_digest:string! --force
invoke active_record
create db/migrate/20250115224625_create_users.rb
generate migration CreateSessions user:references ip_address:string user_agent:string --force
rails generate migration CreateSessions user:references ip_address:string user_agent:string --force
invoke active_record
create db/migrate/20250115224626_create_sessions.rb
```
[source](https://www.bigbinary.com/blog/rails-8-introduces-a-basic-authentication-generator)

View File

@@ -0,0 +1,52 @@
# Set Meta Tags In ERB Views
There are all kinds of meta tags that we may want to set for the pages that our
Rails app serves. A lot of these are for SEO and social sharing. Let's look at
how to add `og:description` meta tags to our views.
I'll start with a helper method in `app/helpers/application_helper.rb`:
```ruby
module ApplicationHelper
def meta_description(desc)
content_for(:description) { desc }
end
end
```
Then, I'll update my `app/views/layouts/application.html.erb` to consume the
description when provided.
```ruby
<!DOCTYPE html>
<html>
<head>
<!-- ... -->
<meta
property="og:description"
content="<%= content_for?(:description) ? yield(:description) : 'Default description' %>"
>
<!-- ... -->
</head>
<!-- ... -->
</html>
```
Now I have a default description for all my views that I can override as needed
with the `meta_description` helper.
```ruby
# app/views/posts/show.html.erb
<%= meta_description @post.body.split("\n").first %>
<!-- ... -->
```
If I reload the page and inspect the meta tags in `<head>`, I should find the
`og:description` tag with the corresponding value.
This can be extended to apply all the different meta tags (e.g. Open Graph and
Twitter) to make links to these pages render well across the internet.

View File

@@ -0,0 +1,54 @@
# Install Latest Version Of Ruby With asdf
When I check the `asdf` Ruby plugin for known versions of Ruby:
```bash
$ asdf list-all ruby | fzf
```
I don't find the latest (`3.4`).
I need to update the plugin. A newer version of the plugin will know about
newer Ruby versions.
```bash
$ asdf plugin-update ruby
```
Now, if I run the `list-all` command again, I'll find the version I'm looking
for — `3.4.1`.
Now that `asdf` and I both know about the version to be installed, I can tell
`asdf` to install it:
```bash
$ asdf install ruby 3.4.1
```
Now, if I check the current Ruby version, I'll see that it is still set to some
other version.
```bash
$ ruby --version
ruby 3.2.2 (2023-03-30 revision e51014f9c0) [x86_64-darwin22]
```
I need to tell `asdf` to start using this newly installed version instead,
either globally or locally.
```bash
$ # globally
$ asdf global ruby 3.4.1
$ # or locally
$ asdf local ruby 3.4.1
```
And now I'm all set:
```bash
$ asdf current ruby
ruby 3.4.1 /Users/jbranchaud/.tool-versions
$ ruby --version
ruby 3.4.1 (2024-12-25 revision 48d4efcb85) +PRISM [x86_64-darwin22]
```

View File

@@ -0,0 +1,43 @@
# Refer To Implicit Block Argument With It
One of the key features of the Ruby 3.4 release is the `it` implicit block
argument.
The vast majority of inline blocks defined in Ruby code receive a single block
argument. Typically we name and reference a block argument explictly like so:
```ruby
items.map { |item| item * item }
```
Ruby likes to cut away excess syntax when possible. To that end, the implicit
`it` block argument has been added. This is an identifier we can reference in
the context of a block and its value is the current
```ruby
items = [1,2,3,4,5]
squares = items.map { it * it }
pp squares
#=> [1, 4, 9, 16, 25]
```
Note: we cannot mix numbered parameters (`_1`, `_2`) with the `it` parameter.
If we do, we'll get the following error:
```ruby
def method_using_block(a, b)
yield(a, b) if block_given?
end
puts method_using_block(4,5) { _2 ** _1 } #=> 625
puts method_using_block(4,5) { _2 ** it }
# it_block.rb:12: syntax error found (SyntaxError)
# 10 |
# 11 | puts method_using_block(4,5) { _2 ** _1 }
# > 12 | ... it }
# | ^~ `it` is not allowed when a numbered parameter is already used
```
[source](https://docs.ruby-lang.org/en/3.4/NEWS_md.html)

View File

@@ -0,0 +1,24 @@
# List All Fonts On Your Machine
In trying to figure out what _FiraCode_ font I have installed on my machine
and what it is called, I came across [this StackOverflow
answer](https://stackoverflow.com/a/52789662/535590) which shares the
following one-liner:
```bash
$ fc-list | awk '{$1=""}1' | cut -d: -f1 | sort | uniq
```
This uses `fc-list` to get the names of all the fonts available on your
machine. This seems to work on both Linux and Mac. Through a series of `awk`,
`cut`, and `sort | uniq`, this command produces a clean, easily-browsed list
of fonts.
I like to take this a step further by piping it all to `fzf` where I can then
narrow down the output to just lines that match _FiraCode_.
```bash
$ fc-list | awk '{$1=""}1' | cut -d: -f1 | sort | uniq | fzf
```
See also [`system_profiler SPFontsDataType`](https://apple.stackexchange.com/questions/35852/list-of-activated-fonts-with-shell-command-in-os-x/243746#243746).

View File

@@ -0,0 +1,29 @@
# See Where asdf Gets Current Tool Version
The other day I [installed the latest version of
Ruby](ruby/install-latest-version-of-ruby-with-asdf.md) with `asdf`. I then set
that version (`3.4.1`) as the global default. However, when I then ran `ruby
--version`, I was getting a `3.2.x` version. I checked my current project's
directory and there was no `.tool-versions` file, so it wasn't being set by my
current directory.
`asdf` looks up the current chain of directories until it encounters a
`.tool-versions` file, so it must have been finding one somewhere up there, but
before it was getting to the _global_ `.tool-versions` file. But where?
The `asdf current` command can tell us for a specific tool what the current
version it is set to and what file is giving that directive.
```bash
asdf current ruby
ruby 3.2.2 /Users/jbranchaud/code/.tool-versions
```
As it turns out, I had a `.tool-versions` file in `$HOME/code` that was setting
that `3.2.x` Ruby version.
I didn't want that directory controlling the Ruby version, so I removed `ruby`
from that file. `asdf` was then able to traverse up to `$HOME/.tool-versions`
for the global setting.
See `asdf help` for more details.

View File

@@ -0,0 +1,48 @@
# Break Justfile Into Separate Hidden Steps
With `just` and a project's `justfile`, I can get a summary of the commands
available to run against my project by running `just --list`. If I try to
breakdown a complex, multi-step command into separate `just` commands, it will
be nice for organization, but it will clutter the list output. I can mark
specific commands as hidden or internal by preceding them with an underscore
(`_`).
Here is a `justfile` from one of my projects that only lists a single command
`setup` which itself is supported by three internal commands: `_check-brew`,
`_install-deps`, and `_install-go-tools`.
```justfile
# Install all required development dependencies
setup: _check-brew _install-deps _install-go-tools
# Check if brew is installed
_check-brew:
#!/usr/bin/env bash
if ! command -v brew &> /dev/null; then
echo "Error: Homebrew is not installed"
echo "Please install from https://brew.sh"
exit 1
fi
brew_deps := '''
go
sqlite3
'''
# Install brew dependencies
_install-deps:
#!/usr/bin/env bash
deps=$(echo '{{brew_deps}}' | tr -s '[:space:]' ' ' | xargs)
for pkg in $deps; do
if ! brew list $pkg &>/dev/null; then
echo "Installing $pkg..."
brew install $pkg
else
echo "✓ $pkg already installed"
fi
done
# Install Go development tools
_install-go-tools:
go install github.com/pressly/goose/v3/cmd/goose@latest
```

View File

@@ -0,0 +1,20 @@
# Control Media With Drop Keyboard
I have a [Drop CTRL](https://drop.com/buy/drop-ctrl-v2-mechanical-keyboard)
mechanical keyboard which mostly works like any other keyboard. It also has a
set of functionality that can be accessed via the `fn` (function) key. The
function key can be used to configure the keyboard's LEDs, but I tend to set
and forget that.
Instead, I like to use the function key to control media. That is, adjust the
volume, play and pause, and skip to the next song.
Here is a listing of the ones I use:
- `Fn + Insert` to Pause / Play
- `Fn + PgUp/PgDown` to Increase / Decrease the volume
- `Fn + Del/End` to go to the Previous / Next song
Here is a [full listing of the function
keys](https://drop.com/talk/9382/how-to-configure-your-drop-keyboard) for Drop
keyboards.

View File

@@ -0,0 +1,21 @@
# Pop Videos Out As Picture-in-Picture
I recently learned that just about any video playing in Chrome and Firefox can
be popped out to a picture-in-picture (PIP) player. A PIP player gives you a sidecar
video player window that you can arrange and resize anywhere on your screen. It
sits on top of other windows so that you can view it while working from other
apps. This is useful if, for instance, you are working through a coding
tutorial on youtube.
For most video players, you can right click on the video and the menu that
appears will include a "Picture in Picture" option. Select that and the arrange
the player to your liking.
Youtube overrides right-click. If you right-click, you'll see one menu of
Youtube-specific options. Right-click a second time to open the standard
browser menu which will include the PIP option.
I noticed while also testing this on Firefox, that they have a PIP icon that
appears as a small overlay on the right side of the video player that you can
click as well. This is useful because I found some site's video players were
(inadvertently) preventing right-click.

View File

@@ -0,0 +1,31 @@
# Send A PDF To Your Kindle
I recently got a Kindle. I already have a bunch of PDF and ePub books on my
computer that I've bought over the years. I wanted to be able to read some of
those books on the Kindle. I found that there is a way to send these formats to
your Kindle via email.
There are a couple steps to get this working.
First, from the Amazon account that is tied to the Kindle device, open the
_Account_ dropdown and click _Devices. Any devices tied to your account will be
listed there. Navigate to the one you want to send to. Under the _Device
Summary_ with be a custom email address for that device. Something like
`youremail_abc123@kindle.com`.
That's the email you'll send the PDF or ePub attachment to.
Second, that Kindle email address will only receive and process documents from
a known, verified email address. Back on the _Devices_page, click on the
_Preferences_ tab. Under _Personal Document Preferences_ make sure that the
_Approved Personal Document Email List_ includes the email address you'll be
sending from. Add it if not.
Everything is set up. Now compose an email to that Kindle address, add the
attachment, and send. Give it 5 or so minutes to process and it should show up
on your device.
Additionally, you can go to the _Content_ tab and then to _Digital Content_ to
see what documents you have set and which devices have received them.
[source](https://goodereader.com/blog/kindle/here-is-how-you-can-read-pdf-files-on-the-amazon-kindle)

View File

@@ -0,0 +1,14 @@
# Temporarily Hide CleanShot X Capture Previews
The _capture previews_ that CleanShot X provides are a useful part of my
workflow. I often capture a screenshot or recording a bit in advance of needing
to add it as, say, an attachment. The preview icons float off to the right of
my screen, generally out of the way. As soon as I need them, I can annotate,
drag-n-drop, etc.
Sometimes, however, they do get in the way. But I'm not ready to dismiss them
and I don't want to save them off to some folder buried in my file system.
To temporarily slide the capture previews down off the screen, I can hit the
shortcut `Ctrl+Opt+Cmd+H`. The arrow at the butto of the screen can be clicked
to unhide them, or I can hit the same shortcut sequence to reveal them.