mirror of
https://github.com/jbranchaud/til
synced 2026-07-07 09:30:34 +00:00
Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4293420c13 | |||
| eb3369d296 | |||
| 6f47e2f057 | |||
| 409201611f | |||
| 77cc07a908 | |||
| 633c1fa0a5 | |||
| 96c394c198 | |||
| 0251157dc4 | |||
| 97c8701a5a | |||
| 1fd64e478a | |||
| 8ea123369b | |||
| 43c6e08b34 | |||
| 61fc021f52 | |||
| 46ad33df7e | |||
| 2028f6cb09 | |||
| 1f039a8958 | |||
| c6eefeac98 | |||
| 31a0224fb7 | |||
| aa71ff5f8b | |||
| 48278c4908 | |||
| 8b3ef4872c | |||
| c2184a5ecf | |||
| e2a8e815e9 | |||
| c61ddcb326 | |||
| 7632664200 | |||
| 872a1d2a00 | |||
| d52a126767 | |||
| d9080cc583 | |||
| 654c65c8f6 | |||
| 138cab4fdc |
@@ -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).
|
For a steady stream of TILs, [sign up for my newsletter](https://crafty-builder-6996.ck.page/e169c61186).
|
||||||
|
|
||||||
_1562 TILs and counting..._
|
_1589 TILs and counting..._
|
||||||
|
|
||||||
See some of the other learning resources I work on:
|
See some of the other learning resources I work on:
|
||||||
- [Ruby Operator Lookup](https://www.visualmode.dev/ruby-operators)
|
- [Ruby Operator Lookup](https://www.visualmode.dev/ruby-operators)
|
||||||
@@ -199,12 +199,15 @@ See some of the other learning resources I work on:
|
|||||||
- [Check The Status of All Services](devops/check-the-status-of-all-services.md)
|
- [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)
|
- [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)
|
- [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)
|
- [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)
|
- [Path Of The Packets](devops/path-of-the-packets.md)
|
||||||
- [Push Non-master Branch To Heroku](devops/push-non-master-branch-to-heroku.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)
|
- [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)
|
- [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)
|
- [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)
|
- [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)
|
- [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)
|
- [Wipe A Heroku Postgres Database](devops/wipe-a-heroku-postgres-database.md)
|
||||||
@@ -304,6 +307,7 @@ See some of the other learning resources I work on:
|
|||||||
- [Configuring The Pager](git/configuring-the-pager.md)
|
- [Configuring The Pager](git/configuring-the-pager.md)
|
||||||
- [Copy A File From Another Branch](git/copy-a-file-from-another-branch.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 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)
|
- [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)
|
- [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)
|
- [Determine The Hash Id For A Blob](git/determine-the-hash-id-for-a-blob.md)
|
||||||
@@ -334,6 +338,7 @@ See some of the other learning resources I work on:
|
|||||||
- [Interactively Unstage Changes](git/interactively-unstage-changes.md)
|
- [Interactively Unstage Changes](git/interactively-unstage-changes.md)
|
||||||
- [Keep File Locally With `git rm`](git/keep-file-locally-with-git-rm.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)
|
- [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 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 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)
|
- [List Commits On A Branch](git/list-commits-on-a-branch.md)
|
||||||
@@ -351,6 +356,7 @@ See some of the other learning resources I work on:
|
|||||||
- [Quicker Commit Fixes With The Fixup Flag](git/quicker-commit-fixes-with-the-fixup-flag.md)
|
- [Quicker Commit Fixes With The Fixup Flag](git/quicker-commit-fixes-with-the-fixup-flag.md)
|
||||||
- [Rebase Commits With An Arbitrary Command](git/rebase-commits-with-an-arbitrary-command.md)
|
- [Rebase Commits With An Arbitrary Command](git/rebase-commits-with-an-arbitrary-command.md)
|
||||||
- [Reference A Commit Via Commit Message Pattern Matching](git/reference-a-commit-via-commit-message-pattern-matching.md)
|
- [Reference A Commit Via Commit Message Pattern Matching](git/reference-a-commit-via-commit-message-pattern-matching.md)
|
||||||
|
- [Reference Commits Earlier Than Reflog Remembers](git/reference-commits-earlier-than-reflog-remembers.md)
|
||||||
- [Remove Untracked Files From A Directory](git/remove-untracked-files-from-a-directory.md)
|
- [Remove Untracked Files From A Directory](git/remove-untracked-files-from-a-directory.md)
|
||||||
- [Rename A Remote](git/rename-a-remote.md)
|
- [Rename A Remote](git/rename-a-remote.md)
|
||||||
- [Renaming A Branch](git/renaming-a-branch.md)
|
- [Renaming A Branch](git/renaming-a-branch.md)
|
||||||
@@ -402,6 +408,7 @@ See some of the other learning resources I work on:
|
|||||||
- [Disable A Workflow With The gh CLI](github-actions/disable-a-workflow-with-the-gh-cli.md)
|
- [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)
|
- [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)
|
- [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
|
### Go
|
||||||
|
|
||||||
@@ -664,6 +671,7 @@ See some of the other learning resources I work on:
|
|||||||
- [Set A Window To Its Default Zoom Level](mac/set-a-window-to-its-default-zoom-level.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)
|
- [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)
|
- [Start Amphetamine Session With AppleScript](mac/start-amphetamine-session-with-applescript.md)
|
||||||
|
- [Uninstall LogiTech G Hub From Mac](mac/uninstall-logitech-g-hub-from-mac.md)
|
||||||
- [Use A Different Font With iTerm2](mac/use-a-different-font-with-iterm2.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)
|
- [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)
|
- [View All Windows Of The Current App](mac/view-all-windows-of-the-current-app.md)
|
||||||
@@ -776,6 +784,7 @@ See some of the other learning resources I work on:
|
|||||||
- [Convert A String To A Timestamp](postgres/convert-a-string-to-a-timestamp.md)
|
- [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 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 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)
|
- [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 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)
|
- [Create A Composite Primary Key](postgres/create-a-composite-primary-key.md)
|
||||||
@@ -893,12 +902,14 @@ See some of the other learning resources I work on:
|
|||||||
- [Timestamp Functions](postgres/timestamp-functions.md)
|
- [Timestamp Functions](postgres/timestamp-functions.md)
|
||||||
- [Toggling The Pager In PSQL](postgres/toggling-the-pager-in-psql.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)
|
- [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 All Rows](postgres/truncate-all-rows.md)
|
||||||
- [Truncate Tables With Dependents](postgres/truncate-tables-with-dependents.md)
|
- [Truncate Tables With Dependents](postgres/truncate-tables-with-dependents.md)
|
||||||
- [Turning Timing On](postgres/turn-timing-on.md)
|
- [Turning Timing On](postgres/turn-timing-on.md)
|
||||||
- [Two Ways To Compute Factorial](postgres/two-ways-to-compute-factorial.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)
|
- [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)
|
- [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)
|
- [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 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)
|
- [Use A Trigger To Mirror Inserts To Another Table](postgres/use-a-trigger-to-mirror-inserts-to-another-table.md)
|
||||||
@@ -939,6 +950,7 @@ See some of the other learning resources I work on:
|
|||||||
- [Add A Check Constraint To A Table](rails/add-a-check-constraint-to-a-table.md)
|
- [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 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 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 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 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)
|
- [Add React With Webpacker To A New Rails App](rails/add-react-with-webpacker-to-a-new-rails-app.md)
|
||||||
@@ -951,6 +963,7 @@ See some of the other learning resources I work on:
|
|||||||
- [All or Nothing Database Transactions](rails/all-or-nothing-database-transactions.md)
|
- [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)
|
- [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)
|
- [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)
|
- [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)
|
- [Attach A File With Capybara](rails/attach-a-file-with-capybara.md)
|
||||||
- [Attribute Getter without the Recursion](rails/attribute-getter-without-the-recursion.md)
|
- [Attribute Getter without the Recursion](rails/attribute-getter-without-the-recursion.md)
|
||||||
@@ -982,6 +995,7 @@ See some of the other learning resources I work on:
|
|||||||
- [Define The Root Path For The App](rails/define-the-root-path-for-the-app.md)
|
- [Define The Root Path For The App](rails/define-the-root-path-for-the-app.md)
|
||||||
- [Delete Paranoid Records](rails/delete-paranoid-records.md)
|
- [Delete Paranoid Records](rails/delete-paranoid-records.md)
|
||||||
- [Demodulize A Class Name](rails/demodulize-a-class-name.md)
|
- [Demodulize A Class Name](rails/demodulize-a-class-name.md)
|
||||||
|
- [Determine The Configured Primary Key Type](rails/determine-the-configured-primary-key-type.md)
|
||||||
- [Different Ways To Add A Foreign Key Reference](rails/different-ways-to-add-a-foreign-key-reference.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)
|
- [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)
|
- [Empty find_by Returns First Record](rails/empty-find-by-returns-first-record.md)
|
||||||
@@ -1007,6 +1021,7 @@ See some of the other learning resources I work on:
|
|||||||
- [Hash Slicing](rails/hash-slicing.md)
|
- [Hash Slicing](rails/hash-slicing.md)
|
||||||
- [Ignore Poltergeist JavaScript Errors](rails/ignore-poltergeist-javascript-errors.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)
|
- [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)
|
- [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)
|
- [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)
|
- [List All Installable Rails Versions](rails/list-all-installable-rails-versions.md)
|
||||||
@@ -1026,6 +1041,7 @@ See some of the other learning resources I work on:
|
|||||||
- [Migrating Up Down Up](rails/migrating-up-down-up.md)
|
- [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)
|
- [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)
|
- [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 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)
|
- [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)
|
- [Parse Query Params From A URL](rails/parse-query-params-from-a-url.md)
|
||||||
@@ -1054,6 +1070,7 @@ See some of the other learning resources I work on:
|
|||||||
- [Run A Rake Task Programmatically](rails/run-a-rake-task-programmatically.md)
|
- [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 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)
|
- [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)
|
- [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)
|
- [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)
|
- [Select A Select By Selector](rails/select-a-select-by-selector.md)
|
||||||
@@ -1064,6 +1081,7 @@ See some of the other learning resources I work on:
|
|||||||
- [Set DateTime To Include Time Zone In Migrations](rails/set-datetime-to-include-time-zone-in-migrations.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 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 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 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 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)
|
- [Set The Default Development Port](rails/set-the-default-development-port.md)
|
||||||
@@ -1263,6 +1281,7 @@ See some of the other learning resources I work on:
|
|||||||
- [Exit A Process With An Error Message](ruby/exit-a-process-with-an-error-message.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)
|
- [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)
|
- [Extract A Column Of Data From A CSV File](ruby/extract-a-column-of-data-from-a-csv-file.md)
|
||||||
|
- [Extract Capture Group Matches With String Slices](ruby/extract-capture-group-matches-with-string-slices.md)
|
||||||
- [FactoryGirl Sequences](ruby/factory-girl-sequences.md)
|
- [FactoryGirl Sequences](ruby/factory-girl-sequences.md)
|
||||||
- [Fail](ruby/fail.md)
|
- [Fail](ruby/fail.md)
|
||||||
- [Fetch Warns About Superseding Block Argument](ruby/fetch-warns-about-superseding-block-argument.md)
|
- [Fetch Warns About Superseding Block Argument](ruby/fetch-warns-about-superseding-block-argument.md)
|
||||||
@@ -1481,6 +1500,7 @@ See some of the other learning resources I work on:
|
|||||||
- [Count The Lines In A CSV Where A Column Is Empty](unix/count-the-lines-in-a-csv-where-a-column-is-empty.md)
|
- [Count The Lines In A CSV Where A Column Is Empty](unix/count-the-lines-in-a-csv-where-a-column-is-empty.md)
|
||||||
- [Count The Number Of Matches In A Grep](unix/count-the-number-of-matches-in-a-grep.md)
|
- [Count The Number Of Matches In A Grep](unix/count-the-number-of-matches-in-a-grep.md)
|
||||||
- [Count The Number Of ripgrep Pattern Matches](unix/count-the-number-of-ripgrep-pattern-matches.md)
|
- [Count The Number Of ripgrep Pattern Matches](unix/count-the-number-of-ripgrep-pattern-matches.md)
|
||||||
|
- [Count The Number Of Words On A Webpage](unix/count-the-number-of-words-on-a-webpage.md)
|
||||||
- [Create A File Descriptor with Process Substitution](unix/create-a-file-descriptor-with-process-substitution.md)
|
- [Create A File Descriptor with Process Substitution](unix/create-a-file-descriptor-with-process-substitution.md)
|
||||||
- [Create A Sequence Of Values With A Step](unix/create-a-sequence-of-values-with-a-step.md)
|
- [Create A Sequence Of Values With A Step](unix/create-a-sequence-of-values-with-a-step.md)
|
||||||
- [Curl With Cookies](unix/curl-with-cookies.md)
|
- [Curl With Cookies](unix/curl-with-cookies.md)
|
||||||
@@ -1517,6 +1537,7 @@ See some of the other learning resources I work on:
|
|||||||
- [Get Matching Filenames As Output From Grep](unix/get-matching-filenames-as-output-from-grep.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 SHA256 Hash For A File](unix/get-the-sha256-hash-for-a-file.md)
|
||||||
- [Get The Unix Timestamp](unix/get-the-unix-timestamp.md)
|
- [Get The Unix Timestamp](unix/get-the-unix-timestamp.md)
|
||||||
|
- [Get Word Count For All Files In Git Repo](unix/get-word-count-for-all-files-in-git-repo.md)
|
||||||
- [Global Substitution On The Previous Command](unix/global-substitution-on-the-previous-command.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 All Directories In Zsh](unix/globbing-for-all-directories-in-zsh.md)
|
||||||
- [Globbing For Filenames In Zsh](unix/globbing-for-filenames-in-zsh.md)
|
- [Globbing For Filenames In Zsh](unix/globbing-for-filenames-in-zsh.md)
|
||||||
@@ -1535,6 +1556,8 @@ See some of the other learning resources I work on:
|
|||||||
- [Killing A Frozen SSH Session](unix/killing-a-frozen-ssh-session.md)
|
- [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)
|
- [Last Argument Of The Last Command](unix/last-argument-of-the-last-command.md)
|
||||||
- [Less With Style](unix/less-with-style.md)
|
- [Less With Style](unix/less-with-style.md)
|
||||||
|
- [Limit Protocols Used In A cURL Command](unix/limit-protocols-used-in-a-curl-command.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 The Enabled ZSH Options](unix/list-all-the-enabled-zsh-options.md)
|
||||||
- [List All Users](unix/list-all-users.md)
|
- [List All Users](unix/list-all-users.md)
|
||||||
- [List Files In A Single Column](unix/list-files-in-a-single-column.md)
|
- [List Files In A Single Column](unix/list-files-in-a-single-column.md)
|
||||||
@@ -1804,6 +1827,7 @@ See some of the other learning resources I work on:
|
|||||||
- [Break Justfile Into Separate Hidden Steps](workflow/break-justfile-into-separate-hidden-steps.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)
|
- [Change Window Name In iTerm](workflow/change-window-name-in-iterm.md)
|
||||||
- [Configure Email Redirect With Cloudflare](workflow/configure-email-redirect-with-cloudflare.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)
|
- [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 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)
|
- [Create A Public URL For A Local Server](workflow/create-a-public-url-for-a-local-server.md)
|
||||||
@@ -1814,12 +1838,15 @@ See some of the other learning resources I work on:
|
|||||||
- [Import A Github Project Into CodeSandbox](workflow/import-a-github-project-into-codesandbox.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)
|
- [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)
|
- [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)
|
- [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)
|
- [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)
|
- [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 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)
|
- [Set Recurring Reminders In Slack](workflow/set-recurring-reminders-in-slack.md)
|
||||||
- [Show Linting Errors In Zed](workflow/show-linting-errors-in-zed.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)
|
- [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)
|
- [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)
|
- [View The PR For The Current GitHub Branch](workflow/view-the-pr-for-the-current-github-branch.md)
|
||||||
|
|||||||
@@ -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.
|
||||||
@@ -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)
|
||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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}'
|
||||||
|
```
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
# Reference Commits Earlier Than Reflog Remembers
|
||||||
|
|
||||||
|
While preparing some stats for a recent blog post on [A Decade of
|
||||||
|
TILs](https://www.visualmode.dev/a-decade-of-tils), I ran into an issue
|
||||||
|
referencing chuncks of time further back than 2020.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
❯ git diff --diff-filter=A --name-only HEAD@{2016-02-06}..HEAD@{2017-02-06} -- "*.md"
|
||||||
|
warning: log for 'HEAD' only goes back to Sun, 20 Dec 2020 00:26:27 -0600
|
||||||
|
warning: log for 'HEAD' only goes back to Sun, 20 Dec 2020 00:26:27 -0600
|
||||||
|
```
|
||||||
|
|
||||||
|
This is because `HEAD@...` is a reference to the `reflog`. The `reflog` is a
|
||||||
|
local-only log of objects and activity in the repository. That date looks
|
||||||
|
suspiciously like the time that I got this specific machine and cloned the
|
||||||
|
repo.
|
||||||
|
|
||||||
|
In order to access this information, I need a different approach of finding
|
||||||
|
references that bound these points in time.
|
||||||
|
|
||||||
|
How about asking `rev-list` for the first commit it can find before the given
|
||||||
|
dates in 2017 and 2016 and then using those.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
❯ git rev-list -1 --before="2017-02-07 00:00" HEAD
|
||||||
|
17db6bc4468616786a8f597a10d252c24183d82e
|
||||||
|
|
||||||
|
❯ git rev-list -1 --before="2016-02-07 00:00" HEAD
|
||||||
|
f1d3d1f796007662ff448d6ba0e3bbf38a2b858d
|
||||||
|
|
||||||
|
❯ git diff --diff-filter=A --name-only f1d3d1f796007662ff448d6ba0e3bbf38a2b858d..17db6bc4468616786a8f597a10d252c24183d82e -- "*.md"
|
||||||
|
|
||||||
|
# git outputs a bunch of files ...
|
||||||
|
```
|
||||||
@@ -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.
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
# Uninstall LogiTech G Hub From Mac
|
||||||
|
|
||||||
|
I rarely uninstall software from my Mac. And unless the software is nice enough
|
||||||
|
to provide a clear 'Uninstall' flow, it is not straightforward how to do it. In
|
||||||
|
fact, it probably varies quite a bit from app to app.
|
||||||
|
|
||||||
|
In the case of LogiTech's G Hub, I was able to find the following instructions
|
||||||
|
for uninstalling it. The thing of note is that the updater app can take an
|
||||||
|
`--uninstall` flag.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo /Applications/lghub.app/Contents/MacOS/lghub_updater.app/Contents/MacOS/lghub_updater --uninstall
|
||||||
|
```
|
||||||
|
|
||||||
|
I still had to remove the app launcher from my `Applications` directory.
|
||||||
|
|
||||||
|
[source](https://www.reddit.com/r/LogitechG/comments/bluth5/comment/lbhctx1/)
|
||||||
@@ -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)
|
||||||
@@ -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.
|
||||||
@@ -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).
|
||||||
@@ -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/)
|
||||||
@@ -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.
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
# Determine The Configured Primary Key Type
|
||||||
|
|
||||||
|
I noticed an interesting helper function in the database migration generated by
|
||||||
|
`bin/rails active_storage:install`.
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
class CreateActiveStorageTables < ActiveRecord::Migration[8.0]
|
||||||
|
def change
|
||||||
|
# Use Active Record's configured type for primary and foreign keys
|
||||||
|
primary_key_type, foreign_key_type = primary_and_foreign_key_types
|
||||||
|
|
||||||
|
# ...
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def primary_and_foreign_key_types
|
||||||
|
config = Rails.configuration.generators
|
||||||
|
setting = config.options[config.orm][:primary_key_type]
|
||||||
|
primary_key_type = setting || :primary_key
|
||||||
|
foreign_key_type = setting || :bigint
|
||||||
|
[ primary_key_type, foreign_key_type ]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
The `primary_and_foreign_key_types` method looks in the generators config for
|
||||||
|
the ORM (`:active_record`) to determine the configured `:primary_key_type`. By
|
||||||
|
default this will return `nil`. This method then uses `:primary_key` as a
|
||||||
|
fallback value which will be `bigint`. That's why the `foreign_key_type` falls
|
||||||
|
back to `:bigint`.
|
||||||
|
|
||||||
|
If desired, this can be manually configured in `config/application.rb` like
|
||||||
|
shown in the [ActiveRecord Migrations
|
||||||
|
docs](https://guides.rubyonrails.org/active_record_migrations.html#enabling-uuids-in-rails).
|
||||||
@@ -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)
|
||||||
@@ -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 %>
|
||||||
|
```
|
||||||
@@ -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)
|
||||||
@@ -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.
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
# Extract Capture Group Matches With String Slices
|
||||||
|
|
||||||
|
Ruby's _string slice_ syntax allows us to use the square brackets to access
|
||||||
|
portions of a string. It's most common to pass positional integer index
|
||||||
|
arguments or a range. However, in true Ruby fashion, another way of thinking
|
||||||
|
about defining the slice of a string is based on a regex match.
|
||||||
|
|
||||||
|
We can pass a regex and an int (specifying which match we want) to extract some
|
||||||
|
portion of a string based on the regex match. That includes capture groups.
|
||||||
|
|
||||||
|
Here are a couple examples of extracting matching capture groups as well as
|
||||||
|
getting the entire regex match:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
> "me+abc123@email.com"[/.+\+(.+)@(.+)/, 1]
|
||||||
|
=> "abc123"
|
||||||
|
|
||||||
|
> "me+abc123@email.com"[/.+\+(.+)@(.+)/, 2]
|
||||||
|
=> "email.com"
|
||||||
|
|
||||||
|
> "me+abc123@email.com"[/.+\+(.+)@(.+)/, 0]
|
||||||
|
=> "me+abc123@email.com"
|
||||||
|
|
||||||
|
> "me+abc123@email.com"[/.+\+(.+)@(.+)/]
|
||||||
|
=> "me+abc123@email.com"
|
||||||
|
```
|
||||||
|
|
||||||
|
The `0`th match (which is the default) corresponds to the full match. Each
|
||||||
|
integer position after that corresponds to any capture groups. This maps
|
||||||
|
directly to the underlying `MatchData` object:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
> /.+\+(.+)@(.+)/.match("me+abc123@email.com")
|
||||||
|
=> #<MatchData "me+abc123@email.com" 1:"abc123" 2:"email.com">
|
||||||
|
```
|
||||||
|
|
||||||
|
[source](https://ruby-doc.org/3.3.6/String.html#class-String-label-String+Slices)
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
# Count The Number Of Words On A Webpage
|
||||||
|
|
||||||
|
I was reading through a couple sections of the `postfix` documentation and I
|
||||||
|
was astounded at how large the webpage is, and that is just for the `main.cf`
|
||||||
|
file format.
|
||||||
|
|
||||||
|
Curiosity got the best of me and I wanted to get a sense of the magnitude of
|
||||||
|
the page. A word count seemed like a good measure.
|
||||||
|
|
||||||
|
Using `pandoc` and a couple other unix utilities, I was able to quickly get
|
||||||
|
that number.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -s http://www.postfix.org/postconf.5.html\#virtual_mailbox_maps | pandoc -f html -t plain | wc -w
|
||||||
|
88383
|
||||||
|
```
|
||||||
|
|
||||||
|
Generically, that is:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -s url | pandoc -f html -t plain | wc -w
|
||||||
|
```
|
||||||
|
|
||||||
|
Pandoc produces a plain-text version of the HTML page that was pulled in by
|
||||||
|
`curl` and then we use `wc` to get a word (`-w`) count.
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
# Get Word Count For All Files In Git Repo
|
||||||
|
|
||||||
|
As part of gathering numbers for [A Decade of TILs](), I wanted to get an word
|
||||||
|
count of all the TIL markdown files I've committed to this project over its 10
|
||||||
|
year history. By using `git ls-files` with a pattern, I can get a list of all
|
||||||
|
file names. Then with `xargs` I can pass that entire list to `wc -w` which
|
||||||
|
gives a word count of each. The final line that `wc -w` outputs is a sum total
|
||||||
|
of all the file word counts. Lastly, piping that through `tail -n1` gives me
|
||||||
|
just that last total count line.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ git ls-files "*/**.md" | xargs wc -w | tail -n1
|
||||||
|
206816 total
|
||||||
|
```
|
||||||
|
|
||||||
|
Since the `tail -n1` obfuscates what the `wc -w` is doing, here is what that
|
||||||
|
looks like before that final pipe.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ git ls-files "*/**.md" | tail -n3 | xargs wc -w
|
||||||
|
115 zsh/add-to-the-path-via-path-array.md
|
||||||
|
190 zsh/link-a-scalar-to-an-array.md
|
||||||
|
214 zsh/use-a-space-to-exclude-command-from-history.md
|
||||||
|
519 total
|
||||||
|
```
|
||||||
|
|
||||||
|
I can even clean up the final output a bit more with `awk`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ git ls-files "*/**.md" | xargs wc -w | tail -n1 | awk '{print $1}'
|
||||||
|
206816
|
||||||
|
```
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# Limit Protocols Used In A cURL Command
|
||||||
|
|
||||||
|
I was about to install [`atuin`](https://github.com/atuinsh/atuin). I went to
|
||||||
|
their _Quick Start_ section to grab whatever command I would need to install
|
||||||
|
it. It was a `curl` statement piped to `sh`. The thing that caught my attention
|
||||||
|
though was I `curl` flag that I didn't recognize — `--proto`.
|
||||||
|
|
||||||
|
> Tells curl to limit what protocols it may use for transfers.
|
||||||
|
|
||||||
|
Using `curl --proto '=https' ...` we can enforce that only an `https` URL can
|
||||||
|
be used in this command.
|
||||||
|
|
||||||
|
Here is what happens if I try to run the `atuin`-provided `curl` command after
|
||||||
|
I have downgraded their URL to be `http`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl --proto '=https' --tlsv1.2 -LsSf http://setup.atuin.sh | sh
|
||||||
|
curl: (1) Protocol "http" not supported or disabled in libcurl
|
||||||
|
```
|
||||||
|
|
||||||
|
It doesn't even attempt the request. The protocol is considered unsupported and
|
||||||
|
the command immediately fails.
|
||||||
|
|
||||||
|
In addition to only installing software we trust, we should make sure we are
|
||||||
|
only doing so over a protocol we trust (namely, `https`).
|
||||||
|
|
||||||
|
See `man curl` for more details, including about the modifiers (`=`, `+`, `-`).
|
||||||
@@ -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).
|
||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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)
|
||||||
@@ -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.
|
||||||
Reference in New Issue
Block a user