<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Sven Schwyn</title>
    <description>The latest articles on DEV Community by Sven Schwyn (@svoop).</description>
    <link>https://dev.to/svoop</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F523569%2Ff52e1f1e-c0ac-47a5-bbab-71d8561b2f79.jpeg</url>
      <title>DEV Community: Sven Schwyn</title>
      <link>https://dev.to/svoop</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/svoop"/>
    <language>en</language>
    <item>
      <title>Bootable macOS Tahoe Installer and Clean Install</title>
      <dc:creator>Sven Schwyn</dc:creator>
      <pubDate>Wed, 17 Sep 2025 10:46:09 +0000</pubDate>
      <link>https://dev.to/svoop/bootable-macos-tahoe-installer-and-clean-install-30ll</link>
      <guid>https://dev.to/svoop/bootable-macos-tahoe-installer-and-clean-install-30ll</guid>
      <description>&lt;p&gt;For clean installs of macOS or installs on multiple computers with only one download, creating a bootable macOS installer on a USB drive is the way to go.&lt;/p&gt;

&lt;p&gt;Up to Sequoia, Apple released standalone installers for the major releases of macOS on the App Store. For Tahoe, however, there's no such thing and probably never will. The installer is available on third-party download sites, but it is an incredibly bad idea to use any of them, no matter how trustworthy it may look. &lt;strong&gt;Just don't do it!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Also, there's no reason to take such unnecessary risks – with a little help of the Terminal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create a Bootable USB macOS Installer
&lt;/h2&gt;

&lt;p&gt;You'll need a big enough USB drive. Use the Disk Utility to erase it to HFS+ format and the default name &lt;code&gt;/Volumes/Untitled&lt;/code&gt;. Then launch the Terminal and:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Find the installer for the version of macOS you wish to install
softwareupdate --list-full-installers

# Download the installer app
softwareupdate --fetch-full-installer --full-installer-version {VERSION}

# Create a bootable installer on the USB drive:
sudo "/Applications/Install macOS {MACOS_NAME}.app/Contents/Resources/createinstallmedia" --volume /Volumes/Untitled
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it, easy as 123 and free as in free beer!&lt;/p&gt;

&lt;h2&gt;
  
  
  Clean Install
&lt;/h2&gt;

&lt;p&gt;I won't go into much detail here, but the basic steps are as follows:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a Time Machine backup.&lt;/li&gt;
&lt;li&gt;Create a second, full backup using &lt;a href="https://www.shirt-pocket.com/SuperDuper" rel="noopener noreferrer"&gt;SuperDuper&lt;/a&gt;, &lt;a href="https://bombich.com" rel="noopener noreferrer"&gt;Carbon Copy Cloner&lt;/a&gt; or a similar tool.&lt;/li&gt;
&lt;li&gt;Make sure you have successfully completed the steps 1 and 2.&lt;/li&gt;
&lt;li&gt;Attach the USB drive containing the installer.&lt;/li&gt;
&lt;li&gt;Shut down your Mac, then restart it into boot options: On an Apple Silicon Mac, push and hold the power button. On an Intel Mac, push the power button, then hold the option key (⌥).&lt;/li&gt;
&lt;li&gt;Use the Disk Utility to erase the startup disk to APFS.&lt;/li&gt;
&lt;li&gt;Use the macOS installer on the USB drive to clean install macOS.&lt;/li&gt;
&lt;li&gt;Migrate your data from one of the backups onto the fresh macOS.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;(Image by &lt;a href="https://pixabay.com/users/leehuzysberg-50653/" rel="noopener noreferrer"&gt;Leehu Zysberg&lt;/a&gt;)&lt;/p&gt;

</description>
      <category>macos</category>
    </item>
    <item>
      <title>Mini Monads</title>
      <dc:creator>Sven Schwyn</dc:creator>
      <pubDate>Thu, 30 Jan 2025 19:33:49 +0000</pubDate>
      <link>https://dev.to/svoop/mini-monads-2990</link>
      <guid>https://dev.to/svoop/mini-monads-2990</guid>
      <description>&lt;p&gt;Here's a quick bite for those using result monads provided by &lt;a href="https://dry-rb.org/gems/dry-monads/1.6/" rel="noopener noreferrer"&gt;dry-monads&lt;/a&gt; to code workflows and test them with &lt;a href="https://github.com/minitest/minitest" rel="noopener noreferrer"&gt;Minitest&lt;/a&gt; . Nothing fancy, please leave suggestions to make it more useful in the comments.&lt;/p&gt;

&lt;p&gt;As a quick reminder: Result monads are simple objects which wrap the return value of methods in a uniform manner thus turning them into building blocks which can be stacked on top of one another like Lego bricks. &lt;a href="https://guides.hanamirb.org/v2.2/operations/overview/" rel="noopener noreferrer"&gt;Operations in Hanami 2&lt;/a&gt; make use of them thru &lt;a href="https://dry-rb.org/gems/dry-operation/1.0/" rel="noopener noreferrer"&gt;dry-operations&lt;/a&gt;, a thin extra layer which adds steps to the mix. Together, these great gems can turn a bowl of messy &lt;code&gt;if&lt;/code&gt;/&lt;code&gt;then&lt;/code&gt;/&lt;code&gt;else&lt;/code&gt;/&lt;code&gt;rescue&lt;/code&gt; spaghetti into a well layered lasagne. You can eat both of them alright, but only the lasagne will earn you applause for the optics and the ability to add more layers ad lib.&lt;/p&gt;

