<?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: Jonathan Yeong</title>
    <description>The latest articles on DEV Community by Jonathan Yeong (@jonathanyeong).</description>
    <link>https://dev.to/jonathanyeong</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%2F1484%2F0cbbeb32-70d4-4ca0-b0dc-83fcc158c72d.jpg</url>
      <title>DEV Community: Jonathan Yeong</title>
      <link>https://dev.to/jonathanyeong</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jonathanyeong"/>
    <language>en</language>
    <item>
      <title>Setting up dev containers in Rails</title>
      <dc:creator>Jonathan Yeong</dc:creator>
      <pubDate>Sun, 30 Jun 2024 00:00:00 +0000</pubDate>
      <link>https://dev.to/jonathanyeong/setting-up-dev-containers-in-rails-3o8n</link>
      <guid>https://dev.to/jonathanyeong/setting-up-dev-containers-in-rails-3o8n</guid>
      <description>&lt;p&gt;&lt;a href="https://containers.dev/" rel="noopener noreferrer"&gt;Development containers&lt;/a&gt;, aka dev containers, have been around since 2019. Made by Microsoft, they allow you to use a container as a development environment. If you've used GitHub Codespaces, you've loaded a dev container. In Rails 7.2, there will be a new &lt;code&gt;devcontainer&lt;/code&gt; flag that will generate a dev container folder for you. Ever since &lt;a href="https://jonathanyeong.com/rails-dev-containers/" rel="noopener noreferrer"&gt;I wrote about Rails dev containers&lt;/a&gt;, I've been eager to try them. If you also want to be an early adopter, here are a few issues I ran into.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installing libraries
&lt;/h2&gt;

&lt;p&gt;Before I can get dev containers working locally, I need to install a few libraries and extensions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers" rel="noopener noreferrer"&gt;VSCode dev container extension&lt;/a&gt;, which will run my application as a dev container inside VSCode.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/abiosoft/colima" rel="noopener noreferrer"&gt;Colima&lt;/a&gt;, which is my container runtime.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;brew install colima&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Docker to run my containers.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;brew install docker&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Docker compose, which is needed to run the dev container &lt;code&gt;compose.yaml&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;brew install docker-compose&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;I also updated my &lt;code&gt;~/.docker/config.json&lt;/code&gt; to add the plugin&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"cliPluginsExtraDirs": [
    "/opt/homebrew/lib/docker/cli-plugins"
]

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Setting up a Rails dev container
&lt;/h2&gt;

&lt;p&gt;First off, I created a new Rails API app with the &lt;code&gt;devcontainers&lt;/code&gt; flag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rails new dev-containers-app --main --database=mysql --devcontainer

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After opening the project in VSCode, you should get a popup asking if you want to &lt;code&gt;Reopen in container&lt;/code&gt;. If you don't, you can use command palette (&lt;code&gt;cmd+shift+p&lt;/code&gt; for mac) and type &lt;code&gt;dev container: Reopen in Container&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Issues with dev container
&lt;/h2&gt;

&lt;p&gt;When I first ran the dev container, the &lt;code&gt;bin/setup&lt;/code&gt; step erroredr:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ActiveRecord::ConnectionNotEstablished: Can't connect to local server through socket '/tmp/mysql.sock'

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The issue is that &lt;code&gt;config/database.yaml&lt;/code&gt; is trying to read a &lt;code&gt;mysql.sock&lt;/code&gt; file that doesn't exist in the container. The easiest fix is to connect to the DB through the network. Modify your &lt;code&gt;config/database.yaml&lt;/code&gt; file with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;default: &amp;amp;default
  adapter: mysql2
  encoding: utf8mb4
  pool: &amp;lt;%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %&amp;gt;
  username: root
  password:
  host: mysql
  port: 3306

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After this change, I was able to get my dev container up and running. However, when I tried to do Git things like &lt;code&gt;git status&lt;/code&gt;, I got an error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fatal: detected dubious ownership in repository at '/workspaces/dev-containers-app'
To add an exception for this directory, call:
    git config --global --add safe.directory /workspaces/dev-containers-app

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To resolve this issue, I updated the &lt;code&gt;devcontainer.json&lt;/code&gt; file. Adding a &lt;code&gt;postStartCommand&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    ... // Rest of config
    "postStartCommand": "git config --global --add safe.directory ${containerWorkspaceFolder}"
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Initial thoughts
&lt;/h2&gt;

&lt;p&gt;Here's what I loved:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Modifying files in the container also modified the files on my local machine. And vice versa! It's a fast development workflow&lt;/li&gt;
&lt;li&gt;Git just works! It pulled my gitconfig from my machine, and I could push verified commits from the container.&lt;/li&gt;
&lt;li&gt;Since the app runs in a container, I don't need to worry about dependency management any more. If I need a new dependency, I can pull it into my docker image.&lt;/li&gt;
&lt;li&gt;It works out of the box with VSCode dev container extension.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's what I didn't like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You can't run &lt;code&gt;--skip-bundle&lt;/code&gt; and generate a &lt;code&gt;.devcontainer&lt;/code&gt; folder. Maybe someone can correct me if I'm wrong. I would like to not have to install anything locally if possible!&lt;/li&gt;
&lt;li&gt;The best dev experience is with VSCode. If you don't use that editor, your workflow would be more involved. You would want to use the &lt;a href="https://github.com/devcontainers/cli" rel="noopener noreferrer"&gt;devcontainers/cli&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;If you have an underpowered machine, dev containers might not be for you. If your container run time isn't beefy enough with RAM &amp;amp; CPU a-plenty, then your dev container might be sluggish.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://github.com/jonathanyeong/rails-dev-containers" rel="noopener noreferrer"&gt;GitHub repo&lt;/a&gt; for reference.&lt;/p&gt;

</description>
      <category>rails</category>
    </item>
    <item>
      <title>Dynamic Nested Forms with Rails and Stimulus</title>
      <dc:creator>Jonathan Yeong</dc:creator>
      <pubDate>Wed, 19 Jun 2024 00:00:00 +0000</pubDate>
      <link>https://dev.to/jonathanyeong/dynamic-nested-forms-with-rails-and-stimulus-4pll</link>
      <guid>https://dev.to/jonathanyeong/dynamic-nested-forms-with-rails-and-stimulus-4pll</guid>
      <description>&lt;p&gt;I've been away from Ruby on Rails for a &lt;a href="https://jonathanyeong.com/what-i-missed-about-ruby/" rel="noopener noreferrer"&gt;couple of years&lt;/a&gt;. And in that time, Rails introduced &lt;a href="https://hotwired.dev/" rel="noopener noreferrer"&gt;Hotwire&lt;/a&gt;, and &lt;a href="https://stimulus.hotwired.dev/" rel="noopener noreferrer"&gt;Stimulus&lt;/a&gt;. In this post, I extend my &lt;a href="https://jonathanyeong.com/what-i-missed-about-ruby/" rel="noopener noreferrer"&gt;Rails nested from tutorial&lt;/a&gt; by adding a button to dynamically add new nested form elements using a &lt;a href="https://www.stimulus-components.com/docs/stimulus-rails-nested-form/" rel="noopener noreferrer"&gt;Stimulus Nested Form component&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We're going all in on Rails, so I'll be using the default &lt;a href="https://github.com/rails/importmap-rails" rel="noopener noreferrer"&gt;importmap JS package manager&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Set up
&lt;/h2&gt;

