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).
|
||||
|
||||
_1562 TILs and counting..._
|
||||
_1589 TILs and counting..._
|
||||
|
||||
See some of the other learning resources I work on:
|
||||
- [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 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)
|
||||
@@ -304,6 +307,7 @@ See some of the other learning resources I work on:
|
||||
- [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)
|
||||
@@ -334,6 +338,7 @@ See some of the other learning resources I work on:
|
||||
- [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)
|
||||
@@ -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)
|
||||
- [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 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)
|
||||
- [Rename A Remote](git/rename-a-remote.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)
|
||||
- [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
|
||||
|
||||
@@ -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)
|
||||
- [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)
|
||||
- [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 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)
|
||||
@@ -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)
|
||||
- [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)
|
||||
@@ -893,12 +902,14 @@ See some of the other learning resources I work on:
|
||||
- [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)
|
||||
@@ -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 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)
|
||||
@@ -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)
|
||||
- [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)
|
||||
@@ -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)
|
||||
- [Delete Paranoid Records](rails/delete-paranoid-records.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)
|
||||
- [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)
|
||||
@@ -1007,6 +1021,7 @@ See some of the other learning resources I work on:
|
||||
- [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)
|
||||
@@ -1026,6 +1041,7 @@ See some of the other learning resources I work on:
|
||||
- [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)
|
||||
@@ -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 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)
|
||||
@@ -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 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)
|
||||
@@ -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)
|
||||
- [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 Capture Group Matches With String Slices](ruby/extract-capture-group-matches-with-string-slices.md)
|
||||
- [FactoryGirl Sequences](ruby/factory-girl-sequences.md)
|
||||
- [Fail](ruby/fail.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 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 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 Sequence Of Values With A Step](unix/create-a-sequence-of-values-with-a-step.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 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 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)
|
||||
- [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)
|
||||
@@ -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)
|
||||
- [Last Argument Of The Last Command](unix/last-argument-of-the-last-command.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 Users](unix/list-all-users.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)
|
||||
- [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)
|
||||
@@ -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)
|
||||
- [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)
|
||||
|
||||
@@ -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