&lt;p&gt;You still want to test this though, so here are the custom Minitest (assertions and) expectations for the job.&lt;/p&gt;

&lt;p&gt;Minitest 6 or newer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'dry-monads'&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Minitest::Assertions&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;assert_success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;actual&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;"Expected &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;mu_pp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;actual&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; to be Success with &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;mu_pp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expected&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;actual&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;value!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;refute_success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;actual&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;"Expected &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;mu_pp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;actual&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; not to be Success with &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;mu_pp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expected&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;actual&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;value!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;assert_failure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;actual&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;"Expected &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;mu_pp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;actual&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; to be Failure with &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;mu_pp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expected&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;actual&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;failure&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;refute_failure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;actual&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;"Expected &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;mu_pp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;actual&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; not to be Failure with &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;mu_pp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expected&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;actual&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;failure&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Minitest::Expectations&lt;/span&gt;
  &lt;span class="n"&gt;infect_an_assertion&lt;/span&gt; &lt;span class="ss"&gt;:assert_success&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:must_be_success&lt;/span&gt;
  &lt;span class="n"&gt;infect_an_assertion&lt;/span&gt; &lt;span class="ss"&gt;:refute_success&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:wont_be_success&lt;/span&gt;

  &lt;span class="n"&gt;infect_an_assertion&lt;/span&gt; &lt;span class="ss"&gt;:assert_failure&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:must_be_failure&lt;/span&gt;
  &lt;span class="n"&gt;infect_an_assertion&lt;/span&gt; &lt;span class="ss"&gt;:refute_failure&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:wont_be_failure&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Minitest 5:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'dry-monads'&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Minitest::Assertions&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;assert_success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;actual&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;"Expected &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;mu_pp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;actual&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; to be Success with &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;mu_pp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expected&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;actual&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;value!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;refute_success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;actual&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;"Expected &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;mu_pp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;actual&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; not to be Success with &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;mu_pp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expected&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;actual&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;value!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;assert_failure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;actual&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;"Expected &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;mu_pp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;actual&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; to be Failure with &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;mu_pp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expected&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;actual&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;failure&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;refute_failure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;actual&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;"Expected &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;mu_pp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;actual&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; not to be Failure with &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;mu_pp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expected&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;actual&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;failure&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="no"&gt;Dry&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Monads&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Success&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;infect_an_assertion&lt;/span&gt; &lt;span class="ss"&gt;:assert_success&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:must_be_success&lt;/span&gt;
&lt;span class="no"&gt;Dry&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Monads&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Success&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;infect_an_assertion&lt;/span&gt; &lt;span class="ss"&gt;:refute_success&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:wont_be_success&lt;/span&gt;

&lt;span class="no"&gt;Dry&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Monads&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Failure&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;infect_an_assertion&lt;/span&gt; &lt;span class="ss"&gt;:assert_failure&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:must_be_failure&lt;/span&gt;
&lt;span class="no"&gt;Dry&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Monads&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Failure&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;infect_an_assertion&lt;/span&gt; &lt;span class="ss"&gt;:refute_failure&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:wont_be_failure&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Say you want to test the following nonsensical example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Lasagne&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Dry&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Monads&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:result&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;  

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;cooking_time&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;tool&lt;/span&gt;
      &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="ss"&gt;:microwave&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt; &lt;span class="no"&gt;Success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="ss"&gt;:oven&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt; &lt;span class="no"&gt;Success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="ss"&gt;:nuke&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt; &lt;span class="no"&gt;Failure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:epic&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="no"&gt;Failure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:unsupported_tool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here are the tests in spec notation (because I like it so much better than assertions):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;describe&lt;/span&gt; &lt;span class="no"&gt;Lasagne&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;subject&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="no"&gt;Lasagne&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"quickly cooks in a microwave (but don't tell any Italians)"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cooking_time&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:microwave&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;must_be_success&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"cooks perfectly in an oven"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cooking_time&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:oven&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;must_be_success&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"ends us all when using a :nuke"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cooking_time&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:nuke&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;must_be_failure&lt;/span&gt; &lt;span class="ss"&gt;:epic&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"doesn't cook with just any tool"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cooking_time&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:nose_hair_trimmer&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;must_be_failure&lt;/span&gt; &lt;span class="ss"&gt;:unsupported_tool&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can of course do without, but I find the vanilla alternative less appealing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"cooks perfectly in an oven"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cooking_time&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:oven&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;must_equal&lt;/span&gt; &lt;span class="no"&gt;Dry&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Monads&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bon appetit!&lt;/p&gt;




&lt;p&gt;&lt;a href="https://www.flickr.com/photos/rainerstropek/14424510354" rel="noopener noreferrer"&gt;Banner image&lt;/a&gt; by Rainer Stropek&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>minitest</category>
      <category>dry</category>
      <category>monads</category>
    </item>
    <item>
      <title>Client side Git hooks 101</title>
      <dc:creator>Sven Schwyn</dc:creator>
      <pubDate>Sun, 31 Mar 2024 12:50:36 +0000</pubDate>
      <link>https://dev.to/svoop/client-side-git-hooks-101-1kbm</link>
      <guid>https://dev.to/svoop/client-side-git-hooks-101-1kbm</guid>
      <description>&lt;p&gt;&lt;a href="https://git-scm.com"&gt;Git&lt;/a&gt; hooks are simple yet powerful, essentially just scripts executed when certain events like commit or push occur. Most notably, they are really useful for enforcing code and commit quality. However, there's one problem: Since the hook scripts are stored inside the &lt;code&gt;.git/&lt;/code&gt; directory, they cannot be committed to the repository and shared with other developers as is. Let's see what we can do about this. &lt;/p&gt;