&lt;p&gt;Let's first install the Stimulus nested form component&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bin/importmap pin @stimulus-components/rails-nested-form 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Update &lt;code&gt;app/javascript/controllers/application.js&lt;/code&gt; to register the stimulus component.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Application&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@hotwired/stimulus&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="c1"&gt;// Should exist already&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;RailsNestedForm&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@stimulus-components/rails-nested-form&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;application&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// Should exist already&lt;/span&gt;
&lt;span class="nx"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;nested-form&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;RailsNestedForm&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's modify our &lt;a href="https://jonathanyeong.com/nested-forms-in-rails/" rel="noopener noreferrer"&gt;previous form&lt;/a&gt; by moving our nested fields into a partial. First create the partial &lt;code&gt;app/views/training_sessions/_training_step_fields.html.erb&lt;/code&gt; and add the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;# app/views/training_sessions/_training_step_fields.html.erb 
&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;label&lt;/span&gt; &lt;span class="ss"&gt;:description&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text_field&lt;/span&gt; &lt;span class="ss"&gt;:description&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After we create our partial, we can modify the form in &lt;code&gt;new.html.erb&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;# app/views/training_sessions/new.html.erb
&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;form_with&lt;/span&gt; &lt;span class="ss"&gt;model: &lt;/span&gt;&lt;span class="vi"&gt;@training_session&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  Session Steps:

  &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fields_for&lt;/span&gt; &lt;span class="ss"&gt;:training_steps&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;training_steps_form&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;partial: &lt;/span&gt;&lt;span class="s2"&gt;"training_step_fields"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;locals: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;f: &lt;/span&gt;&lt;span class="n"&gt;training_steps_form&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;

  &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;submit&lt;/span&gt; &lt;span class="s2"&gt;"Create New Session"&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point, when we go to &lt;code&gt;training_sessions/new&lt;/code&gt; we should see the same behaviour as before.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementing Stimulus Nested Form Component
&lt;/h2&gt;

&lt;p&gt;Let's implement the Stimulus component. Modify your &lt;code&gt;new.html.erb&lt;/code&gt; to add a Stimulus target:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;form_with&lt;/span&gt; &lt;span class="ss"&gt;model: &lt;/span&gt;&lt;span class="vi"&gt;@training_session&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  Session Steps:
  &lt;span class="nt"&gt;&amp;lt;template&lt;/span&gt; &lt;span class="na"&gt;data-nested-form-target=&lt;/span&gt;&lt;span class="s"&gt;"template"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fields_for&lt;/span&gt; &lt;span class="ss"&gt;:training_steps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;TrainingStep&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="ss"&gt;child_index: &lt;/span&gt;&lt;span class="s1"&gt;'NEW_RECORD'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;training_steps_form&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
      &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;partial: &lt;/span&gt;&lt;span class="s2"&gt;"training_step_fields"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;locals: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;f: &lt;/span&gt;&lt;span class="n"&gt;training_steps_form&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;

  &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fields_for&lt;/span&gt; &lt;span class="ss"&gt;:training_steps&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;training_steps_form&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;partial: &lt;/span&gt;&lt;span class="s2"&gt;"training_step_fields"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;locals: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;f: &lt;/span&gt;&lt;span class="n"&gt;training_steps_form&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;data-nested-form-target=&lt;/span&gt;&lt;span class="s"&gt;"target"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt; &lt;span class="na"&gt;data-action=&lt;/span&gt;&lt;span class="s"&gt;"nested-form#add"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Add Training Step&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;

  &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;submit&lt;/span&gt; &lt;span class="s2"&gt;"Create New Session"&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, if we reload our app, there's a button that lets you add more training steps 🎉.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fjonathan-yeong%2Fimage%2Fupload%2Fv1718756584%2Funsigned_obsidian_uploads%2Fiekjw7427bkqf58y8kaf.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fjonathan-yeong%2Fimage%2Fupload%2Fv1718756584%2Funsigned_obsidian_uploads%2Fiekjw7427bkqf58y8kaf.gif" alt="Add nested form field demo using Stimulus Nested Form component"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Breaking down the changes
&lt;/h2&gt;

&lt;p&gt;Let's break down the changes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;template&lt;/span&gt; &lt;span class="na"&gt;data-nested-form-target=&lt;/span&gt;&lt;span class="s"&gt;"template"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
...
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;data-nested-form-target=&lt;/span&gt;&lt;span class="s"&gt;"target"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We register two targets with Stimulus, &lt;code&gt;template&lt;/code&gt; and &lt;code&gt;target&lt;/code&gt;. The &lt;code&gt;template&lt;/code&gt; target, which in Stimulus, gets referred to as &lt;code&gt;this.templateTarget&lt;/code&gt; is inserted into &lt;code&gt;this.targetTarget&lt;/code&gt; via &lt;code&gt;insertAdjacentHTML&lt;/code&gt;. The template is defined similar to our original definition of the training_step fields. But it has two additional params: &lt;code&gt;TrainingSteps.new&lt;/code&gt; and &lt;code&gt;child_index: 'NEW_RECORD'&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fields_for&lt;/span&gt; &lt;span class="ss"&gt;:training_steps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;TrainingStep&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="ss"&gt;child_index: &lt;/span&gt;&lt;span class="s1"&gt;'NEW_RECORD'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;training_steps_form&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  ...
&lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rails requires the index of nested fields to be unique. It does enforce uniqueness via a &lt;code&gt;child_index&lt;/code&gt;. See the &lt;a href="https://apidock.com/rails/ActionView/Helpers/FormHelper/fields_for#512-Setting-child-index-while-using-nested-attributes-mass-assignment" rel="noopener noreferrer"&gt;Rails docs&lt;/a&gt;. We must keep the &lt;code&gt;NEW_RECORD&lt;/code&gt; value. Stimulus nested form component will replace the term &lt;code&gt;NEW_RECORD&lt;/code&gt; with &lt;code&gt;Date.getTime().toString()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;TrainingStep.new&lt;/code&gt; is used to build a new instance of the Training Step model. Without this model, we'll see some odd behaviour where we add two fields every time we press the "Add Training Step" button. Note: the &lt;strong&gt;two&lt;/strong&gt; comes from our controller: &lt;code&gt;2.times { @training_session.training_steps.build }&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fun fact&lt;/strong&gt; : even though two form elements get added, only one of those elements gets saved! Can you guess why? That's right, it's because the child index for those elements are the same. Rails will only save the last of the two values.&lt;/p&gt;

&lt;p&gt;For reference, here's &lt;a href="https://github.com/stimulus-components/stimulus-rails-nested-form/blob/master/src/index.ts" rel="noopener noreferrer"&gt;the component source code&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;At the time of writing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rails 7.1&lt;/li&gt;
&lt;li&gt;Ruby 3.3.2&lt;/li&gt;
&lt;li&gt;Stimulus Nested Form Component 5.0.0&lt;/li&gt;
&lt;li&gt;Stimulus 3.2.2&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>tutorial</category>
      <category>ruby</category>
      <category>rails</category>
    </item>
    <item>
      <title>Tools I use daily as a solo dev</title>
      <dc:creator>Jonathan Yeong</dc:creator>
      <pubDate>Tue, 11 Jun 2024 00:00:00 +0000</pubDate>
      <link>https://dev.to/jonathanyeong/tools-i-use-daily-as-a-solo-dev-40da</link>
      <guid>https://dev.to/jonathanyeong/tools-i-use-daily-as-a-solo-dev-40da</guid>
      <description>&lt;p&gt;I was inspired by &lt;a href="https://dev.to/nickytonline/tools-that-keep-me-productive-1no5"&gt;Nick Taylor's post "Tools that keep me productive"&lt;/a&gt;, to document tools I use daily. These tools help me build and showcase projects, stay organised, and keep me productive. Most of these tools are cross-platform, at least between macOS and Linux.&lt;/p&gt;

&lt;h2&gt;
  
  
  Editor + Terminal
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://code.visualstudio.com/" rel="noopener noreferrer"&gt;VSCode&lt;/a&gt; is my main editor. I've been playing around with Zed, but it doesn't have the plugin ecosystem (yet) for me to switch over. I'm using the &lt;a href="https://draculatheme.com/visual-studio-code" rel="noopener noreferrer"&gt;Dracula&lt;/a&gt; theme, and &lt;a href="https://github.com/Twixes/SF-Mono-Powerline" rel="noopener noreferrer"&gt;SF Mono Powerline font&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Bonus tip: if you're livestreaming, you can enable screencast mode in VSCode to have it show keystrokes. If you want to turn off all keystrokes, go to settings, search for &lt;code&gt;keyboardOptions&lt;/code&gt;, and uncheck &lt;code&gt;show raw keys&lt;/code&gt;. After that setting is turned off, it will only display modifier keys.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://iterm2.com/" rel="noopener noreferrer"&gt;iTerm2&lt;/a&gt; is my terminal of choice. I use &lt;code&gt;zsh&lt;/code&gt; as my shell, with &lt;a href="https://ohmyz.sh/" rel="noopener noreferrer"&gt;oh-my-zsh&lt;/a&gt; installed to manage plugins for &lt;code&gt;zsh&lt;/code&gt;. &lt;a href="https://starship.rs/" rel="noopener noreferrer"&gt;Starship&lt;/a&gt; is my prompt, it shows valuable information on a project out of the box. Finally, I added &lt;a href="https://github.com/rupa/z" rel="noopener noreferrer"&gt;z&lt;/a&gt; to jump around to different folders from anywhere.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--KtLVkUg3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://res.cloudinary.com/jonathan-yeong/image/upload/v1718052393/unsigned_obsidian_uploads/ok17eceaxcovljnbyevx.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--KtLVkUg3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://res.cloudinary.com/jonathan-yeong/image/upload/v1718052393/unsigned_obsidian_uploads/ok17eceaxcovljnbyevx.gif" alt="using z to jump around to a folder anywhere" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;a href="https://asdf-vm.com/" rel="noopener noreferrer"&gt;asdf&lt;/a&gt; is my plugin manager. It can manage versions of multiple different languages.&lt;/p&gt;

&lt;h2&gt;
  
  
  Screen capturing
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://cleanshot.com/" rel="noopener noreferrer"&gt;CleanShotX&lt;/a&gt; (paid) is what I use to capture screenshots, videos, and GIFs. It's a new tool in my workflow, so I haven't had a chance to fully use it yet.&lt;/p&gt;




&lt;p&gt;QuickTime player was what I was using to screen record before CleanShotX. It's a free tool that comes with macOS. The screen recording feature displays mouse clicks and allows you to select a window or area to record.&lt;/p&gt;

&lt;h2&gt;
  
  
  Design
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.figma.com/" rel="noopener noreferrer"&gt;Figma&lt;/a&gt; is my go-to design tool. It has a free plan that gives you a limited number of projects and pages. It also has powerful features like auto-layout, and components which make building designs faster. The only downside is that you have to pay monthly for multiple projects.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://penpot.app/" rel="noopener noreferrer"&gt;Penpot&lt;/a&gt; is a free, open-source Figma alternative that I've been playing around with. I don't have the same muscle memory as Figma, and it's browser only. But it seems like a promising free alternative.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://excalidraw.com/" rel="noopener noreferrer"&gt;Excalidraw&lt;/a&gt; is my whiteboarding / brainstorming tool of choice. I love Excalidraw. I have a whole &lt;a href="https://jonathanyeong.com/tools-i-use-excalidraw/" rel="noopener noreferrer"&gt;post&lt;/a&gt; about it! Figma is a too heavy for rough wireframing or brainstorming. That's where Excalidraw shines.&lt;/p&gt;

&lt;h2&gt;
  
  
  Note-Taking
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://logseq.com/" rel="noopener noreferrer"&gt;Logseq&lt;/a&gt; for learning new concepts, or taking quick notes. I dump all my knowledge into Logseq and let references between those notes form over time. There's no real concept of folder structures, which means I don't need to spend a lot of time figuring out where notes should go. It also comes with a built-in &lt;a href="https://docs.logseq.com/#/page/queries" rel="noopener noreferrer"&gt;query language&lt;/a&gt;, which can help you find information. My favourite part about Logseq is its unlinked references. Meaning you don't need to explicitly add references to things, Logseq can do it for you.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://obsidian.md/" rel="noopener noreferrer"&gt;Obsidian&lt;/a&gt; for long form writing, like blog posts. I have a &lt;a href="https://jonathanyeong.com/writing-blog-posts-with-obsidian/" rel="noopener noreferrer"&gt;blog post workflow in Obsidian&lt;/a&gt;. It's also where I write journal entries or do my goal setting. Both Obsidian and Logseq store notes locally, so I can write offline.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://www.notion.so/" rel="noopener noreferrer"&gt;Notion&lt;/a&gt; for documentation that needs some kind of UI (like a table of contents), that I won't need if I'm offline. It's where my brag doc lives.&lt;/p&gt;

&lt;h2&gt;
  
  
  Project management
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://docs.github.com/en/issues/planning-and-tracking-with-projects" rel="noopener noreferrer"&gt;GitHub Projects&lt;/a&gt; is how I manage all my projects. I've set up a single GitHub Project that pulls in issues from various GitHub repos. It gives me an overview of all the things that I want to do, and then lets me drill down into what I should do next.&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>webdev</category>
      <category>tooling</category>
    </item>
    <item>
      <title>Slow productivity framework</title>
      <dc:creator>Jonathan Yeong</dc:creator>
      <pubDate>Mon, 10 Jun 2024 00:00:00 +0000</pubDate>
      <link>https://dev.to/jonathanyeong/slow-productivity-framework-54a7</link>
      <guid>https://dev.to/jonathanyeong/slow-productivity-framework-54a7</guid>
      <description>&lt;p&gt;Years ago, I was fully hooked to the world of productivity. At the time, I was getting up at the crack of dawn to get as much done. I used time blocking to try to maximise every hour of my day. Working a full-time job, then spending 4–5 hours outside the job working on various side projects. I would cut out any activity that I deemed "unproductive". And when something unexpected came along that interrupted my time block, it caused anxiety.&lt;/p&gt;

&lt;p&gt;Only recently did I realise that this productivity mindset was toxic. Yes, it took me a pandemic and also burning out to realise that toxic productivity exists. Gaslighting myself is my forte. To this day, I'm still battling with the feeling of not doing enough. That there aren't enough hours in the day to be "productive".&lt;/p&gt;

&lt;p&gt;To have a healthier mindset, I needed a new "productivity" framework. I wanted a framework that would help me prioritise what to do outside my 9-5 job. Something that would let me roll with the punches when the unexpected came up. And would help me feel good about what I chose to spend time on. So I present to you my &lt;strong&gt;"slow productivity" framework&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;When I'm trying to decide what to do. I have to answer three questions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Will I have fun?&lt;/li&gt;
&lt;li&gt;Will I learn something?&lt;/li&gt;
&lt;li&gt;Will I regret it if I didn't do it?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If I say yes to any one of these questions, I should do the thing and not feel guilty about doing it. Some activities are more worth doing than others, I can tell if it's a yes to multiple questions. But if it's a no to all questions, then I won't do the activity.&lt;/p&gt;

&lt;p&gt;Here's an example of how I applied the framework this morning.&lt;/p&gt;

&lt;p&gt;I woke up feeling refreshed, which rarely happens. I wanted to immediately start working on a side project before work. It's something that I would have fun with and learn something. Ticking two of the three questions. This task is something that I would consider productive.&lt;/p&gt;

&lt;p&gt;Now I sit at my desk, turn on my laptop, and get ready to code. But my cat jumps up on my table and wanted pets. She was far too cute, and demanding, to ignore. There went my productive morning time. In the past, I would've been annoyed and felt guilty that I was doing something unproductive. But today, I ran through the three questions. Am I having fun? Yes. Will I regret it if I didn't do it? 100% yes. Always pet the cat if it offers.&lt;/p&gt;

&lt;p&gt;Sometimes unexpected events happen that interrupt your productive task. With this framework, I feel less anxiety and guilt around these events. It might not work for everyone, but I hope it helps you feel better about doing the "unproductive" activities.&lt;/p&gt;