&lt;p&gt;(Side note: Every fresh Git repository created with &lt;code&gt;git init&lt;/code&gt; comes with a bunch of example hooks in &lt;code&gt;.git/hooks/&lt;/code&gt;. They are named in a pretty self-explanatory fashion such as &lt;code&gt;pre-commit.sample&lt;/code&gt;. Go ahead and take a look at them.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Issues everywhere
&lt;/h2&gt;

&lt;p&gt;Here's a real life example: Imagine a &lt;a href="https://rubyonrails.org/"&gt;Ruby on Rails&lt;/a&gt; app on which a team of developers are working. The code is hosted on &lt;a href="https://gitlab.com"&gt;GitLab&lt;/a&gt; and all the work is coordinated using GitLab issues. In other words: For every commit, there's an associated issue and the issue number acts as a sort of primary key for documentation, time reporting and so forth. This convention has a few advantages, most notably the ability to easily learn more about how, when and by whom features were implemented as well as how this implementation came to be.&lt;/p&gt;

&lt;p&gt;As far as Git is concerned, there are two rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Feature branches reference the issue ID in their name like &lt;code&gt;1234_signin_with_passkey&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Every commit message begins with a reference to the issue ID as well, for instance &lt;code&gt;GL-1234: Add webauthn gem&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Putting these references to the respective beginning makes it easy to scan and search for them down the road. Also, GitLab hyperlinks all occurrences of &lt;code&gt;GL-....&lt;/code&gt; with the corresponding issue which greatly improves navigation.&lt;/p&gt;

&lt;p&gt;(Side note: Issues are usually hash-prefixed like &lt;code&gt;#1234&lt;/code&gt; both on GitLab and GitHub. However, commit messages must not begin with a hash, they would be considered a comment and ignored. Therefore, GitHub has introduced the alternative prefix &lt;code&gt;GH-&lt;/code&gt; and &lt;a href="https://gitlab.com/gitlab-org/gitlab/-/merge_requests/109024"&gt;I've contributed a similar prefix&lt;/a&gt; &lt;code&gt;GL-&lt;/code&gt; to GitLab a while ago.) &lt;/p&gt;

&lt;p&gt;This convention works fine until one day, the unavoidable happens and someone commits &lt;code&gt;Fix typo (#123)&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hook me up
&lt;/h2&gt;

&lt;p&gt;First, we need a place inside the repository to store the hook scripts. You might go for &lt;code&gt;.githooks/&lt;/code&gt;, but since this example is a Rails app, we'll use &lt;code&gt;lib/git/hooks/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The first script &lt;code&gt;lib/git/hooks/commit-msg&lt;/code&gt; makes sure the commit messages are formatted the way we want:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/sh&lt;/span&gt;

&lt;span class="c"&gt;# Assure the commit message to correctly reference a GitLab issue, CVE&lt;/span&gt;
&lt;span class="c"&gt;# or GHSA identifier.&lt;/span&gt;

&lt;span class="nv"&gt;regexps&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;
    &lt;span class="s2"&gt;"^GL-&lt;/span&gt;&lt;span class="se"&gt;\d&lt;/span&gt;&lt;span class="s2"&gt;+: [A-Z0-9]"&lt;/span&gt;
    &lt;span class="s2"&gt;"^CVE-&lt;/span&gt;&lt;span class="se"&gt;\d&lt;/span&gt;&lt;span class="s2"&gt;{4}-&lt;/span&gt;&lt;span class="se"&gt;\d&lt;/span&gt;&lt;span class="s2"&gt;+: [A-Z0-9]"&lt;/span&gt;
    &lt;span class="s2"&gt;"^GHSA-[a-z&lt;/span&gt;&lt;span class="se"&gt;\d&lt;/span&gt;&lt;span class="s2"&gt;]{4}-[a-z&lt;/span&gt;&lt;span class="se"&gt;\d&lt;/span&gt;&lt;span class="s2"&gt;]{4}-[a-z&lt;/span&gt;&lt;span class="se"&gt;\d&lt;/span&gt;&lt;span class="s2"&gt;]{4}: [A-Z0-9]"&lt;/span&gt;
&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="nv"&gt;passed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false
&lt;/span&gt;&lt;span class="k"&gt;for &lt;/span&gt;regexp &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;regexps&lt;/span&gt;&lt;span class="p"&gt;[@]&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  if &lt;/span&gt;&lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; 1 &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-qE&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$regexp&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nv"&gt;passed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true
  &lt;/span&gt;&lt;span class="k"&gt;fi
done
if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nv"&gt;$passed&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"The commit message does not match any of the following constraints:"&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;regexp &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;regexps&lt;/span&gt;&lt;span class="p"&gt;[@]&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"* &lt;/span&gt;&lt;span class="nv"&gt;$regexp&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;done
  &lt;/span&gt;&lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This hook script receives a file which contains the commit message as the first argument &lt;code&gt;$1&lt;/code&gt; and compares it against the regular expressions which only allow our GitLab issue prefixes as well as two more prefixes for CVE and GHSA advisories.&lt;/p&gt;

&lt;p&gt;(Side note: If you want to allow commit messages without any prefixes as well, you might add something like &lt;code&gt;"^[^:]+$"&lt;/code&gt; to the &lt;code&gt;regexps&lt;/code&gt; array.)&lt;/p&gt;

&lt;p&gt;To make our lives a little easier, we'd like all commits to a properly named branch to get the issue prefix prefilled for us – enter &lt;code&gt;lib/git/hooks/prepare-commit-msg&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/sh&lt;/span&gt;

&lt;span class="c"&gt;# Extract the issue number from the beginning of the branch name (e.g.&lt;/span&gt;
&lt;span class="c"&gt;# 1234_my_cool_feature) and create a commit message template with the &lt;/span&gt;
&lt;span class="c"&gt;# corresponding prefix for GitLab:&lt;/span&gt;

&lt;span class="nv"&gt;prefix&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"GL"&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-qv&lt;/span&gt; &lt;span class="s1"&gt;'^#\|^$'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nv"&gt;issue&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;git branch &lt;span class="nt"&gt;--show-current&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-oE&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\d&lt;/span&gt;&lt;span class="s2"&gt;+[_-]"&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-oE&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\d&lt;/span&gt;&lt;span class="s2"&gt;+"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$issue&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$prefix&lt;/span&gt;&lt;span class="s2"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;$issue&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;fi
fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make sure both scripts are executable!&lt;/p&gt;

&lt;p&gt;At their current location, Git won't use them. You could symlink them into &lt;code&gt;.git/hooks/&lt;/code&gt;, but there's a better way: Tell Git to look for the hook scripts where you've just put them.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git config core.hooksPath lib/git/hooks
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On a Rails app, you might want to add a Rake task &lt;code&gt;lib/tasks/git.rake&lt;/code&gt; for this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;namespace&lt;/span&gt; &lt;span class="ss"&gt;:git&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;namespace&lt;/span&gt; &lt;span class="ss"&gt;:hooks&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;desc&lt;/span&gt; &lt;span class="s2"&gt;"Install client side hooks"&lt;/span&gt;
    &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="ss"&gt;:install&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="sb"&gt;`git config core.hooksPath lib/git/hooks`&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="no"&gt;Dir&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exist?&lt;/span&gt; &lt;span class="s1"&gt;'lib/git/hooks'&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make sure, new developers install the hooks before they start committing by adding the Rake task to the README or the setup script of your project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rake git:hooks:install
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You could even add this line somewhere in the &lt;code&gt;Gemfile&lt;/code&gt; to always run this task when the bundle is installed, however, it's a little bit too intrusive for my taste.&lt;/p&gt;

&lt;h2&gt;
  
  
  And action!
&lt;/h2&gt;

&lt;p&gt;Time to see this at work. Let's create a feature branch and commit some stuff:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git checkout -b 1234_my_awesome_feature
touch foobar.txt
git add .
git commit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The last command opens the preferred editor and shows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GL-1234: 
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# On branch 1234_my_awesome_feature
# Changes to be committed:
#       new file:   foobar.txt
#
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Great, all that's missing is the actual message behind the prefilled &lt;code&gt;GH-1234:&lt;/code&gt; and the commit is complete. But unfortunately, it's early morning, the coffee maker is broken and by accident, we edit the message to either:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;My awesome commit
GL: My awesome commit
GL-ABCD: My awesome commit
GL-1234 My awesome commit
(...)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;commit-msg&lt;/code&gt; hook won't accept any of those and output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;The commit message does not match any of the following constraints:
* ^GL-\d+: [A-Z0-9]
* ^CVE-\d{4}-\d+: [A-Z0-9]
* ^GHSA-[a-z\d]{4}-[a-z\d]{4}-[a-z\d]{4}: [A-Z0-9]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Hook management
&lt;/h2&gt;

&lt;p&gt;This lightweight approach is great for projects which require simple hook scripts only.&lt;/p&gt;

&lt;p&gt;In case you wish to run entire pipelines of actions, say a combination of linting, style and license checking along with a required sign-off in the commit message, you should take a look at hook management tools like &lt;a href="https://github.com/Arkweid/lefthook"&gt;Lefthook&lt;/a&gt; or &lt;a href="https://github.com/sds/overcommit"&gt;overcommit&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(Photo by &lt;a href="https://unsplash.com/@enginakyurt?utm_content=creditCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=unsplash"&gt;engin akyurt&lt;/a&gt; on &lt;a href="https://unsplash.com/photos/a-metal-object-sitting-on-top-of-a-sandy-beach-c4pr2doyAJo?utm_content=creditCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=unsplash"&gt;Unsplash&lt;/a&gt;)&lt;/em&gt;&lt;/p&gt;

</description>
      <category>git</category>
      <category>hook</category>
      <category>commit</category>
      <category>automation</category>
    </item>
    <item>
      <title>10 reasons why 2023 is dead and 2024 is still relevant in 2024</title>
      <dc:creator>Sven Schwyn</dc:creator>
      <pubDate>Thu, 07 Mar 2024 14:14:40 +0000</pubDate>
      <link>https://dev.to/svoop/10-reasons-why-2023-is-dead-and-2024-is-still-relevant-in-2024-49gb</link>
      <guid>https://dev.to/svoop/10-reasons-why-2023-is-dead-and-2024-is-still-relevant-in-2024-49gb</guid>
      <description>&lt;p&gt;The interweb is full of annoying "X is dead" or "Is Y still relevant?" crap. Reason enough to throw out yet another one finally answering the big questions of our days, curried with just the right buzzword tags.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;2023 was &lt;em&gt;the&lt;/em&gt; big deal in 2023 and it is still used on old Polaroids and fraudulent checks. But &lt;strong&gt;the hype is clearly over&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Everybody is talking about 2024 these days.&lt;/strong&gt; It made a magnificent entry on the TIOBE index of current years in 2024 and kicked 2023 from the pole position literally overnight.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;All big companies&lt;/strong&gt; have moved on to 2024 including prominent websites like Airbnb, GibHub and Hulu.&lt;/li&gt;
&lt;li&gt;The community of people living in &lt;strong&gt;2023 has practically vanished&lt;/strong&gt;. There's a stubborn crowd of marauders still stuck in 2020, some in 1864 even, but the sharp folks have arrived in 2024 months ago.&lt;/li&gt;
&lt;li&gt;2024 is all even, as a whole, digit by digit, horizontal checksum, you name it. This is a huge advantage over the &lt;strong&gt;obviously odd 2023&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;2024 has one day more than 2023, that's a whopping &lt;strong&gt;0.2747% more year for free&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;2023 was released on January 1, 2023 whereas 2024 came out a full year later and therefore 2023 has to be &lt;strong&gt;considered out-of-date&lt;/strong&gt;. Also, there are no more security patches for 2023 which is why all major safety agencies advice against using 2023.&lt;/li&gt;
&lt;li&gt;2023 promised us a &lt;a href="https://en.wikipedia.org/wiki/Elon_Musk_vs._Mark_Zuckerberg" rel="noopener noreferrer"&gt;cage fight&lt;/a&gt; to witness &lt;strong&gt;Elon Musk's hairdo be bent into shape&lt;/strong&gt; by this other billionaire weirdo. Yet 2023 failed to deliver miserably.&lt;/li&gt;
&lt;li&gt;In summary, the &lt;strong&gt;continued relevance of 2024 compared to 2023&lt;/strong&gt; can be attributed to ongoing trends, policy impacts, cultural dynamics, global events, and technological advancements that shape the current narrative and influence future trajectories. (Thus spoke ChatGPT, so it must be true.) &lt;/li&gt;
&lt;li&gt;Frack, one point short. I'm sure it would have been &lt;strong&gt;two points in 2023&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;em&gt;(Photo by &lt;a href="https://unsplash.com/@naka_mura?utm_content=creditCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=unsplash" rel="noopener noreferrer"&gt;taichi nakamura&lt;/a&gt; on &lt;a href="https://unsplash.com/photos/white-and-black-no-smoking-sign-TwaenUUkRXo?utm_content=creditCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=unsplash" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;)&lt;/em&gt;&lt;/p&gt;

</description>
      <category>safety</category>
      <category>ai</category>
      <category>musk</category>
      <category>future</category>
    </item>
    <item>
      <title>Shrink UUIDs with PostgreSQL or Ruby</title>
      <dc:creator>Sven Schwyn</dc:creator>
      <pubDate>Fri, 23 Feb 2024 16:17:57 +0000</pubDate>
      <link>https://dev.to/svoop/shrink-uuids-with-postgresql-or-ruby-4i0m</link>
      <guid>https://dev.to/svoop/shrink-uuids-with-postgresql-or-ruby-4i0m</guid>
      <description>&lt;p&gt;For a pet project of mine, I'm experimenting with alternatives to the classic &lt;code&gt;SERIAL&lt;/code&gt; primary keys which are simple, local and performant. However, you don't want to expose them in URLs because this does away with one layer of protection against &lt;a href="https://owasp.org/www-community/attacks/Forced_browsing" rel="noopener noreferrer"&gt;forced browsing&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Weak serial booking codes were also at the root of the &lt;a href="https://www.theguardian.com/technology/2016/dec/28/airline-passenger-details-online-bookings-easy-prey-hackers-say-researchers" rel="noopener noreferrer"&gt;white hat hack of airline passenger details back in 2016&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Let me share my findings with you, but note: This is not a one-size-fits-all thing, you have to pick your primary keys according to your specific needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  UUIDv7
&lt;/h2&gt;

&lt;p&gt;An interesting alternative to &lt;code&gt;SERIAL&lt;/code&gt; are &lt;a href="https://www.ietf.org/archive/id/draft-peabody-dispatch-new-uuid-format-04.html#name-uuid-version-7" rel="noopener noreferrer"&gt;UUIDv7&lt;/a&gt;. They are less performant and more memory hungry given the length, but unless you're dealing with a really huge number of records, the advantages might outweigh these downsides: They are time ordered and therefore &lt;code&gt;INDEX&lt;/code&gt;-friendly (unlike fully random UUIDv4), yet their entropy is way large enough &lt;a href="https://buildkite.com/blog/goodbye-integers-hello-uuids" rel="noopener noreferrer"&gt;for external use and to safely expose them in URLs&lt;/a&gt;. (Even more so if some rate limiting is in place to slow down brute force attacks.)&lt;/p&gt;

&lt;p&gt;However, UUIDs are the IBAN of IDs, a whopping 36 characters in length and therefore not exactly a healthy diet for your URLs. Say, you have unsafe restful routes with URLs looking as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Unnested route
https://example.org/users/533

# Nested route
https://example.org/users/533/transactions/743
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now make these routes safe by using UUIDv7 as primary key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Unnested route
https://example.org/users/26d45f0c-1fa4-4875-aac7-01a5ec424aac

# Nested route
https://example.org/users/26d45f0c-1fa4-4875-aac7-01a5ec424aac/transactions/b436b1f2-711c-4a50-bb44-4e41acb7e5d6
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Boom! Even unnested routes are no longer handy. Is it really necessary for those UUIDs to take up so much space? Not really.&lt;/p&gt;

&lt;h2&gt;
  
  
  Shrink them UUIDs
&lt;/h2&gt;

&lt;p&gt;It's quite easy to reduce the length of UUIDs by almost 40% from 36 to 22 characters without loosing any data or entropy. Here's how to do it in Ruby:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'base64'&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;uuid_encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gsub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'-'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)].&lt;/span&gt;
    &lt;span class="nf"&gt;pack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'H*'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;
    &lt;span class="nf"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
    &lt;span class="nf"&gt;pack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'C*'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;
    &lt;span class="nf"&gt;then&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="no"&gt;Base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;urlsafe_encode64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}.&lt;/span&gt;
    &lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;uuid_decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encoded_uuid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="no"&gt;Base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;urlsafe_decode64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encoded_uuid&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;
    &lt;span class="nf"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
    &lt;span class="nf"&gt;pack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'C*'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;
    &lt;span class="nf"&gt;unpack1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'H*'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;
    &lt;span class="nf"&gt;unpack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'a8a4a4a4a12'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'-'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(I'm looking forward to Ruby 3.4 which will allow to rewrite the above as &lt;code&gt;then { Base64.urlsafe_encode64(it) }&lt;/code&gt;, neat!)&lt;/p&gt;

&lt;p&gt;Let's quickly do some roundtrips to check whether this really works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'securerandom'&lt;/span&gt;

&lt;span class="mi"&gt;1000000&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;uuid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;SecureRandom&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uuid&lt;/span&gt;
  &lt;span class="nb"&gt;fail&lt;/span&gt; &lt;span class="s1"&gt;'mismatch'&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;uuid_decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uuid_encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The resulting URLs are quite a bit shorter now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Unnested route
https://example.org/users/JtRfDB-kSHWqxwGl7EJKrA

# Nested route
https://example.org/users/JtRfDB-kSHWqxwGl7EJKrA/transactions/tDax8nEcSlC7RE5BrLfl1g
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;They still don't win a beauty contest, but if that is what you're after, you'd &lt;a href="https://www.semrush.com/blog/what-is-a-url-slug/" rel="noopener noreferrer"&gt;better use slugs&lt;/a&gt; where it's safely possible.&lt;/p&gt;

&lt;h2&gt;
  
  
  PostgreSQL – take over
&lt;/h2&gt;

&lt;p&gt;You might not want to encode and decode UUIDs on the application layer but do this on the database itself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="k"&gt;FUNCTION&lt;/span&gt; &lt;span class="n"&gt;uuid_encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uuid&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;RETURNS&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="err"&gt;$$&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;translate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="n"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="s1"&gt;'-'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="s1"&gt;'hex'&lt;/span&gt;
      &lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="s1"&gt;'base64'&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="s1"&gt;'+/='&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'-_'&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="err"&gt;$$&lt;/span&gt; &lt;span class="k"&gt;LANGUAGE&lt;/span&gt; &lt;span class="k"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="k"&gt;FUNCTION&lt;/span&gt; &lt;span class="n"&gt;uuid_decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encoded_uuid&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;RETURNS&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="err"&gt;$$&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;regexp_replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="n"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;translate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="n"&gt;encoded_uuid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="s1"&gt;'-_'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'+/'&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;'=='&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'base64'&lt;/span&gt;
      &lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="s1"&gt;'hex'&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="s1"&gt;'(.{8})(.{4})(.{4})(.{4})(.{12})'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\1&lt;/span&gt;&lt;span class="s1"&gt;-&lt;/span&gt;&lt;span class="se"&gt;\2&lt;/span&gt;&lt;span class="s1"&gt;-&lt;/span&gt;&lt;span class="se"&gt;\3&lt;/span&gt;&lt;span class="s1"&gt;-&lt;/span&gt;&lt;span class="se"&gt;\4&lt;/span&gt;&lt;span class="s1"&gt;-&lt;/span&gt;&lt;span class="se"&gt;\5&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;)::&lt;/span&gt;&lt;span class="n"&gt;uuid&lt;/span&gt;
&lt;span class="err"&gt;$$&lt;/span&gt; &lt;span class="k"&gt;LANGUAGE&lt;/span&gt; &lt;span class="k"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again, let's check whether this works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;DO&lt;/span&gt;
&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;
&lt;span class="k"&gt;DECLARE&lt;/span&gt;
  &lt;span class="n"&gt;uuid&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;BEGIN&lt;/span&gt;
 &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;1000000&lt;/span&gt; &lt;span class="n"&gt;LOOP&lt;/span&gt;
   &lt;span class="n"&gt;uuid&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;gen_random_uuid&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
   &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="n"&gt;uuid_decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uuid_encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt;
     &lt;span class="n"&gt;RAISE&lt;/span&gt; &lt;span class="n"&gt;EXCEPTION&lt;/span&gt; &lt;span class="s1"&gt;'mismatch'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="k"&gt;END&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
 &lt;span class="k"&gt;END&lt;/span&gt; &lt;span class="n"&gt;LOOP&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;END&lt;/span&gt;
&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unfortunately, as of PostgreSQL 16, UUIDv7 are not yet supported out of the box. For the time being, use an extension such as &lt;a href="https://github.com/fboulnois/pg_uuidv7" rel="noopener noreferrer"&gt;pg_uuidv7&lt;/a&gt; or &lt;a href="https://github.com/VADOSWARE/pg_idkit" rel="noopener noreferrer"&gt;pg_idkit&lt;/a&gt; to generate UUIDv7 e.g. as default primary key when you &lt;code&gt;CREATE&lt;/code&gt; new records.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Update December 2024&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Still unfortunately, UUIDv7 didn't quite make it to PostgreSQL 17 neither but is en route to be merged before PostgreSQL 18 due in fall 2025. Meanwhile, you can either compile the pg_uuidv7 extension yourself or use one of my packages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/svoop/bitcetera-overlay" rel="noopener noreferrer"&gt;pg_uuidv7 ebuild for Gentoo Linux in my overlay&lt;/a&gt; (for PostgreSQL 13 thru 17)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/svoop/homebrew-postgresql" rel="noopener noreferrer"&gt;pg_uuidv7 formula for Homebrew&lt;/a&gt; (for PostgreSQL versions available on Homebrew)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Update January 2025&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;UUIDv7 support &lt;a href="https://github.com/postgres/postgres/commit/78c5e141e9c139fc2ff36a220334e4aa25e1b0eb#diff-a47d642f8783e262f07dd10506fedb3789a7ec06c7c4a6b5b9a8dc06b4ce5cfe" rel="noopener noreferrer"&gt;has been merged&lt;/a&gt; and therefore should become available as of PostgreSQL 18 next fall. The function will be called &lt;code&gt;uuidv7()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Update September 2025&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;With PostgreSQL 18 out, &lt;code&gt;uuidv7()&lt;/code&gt; is now &lt;a href="https://www.postgresql.org/docs/current/functions-uuid.html" rel="noopener noreferrer"&gt;available out of the box&lt;/a&gt;, no extensions required.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://ruby.social/@svoop" rel="noopener noreferrer"&gt;More chit chat on Mastodon&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(Photo by &lt;a href="https://www.pexels.com/photo/silhouette-photo-of-miniature-toy-2590533/" rel="noopener noreferrer"&gt;Magda Ehlers&lt;/a&gt;)&lt;/em&gt; &lt;/p&gt;