</description>
      <category>productivity</category>
    </item>
    <item>
      <title>Advice for Effective Developers</title>
      <dc:creator>Jonathan Yeong</dc:creator>
      <pubDate>Fri, 07 Jun 2024 01:24:52 +0000</pubDate>
      <link>https://dev.to/jonathanyeong/advice-for-effective-developers-5bk4</link>
      <guid>https://dev.to/jonathanyeong/advice-for-effective-developers-5bk4</guid>
      <description>&lt;p&gt;Syntax FM released a podcast episode &lt;a href="https://syntax.fm/show/778/11-habits-of-highly-effective-developers/transcript" rel="noopener noreferrer"&gt;11 habits of highly effective developers&lt;/a&gt; yesterday, it's filled with anecdotes, and great advice. If you haven't already, give it a listen! Scott Tolinksi and Wes Bos shared these 11 habits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Understand stakeholders and business goals&lt;/li&gt;
&lt;li&gt;Have an open mind about new tech&lt;/li&gt;
&lt;li&gt;Help others with problems&lt;/li&gt;
&lt;li&gt;Understand work-life balance&lt;/li&gt;
&lt;li&gt;Pay attention to details&lt;/li&gt;
&lt;li&gt;Be curious and always learning&lt;/li&gt;
&lt;li&gt;Ask for help when needed&lt;/li&gt;
&lt;li&gt;Have fun with development&lt;/li&gt;
&lt;li&gt;Be empathetic to users and coworkers&lt;/li&gt;
&lt;li&gt;Be part of the developer community&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While listening, I made a mental note to keep practising these habits, I loved them! Especially, having fun with development. Usually, I see programming as a way to solve business problems. I forget that it can also be used to make fun things!&lt;/p&gt;

&lt;p&gt;After the podcast ended, I thought about what advice would I have given my past self to be a more effective developer? So I made a list! I'll add to it over time, and I hope you'll find the advice useful.&lt;/p&gt;




&lt;p&gt;Naming is hard. Understanding the domain of your application can help. If possible, err on being specific with your name rather than generic.&lt;/p&gt;

&lt;p&gt;Build your pattern matching muscle. Not regex (although that can be useful), but patterns with solving problems. Watch how others solve problems, debug, and write code. Go through the same process yourself over and over again.&lt;/p&gt;

&lt;p&gt;Pair frequently and with different people. The benefits of pairing greatly outweigh working solo on complex issues.&lt;/p&gt;

&lt;p&gt;"For each desired change, make the change easy (warning: this may be hard), then make the easy change" - &lt;a href="https://x.com/KentBeck/status/250733358307500032" rel="noopener noreferrer"&gt;Kent Beck&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Find the most straightforward path to solve a problem. If this path doesn't exist, can you make it happen? Only when you've exhausted your options should you look at more complicated solutions. &lt;em&gt;I find this advice the hardest to follow.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Introspect often and stay away from autopilot mode. After finishing a tutorial, take the time to understand what you've learnt and how you could use that knowledge in other ways.&lt;/p&gt;

&lt;p&gt;Pursue what is interesting to you. Drown out the hubbub, the trends, the absolutists. If you don't know what fascinates you yet; experiment.&lt;/p&gt;

&lt;p&gt;Go broad on a topic before going deep. This advice ties in with the one above. Experiment until you find something, then go deep on it. Going broad diversifies your mental models. Going deep strengthens your models.&lt;/p&gt;

&lt;p&gt;Have a career document aka brag document. Use it to show your impact at your role. Save all the good stuff people say about you or things that you're proud of. Look at it whenever you're feeling down or feeling like an imposter.&lt;/p&gt;

&lt;p&gt;Practice saying no. Overcommitting is stressful. Burnout is real, take the time to care for yourself.&lt;/p&gt;

&lt;p&gt;You don't need to write blog posts, produce content, or commit to open source to be a successful developer. But it can be a nice way to give back to the community.&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>career</category>
      <category>learning</category>
    </item>
    <item>
      <title>Making a custom kebab case method for pretty URLs</title>
      <dc:creator>Jonathan Yeong</dc:creator>
      <pubDate>Sat, 01 Jun 2024 00:00:00 +0000</pubDate>
      <link>https://dev.to/jonathanyeong/making-a-custom-kebab-case-method-for-pretty-urls-5485</link>
      <guid>https://dev.to/jonathanyeong/making-a-custom-kebab-case-method-for-pretty-urls-5485</guid>
      <description>&lt;p&gt;Recently, I was working on a Rails app where I wanted to make some pretty URLs for it. I have a route where you can view a Dog resource &lt;code&gt;GET /dog/1&lt;/code&gt;. Having an ID in the URL is not what I want. People could increment the ID to potentially see other dogs. But more importantly, it's not personal! I wanted the URL to show your dog name instead, like &lt;code&gt;/dog/albie&lt;/code&gt; or &lt;code&gt;/dog/st-george-bernard-the-3rd&lt;/code&gt;. I needed a way to make pretty kebab case URLs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pretty kebab case URLs
&lt;/h2&gt;

&lt;p&gt;To get a URL that's not the default &lt;code&gt;id&lt;/code&gt;, I added the &lt;a href="https://edgeapi.rubyonrails.org/classes/ActiveRecord/Integration/ClassMethods.html#method-i-to_param" rel="noopener noreferrer"&gt;&lt;code&gt;to_param&lt;/code&gt; method&lt;/a&gt; to my model. Where &lt;code&gt;slug&lt;/code&gt; was a column on my Model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def to_param
  slug
end

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since I wanted the slug to be auto-generated, I had to transform the &lt;code&gt;name&lt;/code&gt; column to kebab case (see below). Much to my surprise, there wasn't a Ruby / Rails &lt;code&gt;kebabcase&lt;/code&gt; method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class Dog &amp;lt; ApplicationRecord
  before_create -&amp;gt;(dog) { dog.slug = dog.name.kebabcase } # .kebabcase doesn't exist

  def to_param
    slug
  end
end

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Looking at the docs, I found &lt;code&gt;.underscore&lt;/code&gt; and &lt;code&gt;.dasherize&lt;/code&gt;. Unfortunately, they only work for camel case names:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"DogName".underscore.dasherize
# =&amp;gt; "dog-name"

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But who names their dog in camel case? Maybe people do, but I'm assuming most people use spaces. Unfortunately, &lt;code&gt;.underscore&lt;/code&gt; only works on camel case strings.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Dog Name".underscore.dasherize
# =&amp;gt; "dog name"

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Searching for a Gem
&lt;/h2&gt;

&lt;p&gt;If Ruby and Rails didn't have a kebab case method. I'm sure there's a gem out there that does. In comes &lt;a href="https://github.com/piotrmurach/strings-case/tree/master" rel="noopener noreferrer"&gt;Strings::Case&lt;/a&gt; to the rescue:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;strings.kebabcase("PostgreSQL adapter")
# =&amp;gt; "postgre-sql-adapter"

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I could've called it a day here, but looking at the gem, it has so many other methods that I wouldn't use. Honestly, it seems like overkill for my tiny side project. It's also another library I would have to maintain and make security updates to. In the end, I decided to roll a custom kebab case method.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rolling a custom kebab case method
&lt;/h2&gt;

&lt;p&gt;Before jumping into a solution, I wanted some inspiration from Strings::Case.&lt;/p&gt;