</description>
      <category>uuid</category>
      <category>postgres</category>
      <category>ruby</category>
      <category>primarykey</category>
    </item>
    <item>
      <title>Mirror Android device on desktop screen with scrcpy</title>
      <dc:creator>Sven Schwyn</dc:creator>
      <pubDate>Wed, 29 Dec 2021 15:16:05 +0000</pubDate>
      <link>https://dev.to/svoop/mirror-android-device-on-desktop-screen-with-scrcpy-5gkd</link>
      <guid>https://dev.to/svoop/mirror-android-device-on-desktop-screen-with-scrcpy-5gkd</guid>
      <description>&lt;p&gt;A growing number of services such as online banking are accessible on mobile devices only these days, so you may find yourself typing IBANs or reference numbers on tiny on-screen keyboards while your desktop computer keyboard sits right next to it laughing at you.&lt;/p&gt;

&lt;p&gt;Stop this, let's mirror the Android device on the desktop screen!&lt;/p&gt;

&lt;p&gt;There are quite a few commercial remote control solutions out there, some of which offer limited free tiers once you've registered online. However, particularly when it comes to remote access, I prefer open-source tools which don't require any third party server.&lt;/p&gt;

&lt;p&gt;Enter &lt;a href="https://github.com/Genymobile/scrcpy"&gt;scrcpy&lt;/a&gt;, a lightweight CLI tool available for macOS, Linux and Windows. It communicates via ADB which is preinstalled on every Android device and doesn't require it to be rooted.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prep the Android device
&lt;/h2&gt;