&lt;p&gt;Under the hood, the gem scans through a string, building words determined by whether there is a specific delimiter or if there's an uppercase. "DogName" would be two words "Dog" and "Name" since there's an upper case in the middle. And "Dog Name" would be two words, since there's a space in the middle. These words get put into an array, and after the string has been scanned, a &lt;code&gt;.join&lt;/code&gt; method is called with a separator. In kebab case, the separator would be &lt;code&gt;-&lt;/code&gt;. I'm oversimplifying this approach. If you're curious, about the source code, you can view it &lt;a href="https://github.com/piotrmurach/strings-case/blob/master/lib/strings/case.rb#L365" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For the gem, this process makes total sense. It handles many use cases as well as includes multiple string manipulation functions. Since I only wanted kebab case, I decided to go with a regex approach:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dog.name.downcase.gsub(/[^\w\d\s]/, "").gsub(/\s/u, "-")

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's break down what this is doing:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;.downcase&lt;/code&gt; - A built-in function that makes the string lowercase.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.gsub(/[^\w\d\s]/, "")&lt;/code&gt; - Regex that matches anything that's not a character, digit or space, i.e. any special characters and removes it.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.gsub(/\s/u, "-")&lt;/code&gt; - Regex that matches a space and replaces with a dash
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"St. George Bernard the 3rd".downcase.gsub(/[^\w\d\s]/, "").gsub(/\s/u, "-")
# =&amp;gt; st-george-bernard-the-3rd

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The biggest limitation with my approach is the regex. I make plenty of assumptions with what I think names should be, and it's focused primarily on English. But what if someone uses a character language like Japanese?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"餅".downcase.gsub(/[^\w\d\s]/, "").gsub(/\s/u, "-")
# =&amp;gt; ""

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If I wanted to make this more robust, I should handle non-English characters properly. If I can, I could transliterate it. Like &lt;code&gt;é&lt;/code&gt; becomes &lt;code&gt;e&lt;/code&gt;. If it's in a character language, I could leave it because non-English characters can be used in URLs.&lt;/p&gt;

&lt;p&gt;However, for a small side project, what I have is enough. And I didn't need to pull in a new gem to achieve it.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Using Ruby &lt;code&gt;3.2.0&lt;/code&gt; and Rails &lt;code&gt;7.1.3.3&lt;/code&gt; at time of writing&lt;/em&gt;&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>ruby</category>
      <category>rails</category>
      <category>webdev</category>
    </item>
    <item>
      <title>What I missed about Ruby</title>
      <dc:creator>Jonathan Yeong</dc:creator>
      <pubDate>Wed, 29 May 2024 00:00:00 +0000</pubDate>
      <link>https://dev.to/jonathanyeong/what-i-missed-about-ruby-583m</link>
      <guid>https://dev.to/jonathanyeong/what-i-missed-about-ruby-583m</guid>
      <description>&lt;p&gt;I'm back in Ruby on Rails land after two years using only Node/TS. And I'm thrilled to be back! I feel like I missed so much progress with Rails 7 over the last 2 years. But now that I'm back, I can finally use all the things I missed. Here's all the things that I took for granted before my break:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Convention is enforced in Rails. Organization is much easier in a rails project, and I'm able to hop into any Rails project and find my way around.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Some&lt;/em&gt; magic is nice. Such as auto-loading files!&lt;/li&gt;
&lt;li&gt;Rails console!!! Omg being able to run code snippets is amazing. I downloaded &lt;a href="https://runjs.app/" rel="noopener noreferrer"&gt;RunJS&lt;/a&gt; to be able to achieve the same thing. But it's not a replacement for being able to play around with the data along with your code.&lt;/li&gt;
&lt;li&gt;Mocking with RSpec is way easier than Jest. In Node/TS, we used dependency injection to be able to mock classes and methods. But it was annoying to use dependency injection when it was only used in testing. &lt;em&gt;Please let me know if there's a better way to do it&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Having Rails routes to get an overview of all the entry points to the app is so underrated.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One thing that I took away from the last two years, is typing. I'm not going to lie. I like it. It's nice to be able to hover over an object and know what fields exist in it. I would love with Ruby to be able to hover over a hash and see all the keys that exist. I added &lt;a href="https://sorbet.org/" rel="noopener noreferrer"&gt;Sorbet&lt;/a&gt; and &lt;a href="https://github.com/ruby/rbs" rel="noopener noreferrer"&gt;RBS&lt;/a&gt; to my things to learn list.&lt;/p&gt;

</description>
      <category>ruby</category>
    </item>
    <item>
      <title>Adding Topics (aka tags) in Astro</title>
      <dc:creator>Jonathan Yeong</dc:creator>
      <pubDate>Tue, 11 Jul 2023 00:00:00 +0000</pubDate>
      <link>https://dev.to/jonathanyeong/adding-topics-aka-tags-in-astro-hk</link>
      <guid>https://dev.to/jonathanyeong/adding-topics-aka-tags-in-astro-hk</guid>
      <description>&lt;p&gt;This month's Virtual Coffee challenge is &lt;a href="https://dev.to/virtualcoffee/join-virtual-coffee-for-the-build-in-public-the-power-of-daily-standup-and-demo-challenge-35kb"&gt;Build in Public&lt;/a&gt;. And I wanted to kick things off with some website updates. I've been meaning to add tags to my site for a long time. After poking around at other people's sites, I decided to switch from the concept of tagging to the concept of topics. While the naming is different the process to adding topics was the same. In this post I want to share how I added topics, what I learnt, and what's next.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I added topics to my site
&lt;/h2&gt;