&lt;p&gt;On the Android device, open developer options and enable «Android debugging (ADB)». (In case you don't find the developer options, you have to &lt;a href="https://developer.android.com/studio/debug/dev-options"&gt;enable those beforehand&lt;/a&gt;.)&lt;/p&gt;

&lt;p&gt;ADB requires a USB connection by default which is secure, but not very handy. If &lt;strong&gt;and only if&lt;/strong&gt; you are on a secure private network, you may use a TCP/IP connection instead, say your home wifi network. Check available widgets, you might find one called «ADB over network» to easily switch this feature on and off.&lt;/p&gt;

&lt;p&gt;Just to make sure you've understood the risks: &lt;strong&gt;Never enable «ADB over network» on insecure public networks!&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Install scrcpy on the desktop computer
&lt;/h2&gt;

&lt;p&gt;The following is for Macs, but you find instructions for Linux or Windows on the &lt;a href="https://github.com/Genymobile/scrcpy"&gt;scrcpy README&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Unless already present, install &lt;a href="https://brew.sh"&gt;Homebrew&lt;/a&gt; on your Mac. Then open a terminal and install scrcpy as well as the offical Android development tools.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;scrcpy
brew &lt;span class="nb"&gt;install &lt;/span&gt;android-platform-tools
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You might also want to set an aliases in order not having to memorize scrcpy and it's options. Here's the alias when using a USB connection:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# macOS 12 or newer&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'alias android="scrcpy --stay-awake"'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;~/.zshrc
&lt;span class="nb"&gt;source&lt;/span&gt; ~/.zshrc

&lt;span class="c"&gt;# macOS 11 or older&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'alias android="scrcpy --stay-awake"'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;~/.bash_profile
&lt;span class="nb"&gt;source&lt;/span&gt; ~/.bash_profile
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When using a TCP/IP connection, you have to make sure your Android device always gets the same IP address assigned by the router. The following assumes the device on IP 192.168.1.10:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# macOS 12 or newer&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'alias android="scrcpy --tcpip=192.168.1.10 --bit-rate 2M --max-size 800 --stay-awake"'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;~/.zshrc
&lt;span class="nb"&gt;source&lt;/span&gt; ~/.zshrc

&lt;span class="c"&gt;# macOS 11 or older&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'alias android="scrcpy --tcpip=192.168.1.10 --bit-rate 2M --max-size 800 --stay-awake"'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;~/.bash_profile
&lt;span class="nb"&gt;source&lt;/span&gt; ~/.bash_profile
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Spieglein, Spieglein
&lt;/h2&gt;

&lt;p&gt;Time to mirror! Either connect the Android device to the computer or enable «ADB over network», then open a terminal on the Mac and type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;android
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A window should pop up and mirror the Android screen. You can mouse around, type input, even copy/paste as if it were just another application running. 🎉&lt;/p&gt;

&lt;p&gt;Given the multitude of different Android devices, this might not work at the first attempt. You find troubleshooting hints in the &lt;a href="https://github.com/Genymobile/scrcpy"&gt;scrcpy README&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;(Photo by &lt;a href="https://unsplash.com/@rishabhdharmani?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Rishabh Dharmani&lt;/a&gt; on &lt;a href="https://unsplash.com/s/photos/mirror?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Unsplash&lt;/a&gt;)&lt;/p&gt;

</description>
      <category>android</category>
      <category>linux</category>
      <category>macos</category>
      <category>windows</category>
    </item>
    <item>
      <title>Command aliases for Ruby debug</title>
      <dc:creator>Sven Schwyn</dc:creator>
      <pubDate>Mon, 13 Dec 2021 21:01:03 +0000</pubDate>
      <link>https://dev.to/svoop/command-aliases-for-ruby-debug-2loh</link>
      <guid>https://dev.to/svoop/command-aliases-for-ruby-debug-2loh</guid>
      <description>&lt;p&gt;After years with &lt;a href="http://pry.github.io"&gt;Pry&lt;/a&gt;, there's a new old kid on the block now: &lt;a href="https://github.com/ruby/debug"&gt;debug.rb&lt;/a&gt; &amp;gt;=1.0.0 – all new, shiny and ready for Ruby 3.&lt;/p&gt;

&lt;p&gt;One thing I miss though is the ability to create command aliases. &lt;/p&gt;

&lt;p&gt;To quickly get out of the debugger, typing «exit-program» involves too many hands for my taste, I prefer something quick like «eee» instead. Pry lets you create such command aliases in &lt;code&gt;~/.pryrc&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Pry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;commands&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;alias_command&lt;/span&gt; &lt;span class="s2"&gt;"eee"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"exit-program"&lt;/span&gt;
&lt;span class="no"&gt;Pry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;commands&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;alias_command&lt;/span&gt; &lt;span class="s2"&gt;"ee"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"exit"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Typing «kill!» or its shorter twin «k!» with debug.rb is no fun either. Here's a quick and dirty monkey patch to do a similar trick in &lt;code&gt;~/.rdbgrc.rb&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;DEBUGGER__&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Session&lt;/span&gt;
    &lt;span class="no"&gt;COMMAND_ALIASES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="s1"&gt;'ee'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'quit!'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s1"&gt;'eee'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'kill!'&lt;/span&gt;
    &lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;freeze&lt;/span&gt;

    &lt;span class="kp"&gt;alias_method&lt;/span&gt; &lt;span class="ss"&gt;:original_process_command&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:process_command&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_command&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;
      &lt;span class="n"&gt;original_process_command&lt;/span&gt; &lt;span class="no"&gt;COMMAND_ALIASES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Happy hammering your "e" key!&lt;/p&gt;




&lt;p&gt;Photo by &lt;a href="https://unsplash.com/@jhaland?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Jørgen Håland&lt;/a&gt; on &lt;a href="https://unsplash.com/s/photos/twins?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>debug</category>
    </item>
    <item>
      <title>Keyoxide proof</title>
      <dc:creator>Sven Schwyn</dc:creator>
      <pubDate>Sun, 28 Nov 2021 15:49:47 +0000</pubDate>
      <link>https://dev.to/svoop/keyoxide-proof-46ch</link>
      <guid>https://dev.to/svoop/keyoxide-proof-46ch</guid>
      <description>&lt;p&gt;This is an OpenPGP proof that connects &lt;a href="https://keyoxide.org/d0654ab99d5016af7c4e06bff72b2e4be8d06a87"&gt;my OpenPGP key&lt;/a&gt; to &lt;a href="https://dev.to/svoop"&gt;this dev.to account&lt;/a&gt;. For details check out &lt;a href="https://keyoxide.org/guides/openpgp-proofs"&gt;https://keyoxide.org/guides/openpgp-proofs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[Verifying my OpenPGP key: openpgp4fpr:d0654ab99d5016af7c4e06bff72b2e4be8d06a87]&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