&lt;p&gt;I mainly followed the &lt;a href="https://docs.astro.build/en/tutorial/5-astro-api/3/" rel="noopener noreferrer"&gt;Astro guide on building a tag index page&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Firstly, I created this structure in my project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pages/
    topics/
        [topic].astro
        index.astro

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;index.astro&lt;/code&gt; is rendered when you go to &lt;code&gt;/topics&lt;/code&gt; (&lt;a href="https://jonathanyeong.com/topics/" rel="noopener noreferrer"&gt;example link&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;code&gt;[topic].astro&lt;/code&gt; is rendered when you go to &lt;code&gt;/topics/:topic&lt;/code&gt;. Where &lt;code&gt;:topic&lt;/code&gt; is something I define in my blog posts. Example, &lt;code&gt;topics/api%20design/&lt;/code&gt; (&lt;a href="https://jonathanyeong.com/topics/api%20design/" rel="noopener noreferrer"&gt;example link&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;I then added the &lt;code&gt;topics&lt;/code&gt; field to my front matter yml in my blog posts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;---
pubDate: 2023-04-05
updatedDate: 2023-04-05
title: "template"
description: "A template"
draft: true
topics: []
---

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Added &lt;code&gt;topics&lt;/code&gt; key in my &lt;code&gt;config.ts&lt;/code&gt; file (&lt;a href="https://github.com/jonathanyeong/personal-website/blob/main/src/content/config.ts#L21" rel="noopener noreferrer"&gt;source code&lt;/a&gt;)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;topics: z
    .array(z.string())
    .optional()
    .default([])

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, to display the topics on my blog post, I made a component that is pulled into the Blog Post Layout:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/jonathanyeong/personal-website/blob/main/src/layouts/BlogPost.astro#L25" rel="noopener noreferrer"&gt;BlogPost.astro&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
{topics &amp;amp;&amp;amp; topics.length &amp;gt; 0 &amp;amp;&amp;amp; &amp;lt;TopicList topics={topics} /&amp;gt;}
...

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://github.com/jonathanyeong/personal-website/blob/main/src/components/TopicList.astro" rel="noopener noreferrer"&gt;TopicList.astro&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;---
const { topics } = Astro.props;
---  

&amp;lt;p class="flex space-x-3"&amp;gt;
{topics.map((topic: string) =&amp;gt; (
    &amp;lt;a href={`/topics/${topic}`}&amp;gt;{topic}&amp;lt;/a&amp;gt;
))}
&amp;lt;/p&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Deep dive (aka what did I learn)
&lt;/h3&gt;

&lt;p&gt;Digging into the &lt;code&gt;index.astro&lt;/code&gt; and &lt;code&gt;[topic].astro&lt;/code&gt; files a bit more.&lt;/p&gt;

&lt;h4&gt;
  
  
  index.astro
&lt;/h4&gt;

&lt;p&gt;The way we get a list of topics to display on the &lt;code&gt;/topic&lt;/code&gt; page is through this code (&lt;a href="https://github.com/jonathanyeong/personal-website/blob/main/src/pages/topics/index.astro#L9" rel="noopener noreferrer"&gt;source code&lt;/a&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const posts = (await getCollection('blog'))
const topics = [...new Set(posts.flatMap((post) =&amp;gt; post.data.topics))];

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This line will get all my posts from my blog folder, pull out all of the topics, and return an array of topics with no duplicates.&lt;/p&gt;

&lt;h4&gt;
  
  
  [topic].astro
&lt;/h4&gt;

&lt;p&gt;To generate the corresponding topic endpoints like &lt;code&gt;/topic/api%20design/&lt;/code&gt; I needed to modify the &lt;code&gt;getStaticPaths()&lt;/code&gt; function in the &lt;code&gt;[topic].astro&lt;/code&gt;. (&lt;a href="https://github.com/jonathanyeong/personal-website/blob/main/src/pages/topics/%5Btopic%5D.astro#L7" rel="noopener noreferrer"&gt;source code&lt;/a&gt;). I forgot to modify it originally, and I was very confused when I kept getting a 404 not found error when trying to navigate to a topic page 🤦🏻!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export async function getStaticPaths() {
    const posts = (await getCollection('blog'))
    const topics = [...new Set(posts.flatMap((post) =&amp;gt; post.data.topics))];
    return topics.map((topic) =&amp;gt; ({
        params: { topic }
    }));
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://docs.astro.build/en/reference/api-reference/#getstaticpaths" rel="noopener noreferrer"&gt;getStaticPaths()&lt;/a&gt; is an Astro function that Astro uses to generate each page at build time. It returns an array that looks something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[
    { params: {topic: "API Design"} },
    { params: {topic: "Productivity"} }
]

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;I'm not super happy with how my topics are styled. But I'm also not sure how I would style them - I'm open to suggestions! Next up for me is writing an About me page. I don't know why but I find it incredibly hard to write! I'll be chugging away at it while trying to come up with a better styling in the meantime.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>astro</category>
      <category>buildinpublic</category>
    </item>
    <item>
      <title>The Art of Determining the Right Amount of Testing</title>
      <dc:creator>Jonathan Yeong</dc:creator>
      <pubDate>Sun, 11 Jun 2023 00:00:00 +0000</pubDate>
      <link>https://dev.to/jonathanyeong/what-is-the-right-amount-of-testing-1n7h</link>
      <guid>https://dev.to/jonathanyeong/what-is-the-right-amount-of-testing-1n7h</guid>
      <description>&lt;p&gt;What is the right amount of testing?&lt;/p&gt;

&lt;p&gt;Over the years my answer to this question has changed drastically. From fully embracing integration testing and requiring code coverage numbers on Pull Requests (PRs). To writing only unit tests covering the critical paths of the application. Leaving much of code untested.&lt;/p&gt;

&lt;p&gt;Of course there's no definitive answer for the right amount of testing. If there was, everyone would be doing it. Instead, I'd like to share what I consider a good default amount of testing. But before diving into that, let's discuss the problem we're trying to solve.&lt;/p&gt;

&lt;p&gt;The primary goal of testing is to prevent bugs from reaching production. Too little testing leads to a brittle application. As more features get pushed out, more fires need to be fought. And eventually you're worried that any change might cause something unrelated to break.&lt;/p&gt;

&lt;p&gt;In response, we swing the pendulum to the other side. We write extensive suites of integration tests that get run on every PR. Code coverage numbers need to be met before a PR can be approved. We aim to test every possible path, leaving no room for bugs to slip through. Unfortunately, our feedback loops begins to slow down. And meaningful features take longer to be released.&lt;/p&gt;

&lt;p&gt;Both of these are extreme ends of the spectrum. Too little testing leads to brittle applications. Exhaustive integration tests slow down feedback loops and delay feature releases. So what is a happy middle ground? Here's what I think we should default to.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Write unit tests for all your changes. Testing error paths but not every combination of errors/inputs. Pick your edge cases that cover critical aspects of your code. Here's some examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Test paths that have a complex outcome and/or an error you display to the user.
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;condition&lt;/span&gt;
    &lt;span class="n"&gt;complex_outcome&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="n"&gt;error_to_user&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;In general, you don't need to test well-established, trusted libraries. But you should manually validate library calls (see more on manual testing below).
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;  &lt;span class="c1"&gt;# Don't test that Rails will save a model to the database.&lt;/span&gt;
  &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;
  &lt;span class="c1"&gt;# Don't test S3 library will delete the bucket&lt;/span&gt;
  &lt;span class="n"&gt;s3Client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delete_bucket&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;  &lt;span class="ss"&gt;bucket: &lt;/span&gt;&lt;span class="s2"&gt;"bucketName"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Test for public not private methods. Unexpected changes to private methods should be caught in the public method tests.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;  &lt;span class="c1"&gt;# Test this method&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;public_method&lt;/span&gt;
    &lt;span class="n"&gt;something&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;do_something&lt;/span&gt;
    &lt;span class="n"&gt;do_something_with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;something&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="kp"&gt;private&lt;/span&gt;

  &lt;span class="c1"&gt;# Don't test these methods&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;do_something&lt;/span&gt;
    &lt;span class="o"&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;do_something_with&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Test suites should run fast enough that by the time you make a coffee your build is complete (faster is better). That means unit tests over integration tests.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If possible, always manually test your change. Keep the process lightweight and repeatable by writing down your steps. Don't test the entire app behaviour on every change. But allow someone on your team to vet your change by running through the testing steps you wrote down. If you find yourself testing the same thing over and over again, look to automate it (i.e. write a unit test).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Manually testing is especially important if testing a third party service/api. Mocking in unit tests is needed for fast tests but you need to validate your mocks in the real world. Validating third party services/api is the purpose of integration tests. But arguably, a quick manual test may be all that's needed along with good observability.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Bonus points if you have a cloud developer environment or a developer preview on your PR that allows someone to validate your changes without having to run your code on their machine. This approach also promotes collaboration between your team!&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Finally, have a robust CI/CD pipeline that lets you rollback bad builds, or feature flags to quickly turn off a breaking change. Having the right monitors in place will tell you if something is broken before it causes too much damage. Expect that bugs will happen even with a proper amount of testing.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While there's no one size fits all solution for the right amount of testing. I've found that these defaults have worked well in the past. They strike a balance between maintaining healthy developer feedback loops while minimizing game breaking bugs that make it to production.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>beginners</category>
      <category>programming</category>
    </item>
    <item>
      <title>Developer productivity with Github Codespaces</title>
      <dc:creator>Jonathan Yeong</dc:creator>
      <pubDate>Fri, 09 Jun 2023 01:27:01 +0000</pubDate>
      <link>https://dev.to/jonathanyeong/developer-productivity-with-github-codespaces-jpb</link>
      <guid>https://dev.to/jonathanyeong/developer-productivity-with-github-codespaces-jpb</guid>
      <description>&lt;p&gt;During my time at Shopify, I had the opportunity to work with a cloud development environment - and I was sold. Since then, I set out trying to recreate a similar experience for my personal development. Enter, Github Codespaces. I'd like to share my thoughts on using Codespaces and why I believe it will greatly benefit developer productivity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Exploring Codespaces: My Motivations
&lt;/h2&gt;

&lt;p&gt;There are a couple of reasons why I decided to explore GitHub Codespaces:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Escape from Bare Metal Setup&lt;/strong&gt;: I'm tired of installing and managing specific versions of Ruby, Node, Postgresql across my various projects.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Onboarding Developers&lt;/strong&gt;: one of my aspirations is to run my own company, I wanted to explore how cloud development environments like Codespaces could facilitate the smooth onboarding of new engineers.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Github Codespaces Workflow
&lt;/h2&gt;

&lt;p&gt;Firstly, install the Github CLI tool. Being able to use the terminal over the browser has significantly streamlined my experience. Once authenticated with the Github CLI, my workflow is as follows:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Creating a Codespace:&lt;/strong&gt; Initially, I needed to create a Codespace. Note that if you cloned a project and you're in that folder, the Github CLI tool will use that project as the default
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gh codespace create
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Getting to Work:&lt;/strong&gt; To start working on my application, I ran the following command, selecting the Codespace I had just created. There's a short delay as the environment is being set up:
&lt;/li&gt;
&lt;/ol&gt;

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

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Astro Aside:&lt;/strong&gt; Since I'm running the Codespace for my Astro site, I made an update to the &lt;code&gt;dev&lt;/code&gt; command in the &lt;code&gt;package.json&lt;/code&gt; file:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;astro dev &lt;span class="nt"&gt;--host&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This configuration binds the Astro local server to any IP, allowing proper connection when Codespace forwards the localhost port.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Codespace Logging:&lt;/strong&gt; I also experimented with &lt;a href="https://github.com/jonathanyeong/dotfiles" rel="noopener noreferrer"&gt;Dotfiles&lt;/a&gt; for my Codespace, which customize the Codespace developer environment. The Codespace logs proved invaluable for debugging my changes as the Codespace started:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gh codespace logs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Initial Considerations
&lt;/h2&gt;

&lt;p&gt;Based on my early experience, here are a few considerations regarding Codespaces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Overkill for Simple Projects&lt;/strong&gt;: Codespaces is overkill for simple projects like my Astro blog. Given the minimal version dependencies and longer startup time compared to setting up my Astro project directly, it doesn't give that much benefit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Treat Codespaces as Ephemeral&lt;/strong&gt;: It's important to treat Codespaces as ephemeral environments. That means commit often! Any uncommitted changes will be lost when the Codespace is destroyed (which occurs by default every 30 days).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Untapped Potential
&lt;/h2&gt;

&lt;p&gt;I'm totally bought into the idea of cloud development environments. Github Codespace is one option there's also others such as Gitpod.&lt;/p&gt;

&lt;p&gt;Imagine this situation, you're starting a new job, excitedly setting up a fresh codebase, only to encounter a mysterious, undocumented error. You turn to your onboarding buddy for help. They speak the dreaded words, "weird - works on my machine". A response that leaves you navigating a labyrinth of debugging all by your lonesome. Okay I'm being a little dramatic, but we've all experienced these "works on my machine" moments that can send you spiraling.&lt;/p&gt;

&lt;p&gt;With a cloud developer environment like Codespaces, someone can get their project up and running in minutes rather then days. Think about the time and headache saved! These environments can be used for more then onboarding too. You could use them as developer previews, sharing a URL of your app running in the Codespace so anyone can test your changes.&lt;/p&gt;

&lt;p&gt;While I haven't found a project to fully utilize Codespaces yet, I'm psyched to keep exploring it. Stay tuned, I'll keep this post updated.&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>learning</category>
      <category>programming</category>
    </item>
    <item>
      <title>The Tale of the Whitespace</title>
      <dc:creator>Jonathan Yeong</dc:creator>
      <pubDate>Sun, 26 Sep 2021 00:29:40 +0000</pubDate>
      <link>https://dev.to/jonathanyeong/the-tale-of-the-whitespace-hha</link>
      <guid>https://dev.to/jonathanyeong/the-tale-of-the-whitespace-hha</guid>
      <description>&lt;p&gt;Can you tell the difference between " " and " "? I certainly can't. But the computer can.&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="s2"&gt;" "&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ord&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; 32&lt;/span&gt;
&lt;span class="s2"&gt;" "&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ord&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; 160&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This whitespace led to one of the more confusing bugs that I've seen as a developer. I wanted to convert a string from Unicode to ASCII and this whitespace was causing my tests to fail. For the longest time, I trusted my eyes. It was only until I tested my assumptions that I realized that I wasn't dealing with any normal whitespace. I was dealing with a Unicode whitespace. Before I share how I solved the bug, I first want to do a deep dive into character encodings. Encodings like Unicode, ASCII, ANSI, ISO, are things we see all the time. But we hardly question, because it just works. Of course, until it doesn't.&lt;/p&gt;

&lt;p&gt;Every character that we see on a computer screen, in your browser, or as a string object in your code, is represented as a number on the computer. To be specific, it's represented as a series of bits. This process of transforming a visual character into a number is done through character encoding.  Character encoding is a map for the computer to transform this number into the character we see. It's a cipher, a key, to crack the code.&lt;/p&gt;

&lt;p&gt;This number that we assign to a character is known as a code point. That is what &lt;code&gt;" ".ord&lt;/code&gt; is outputting in the code above. If we take a look at a table of ASCII codes we can see that the codepoint &lt;code&gt;32&lt;/code&gt; corresponds to a space in ASCII (see an &lt;a href="https://www.ascii-code.com/" rel="noopener noreferrer"&gt;ASCII table&lt;/a&gt;). The codepoint here is a decimal representation of the bits, if we were to break this down into binary for an ASCII encoding the bits &lt;code&gt;00100000&lt;/code&gt; would map to the whitespace we see.&lt;/p&gt;

&lt;p&gt;If we have something like ASCII doing this for us already, why do we need something like Unicode? The first manifestation of Unicode (&lt;a href="https://www.unicode.org/history/unicode88.pdf" rel="noopener noreferrer"&gt;Unicode 88&lt;/a&gt;) was an idea published in 1988 by Joseph D. Becker. Unicode was created to extend ASCII. Becker realized that ASCII wasn't enough to represent all languages around the world.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What is needed is a new international/multilingual text encoding standard that is as workable and reliable as ASCII, but that covers all the scripts in the world.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;ASCII being a 7 bit encoding structure within an 8-bit byte could only represent 256 characters. The Unicode standard introduced three new encoding forms, UTF-8, UTF-16, UTF-32. Each of these encoding forms are interchangeable and they all extend the original ASCII encoding. UTF-8 is the most widely seen encoding format. It represents a string in a variable amount of 8 bit bytes, from one to four. If we were representing an ASCII character say &lt;code&gt;A&lt;/code&gt; in Unicode we would have:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Encoding&lt;/th&gt;
&lt;th&gt;Codepoint&lt;/th&gt;
&lt;th&gt;Binary Representation&lt;/th&gt;
&lt;th&gt;Character&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ASCII&lt;/td&gt;
&lt;td&gt;65&lt;/td&gt;
&lt;td&gt;01000001&lt;/td&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UTF-8&lt;/td&gt;
&lt;td&gt;U+0041&lt;/td&gt;
&lt;td&gt;01000001&lt;/td&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UTF-16&lt;/td&gt;
&lt;td&gt;U+0041&lt;/td&gt;
&lt;td&gt;00000000 01000001&lt;/td&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UTF-32&lt;/td&gt;
&lt;td&gt;U+0041&lt;/td&gt;
&lt;td&gt;00000000 00000000 00000000 01000001&lt;/td&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;See &lt;a href="https://unicode-table.com/en/0041/" rel="noopener noreferrer"&gt;Unicode Table for A&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: Unicode codepoints are represented via a &lt;code&gt;U+&lt;/code&gt; and the hexidecimal value. Using a function like &lt;code&gt;.ord&lt;/code&gt; will convert that codepoint to a decimal.&lt;/p&gt;

&lt;p&gt;As you can see there is a difference between the three Unicode formats. As mentioned above, UTF-8 is a variable width encoding. Where the width is between one to four 8 bit bytes. UTF-16 is also a variable length encoding but the width is one or two 16 bit bytes. And UTF 32 is a fixed width encoding that uses exactly 32 bits (four bytes). While UTF-8 might be the most popular, you might want to use UTF-16 if you wanted a balance between efficient access to characters with economical use of storage. Or UTF-32 if space wasn't a concern and you wanted faster access.&lt;/p&gt;

&lt;p&gt;At this point, we know what character encodings are. We know that ASCII and Unicode are both types of character encoding. And that Unicode was introduced to extend ASCII so that we can represent all languages. We also know that an ASCII character can also be represented as a Unicode character, interchangeably. The ASCII bit representation is the basically the same as the Unicode bit representation. But what about the flip side to this statement? What if we want to represent Unicode characters as ASCII characters?&lt;/p&gt;

&lt;h2&gt;
  
  
  Transforming Unicode to ASCII
&lt;/h2&gt;

&lt;p&gt;When building with Rails, there's a handy method that's found on the localization (&lt;code&gt;I18n&lt;/code&gt;) library - &lt;code&gt;.transliterate&lt;/code&gt;. Transliteration means to swap characters with something that looks similar. Not taking into account how the character sounds. For example, transliterating &lt;code&gt;é&lt;/code&gt; will give &lt;code&gt;e&lt;/code&gt;. Why would you do this? You might have a blog post with special unicode characters, but a URL isn't able to handle it. So we need to transform those characters to the ASCII approximation. When running &lt;code&gt;transliterate&lt;/code&gt; using the whitespace example given in the beginning we have a problem.&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;# ASCII Space&lt;/span&gt;
&lt;span class="no"&gt;I18n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transliterate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"hëllo wōrld"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; "hello world"&lt;/span&gt;

&lt;span class="c1"&gt;# Hard to tell but this line is a non breaking space!&lt;/span&gt;
&lt;span class="no"&gt;I18n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transliterate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"hëllo wōrld"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; "hello?world"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The transliterate method doesn't know what to do with the unicode whitespace, which is a non breaking space (nbsp). Because it can't handle it, it will replace the nbsp with a question mark. Not something that we want. To solve this one use case, we can use regex. Unfortunately, in Ruby, the whitespace meta-character doesn't recognize non-ASCII characters (source)  We can't use the regex meta-characters because they don't encompass non-ASCII characters.&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;# Unicode non breaking space&lt;/span&gt;
&lt;span class="sr"&gt;/\s/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"hello world"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; nil&lt;/span&gt;

&lt;span class="c1"&gt;# ASCII Space&lt;/span&gt;
&lt;span class="sr"&gt;/\s/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"hello world"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; #&amp;lt;MatchData " "&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Thankfully, we can use the POSIX bracket expressions to solve this issue:&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;# Unicode non breaking space&lt;/span&gt;
&lt;span class="sr"&gt;/[[:space:]]/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"hello world"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; #&amp;lt;MatchData " "&amp;gt;&lt;/span&gt;

&lt;span class="c1"&gt;# ASCII Space&lt;/span&gt;
&lt;span class="sr"&gt;/[[:space:]]/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"hello world"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; #&amp;lt;MatchData " "&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can then use the bracket expression to substitute the non breaking space with an ASCII space.&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;I18n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transliterate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"hëllo wōrld"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; "hello?world"&lt;/span&gt;

&lt;span class="no"&gt;I18n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transliterate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"hëllo wōrld"&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="sr"&gt;/[[:space:]]/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;" "&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; "hello world"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While this solution works for unifying whitespaces. We start running into problems when we want to expand our solution. For example, the transliterator won't be able to transliterate unicode dashes. To expand this solution, &lt;code&gt;I18n&lt;/code&gt; gives you the ability to add transliteration rules to a locale file:&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;# &amp;lt;locale&amp;gt;.yml&lt;/span&gt;
&lt;span class="ss"&gt;i18n:
  transliterate:
    rule:
      &lt;/span&gt;&lt;span class="err"&gt;ü&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"ue"&lt;/span&gt;
      &lt;span class="err"&gt;ö&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"oe"&lt;/span&gt;
      &lt;span class="err"&gt;—&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"-"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also set these rules using a Hash or a Proc. This solution is similar to how &lt;code&gt;I18n.transliterate&lt;/code&gt; is set up. Under the hood, the transliterator has a hash that contains a certain set of Unicode to ASCII permutations (see &lt;a href="https://github.com/ruby-i18n/i18n/blob/0888807ab2fe4f4c8a4b780f5654a8175df61feb/lib/i18n/backend/transliterator.rb#L43" rel="noopener noreferrer"&gt;source&lt;/a&gt;). If we wanted to create a transliterator based on some hash rules we can do something like 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;my_transliterator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;I18n&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Backend&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Transliterator&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;HashTransliterator&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="s2"&gt;"—"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"ASCII-DASH"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="n"&gt;my_transliterator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transliterate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"hello—world"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;#=&amp;gt; "helloASCII-DASHworld"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>ruby</category>
      <category>programming</category>
    </item>
    <item>
      <title>Deploying an Elixir app</title>
      <dc:creator>Jonathan Yeong</dc:creator>
      <pubDate>Sat, 01 May 2021 02:16:10 +0000</pubDate>
      <link>https://dev.to/jonathanyeong/deploying-an-elixir-app-4p4c</link>
      <guid>https://dev.to/jonathanyeong/deploying-an-elixir-app-4p4c</guid>
      <description>&lt;p&gt;I'm amazed by all the ways we can deploy an Elixir app. It's a little overwhelming (see: analysis paralysis). But it's a good problem to have. Especially now that Elixir has native support for deployment. Here are some notes that I wrote up while learning about deploying an Elixir app. Note, I didn't dive into &lt;em&gt;where&lt;/em&gt; you should put your app. Just the deployment methods:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;With Mix&lt;/strong&gt;. You host the source code on the production server and you run a mix command to start the app, e.g. &lt;code&gt;mix run --no-halt&lt;/code&gt; (with the corresponding flags).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Using Elixir Releases&lt;/strong&gt;. This is a built in feature since Elixir 1.9. There's a command, &lt;code&gt;mix release&lt;/code&gt; that will compile your elixir code and packages it into an artifact. This artifact will then be uploaded to a production server. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Using &lt;a href="https://github.com/bitwalker/distillery" rel="noopener noreferrer"&gt;Distillery&lt;/a&gt;&lt;/strong&gt;. Distillery is a library that is used to produce an Erlang/OTP release. It works with Elixir 1.6+, presumably solving release problems before 1.9 was released.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are various features of each of these ways to release as well. When we deploy with Mix, we can run mix commands, e.g. &lt;code&gt;mix phx.server&lt;/code&gt;. But there are some trade-offs. The source code is visible to anyone with access to the production server. And we don't have access to the Remote Observer. &lt;/p&gt;

&lt;p&gt;Alternatively, to start our app with Elixir releases we can use a command similar to this: &lt;code&gt;_build/prod/rel/my_app/bin/my_app start&lt;/code&gt;. With Elixir releases (and Distillery) we have access to the Remote Observer and our source code is hidden. Because we're compiling our app it also results in smaller slug sizes which means faster startup times &lt;sup id="fnref1"&gt;1&lt;/sup&gt;. However, we don't have access to Mix. Since Elixir 1.11, we have access to &lt;code&gt;runtime.exs&lt;/code&gt; which is executed after the code is compiled. In a Phoenix app, this means renaming &lt;code&gt;config/prod.secret.exs&lt;/code&gt; to &lt;code&gt;config/runtime.exs&lt;/code&gt; and replacing &lt;code&gt;use Mix.config&lt;/code&gt; with &lt;code&gt;import Config&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Finally, a feature that Distillery has that Elixir releases doesn't is hot upgrades. With hot upgrades we can release a new version of our app without bringing the old app down. There is a lot of overlap between Distillery and Elixir releases. Distillery brings its own configuration so you shouldn't mix deployment methods. Choose one or the other. One reason you'd use Distillery over Elixir releases is if you need to deploy an Elixir app pre 1.9. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Further reading&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://gigalixir.readthedocs.io/en/latest/modify-app/index.html" rel="noopener noreferrer"&gt;Gigalixir: Mix vs Distillery vs Elixir Releases&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://elixir-lang.org/blog/2020/10/06/elixir-v1-11-0-released/" rel="noopener noreferrer"&gt;Elixir v1.11 released introduces runtime.exs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://elixirforum.com/t/elixir-1-9-release-vs-distillery/23454" rel="noopener noreferrer"&gt;Elixir 1.9 release vs Distillery discussion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;&lt;a href="https://gigalixir.readthedocs.io/en/latest/modify-app/index.html" rel="noopener noreferrer"&gt;https://gigalixir.readthedocs.io/en/latest/modify-app/index.html&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>elixir</category>
      <category>deployment</category>
    </item>
  </channel>
</rss>
