<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Tom de Bruijn</title>
  <id>https://tomdebruijn.com/</id>
  <link href="https://tomdebruijn.com/"/>
  <link href="https://tomdebruijn.com/feed.xml" rel="self"/>
  <updated>2025-10-10T00:00:00+00:00</updated>
  <author>
    <name>Tom de Bruijn</name>
  </author>
  <entry>
    <title>Git: Pull changes from a local repository</title>
    <link rel="alternate" href="https://tomdebruijn.com/posts/git-pull-from-local-repository/"/>
    <id>https://tomdebruijn.com/posts/git-pull-from-local-repository/</id>
    <published>2025-10-10T00:00:00+00:00</published>
    <updated>2025-10-09T17:47:37+00:00</updated>
    <author>
      <name>Tom de Bruijn</name>
    </author>
    <description>Did you know you can pull the changes from one Git repository's local clone to another local clone?
</description>
    <content type="html">&lt;p&gt;This may a bit of niche problem to have, to pull changes from one clone of a repository to another clone, on the same machine.
Why would I need to do this?&lt;/p&gt;
&lt;h2&gt;&lt;span class="anchor" id="the-why"&gt;&lt;/span&gt;&lt;a href="#the-why"&gt;The why&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;At work, I have multiple of the same repository locally on my machine.
One &amp;#39;main&amp;#39; clone and multiple clones of the repository as submodules in another repository.
I work on these local clones in different contexts, and I don&amp;#39;t always know or realize I need to use it in a different context later.&lt;/p&gt;

&lt;p&gt;If I start work in the main repository, then want to test it as part of one of the test setups, what do I do?
Push the changes to GitHub, then pull the branch in the other local repository?
And risk everyone seeing my terrible &lt;a href="/source/posts/git-is-about-communication.html"&gt;Git commits&lt;/a&gt;?
No~ooo.&lt;/p&gt;
&lt;h2&gt;&lt;span class="anchor" id="the-setup"&gt;&lt;/span&gt;&lt;a href="#the-setup"&gt;The setup&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;What I do instead is connect the local repositories, so I can pull the changes from one local repository to another.&lt;/p&gt;

&lt;p&gt;To set this up, I first find the path of the local clone of the repository I want to pull from, e.g. &lt;code&gt;/home/tomdebruijn/work/main-repo&lt;/code&gt;.
Then, in the repository I want to pull the changes into, set up a remote to that repository:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Add a new remote with the name 'local' that points to the other local clone&lt;/span&gt;
git remote add &lt;span class="nb"&gt;local&lt;/span&gt; /home/tomdebruijn/work/main-repo
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then pull fetch changes from that repository and check out the branch I need.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Fetch the contents of the other local clone&lt;/span&gt;
git fetch &lt;span class="nb"&gt;local&lt;/span&gt;
&lt;span class="c"&gt;# Check out the other local clone's branch&lt;/span&gt;
git checkout &amp;lt;branch name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now I have the changes I committed in one local clone, available to me in another local clone as well, without having to sync it via an external SCM hosting provider.&lt;/p&gt;
&lt;h2&gt;&lt;span class="anchor" id="pull-instead-of-push"&gt;&lt;/span&gt;&lt;a href="#pull-instead-of-push"&gt;Pull instead of push&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Knowing this, we can also use this for other Git commands.&lt;/p&gt;

&lt;p&gt;For example, you can push changes to another local repository clone, instead of pulling changes:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create a branch and add some changes to it&lt;/span&gt;
git checkout &lt;span class="nt"&gt;-b&lt;/span&gt; test-branch
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Test"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; test.txt
git add &lt;span class="nb"&gt;.&lt;/span&gt;
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Test commit"&lt;/span&gt;

&lt;span class="c"&gt;# Push the branch to the other local clone of the repository&lt;/span&gt;
git push &lt;span class="nb"&gt;local &lt;/span&gt;test-branch
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;No need to fetch the changes in the other clone.
It works like any remote repository.&lt;/p&gt;
&lt;h2&gt;&lt;span class="anchor" id="extra-clone-a-local-repository"&gt;&lt;/span&gt;&lt;a href="#extra-clone-a-local-repository"&gt;Extra: Clone a local repository&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You can also clone a local repository by pointing to its path.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Go to another directory&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; /home/tomdebruijn/experiments
&lt;span class="c"&gt;# Clone the local repository into this directory&lt;/span&gt;
git clone /home/tomdebruijn/work/main-repo 
&lt;span class="c"&gt;# Open the newly cloned repository&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;main-repo
&lt;span class="c"&gt;# List the repository contents&lt;/span&gt;
&lt;span class="nb"&gt;ls&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content>
  </entry>
  <entry>
    <title>Switching Zellij and Vim panes with ease</title>
    <link rel="alternate" href="https://tomdebruijn.com/posts/zellij-vim-fzf-pane-switching/"/>
    <id>https://tomdebruijn.com/posts/zellij-vim-fzf-pane-switching/</id>
    <published>2024-11-28T00:00:00+00:00</published>
    <updated>2025-10-09T17:47:37+00:00</updated>
    <author>
      <name>Tom de Bruijn</name>
    </author>
    <description>Set up seamless pane switching in Zellij and Vim with custom keybindings.</description>
    <content type="html">&lt;p&gt;I&amp;#39;m trying out &lt;a href="https://zellij.dev/"&gt;Zellij&lt;/a&gt; after it &lt;a href="https://zellij.dev/news/colliding-keybinds-plugin-manager/"&gt;announced non-colliding keybindings&lt;/a&gt; in version 0.41.0. Currently, I&amp;#39;m using iTerm and MacVim as two separate apps, but I&amp;#39;d like to try running everything in the terminal and switch to NeoVim.&lt;/p&gt;

&lt;p&gt;I never got into &lt;a href="https://github.com/tmux/tmux"&gt;tmux&lt;/a&gt;, but I know there are &lt;a href="https://github.com/christoomey/vim-tmux-navigator"&gt;plugins&lt;/a&gt; to have you smoothly switch between tmux and Vim panes like they are from the same application. I want this behavior in Zellij too without having to switch Zellij modes.&lt;/p&gt;

&lt;p&gt;As I got started with Zellij, I found a couple of different projects with different ideas on how to accomplish this.&lt;/p&gt;

&lt;p&gt;For the default Zellij colliding keybindings, &lt;a href="https://github.com/fresh2dev/zellij-autolock/"&gt;zellij-autolock&lt;/a&gt; looks like the way to go. It automatically switches between the &amp;quot;normal&amp;quot; and &amp;quot;locked&amp;quot; modes when entering and leaving Vim. But, as the &lt;a href="https://github.com/fresh2dev/zellij-autolock/issues/9#issuecomment-2497063725"&gt;author pointed out&lt;/a&gt;, it is not tested with non-colliding keybindings.&lt;/p&gt;

&lt;p&gt;I couldn&amp;#39;t find a solution that worked for me using the Zellij non-colliding keybindings in combination with Vim, while also allowing other applications that use the &lt;code&gt;ctrl+hjkl&lt;/code&gt; keybindings to work, but please let me know if I missed one!&lt;/p&gt;

&lt;p&gt;What I ended up doing to make it work for me is the following:&lt;/p&gt;
&lt;h2&gt;&lt;span class="anchor" id="install-a-neovim-zellij-plugin"&gt;&lt;/span&gt;&lt;a href="#install-a-neovim-zellij-plugin"&gt;Install a Neovim Zellij plugin&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Add the &lt;a href="https://github.com/swaits/zellij-nav.nvim"&gt;zellij-nav.nvim plugin&lt;/a&gt; * to your Vim config that will send commands to Zellij when you try to press one of the &lt;code&gt;ctrl+hjkl&lt;/code&gt; keybindings and are on the edge of a Vim window.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;*: There may be other plugins that work, too; this is the one that worked for me.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I&amp;#39;m using &lt;a href="https://github.com/junegunn/vim-plug"&gt;vim-plug for package management&lt;/a&gt;. There are other Vim package managers &lt;a href="https://github.com/swaits/zellij-nav.nvim?tab=readme-ov-file#installing"&gt;listed on the plugin&amp;#39;s README&lt;/a&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight viml"&gt;&lt;code&gt;&lt;span class="c"&gt;" ~/.vimrc&lt;/span&gt;

&lt;span class="k"&gt;call&lt;/span&gt; plug#begin&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'~/.vim/plugged'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;" Other plugins here&lt;/span&gt;

Plug &lt;span class="s1"&gt;'swaits/zellij-nav.nvim'&lt;/span&gt;

&lt;span class="k"&gt;call&lt;/span&gt; plug#end&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;lua&lt;/span&gt; require&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"zellij-nav"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;setup&lt;span class="p"&gt;()&lt;/span&gt;

nnoremap &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="k"&gt;h&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;cmd&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;ZellijNavigateLeft&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;cr&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
nnoremap &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="k"&gt;j&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;cmd&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;ZellijNavigateDown&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;cr&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
nnoremap &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="k"&gt;k&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;cmd&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;ZellijNavigateUp&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;cr&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
nnoremap &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="k"&gt;l&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;cmd&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;ZellijNavigateRight&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;cr&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then quit Vim, restart it, and run the &lt;code&gt;:PlugInstall&lt;/code&gt; command.&lt;/p&gt;
&lt;h2&gt;&lt;span class="anchor" id="install-the-zellij-plugin"&gt;&lt;/span&gt;&lt;a href="#install-the-zellij-plugin"&gt;Install the Zellij plugin&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Next up, we&amp;#39;ll install a Zellij plugin. I started using the &lt;a href="https://github.com/hiasr/vim-zellij-navigator"&gt;vim-zellij-navigator&lt;/a&gt;. It works great for switching between Vim and Zellij.&lt;/p&gt;

&lt;p&gt;However, I ran into issues with other applications like &lt;a href="https://github.com/junegunn/fzf"&gt;fzf&lt;/a&gt;, triggered by running &lt;code&gt;fzf&lt;/code&gt; or pressing &lt;code&gt;ctrl-r&lt;/code&gt;/&lt;code&gt;ctrl-t&lt;/code&gt;. When the fzf prompt is open, pressing &lt;code&gt;ctrl-j&lt;/code&gt; or &lt;code&gt;ctrl-k&lt;/code&gt; now does nothing. Instead, the Zellij plugin captures the key presses and switches panes or tabs, but the fzf prompt stays unchanged.&lt;/p&gt;

&lt;p&gt;I forked the plugin and made some changes to support these scenarios. Don&amp;#39;t worry &lt;a href="https://github.com/hiasr/vim-zellij-navigator/pull/16"&gt;I submitted a Pull Request&lt;/a&gt; to merge them upstream.&lt;/p&gt;

&lt;p&gt;Until those are merged and released, you&amp;#39;ll need to build the plugin manually.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Install Rust via &lt;a href="https://rustup.rs/"&gt;rustup&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Clone the repository fork:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/tombruijn/vim-zellij-navigator.git
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Compile the plugin.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Open the project directory&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;vim-zellij-navigator
&lt;span class="c"&gt;# Install the latest Rust version (at time of writing)&lt;/span&gt;
rustup override &lt;span class="nb"&gt;set &lt;/span&gt;1.82
&lt;span class="c"&gt;# Install Rust WASM target&lt;/span&gt;
rustup target add wasm32-wasi
&lt;span class="c"&gt;# Compile the project&lt;/span&gt;
cargo build &lt;span class="nt"&gt;--release&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Copy the plugin to the Zellij plugin directory.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create the plugins directory if it does not exist yet&lt;/span&gt;
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ~/.config/zellij/plugins
&lt;span class="c"&gt;# Copy the plugin to the plugins directory&lt;/span&gt;
&lt;span class="nb"&gt;cp&lt;/span&gt; ~/target/wasm32-wasi/release/vim-zellij-navigator.wasm ~/.config/zellij/plugins
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Finally, configure Zellij to load the plugin and configure keybindings to call the plugin.&lt;/p&gt;

&lt;p&gt;My configuration is down below, which is complete for my use case.&lt;/p&gt;

&lt;p&gt;After configuring Zellij, restart it. The first time you press any of the keybindings you&amp;#39;ve configured to call the plugin (indicated with &lt;code&gt;MessagePlugin&lt;/code&gt;), you&amp;#39;ll get a prompt from the plugin asking permission to do a couple things. Press `y&amp;#39; to approve the permissions.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ~/.config/zellij/config.kdl&lt;/span&gt;

&lt;span class="nx"&gt;plugins&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;vim&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;zellij&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nb"&gt;navigator&lt;/span&gt; &lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;file:~/.config/zellij/plugins/vim-zellij-navigator.wasm&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// I use the locked mode as the default mode.&lt;/span&gt;
&lt;span class="c1"&gt;// By default this is set to "normal".&lt;/span&gt;
&lt;span class="nx"&gt;default_mode&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;locked&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="nx"&gt;keybinds&lt;/span&gt; &lt;span class="nx"&gt;clear&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;defaults&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;locked&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c1"&gt;// Change this to "normal" if your default mode is "normal"&lt;/span&gt;
        &lt;span class="c1"&gt;// Switch panes with `Ctrl+hjkl`&lt;/span&gt;
        &lt;span class="nx"&gt;bind&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Ctrl h&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;MessagePlugin&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vim-zellij-navigator&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;move_focus_or_tab&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;left&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nx"&gt;bind&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Ctrl j&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;MessagePlugin&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vim-zellij-navigator&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;move_focus&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;down&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nx"&gt;bind&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Ctrl k&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;MessagePlugin&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vim-zellij-navigator&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;move_focus&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;up&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nx"&gt;bind&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Ctrl l&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;MessagePlugin&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vim-zellij-navigator&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;move_focus_or_tab&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;right&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// Disable the plugin when any of these keybindings are used and still press that key too&lt;/span&gt;
        &lt;span class="nx"&gt;bind&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Ctrl r&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c1"&gt;// `Ctrl+r` is for forward shell history&lt;/span&gt;
            &lt;span class="nx"&gt;WriteChars&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;u{0012}&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Passthrough `Ctrl+r`&lt;/span&gt;
            &lt;span class="nx"&gt;MessagePlugin&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vim-zellij-navigator&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;disable&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nx"&gt;bind&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Ctrl s&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c1"&gt;// `Ctrl+s` is for backward shell history&lt;/span&gt;
            &lt;span class="nx"&gt;WriteChars&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;u{0013}&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Passthrough `Ctrl+s`&lt;/span&gt;
            &lt;span class="nx"&gt;MessagePlugin&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vim-zellij-navigator&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;disable&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nx"&gt;bind&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Ctrl t&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c1"&gt;// `Ctrl+s` is for backward shell history&lt;/span&gt;
            &lt;span class="nx"&gt;WriteChars&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;u{0014}&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Passthrough `Ctrl+t`&lt;/span&gt;
            &lt;span class="nx"&gt;MessagePlugin&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vim-zellij-navigator&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;disable&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="c1"&gt;// Reenable the plugin when any of these keybindings are used and still press that key too&lt;/span&gt;
        &lt;span class="nx"&gt;bind&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ESC&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;WriteChars&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;u{001B}&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Passthrough `ESC`&lt;/span&gt;
            &lt;span class="nx"&gt;MessagePlugin&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vim-zellij-navigator&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;enable&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nx"&gt;bind&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Enter&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;WriteChars&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;u{000D}&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Passthrough `Enter`&lt;/span&gt;
            &lt;span class="nx"&gt;MessagePlugin&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vim-zellij-navigator&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;enable&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nx"&gt;bind&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Ctrl c&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;WriteChars&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;u{0003}&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Passthrough `Ctrl+c`&lt;/span&gt;
            &lt;span class="nx"&gt;MessagePlugin&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vim-zellij-navigator&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;enable&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="c1"&gt;// Toggle the plugin on or off manually&lt;/span&gt;
        &lt;span class="nx"&gt;bind&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Ctrl Alt a&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;MessagePlugin&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vim-zellij-navigator&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;toggle&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="p"&gt;}&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;</content>
  </entry>
  <entry>
    <title>Write your own Domain Specific Language in Ruby</title>
    <link rel="alternate" href="https://tomdebruijn.com/posts/ruby-write-your-own-domain-specific-language/"/>
    <id>https://tomdebruijn.com/posts/ruby-write-your-own-domain-specific-language/</id>
    <published>2023-02-01T00:00:00+00:00</published>
    <updated>2025-10-09T17:47:37+00:00</updated>
    <author>
      <name>Tom de Bruijn</name>
    </author>
    <description>Let's create our own Domain Specific Language. We'll use our metaprogramming toolbox for Ruby to configure a gem and have Ruby write code for us.</description>
    <content type="html">&lt;p&gt;Let&amp;#39;s do some metaprogramming in Ruby! This article assumes you have some familiarity with writing Ruby and are interested in learning how to use metaprogramming.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This article is also available as a &lt;a href="/talks/ruby-write-your-own-domain-specific-language/"&gt;video recording of a talk&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;&lt;span class="anchor" id="what-39-s-a-dsl"&gt;&lt;/span&gt;&lt;a href="#what-39-s-a-dsl"&gt;What&amp;#39;s a DSL?&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Let&amp;#39;s start with some definitions on what we&amp;#39;re going to be looking at. The abbreviation &lt;em&gt;DSL&lt;/em&gt; stands for &lt;em&gt;Domain Specific Language&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;A DSL is a sub-language within a Ruby app (or any other programming language) that is specific to a certain domain: like a Ruby gem or part of an app. Without that gem or app, the DSL won&amp;#39;t work. It&amp;#39;s specific to that domain.&lt;/p&gt;

&lt;p&gt;In Ruby there&amp;#39;s a gem named &lt;a href="https://rubygems.org/gems/rake"&gt;Rake&lt;/a&gt;. This gem provides a DSL to create tasks to be run from the command line. A small example looks like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Rakefile&lt;/span&gt;
&lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="ss"&gt;:hello&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Hello there"&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 call this task from the terminal with the following command. When called, it will perform the block we&amp;#39;ve given to the &lt;code&gt;task&lt;/code&gt; method.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;rake hello
Hello there
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This is the power of a DSL. We don&amp;#39;t need to know how to listen to arguments given to Ruby from the command line. Rake does this for us. We only need to define the commands.&lt;/p&gt;
&lt;h2&gt;&lt;span class="anchor" id="why-use-a-dsl"&gt;&lt;/span&gt;&lt;a href="#why-use-a-dsl"&gt;Why use a DSL?&lt;/a&gt;&lt;/h2&gt;&lt;h3&gt;&lt;span class="anchor" id="configuration"&gt;&lt;/span&gt;&lt;a href="#configuration"&gt;Configuration&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;One of the most common reasons to create your own DSL is for configuration, for an app or part of a gem. In Rails, the &lt;code&gt;Application&lt;/code&gt; class contains configuration for the different gems that make up Rails. It&amp;#39;s even possible to define your own &lt;a href="https://guides.rubyonrails.org/configuring.html#custom-configuration"&gt;custom config options&lt;/a&gt; specific to your app.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/application.rb&lt;/span&gt;
&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;MyApp&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Application&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Application&lt;/span&gt;
    &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load_defaults&lt;/span&gt; &lt;span class="mf"&gt;7.0&lt;/span&gt;

    &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time_zone&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"UTC"&lt;/span&gt;

    &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;custom_option&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"some value"&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;Within the Application class, the &lt;code&gt;config&lt;/code&gt; object can be used to set config options or load defaults, like shown above.&lt;/p&gt;
&lt;h3&gt;&lt;span class="anchor" id="automate-writing-code"&gt;&lt;/span&gt;&lt;a href="#automate-writing-code"&gt;Automate writing code&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;A DSL can be used to abstract away a bunch of repetitive code. In a Rails app there can be many different HTTP endpoints the app listens to. To write a Ruby app that listens to these endpoints can require a lot of code. In Rails, we can declare several different routes with one method: &lt;code&gt;resources&lt;/code&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;draw&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;resources&lt;/span&gt; &lt;span class="ss"&gt;:posts&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;resources&lt;/span&gt; &lt;span class="ss"&gt;:comments&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="s2"&gt;"homepage#show"&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 &lt;code&gt;resources&lt;/code&gt; method will do several things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;create several endpoints like &lt;code&gt;GET /posts&lt;/code&gt;, &lt;code&gt;GET /posts/new&lt;/code&gt;, &lt;code&gt;POST /posts&lt;/code&gt;, &lt;code&gt;DELETE /posts&lt;/code&gt;, etc.;&lt;/li&gt;
&lt;li&gt;nest endpoints, if they are nested within another &lt;code&gt;resources&lt;/code&gt; method call&amp;#39;s block, like in the example above for &lt;code&gt;comments&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;route the request data on the endpoint to the relevant controller.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is all defined with one method call. We don&amp;#39;t need to go through several files and methods to make sure these steps are all done for every route. This makes, what would be high churn code, more maintainable*.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;*: At times, we may be swapping out a lot of code with less code, but that code is more complex because of the metaprogramming involved.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;&lt;span class="anchor" id="writing-your-own-ruby-dsl"&gt;&lt;/span&gt;&lt;a href="#writing-your-own-ruby-dsl"&gt;Writing your own Ruby DSL&lt;/a&gt;&lt;/h2&gt;&lt;h3&gt;&lt;span class="anchor" id="the-start-of-a-dsl"&gt;&lt;/span&gt;&lt;a href="#the-start-of-a-dsl"&gt;The start of a DSL&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The first thing that usually gets a DSL is configuration. A gem ships with a configuration class or a part of an app does. A new &lt;code&gt;Config&lt;/code&gt; class gets created with some config options. (In this case a CLI tool that has options to print more information with &lt;code&gt;verbose&lt;/code&gt; and a particular output format called &lt;code&gt;documentation&lt;/code&gt;.)&lt;/p&gt;
&lt;div class="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;Config&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;
    &lt;span class="vi"&gt;@options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Config&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;:verbose&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;:format&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="ss"&gt;:documentation&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; #&amp;lt;Config:...&lt;/span&gt;
&lt;span class="c1"&gt;#        @options={:verbose=&amp;gt;true, :format=&amp;gt;:documentation}&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The above example is usually enough for a small component.&lt;/p&gt;

&lt;p&gt;Let&amp;#39;s look how we can make this DSL feel more like some of the DSLs we&amp;#39;ve seen. In the example below we don&amp;#39;t configure the Config class with a Hash of options, but use method calls like the &lt;code&gt;verbose=&lt;/code&gt; attribute writer.&lt;/p&gt;

&lt;p&gt;Ruby has helpers for defining reader and writer methods on a class for instance variables. Calling &lt;code&gt;attr_accessor :verbose&lt;/code&gt; in a class definition will define the &lt;code&gt;verbose&lt;/code&gt; or &lt;code&gt;verbose=&lt;/code&gt; methods.&lt;/p&gt;
&lt;div class="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;Config&lt;/span&gt;
  &lt;span class="nb"&gt;attr_accessor&lt;/span&gt; &lt;span class="ss"&gt;:verbose&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:format&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;verbose&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;verbose&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; true&lt;/span&gt;
&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;:documentation&lt;/span&gt;

&lt;span class="n"&gt;pp&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; #&amp;lt;Config:...&lt;/span&gt;
         &lt;span class="vi"&gt;@format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="ss"&gt;:documentation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
         &lt;span class="vi"&gt;@verbose&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;While this is quick to set up, it has some downsides. Like before, we want to store all config options on a &lt;code&gt;@options&lt;/code&gt; Hash instead of on their own instance variables. To export the config options as a Hash, we would need to export them all manually. Now there are more places that need to be updated the more config options are added. Easily forgotten, easily broken.&lt;/p&gt;
&lt;div class="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;Config&lt;/span&gt;
  &lt;span class="c1"&gt;# ...&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;options&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="ss"&gt;:verbose&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="vi"&gt;@verbose&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;:format&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="vi"&gt;@format&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="n"&gt;pp&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;options&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; {:verbose=&amp;gt;true, :format=&amp;gt;:documentation}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;To make the Config class work with a Hash of options, we can define our own methods for each config option. In these methods we set the values on the &lt;code&gt;@options&lt;/code&gt; Hash, using the key of the config option we want to set. Great! We now can export our configuration with ease.&lt;/p&gt;
&lt;div class="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;Config&lt;/span&gt;
  &lt;span class="nb"&gt;attr_reader&lt;/span&gt; &lt;span class="ss"&gt;:options&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;
    &lt;span class="vi"&gt;@options&lt;/span&gt; &lt;span class="o"&gt;=&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;verbose&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:verbose&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;value&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;format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:format&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;verbose&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;:documentation&lt;/span&gt;

&lt;span class="n"&gt;pp&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;options&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; {:verbose=&amp;gt;true, :format=&amp;gt;:documentation}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;With this setup, we run into a familiar problem. Every time a new config option is added we need to add another method for that config option. As more config options are added, the Config class grows and grows, becoming more difficult to maintain.&lt;/p&gt;
&lt;h2&gt;&lt;span class="anchor" id="dynamically-define-methods-in-ruby"&gt;&lt;/span&gt;&lt;a href="#dynamically-define-methods-in-ruby"&gt;Dynamically define methods in Ruby&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Instead of defining a method per config option, we can dynamically define these methods. Dynamically defining methods allow us to quickly define several methods that share almost the same implementation.&lt;/p&gt;

&lt;p&gt;In the example below I&amp;#39;ve created a new class method, as indicated with the &lt;code&gt;def self.def_options&lt;/code&gt; syntax (1). This method, &amp;quot;define options&amp;quot;, receives an array of option names to define methods for (2). In the &lt;code&gt;def_options&lt;/code&gt; method it will loop through these option names (3). In each iteration of the loop it will call the &lt;a href="https://ruby-doc.org/core-3.1.2/Module.html#define_method-method"&gt;&lt;code&gt;define_method&lt;/code&gt; method&lt;/a&gt; (4). With this method we can define a new method, like with the &lt;code&gt;def &amp;lt;method name&amp;gt;&lt;/code&gt; syntax, but with a dynamic method name.&lt;/p&gt;
&lt;div class="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;Config&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;def_options&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;option_names&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# 1&lt;/span&gt;
    &lt;span class="n"&gt;option_names&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&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;option_name&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="c1"&gt;# 3&lt;/span&gt;
      &lt;span class="n"&gt;define_method&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;option_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;="&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;value&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="c1"&gt;# 4&lt;/span&gt;
        &lt;span class="vi"&gt;@options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;option_name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="c1"&gt;# 5&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;span class="n"&gt;def_options&lt;/span&gt; &lt;span class="ss"&gt;:verbose&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:format&lt;/span&gt; &lt;span class="c1"&gt;# 2&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;
    &lt;span class="vi"&gt;@options&lt;/span&gt; &lt;span class="o"&gt;=&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;options&lt;/span&gt;
    &lt;span class="vi"&gt;@options&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;verbose&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;:documentation&lt;/span&gt;

&lt;span class="n"&gt;pp&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;options&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; {:verbose=&amp;gt;true, :format=&amp;gt;:documentation}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The &lt;code&gt;define_method&lt;/code&gt; method receives the name of the method, and a block (4). This block will be the implementation of the method we are defining. In this case, it will call the &lt;code&gt;@options&lt;/code&gt; instance variable to receive the Hash of options configured, and add a new value for the option name we&amp;#39;ve declared (5). The DSL syntax for the end-user remains the same.&lt;/p&gt;
&lt;h2&gt;&lt;span class="anchor" id="using-ruby-blocks"&gt;&lt;/span&gt;&lt;a href="#using-ruby-blocks"&gt;Using Ruby blocks&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In my opinion, no Ruby DSL is complete without blocks*. They look good for one, but they also provide a new scope of context to users of our DSL, in which our new DSL rules apply.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;*: It&amp;#39;s perfectly fine to have a DSL without blocks.&lt;/em&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&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;config&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;verbose&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;:documentation&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In the next example, I&amp;#39;ve created a new class method called &lt;code&gt;configure&lt;/code&gt;. To interact with blocks given to a method, we can use the &lt;code&gt;yield&lt;/code&gt; keyword. If a method has been given a block, &lt;code&gt;yield&lt;/code&gt; will call that block. If we give a value to &lt;code&gt;yield&lt;/code&gt;, it will make it so that the block receives that value as an argument in the block, as is shown with &lt;code&gt;|config|&lt;/code&gt; in the example above.&lt;/p&gt;
&lt;div class="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;Config&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt;
    &lt;span class="n"&gt;instance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;new&lt;/span&gt; &lt;span class="c1"&gt;# Create a new instance of the Config class&lt;/span&gt;
    &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;instance&lt;/span&gt; &lt;span class="c1"&gt;# Call the block and give the Config instance as an argument&lt;/span&gt;
    &lt;span class="n"&gt;instance&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3&gt;&lt;span class="anchor" id="using-instance_eval"&gt;&lt;/span&gt;&lt;a href="#using-instance_eval"&gt;Using instance_eval&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;We can make this shorter, and in my opinion, feel more like a DSL with its own block context. We can drop the &lt;code&gt;|config|&lt;/code&gt; block argument and avoid having to call &lt;code&gt;config.&lt;/code&gt; in front of every config option.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;verbose&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
  &lt;span class="nb"&gt;format&lt;/span&gt; &lt;span class="ss"&gt;:documentation&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;I&amp;#39;ve omitted the equals sign (&lt;code&gt;=&lt;/code&gt;) from the example here. More on that later.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;To make this work we go back to our &lt;code&gt;configure&lt;/code&gt; class method. Instead of &lt;code&gt;yield&lt;/code&gt;, we&amp;#39;ll use &lt;a href="https://ruby-doc.org/core-3.1.2/BasicObject.html#instance_eval-method"&gt;&lt;code&gt;instance_eval&lt;/code&gt;&lt;/a&gt;. (Eval may sound scary, and it has some things to look out for, but in this small example it&amp;#39;s quite safe.)&lt;/p&gt;

&lt;p&gt;To call &lt;code&gt;instance_eval&lt;/code&gt; we need to pass in the block given to the method. We can&amp;#39;t use &lt;code&gt;yield&lt;/code&gt; for this. We need to explicitly specify the block argument of the &lt;code&gt;configure&lt;/code&gt; method with &lt;code&gt;&amp;amp;block&lt;/code&gt;. The key part being the ampersand (&lt;code&gt;&amp;amp;&lt;/code&gt;) of the argument name &lt;code&gt;&amp;amp;block&lt;/code&gt;, telling Ruby it&amp;#39;s the block argument.&lt;/p&gt;
&lt;div class="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;Config&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;instance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;new&lt;/span&gt;
    &lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;instance_eval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;instance&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&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;(In Ruby 3.1 and newer the &lt;code&gt;&amp;amp;block&lt;/code&gt; argument can also be written as only the ampersand &lt;code&gt;&amp;amp;&lt;/code&gt;, the name is optional.)&lt;/p&gt;

&lt;p&gt;When called on another object, like our Config class instance, &lt;code&gt;instance_eval&lt;/code&gt; will execute that block within the context of the Config instance. The context of that block is changed from where it was defined to the Config instance. When a method is called within that block, it will call it directly on the Config instance.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Local variable assignment gotcha&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Remember how in the example above I omitted the equals sign from the method names? When calling a method name ending with an equals sign in &lt;code&gt;instance_eval&lt;/code&gt;, Ruby will interpret it as a local variable assignment instead. It will not call the method on the Config instance. That&amp;#39;s how Ruby works, and we can&amp;#39;t change that. I omitted the equals sign to make the DSL still work.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="c1"&gt;# This won't work&lt;/span&gt;
  &lt;span class="n"&gt;verbose&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
  &lt;span class="nb"&gt;format&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;:documentation&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;It&amp;#39;s possible to work around this by explicitly calling the methods on &lt;code&gt;self.&lt;/code&gt;, but this syntax becomes basically the same as using &lt;code&gt;yield&lt;/code&gt;. Instead, I removed the equals sign from the method name.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="c1"&gt;# This works, but requires `self.` in front of every method call&lt;/span&gt;
  &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;verbose&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
  &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;:documentation&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/blockquote&gt;
&lt;h3&gt;&lt;span class="anchor" id="when-to-use-yield-or-instance_eval"&gt;&lt;/span&gt;&lt;a href="#when-to-use-yield-or-instance_eval"&gt;When to use yield or instance_eval?&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I&amp;#39;ve shown two ways of calling blocks in Ruby. Which one should be used is up to the authors of the DSL. I have my personal preference for using &lt;code&gt;instance_eval&lt;/code&gt;, but there are definitely scenarios where using &lt;code&gt;yield&lt;/code&gt; works better.&lt;/p&gt;

&lt;p&gt;When something needs to be configured, using a lot of methods with an equals sign (writer methods), using &lt;code&gt;yield&lt;/code&gt; with a block argument is quite common.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&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;config&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;verbose&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt; &lt;span class="c1"&gt;# Set a value&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;When defining something or calling actions, I see the &lt;code&gt;instance_eval&lt;/code&gt; approach used a lot.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;load_defaults!&lt;/span&gt; &lt;span class="c1"&gt;# Perform an action&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That doesn&amp;#39;t mean one excludes the other. Gems like &lt;a href="https://github.com/puma/puma"&gt;Puma&lt;/a&gt; use the &lt;code&gt;instance_eval&lt;/code&gt; method for their configuration file.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/puma.rb&lt;/span&gt;
&lt;span class="n"&gt;workers&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
&lt;span class="n"&gt;preload_app!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Use the approach you feel works best for your DSL.&lt;/p&gt;
&lt;h2&gt;&lt;span class="anchor" id="share-dsl-behavior-with-modules"&gt;&lt;/span&gt;&lt;a href="#share-dsl-behavior-with-modules"&gt;Share DSL behavior with modules&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;With the above methods you can start building your DSL. This gives us a good toolbox. As the DSL grows, or more DSLs get added to the domain, it&amp;#39;s good to share behavior between the DSL classes.&lt;/p&gt;

&lt;p&gt;For example, this domain has two separate configuration classes: a Config and an Output class. One configures how the gem works, the other how it outputs results to the terminal.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Multiple configuration classes&lt;/span&gt;
&lt;span class="no"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;enabled&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="no"&gt;Output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;verbose&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
  &lt;span class="nb"&gt;format&lt;/span&gt; &lt;span class="ss"&gt;:documentation&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We don&amp;#39;t want to repeat ourselves, so we move the DSL definition behavior to a module called Configurable. When that module is included the class method &lt;code&gt;def_options&lt;/code&gt; is made available with the class definition.&lt;/p&gt;
&lt;div class="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;Config&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Configurable&lt;/span&gt;

  &lt;span class="n"&gt;def_options&lt;/span&gt; &lt;span class="ss"&gt;:enabled&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Output&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Configurable&lt;/span&gt;

  &lt;span class="n"&gt;def_options&lt;/span&gt; &lt;span class="ss"&gt;:verbose&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:format&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;When the Configurable module is included, it adds the &lt;code&gt;options&lt;/code&gt; method to the class. The module also extends the class it&amp;#39;s included in, using the &lt;a href="https://ruby-doc.org/core-3.1.2/Module.html#included-method"&gt;&lt;code&gt;included&lt;/code&gt; callback in Ruby modules&lt;/a&gt;. In that callback, the class is extended with the ClassMethods module, defined inside the Configurable module. This module will add the class methods (&lt;code&gt;def_options&lt;/code&gt; and &lt;code&gt;configure&lt;/code&gt;) to the class. This is also how things like &lt;a href="https://api.rubyonrails.org/classes/ActiveSupport/Concern.html"&gt;ActiveSupport::Concern&lt;/a&gt; work.&lt;/p&gt;
&lt;div class="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;Configurable&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;included&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extend&lt;/span&gt; &lt;span class="no"&gt;ClassMethods&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;ClassMethods&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;def_options&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;option_names&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="c1"&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;configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="c1"&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;def&lt;/span&gt; &lt;span class="nf"&gt;options&lt;/span&gt;
    &lt;span class="vi"&gt;@options&lt;/span&gt; &lt;span class="o"&gt;||=&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;With all the logic moved to the Configurable module, we now have a reusable module to create as many config classes as the domain needs.&lt;/p&gt;
&lt;h2&gt;&lt;span class="anchor" id="nesting-dsls"&gt;&lt;/span&gt;&lt;a href="#nesting-dsls"&gt;Nesting DSLs&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The Output class is really a sub domain of the Config class though. It&amp;#39;s part of the gem config: the configurations should be nested within one another.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;enabled&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;

  &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;verbose&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
    &lt;span class="nb"&gt;format&lt;/span&gt; &lt;span class="ss"&gt;:documentation&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;Rather than Output being a top-level config class, we store it on the Config class. In the &lt;code&gt;output&lt;/code&gt; method in the example below, the method creates a new Output instance. When a block is given, it will &lt;code&gt;instance_eval&lt;/code&gt; the block on the &lt;code&gt;@output&lt;/code&gt; object. The method will always return the created &lt;code&gt;@output&lt;/code&gt; object so the sub-config can be accessed.&lt;/p&gt;
&lt;div class="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;Config&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Configurable&lt;/span&gt;

  &lt;span class="n"&gt;def_options&lt;/span&gt; &lt;span class="ss"&gt;:enabled&lt;/span&gt;

  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Output&lt;/span&gt;
    &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Configurable&lt;/span&gt;

    &lt;span class="n"&gt;def_options&lt;/span&gt; &lt;span class="ss"&gt;:verbose&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:format&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;output&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@output&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="no"&gt;Output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
    &lt;span class="vi"&gt;@output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;instance_eval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;block_given?&lt;/span&gt;
    &lt;span class="vi"&gt;@output&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;h2&gt;&lt;span class="anchor" id="module-builder-pattern"&gt;&lt;/span&gt;&lt;a href="#module-builder-pattern"&gt;Module builder pattern&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The Module builder pattern is a really neat design pattern in Ruby that allows us to do away with the two step process of including a module and then calling methods included by it. This pattern is described in more detail in &lt;a href="https://dejimata.com/2017/5/20/the-ruby-module-builder-pattern"&gt;The Ruby Module Builder Pattern by Chris Salzberg&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In the example below, a new &lt;code&gt;ConfigOption&lt;/code&gt; module is used to include a dynamically defined module. For the end-user, the resulting DSL remains the same.&lt;/p&gt;
&lt;div class="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;Config&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;ConfigOption&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;:verbose&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:format&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;When &lt;code&gt;ConfigOption.new&lt;/code&gt; is called, the desired config option names are given to the &lt;code&gt;initialize&lt;/code&gt; method. Like before, we iterate over this list. Using &lt;code&gt;define_method&lt;/code&gt; the necessary methods are defined on the module. It&amp;#39;s possible to do so in this &lt;code&gt;initialize&lt;/code&gt; method, because it&amp;#39;s creating a new implementation of the ConfigOption module.&lt;/p&gt;
&lt;div class="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;ConfigOption&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Module&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;option_names&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;define_method&lt;/span&gt; &lt;span class="ss"&gt;:options&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="vi"&gt;@options&lt;/span&gt; &lt;span class="o"&gt;||=&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;option_names&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&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;option_name&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;define_method&lt;/span&gt; &lt;span class="n"&gt;option_name&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;value&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
        &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;option_name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;value&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;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;For a much more in-depth look, I can highly recommend reading &lt;a href="https://dejimata.com/2017/5/20/the-ruby-module-builder-pattern"&gt;The Ruby Module Builder Pattern by Chris Salzberg&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;&lt;span class="anchor" id="create-dsl-objects"&gt;&lt;/span&gt;&lt;a href="#create-dsl-objects"&gt;Create DSL objects&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Another step I recommend is to separate the behavior of the DSL itself and the actual app code that uses it, by creating DSL classes. These classes are only used when the end-user interacts with the DSL, like configuring a gem. When the configuration is done, the options are read from the DSL class and set on whatever config object the gem uses.&lt;/p&gt;

&lt;p&gt;Like before, we have a Config class. When configured using &lt;code&gt;Config.configure&lt;/code&gt;, it creates a ConfigDSL class (1). This ConfigDSL class will become the context of the block given to the &lt;code&gt;configure&lt;/code&gt; class method.&lt;/p&gt;
&lt;div class="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;Config&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="n"&gt;dsl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;ConfigDSL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dsl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# 1&lt;/span&gt;
   &lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dsl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# 2&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="nb"&gt;attr_reader&lt;/span&gt; &lt;span class="ss"&gt;:options&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;options&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;class&lt;/span&gt; &lt;span class="nc"&gt;ConfigDSL&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;ConfigOption&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;:verbose&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:format&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dsl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;instance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;new&lt;/span&gt;
    &lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;instance_eval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;instance&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;After the &lt;code&gt;configure&lt;/code&gt; block has been called, the Config class reads the options from the ConfigDSL and initializes a new instance of itself with these options (2).&lt;/p&gt;

&lt;p&gt;With this approach the DSL that configures the gem is only used when the app starts. The app doesn&amp;#39;t carry around all that extra weight of how the DSL works all the time. The separation of this behavior makes it easier to not accidentally call the configure DSL when it should not be available.&lt;/p&gt;

&lt;p&gt;This approach also solves a downside of using &lt;code&gt;instance_eval&lt;/code&gt;. When calling blocks this way, private methods are accessible inside the block. That&amp;#39;s something we usually don&amp;#39;t want to allow.&lt;/p&gt;
&lt;div class="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;Config&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;instance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;new&lt;/span&gt;
    &lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;instance_eval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;instance&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

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

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;secret_method&lt;/span&gt;
    &lt;span class="s2"&gt;"super secret method"&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;Config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="c1"&gt;# This should raise an error about calling&lt;/span&gt;
  &lt;span class="c1"&gt;# a private method, but it doesn't&lt;/span&gt;
  &lt;span class="n"&gt;secret_method&lt;/span&gt;
  &lt;span class="c1"&gt;# =&amp;gt; "super secret method"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Private methods can be accessed in blocks called using &lt;code&gt;instance_eval&lt;/code&gt;, because the block is evaluated in the context of the instance. It&amp;#39;s as if it&amp;#39;s being run from a method within that object. With separate DSL classes you have to worry less about private methods that you don&amp;#39;t want anyone to call.&lt;/p&gt;
&lt;h2&gt;&lt;span class="anchor" id="conclusion"&gt;&lt;/span&gt;&lt;a href="#conclusion"&gt;Conclusion&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Now that our toolbox is complete we can create our own Domain Specific Language using Ruby. We have the following tools at our disposal:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;define_method&lt;/code&gt; to dynamically define new methods on classes.&lt;/li&gt;
&lt;li&gt;Use Ruby blocks to give your DSL that real DSL feeling and create a context wherein your DSL shines.

&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;yield&lt;/code&gt; to return a DSL object as a block argument, or;&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;instance_eval&lt;/code&gt; to change how to block works, and allow end-users to directly call methods on the new context of new block.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Use Modules to:

&lt;ul&gt;
&lt;li&gt;share behavior between many classes, and;&lt;/li&gt;
&lt;li&gt;use the &lt;a href="https://dejimata.com/2017/5/20/the-ruby-module-builder-pattern"&gt;Module builder pattern&lt;/a&gt; to remove any logic of how the DSL is constructed from the class that uses the DSL.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;DSL objects separate the logic of how the DSL works and how the rest of the gem works. Creating a better separation of responsibilities in your code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now go forth, and build your own DSL!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Calculating String length and width – Fun with Unicode</title>
    <link rel="alternate" href="https://tomdebruijn.com/posts/rust-string-length-width-calculations/"/>
    <id>https://tomdebruijn.com/posts/rust-string-length-width-calculations/</id>
    <published>2022-06-14T00:00:00+00:00</published>
    <updated>2025-10-09T17:47:37+00:00</updated>
    <author>
      <name>Tom de Bruijn</name>
    </author>
    <description>Finding the string length sounds easier than it is, if we want to be really accurate. Let's explore how does Unicode and emoji make this step more complex.</description>
    <content type="html">&lt;p&gt;Let&amp;#39;s calculate string length in Rust¹! How many characters there really are in a string and how much space these strings take up when displayed.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;¹: This article may also apply to other languages. This article will only focus on strings with the Rust default UTF-8 encoding. There&amp;#39;s an appendix for how it works in Ruby at the end of the article. I&amp;#39;m simplifying this content to keep the article short.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;&lt;span class="anchor" id="string-len"&gt;&lt;/span&gt;&lt;a href="#string-len"&gt;String.len()&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The first function you&amp;#39;ll probably come across is &lt;a href="http://doc.rust-lang.org/1.61.0/std/string/struct.String.html#method.len"&gt;&lt;code&gt;String.len()&lt;/code&gt;&lt;/a&gt;/&lt;a href="http://doc.rust-lang.org/1.61.0/core/primitive.str.html#method.len"&gt;&lt;code&gt;str/len()&lt;/code&gt;&lt;/a&gt;, or length of string. Given the string &lt;code&gt;&amp;quot;abc&amp;quot;&lt;/code&gt; it will returns the length of three. All looks good so far.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="s"&gt;"abc"&lt;/span&gt;&lt;span class="nf"&gt;.len&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c"&gt;// =&amp;gt; 3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That is, until we take a closer look at the docs for this function. It says the following:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Returns the length of this string, in bytes, not &lt;code&gt;char&lt;/code&gt;s or graphemes. In other words, it might not be what a human considers the length of the string.&lt;/p&gt;

&lt;p&gt;Source: &lt;a href="http://doc.rust-lang.org/1.61.0/std/string/struct.String.html#method.len"&gt;&lt;code&gt;String.len()&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The Rust docs are giving us a warning here that it may not always return the number we&amp;#39;d expect. It will return the string length in bytes, and it sounds like not all characters are counted as one byte.&lt;/p&gt;

&lt;p&gt;Let&amp;#39;s try something that&amp;#39;s not just plain &amp;quot;a&amp;quot; through &amp;quot;z&amp;quot;, but something like a character with an accent.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="s"&gt;"é"&lt;/span&gt;&lt;span class="nf"&gt;.len&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c"&gt;// =&amp;gt; 2 bytes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We can see here that the result is a larger number than what we consider the string length to be. A lot of characters are comprised of multiple bytes. There are only so many characters we can make from an eight number byte, this is &lt;a href="https://en.wikipedia.org/wiki/ASCII"&gt;what ASCII is&lt;/a&gt;. To support all characters of all languages in the world in 256 possible different bytes wouldn&amp;#39;t fit. Let&amp;#39;s try another approach.&lt;/p&gt;
&lt;h2&gt;&lt;span class="anchor" id="chars-count"&gt;&lt;/span&gt;&lt;a href="#chars-count"&gt;Chars.count()&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Rust has a built-in &amp;quot;Chars&amp;quot; module we can use to split up the string into a list of characters, this gives a more accurate result.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="s"&gt;"abc"&lt;/span&gt;&lt;span class="nf"&gt;.chars&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c"&gt;// =&amp;gt; 3 characters&lt;/span&gt;
&lt;span class="s"&gt;"é"&lt;/span&gt;&lt;span class="nf"&gt;.chars&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;   &lt;span class="c"&gt;// =&amp;gt; 1 characters&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;When we ask the &lt;a href="http://doc.rust-lang.org/1.60.0/core/primitive.str.html#method.chars"&gt;&lt;code&gt;str.chars()&lt;/code&gt;&lt;/a&gt; method to give us a breakdown of what it considers characters we get a pretty good result. The character with the accent is seen as one character.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="s"&gt;"é"&lt;/span&gt;&lt;span class="nf"&gt;.chars&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c"&gt;// =&amp;gt; Chars(['é'])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We&amp;#39;ve learned that characters can be composed of multiple bytes. Relying on the string byte length for the actual length is not accurate enough.&lt;/p&gt;

&lt;p&gt;Will Chars always return an accurate string length? This depends on your use-case. If you&amp;#39;re looking for the number of characters in a string, probably yes, but also no. If you&amp;#39;re looking for the actual string display width as rendered, the size it takes up on screen, then very much no.&lt;/p&gt;

&lt;p&gt;If we look at the documentation again it will give us another warning:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;It&amp;#39;s important to remember that &lt;code&gt;char&lt;/code&gt; represents a Unicode Scalar Value, and might not match your idea of what a &amp;#39;character&amp;#39; is. Iteration over grapheme clusters may be what you actually want.&lt;/p&gt;

&lt;p&gt;Source: &lt;a href="http://doc.rust-lang.org/1.60.0/core/primitive.str.html#method.chars"&gt;&lt;code&gt;str.chars()&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;&lt;span class="anchor" id="graphemes"&gt;&lt;/span&gt;&lt;a href="#graphemes"&gt;Graphemes&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;What if the string being checked doesn&amp;#39;t only contain numbers and letters, accents or not? Emojis are very popular nowadays and present in every kind of string. Emojis can have a byte size of much more than two bytes, but also consist of multiple &lt;em&gt;characters&lt;/em&gt; even though it looks like one object.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c"&gt;// Person emoji&lt;/span&gt;
&lt;span class="s"&gt;"🧑"&lt;/span&gt;&lt;span class="nf"&gt;.len&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;           &lt;span class="c"&gt;// =&amp;gt; 4 bytes&lt;/span&gt;
&lt;span class="s"&gt;"🧑"&lt;/span&gt;&lt;span class="nf"&gt;.chars&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c"&gt;// =&amp;gt; 1 characters&lt;/span&gt;

&lt;span class="c"&gt;// Woman scientist emoji&lt;/span&gt;
&lt;span class="s"&gt;"👩‍🔬"&lt;/span&gt;&lt;span class="nf"&gt;.len&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;           &lt;span class="c"&gt;// =&amp;gt; 11 bytes&lt;/span&gt;
&lt;span class="s"&gt;"👩‍🔬"&lt;/span&gt;&lt;span class="nf"&gt;.chars&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c"&gt;// =&amp;gt; 3 characters&lt;/span&gt;

&lt;span class="c"&gt;// Family: Man, Man, Girl, Boy emoji&lt;/span&gt;
&lt;span class="s"&gt;"👨‍👨‍👧‍👦"&lt;/span&gt;&lt;span class="nf"&gt;.len&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;           &lt;span class="c"&gt;// =&amp;gt; 25 bytes&lt;/span&gt;
&lt;span class="s"&gt;"👨‍👨‍👧‍👦"&lt;/span&gt;&lt;span class="nf"&gt;.chars&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c"&gt;// =&amp;gt; 7 characters&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The &lt;a href="https://emojipedia.org/woman-scientist"&gt;&amp;quot;woman scientist&amp;quot; emoji&lt;/a&gt; shown above takes up eleven bytes and three characters. The family takes up 27 bytes and seven characters, even though we only see one item in the string.&lt;/p&gt;

&lt;p&gt;If we print the list of characters we&amp;#39;ll get a better idea of what is happening.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="s"&gt;"👩‍🔬"&lt;/span&gt;&lt;span class="nf"&gt;.chars&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c"&gt;// =&amp;gt; Chars(['👩', '\u{200d}', '🔬'])&lt;/span&gt;

&lt;span class="s"&gt;"👨‍👨‍👧‍👦"&lt;/span&gt;&lt;span class="nf"&gt;.chars&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c"&gt;// =&amp;gt; Chars([&lt;/span&gt;
&lt;span class="c"&gt;//   '👨',&lt;/span&gt;
&lt;span class="c"&gt;//   '\u{200d}',&lt;/span&gt;
&lt;span class="c"&gt;//   '👨',&lt;/span&gt;
&lt;span class="c"&gt;//   '\u{200d}',&lt;/span&gt;
&lt;span class="c"&gt;//   '👧',&lt;/span&gt;
&lt;span class="c"&gt;//   '\u{200d}',&lt;/span&gt;
&lt;span class="c"&gt;//   '👦'&lt;/span&gt;
&lt;span class="c"&gt;// ])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Emoji can consists of multiple characters, that together construct another emoji and tell computers what to render. In the example above, we first see the &lt;a href="https://emojipedia.org/woman"&gt;&amp;quot;woman&amp;quot; emoji&lt;/a&gt;, then what is called a &lt;a href="https://en.wikipedia.org/wiki/Zero-width_joiner"&gt;&amp;quot;Zero Width Joiner&amp;quot; character&lt;/a&gt;, and finally the &lt;a href="https://emojipedia.org/microscope"&gt;microscope emoji&lt;/a&gt;. For the family we see the whole list of different genders and ages joined together in the same way.&lt;/p&gt;

&lt;p&gt;The Zero Width Joiner character used by these combined emoji is, as the name describes, a Unicode character with zero width (which makes it invisible). This character is used to &lt;a href="https://unicode.org/emoji/charts/emoji-zwj-sequences.html"&gt;join together multiple emoji to make a new one&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;These additional emoji characters do mess up our string length calculation. Luckily we can use the &lt;a href="https://crates.io/crates/unicode-segmentation"&gt;unicode-segmentation Rust crate&lt;/a&gt; to do more accurate character counting. We can ask the crate to split the string based on something called Graphemes and return the list of characters. A grapheme cluster in this context is a group of Unicode codepoints that consist of &lt;a href="https://www.unicode.org/reports/tr29/"&gt;&amp;quot;user-perceived character&amp;quot;&lt;/a&gt;, what we consider a character when we type it.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;unicode_segmentation&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;UnicodeSegmentation&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="s"&gt;"abc"&lt;/span&gt;&lt;span class="nf"&gt;.graphemes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="py"&gt;.count&lt;/span&gt; &lt;span class="c"&gt;// =&amp;gt; 3&lt;/span&gt;
&lt;span class="s"&gt;"é"&lt;/span&gt;&lt;span class="nf"&gt;.graphemes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="py"&gt;.count&lt;/span&gt;   &lt;span class="c"&gt;// =&amp;gt; 1&lt;/span&gt;
&lt;span class="s"&gt;"🧑"&lt;/span&gt;&lt;span class="nf"&gt;.graphemes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c"&gt;// =&amp;gt; 1&lt;/span&gt;
&lt;span class="s"&gt;"👩‍🔬"&lt;/span&gt;&lt;span class="nf"&gt;.graphemes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c"&gt;// =&amp;gt; 1&lt;/span&gt;
&lt;span class="s"&gt;"👨‍👨‍👧‍👦"&lt;/span&gt;&lt;span class="nf"&gt;.graphemes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c"&gt;// =&amp;gt; 1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;(The &lt;code&gt;true&lt;/code&gt; argument given to &lt;a href="https://unicode-rs.github.io/unicode-segmentation/unicode_segmentation/trait.UnicodeSegmentation.html#tymethod.graphemes"&gt;&lt;code&gt;graphemes&lt;/code&gt; function&lt;/a&gt; means we want to count it using the Unicode extended grapheme clusters and not the legacy clusters. Let&amp;#39;s use the non-legacy cluster.)&lt;/p&gt;

&lt;p&gt;Finally! We have an accurate string length. Right?&lt;/p&gt;

&lt;p&gt;Yes. For the scenarios in which you want to know the number of characters in a string, I&amp;#39;d say yes.&lt;/p&gt;

&lt;p&gt;But...&lt;/p&gt;
&lt;h2&gt;&lt;span class="anchor" id="graphemes-widths"&gt;&lt;/span&gt;&lt;a href="#graphemes-widths"&gt;Graphemes widths&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;As you can see in the code example below, emoji aren&amp;#39;t shown one character column wide. Most emoji are rendered with a display width of two columns, two other Latin characters, like A through Z. For illustration purposes I&amp;#39;ll be using a monospaced font so that the latin characters have a predicable width.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c"&gt;// These two lines should render with the same width&lt;/span&gt;
&lt;span class="s"&gt;"ab"&lt;/span&gt;
&lt;span class="s"&gt;"🧑"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;While the graphemes-count-method (shown in the previous section) will give you a correct string length in terms of how many characters you see, it&amp;#39;s not always same as the horizontal space in columns a string takes up.&lt;/p&gt;

&lt;p&gt;In my &lt;a href="https://github.com/lintje/lintje/"&gt;Lintje project&lt;/a&gt; (a Git linter) I ran into this string length vs display width problem for the program&amp;#39;s rules and output formatting. The terminal output didn&amp;#39;t align properly when a string in a Git commit message included a emoji. In the example output below, the &lt;code&gt;^&lt;/code&gt; marker should be aligned with the last character of the string, but with an emoji in the string it wouldn&amp;#39;t be properly aligned.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Badly aligned output&lt;/span&gt;
String with a ❌ emoji
                    ^ End of sentence
&lt;span class="c"&gt;# Well aligned output&lt;/span&gt;
String with a ✅ emoji
                     ^ End of sentence
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;For rules that checked string length, I decided to count the display width of how every character is rendered as the string length. If a string has a max length of 50 characters, because of readability and width constraints, it should not be allowed to jam that string full with 50 emoji. It would take up double the horizontal space of a string with the same length without emoji.&lt;/p&gt;

&lt;p&gt;Using the &lt;a href="https://crates.io/crates/unicode-width"&gt;unicode-width Rust crate&lt;/a&gt; we can calculate a more accurate string display width.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nn"&gt;UnicodeWidthStr&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;width&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"🧑"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// =&amp;gt; 2&lt;/span&gt;
&lt;span class="nn"&gt;UnicodeWidthStr&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;width&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"👩‍🔬"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// =&amp;gt; 4&lt;/span&gt;
&lt;span class="c"&gt;// Consists of 👩, a Zero Width Joiner and 🔬&lt;/span&gt;
&lt;span class="nn"&gt;UnicodeWidthStr&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;width&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"👨‍👨‍👧‍👦"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// =&amp;gt; 8&lt;/span&gt;
&lt;span class="c"&gt;// Consists of multiple face emoji and Zero Width Joiner characters&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That is, the display width that every character is defined as being part of each grapheme cluster in Unicode. As you can see, the emoji with a Zero Width Joiner characters are as wide as the number of emoji they join times two.&lt;/p&gt;

&lt;p&gt;There are also &amp;quot;emoji&amp;quot; that only return a display width of one, not two, columns. These one column display width characters are considered to have a &amp;quot;narrow&amp;quot; display width. The two column wide characters are &amp;quot;wide&amp;quot;. This difference is something you can see if your terminal and other applications, as it sometimes overlaps with the next character. This will not be visible in the browser you&amp;#39;re reading this in.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="s"&gt;"❤️"&lt;/span&gt;&lt;span class="nf"&gt;.len&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;           &lt;span class="c"&gt;// =&amp;gt; 6 bytes&lt;/span&gt;
&lt;span class="s"&gt;"❤️"&lt;/span&gt;&lt;span class="nf"&gt;.chars&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c"&gt;// =&amp;gt; 2 characters&lt;/span&gt;
&lt;span class="s"&gt;"❤️"&lt;/span&gt;&lt;span class="nf"&gt;.chars&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;         &lt;span class="c"&gt;// =&amp;gt; Chars(['❤', '\u{fe0f}'])&lt;/span&gt;
&lt;span class="nn"&gt;UnicodeWidthStr&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;width&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"❤️"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// =&amp;gt; 1 display width&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;The heart emoji ❤️ is a &lt;a href="https://en.wikipedia.org/wiki/Dingbat"&gt;&amp;quot;Heavy black heart&amp;quot; character&lt;/a&gt; &lt;span style="font-family: sans-serif;"&gt;&amp;#x2764;&lt;/span&gt; from the dingbat collection combined with what&amp;#39;s called a &lt;a href="https://en.wikipedia.org/wiki/Variation_Selectors_(Unicode_block)"&gt;&amp;quot;Variation Selector-16&amp;quot; character&lt;/a&gt;. This is another invisible character in graphemes clusters to indicate certain characters should be displayed as their emoji counterparts.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;&lt;span class="anchor" id="and-the-actual-width-is"&gt;&lt;/span&gt;&lt;a href="#and-the-actual-width-is"&gt;And the actual width is?&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Why is this happening? From what I understand the unicode-width library is following the Unicode reference to the letter, counting only the base character&amp;#39;s display width and not the emoji variation. That results in output that I would not have expected. It&amp;#39;s not something that can be fixed, unless you change something in Unicode first.&lt;/p&gt;

&lt;p&gt;Unfortunately I don&amp;#39;t have another function or library to call on to fix this and return what we see as the actual display width.&lt;/p&gt;

&lt;p&gt;What I did in my Lintje project was first split up the string into graphemes clusters, then scan every sub string for Zero Width Joiner characters, and then only return &amp;quot;two&amp;quot; as the width. This is somewhat more accurate to what we see displayed, but it&amp;#39;s not 100% accurate. It&amp;#39;s good enough for my purpose right now. There probably are better ways out there to do this, but I fear that it will mean maintaining a list of the display width of all emojis. At least the exceptions with a display width of one. Let me know if you know of a better solution!&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c"&gt;// Very simplified example&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"👨‍👨‍👧‍👦"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;unicode_chars&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="nf"&gt;.graphemes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;character&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt; &lt;span class="n"&gt;unicode_chars&lt;/span&gt;&lt;span class="nf"&gt;.into_iter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;character&lt;/span&gt;&lt;span class="nf"&gt;.contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;u{200d}"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nn"&gt;UnicodeWidthStr&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;width&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;character&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="c"&gt;// 2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The full implementation with additional checks for other modifier characters can be found in the &lt;a href="https://github.com/lintje/lintje/blob/501aab06e19008e787237438a69ac961f38bb4b7/src/utils.rs#L22-L71"&gt;Lintje GitHub project&amp;#39;s utils module&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;&lt;span class="anchor" id="in-conclusion"&gt;&lt;/span&gt;&lt;a href="#in-conclusion"&gt;In conclusion&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We&amp;#39;ve learned that what we humans consider string length is not the byte length. Characters can be multiple characters joined together, like emoji. Not all characters have the same display width. Some characters take up more horizontal space than others and some don&amp;#39;t even take up any horizontal space.&lt;/p&gt;

&lt;p&gt;So what should you use to calculate string length or display width?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Do you want the length of a string in bytes? Use &lt;a href="http://doc.rust-lang.org/1.61.0/std/string/struct.String.html#method.len"&gt;&lt;code&gt;String.len()&lt;/code&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Do you want the number of characters in a string? The &lt;a href="http://doc.rust-lang.org/1.60.0/std/str/struct.Chars.html#method.count"&gt;&lt;code&gt;Chars.count()&lt;/code&gt;&lt;/a&gt; method is an option, but I suggest the next solution.&lt;/li&gt;
&lt;li&gt;Do you want the visible number of characters in a string? Use &lt;a href="https://crates.io/crates/unicode-segmentation"&gt;&lt;code&gt;str.graphemes(true).count()&lt;/code&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Do you want the somewhat accurate width of the string? Use &lt;a href="https://crates.io/crates/unicode-width"&gt;&lt;code&gt;UnicodeWidthStr.width(str)&lt;/code&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;If you&amp;#39;re like me and want the string display width calculated more accurately? You&amp;#39;ll have to write something yourself I&amp;#39;m afraid.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;&lt;span class="anchor" id="appendix-ruby"&gt;&lt;/span&gt;&lt;a href="#appendix-ruby"&gt;Appendix: Ruby&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I write a lot of Ruby code, so here&amp;#39;s how the above applies to Ruby, gor as much as I looked into it.&lt;/p&gt;

&lt;p&gt;When calling Ruby&amp;#39;s &lt;a href="https://ruby-doc.org/core-3.1.2/String.html#length-method"&gt;&lt;code&gt;String#length&lt;/code&gt;&lt;/a&gt;, it returns the length of characters like Rust&amp;#39;s &lt;code&gt;Chars.count&lt;/code&gt;. If you want the length in bytes you need to call &lt;a href="https://ruby-doc.org/core-3.1.2/String.html#bytesize-method"&gt;&lt;code&gt;String#bytesize&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="s2"&gt;"abc"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;length&lt;/span&gt;   &lt;span class="c1"&gt;# =&amp;gt; 3 characters&lt;/span&gt;
&lt;span class="s2"&gt;"abc"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bytesize&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; 3 bytes&lt;/span&gt;
&lt;span class="s2"&gt;"é"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;length&lt;/span&gt;     &lt;span class="c1"&gt;# =&amp;gt; 1 characters&lt;/span&gt;
&lt;span class="s2"&gt;"é"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bytesize&lt;/span&gt;   &lt;span class="c1"&gt;# =&amp;gt; 2 bytes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Calling the length on emoji will return the individual characters as the length. The 👩‍🔬 emoji is three characters and eleven bytes in Ruby as well.&lt;/p&gt;
&lt;div class="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;length&lt;/span&gt;   &lt;span class="c1"&gt;# =&amp;gt; 3 characters&lt;/span&gt;
&lt;span class="s2"&gt;"👩‍🔬"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bytesize&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; 11 bytes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Do you want grapheme clusters instead? You&amp;#39;re in luck: it&amp;#39;s built-in to Ruby with &lt;a href="https://ruby-doc.org/core-3.1.2/String.html#grapheme_clusters-method"&gt;&lt;code&gt;String#grapheme_clusters&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;div class="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;grapheme_clusters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;length&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; 1 cluster&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;To calculate the display with, we can use the &lt;a href="https://rubygems.org/gems/unicode-display_width"&gt;unicode-display_width gem&lt;/a&gt;. The same multiple counting of emoji in the grapheme cluster still applies here.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"unicode/display_width"&lt;/span&gt;

&lt;span class="no"&gt;Unicode&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;DisplayWidth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;of&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; 4&lt;/span&gt;
&lt;span class="no"&gt;Unicode&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;DisplayWidth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;of&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; 1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content>
  </entry>
  <entry>
    <title>When not to use instance variables in RSpec</title>
    <link rel="alternate" href="https://tomdebruijn.com/posts/ruby-rspec-instance-variables/"/>
    <id>https://tomdebruijn.com/posts/ruby-rspec-instance-variables/</id>
    <published>2021-02-02T00:00:00+00:00</published>
    <updated>2025-10-09T17:47:37+00:00</updated>
    <author>
      <name>Tom de Bruijn</name>
    </author>
    <description>Using instance variables in RSpec tests can lead to brittle specs and make them fail. Let's see when and when not to use instance variables. RSpec will help!
</description>
    <content type="html">&lt;p&gt;Using RSpec there is some confusion about the differences between &lt;code&gt;let&lt;/code&gt;, &lt;code&gt;let!&lt;/code&gt;, and instance variables in specs. I&amp;#39;d like to focus on how instance variables work in RSpec in combination with &lt;code&gt;before :context&lt;/code&gt; blocks, and in what kind of scenarios you should and should not use them.&lt;/p&gt;
&lt;h2&gt;&lt;span class="anchor" id="why-use-instance-variables"&gt;&lt;/span&gt;&lt;a href="#why-use-instance-variables"&gt;Why use instance variables?&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The advantage of declaring instance variables in &lt;code&gt;before :context&lt;/code&gt; (or &lt;code&gt;before :all&lt;/code&gt;) blocks is that whatever value is assigned is only queried or calculated once for many specs. The &lt;code&gt;before :context&lt;/code&gt; block is only executed once for all specs in that context. Those specs can use the instance variable without repeating the same setup for every spec, which should speed up the test suite.&lt;/p&gt;

&lt;p&gt;Reading the above it might be tempting to put a lot of spec setup in &lt;code&gt;before :context&lt;/code&gt; blocks. A fast test suite creates happy developers, right? But there&amp;#39;s a downside to using instance variables in RSpec, which could make for very unhappy developers.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;From the &lt;a href="https://github.com/rspec/rspec-core/blob/c6315d6e899796d3d0203dc8b656708a3ebca9a1/lib/rspec/core/hooks.rb#L122-L138"&gt;RSpec docs&lt;/a&gt;&lt;/em&gt;:&lt;/p&gt;

&lt;p&gt;It is very tempting to use &lt;code&gt;before(:context)&lt;/code&gt; to speed things up, but we recommend that you avoid this as there are a number of gotchas, as well as things that simply don&amp;#39;t work.&lt;/p&gt;

&lt;p&gt;[...]&lt;/p&gt;

&lt;p&gt;Instance variables declared in &lt;code&gt;before(:context)&lt;/code&gt; are shared across all the examples in the group. This means that each example can change the state of a shared object, resulting in an ordering dependency that can make it difficult to reason about failures.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The RSpec docs give us a warning about changing values of the instance variable. State can leak between specs using instance variables defined in a &lt;code&gt;before :context&lt;/code&gt; block this way.&lt;/p&gt;

&lt;p&gt;Let&amp;#39;s look at some examples of specs using instance variables and in what scenarios in will break.&lt;/p&gt;
&lt;h2&gt;&lt;span class="anchor" id="specs-sharing-instance-variables"&gt;&lt;/span&gt;&lt;a href="#specs-sharing-instance-variables"&gt;Specs sharing instance variables&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In the example below specs only assert if the value of the instance variable matches the expected value. Since the instance variable is not changed, all the specs will pass. If the instance variable value took a long time to query or calculate we have saved that time for two specs in this file.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# spec/lib/example_1_spec.rb&lt;/span&gt;
&lt;span class="n"&gt;describe&lt;/span&gt; &lt;span class="s2"&gt;"Example 1"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;before&lt;/span&gt; &lt;span class="ss"&gt;:context&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="c1"&gt;# Imagine this being a complex value to prepare&lt;/span&gt;
    &lt;span class="c1"&gt;# This block is only run once in the `describe "Example 1"` block&lt;/span&gt;
    &lt;span class="vi"&gt;@my_instance_variable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;:my_value&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;"spec 1"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@my_instance_variable&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eql&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:my_value&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;it&lt;/span&gt; &lt;span class="s2"&gt;"spec 2"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@my_instance_variable&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eql&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:my_value&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ bundle exec rspec spec/lib/example_1_spec.rb --order defined
Finished in 0.00223 seconds
2 examples, 0 failures
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;(&lt;em&gt;I&amp;#39;m using &lt;code&gt;--order defined&lt;/code&gt; in the examples in this post so that the spec execution order is predictable and reproducible.&lt;/em&gt;)&lt;/p&gt;

&lt;p&gt;But specs can be more complicated than this. They may pass the instance variable to some other part of the app, which modifies the given value. This is where things go wrong, what the RSpec docs warn us about.&lt;/p&gt;
&lt;h2&gt;&lt;span class="anchor" id="reassigning-the-instance-variable"&gt;&lt;/span&gt;&lt;a href="#reassigning-the-instance-variable"&gt;Reassigning the instance variable&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If changing the instance variable is the problem, let&amp;#39;s reassign it and see what happens in other specs.&lt;/p&gt;

&lt;p&gt;In the example below the &amp;quot;spec 1&amp;quot; spec changes the instance variable to test a slightly different scenario.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# spec/lib/example_2_spec.rb&lt;/span&gt;
&lt;span class="n"&gt;describe&lt;/span&gt; &lt;span class="s2"&gt;"Example 2"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;before&lt;/span&gt; &lt;span class="ss"&gt;:context&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="c1"&gt;# Imagine this being a complex value to prepare&lt;/span&gt;
    &lt;span class="vi"&gt;@my_instance_variable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;:my_value&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;"spec 1"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="vi"&gt;@my_instance_variable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;:new_value&lt;/span&gt;
    &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@my_instance_variable&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eql&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:new_value&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;it&lt;/span&gt; &lt;span class="s2"&gt;"spec 2"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@my_instance_variable&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eql&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:my_value&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ bundle exec rspec spec/lib/example_2_spec.rb --order defined
Finished in 0.00223 seconds
2 examples, 0 failures
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In this example &amp;quot;spec 2&amp;quot; does not fail, even though &amp;quot;spec 1&amp;quot;—which runs before &amp;quot;spec 2&amp;quot;—changes the instance variable. There was no need for us to reset the original value of the instance variable at the end of the spec even though we changed it.&lt;/p&gt;

&lt;p&gt;The way that RSpec runs the specs ensures that every spec uses the original instance variables. Every spec in RSpec is its own Ruby class, in which the spec is performed. Before the instance of the spec class is performed, RSpec sets the instance variables from the &lt;code&gt;before :context&lt;/code&gt; block on the spec class. When an instance variable is reassigned in a spec, it only reassigns it on that spec class instance. It doesn&amp;#39;t not reassign the instance variable on the same scope as the &lt;code&gt;before :context&lt;/code&gt; instance variables are stored, and does not interfere with any other specs. An example of how this looks can be found later on in this post.&lt;/p&gt;

&lt;p&gt;This behavior doesn&amp;#39;t always quite work though. Let&amp;#39;s see what happens if we use a bit more complex value. That way we know the limitations of using instance variables in RSpec.&lt;/p&gt;
&lt;h2&gt;&lt;span class="anchor" id="modifying-instance-variable-values"&gt;&lt;/span&gt;&lt;a href="#modifying-instance-variable-values"&gt;Modifying instance variable values&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In the next example the &lt;code&gt;@my_instance_variable&lt;/code&gt; is assigned a more complex value: an array with multiple values. We will intentionally break the spec in this scenario.&lt;/p&gt;

&lt;p&gt;In &amp;quot;spec 1&amp;quot; we&amp;#39;re testing a slightly different scenario again, modifying the value before running the assertion. Instead of reassigning the variable we&amp;#39;re adding a value to the array on &lt;code&gt;@my_instance_variable&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# spec/lib/example_3_spec.rb&lt;/span&gt;
&lt;span class="n"&gt;describe&lt;/span&gt; &lt;span class="s2"&gt;"Example 3"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;before&lt;/span&gt; &lt;span class="ss"&gt;:context&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="vi"&gt;@my_instance_variable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:one&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:two&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;it&lt;/span&gt; &lt;span class="s2"&gt;"spec 1"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="vi"&gt;@my_instance_variable&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="ss"&gt;:three&lt;/span&gt;
    &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@my_instance_variable&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eql&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="ss"&gt;:one&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:two&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:three&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;it&lt;/span&gt; &lt;span class="s2"&gt;"spec 2"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@my_instance_variable&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eql&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="ss"&gt;:one&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:two&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ bundle exec rspec spec/lib/example_3_spec.rb
Failures:

  1) Example 3 spec 2
     Failure/Error: expect(@my_instance_variable).to eql([:one, :two])

       expected: [:one, :two]
            got: [:one, :two, :three]

       (compared using eql?)

       Diff:
       @@ -1 +1 @@
       -[:one, :two]
       +[:one, :two, :three]

     # ./spec/lib/example_3_spec.rb:14:in `block (2 levels) in &amp;lt;top (required)&amp;gt;'

Finished in 0.01596 seconds (files took 0.10576 seconds to load)
2 examples, 1 failure

Failed examples:

rspec ./spec/lib/example_3_spec.rb:12 # Example 3 spec 2
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Unlike before, the &amp;quot;spec 2&amp;quot; spec has now failed. It fails because the instance variable still has the value from the first spec. State has leaked from &amp;quot;spec 1&amp;quot; into &amp;quot;spec 2&amp;quot;. Let&amp;#39;s look at how the values from these instance variables have moved between specs.&lt;/p&gt;
&lt;h2&gt;&lt;span class="anchor" id="how-instance-variables-work-in-rspec"&gt;&lt;/span&gt;&lt;a href="#how-instance-variables-work-in-rspec"&gt;How instance variables work in RSpec&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To recap what we learned from the examples earlier:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Assigning instance variables in &lt;code&gt;before :context&lt;/code&gt; means they&amp;#39;ll only be assigned once, as the &lt;code&gt;before :context&lt;/code&gt; block is only once run before all specs in the spec context.&lt;/li&gt;
&lt;li&gt;Every spec in RSpec is performed as its own class, with its own scope. Instance variable from the &lt;code&gt;before :context&lt;/code&gt; block are set on the spec class before it is performed.&lt;/li&gt;
&lt;li&gt;Instance variable values do not leak between specs when the values are basic Ruby objects such as Symbols, numbers, etc.&lt;/li&gt;
&lt;li&gt;Instance variable values do leak between specs when more complex object types such as Arrays, Strings, Class instances, etc. are modified.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let&amp;#39;s take a closer look at how RSpec handles instance variables for spec classes to see how this could break in our test suite.&lt;/p&gt;
&lt;h3&gt;&lt;span class="anchor" id="how-rspec-handles-instance-variables"&gt;&lt;/span&gt;&lt;a href="#how-rspec-handles-instance-variables"&gt;How RSpec handles instance variables&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;When RSpec runs specs in a context, it first runs the &lt;code&gt;before :context&lt;/code&gt; blocks. After a &lt;code&gt;before :context&lt;/code&gt; block is executed RSpec then stores the list of the instance variables on the class it creates for the context.&lt;/p&gt;

&lt;p&gt;When RSpec then starts a spec in that context it creates a new class for that spec and sets instance variables of that context on the spec class.&lt;/p&gt;

&lt;p&gt;Let&amp;#39;s look at how this works using the same scenario from the second example, where we reassigned the instance variables.&lt;/p&gt;
&lt;div class="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;Context&lt;/span&gt; &lt;span class="c1"&gt;# RSpec context class&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;before_context_ivars&lt;/span&gt;
    &lt;span class="c1"&gt;# Where the `before :context` instance variables are stored&lt;/span&gt;
    &lt;span class="vi"&gt;@ivars&lt;/span&gt; &lt;span class="o"&gt;||=&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="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_before_context_ivars_on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;spec_instance&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# Set instance variables from `before :context` on spec instance&lt;/span&gt;
    &lt;span class="n"&gt;before_context_ivars&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;spec_instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;instance_variable_set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&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;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Spec&lt;/span&gt; &lt;span class="c1"&gt;# RSpec spec class&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt; &lt;span class="c1"&gt;# During spec&lt;/span&gt;
    &lt;span class="vi"&gt;@var&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="c1"&gt;# Reassign instance variable&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# Mock a `before :context` block and&lt;/span&gt;
&lt;span class="c1"&gt;# store an instance variable on the Context class&lt;/span&gt;
&lt;span class="no"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;before_context_ivars&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:@var&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="c1"&gt;# Basic Ruby object value&lt;/span&gt;
&lt;span class="c1"&gt;# Initialize the spec class&lt;/span&gt;
&lt;span class="n"&gt;spec_instance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Spec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
&lt;span class="c1"&gt;# Set the `before :context` instance variables on the spec&lt;/span&gt;
&lt;span class="no"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_before_context_ivars_on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;spec_instance&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Context @var before spec:"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="no"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;before_context_ivars&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:@var&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;inspect&lt;/span&gt;

&lt;span class="c1"&gt;# Run the spec&lt;/span&gt;
&lt;span class="n"&gt;spec_instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;

&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Spec @var:"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;spec_instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;instance_variable_get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:@var&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;inspect&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Context @var after spec:"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="no"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;before_context_ivars&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:@var&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;inspect&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;(Simplified example for demonstration purposes.)&lt;/em&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Context @var before spec:
1
Spec1 @var:
2
Context @var after spec:
1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;RSpec sets the original value of the instance variable on every spec class instance. Reassigning the instance variable in the spec class does not modify the value of the instance variable on the context. It only changes it on the spec class instance.&lt;/p&gt;

&lt;p&gt;When the instance variable value was modified however, the value stored on the Context class is also modified, as it is still the same value.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Using the same context class as from the previous example&lt;/span&gt;

&lt;span class="c1"&gt;# Mock a `before :context` block and&lt;/span&gt;
&lt;span class="c1"&gt;# store an instance variable on the Context class&lt;/span&gt;
&lt;span class="no"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;before_context_ivars&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:@var&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&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;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;# More complex Ruby object&lt;/span&gt;
&lt;span class="c1"&gt;# Initialize the spec class&lt;/span&gt;
&lt;span class="n"&gt;spec_instance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Spec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
&lt;span class="c1"&gt;# Set the `before :context` instance variables on the spec&lt;/span&gt;
&lt;span class="no"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_before_context_ivars_on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;spec_instance&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Context @var before spec:"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="no"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;before_context_ivars&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:@var&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;inspect&lt;/span&gt;

&lt;span class="c1"&gt;# Run the spec&lt;/span&gt;
&lt;span class="n"&gt;spec_instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;

&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Spec @var:"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;spec_instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;instance_variable_get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:@var&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;inspect&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Context @var after spec:"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="no"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;before_context_ivars&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:@var&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;inspect&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Context @var before spec:
[1, 2]
Spec @var:
[1, 2, 3]
Context @var after spec:
[1, 2, 3]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In this example we can see the instance variable&amp;#39;s value has changed, because the value was modified, rather than the instance variable being reassigned. This value was modified in memory, also changing the value in the context instance variable storage, which causes the modified state to leak into other specs.&lt;/p&gt;
&lt;h3&gt;&lt;span class="anchor" id="pointers"&gt;&lt;/span&gt;&lt;a href="#pointers"&gt;Pointers&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;What happens is, RSpec (or rather Ruby) is assigning pointers to the values in memory when it sets the instance variable values on the spec class instance. Variables in Ruby are pointers to a place in the application&amp;#39;s memory. Assigning a value to another variable does not make a copy of it, but points to the same location in memory.&lt;/p&gt;

&lt;p&gt;When RSpec sets the instance variables, it doesn&amp;#39;t set a copy of the original Array value. Instead it sets the pointer to the Array value in memory. If the Array in memory has changed during the spec run, it will set not the original value for the next spec, but the modified Array instead. This is part of how Ruby works, this is not something RSpec can &amp;quot;fix&amp;quot;. And which is why the RSpec docs warn us about using instance variables in &lt;code&gt;before :context&lt;/code&gt; blocks.&lt;/p&gt;
&lt;h2&gt;&lt;span class="anchor" id="alternatives"&gt;&lt;/span&gt;&lt;a href="#alternatives"&gt;Alternatives&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To prevent state from leaking into other specs by modified values, it&amp;#39;s possible to &lt;a href="https://ruby-doc.org/core-2.7.1/Object.html#freeze-method"&gt;&amp;quot;freeze&amp;quot; objects in Ruby&lt;/a&gt;. If we freeze an Array, String or other object instance, Ruby will not allow any modifications.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;var&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&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;2&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;freeze&lt;/span&gt;
&lt;span class="n"&gt;var&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
&lt;span class="c1"&gt;# Raises an error to prevent modification&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; FrozenError (can't modify frozen Array: [1, 2])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;But this will be more difficult to do for larger objects with nested objects, as it only freezes the top object and not all nested objects.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;var&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[[&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;2&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]].&lt;/span&gt;&lt;span class="nf"&gt;freeze&lt;/span&gt;
&lt;span class="n"&gt;var&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="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;inspect&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; [[1, 2, 3], [4, 5]] # The nested value was modified&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Alternatively it&amp;#39;s possible to &lt;a href="https://api.rubyonrails.org/v5.2.4/classes/Object.html#method-i-deep_dup"&gt;deep clone or dup&lt;/a&gt; the object. The problem with this is that it will take up a lot more memory, as every object will be kept in memory multiple times, so I can&amp;#39;t recommend it.&lt;/p&gt;
&lt;h2&gt;&lt;span class="anchor" id="conclusion"&gt;&lt;/span&gt;&lt;a href="#conclusion"&gt;Conclusion&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;RSpec scoping instance variables between specs in classes is a great help from RSpec for basic Ruby objects, but this behavior shouldn&amp;#39;t be relied upon for more complex Ruby objects such as Arrays, Strings, and other object instances.&lt;/p&gt;

&lt;p&gt;If a spec is modifying an instance variable value, you can&amp;#39;t be sure what the value of that instance variable will be in the next spec. State may leak to other specs, breaking them in unexpected ways. This will be especially difficult to track down when the specs are run in a random order each time.&lt;/p&gt;

&lt;p&gt;Make sure that if you use instance variables, you absolutely do &lt;em&gt;not&lt;/em&gt; modify any value set on the instance variable if you want a predictable and reproducible test suite. And that&amp;#39;s something we should all want. I&amp;#39;m all for fast test suites, but what I like more is a stable test suite.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;A big thanks to &lt;a href="https://twitter.com/Benoit_Tgt"&gt;Benoit Tigeot&lt;/a&gt; for fact checking this article!&lt;/em&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Git: Review changes before committing</title>
    <link rel="alternate" href="https://tomdebruijn.com/posts/git-review-changes-before-committing/"/>
    <id>https://tomdebruijn.com/posts/git-review-changes-before-committing/</id>
    <published>2020-10-26T00:00:00+00:00</published>
    <updated>2025-10-09T17:47:37+00:00</updated>
    <author>
      <name>Tom de Bruijn</name>
    </author>
    <description>Git commits can be made very quickly, but we don't see what changes are committed. We can improve the way we make commits by reviewing what we commit first.
</description>
    <content type="html">&lt;p&gt;Making a git commit can be as quick as &lt;code&gt;git add --all &amp;amp;&amp;amp; git commit -m &amp;quot;WIP&amp;quot;&lt;/code&gt;. The problem with this approach is that we didn&amp;#39;t check what changes we committed. Who knows what got committed that shouldn&amp;#39;t have. Usually we find out when it&amp;#39;s too late. We want to see the Git changes before we commit.&lt;/p&gt;

&lt;p&gt;While working on an issue, I personally make a lot of changes that don&amp;#39;t belong in the same commit. For that reason the approach of &amp;quot;now commit everything I have changed&amp;quot; rarely works for me.&lt;/p&gt;

&lt;p&gt;Let&amp;#39;s look at the things we don&amp;#39;t want to commit first and then improve the way we make commits by reviewing what we commit first and how.&lt;/p&gt;
&lt;h2&gt;&lt;span class="anchor" id="things-we-don-39-t-want-to-commit"&gt;&lt;/span&gt;&lt;a href="#things-we-don-39-t-want-to-commit"&gt;Things we don&amp;#39;t want to commit&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Some things that we don&amp;#39;t want stored in git commits are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;App secrets

&lt;ul&gt;
&lt;li&gt;Tokens, passwords, personal information, etc. These are things we don&amp;#39;t want to commit to the git history, especially public repositories. A bot will scrape our hosting provider credentials and spin up a lot of machines on our dime before we know what happened.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Debugging code

&lt;ul&gt;
&lt;li&gt;Some code may still be present in the project that helped debug an issue, such as a &lt;code&gt;print&lt;/code&gt;/&lt;code&gt;puts&lt;/code&gt;, a &lt;code&gt;console.log&lt;/code&gt;, or a &lt;code&gt;binding.pry&lt;/code&gt;/&lt;code&gt;debugger&lt;/code&gt;-statement. While not necessarily harmful it creates a lot of output or hangs the process while running the app and test suites. It creates unwanted noise.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Removing these changes from commits isn&amp;#39;t always as easy. Force pushing branches can mess up the team&amp;#39;s flow and commits in public repositories don&amp;#39;t disappear. Someone with a direct link can still access the old commits.&lt;/p&gt;

&lt;p&gt;To go through the process of creating new app secrets, rolling those out across the app is very time consuming and cumbersome. Removing debugging code isn&amp;#39;t as much trouble but it does require another commit, which adds noise to the git history. This doesn&amp;#39;t help &lt;a href="/posts/git-is-about-communication/"&gt;communicating in git&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;&lt;span class="anchor" id="review-what-to-commit"&gt;&lt;/span&gt;&lt;a href="#review-what-to-commit"&gt;Review what to commit&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To avoid including things that shouldn&amp;#39;t be committed and create better commits, let&amp;#39;s look at ways to create commits.&lt;/p&gt;

&lt;p&gt;This is the process I recommend going through when creating a commit:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Start with reviewing changes:

&lt;ul&gt;
&lt;li&gt;Are we about to stage files we should not be staging, such as credential files?&lt;/li&gt;
&lt;li&gt;Are there debug statements in the changes we should remove first?&lt;/li&gt;
&lt;li&gt;Are there changes that are not required to fix the bug or add the feature? In a commit where a bug is fixed, don&amp;#39;t include code style changes in an unrelated part of the code.&lt;/li&gt;
&lt;li&gt;Finally, this is also a review for you, the committer, to double check the changes that were made are actually all necessary and correct. Are there tests for every logic change? Did we add or update the docs?&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Stage the changes that you approve and are necessary.&lt;/li&gt;
&lt;li&gt;Only after this review, commit the changes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The main idea of this is that we look at what files we stage, but more importantly, we review the changes in each file before we stage them.&lt;/p&gt;
&lt;h2&gt;&lt;span class="anchor" id="commit-tools"&gt;&lt;/span&gt;&lt;a href="#commit-tools"&gt;Commit tools&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Let&amp;#39;s look at some tools that help with creating git commits. These are alternatives to &lt;code&gt;git add --all&lt;/code&gt; command from the beginning of this post, which stages all changes: modifications, additions and deletions.&lt;/p&gt;

&lt;p&gt;There are some basic tools that git provides us and there are third party tools we can use to make staging specific changes easier.&lt;/p&gt;
&lt;h3&gt;&lt;span class="anchor" id="git-add-file"&gt;&lt;/span&gt;&lt;a href="#git-add-file"&gt;Git add file&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Using the &lt;code&gt;git add&lt;/code&gt; command it&amp;#39;s possible to add one file at a time with &lt;code&gt;git add path/to/file.md&lt;/code&gt;. This makes sure we don&amp;#39;t add files we shouldn&amp;#39;t commit, such as secret files, which is good.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;git status
On branch main
Changes not staged &lt;span class="k"&gt;for &lt;/span&gt;commit:
  modified:   path/to/file.md

&lt;span class="nv"&gt;$ &lt;/span&gt;git add path/to/file.md

&lt;span class="nv"&gt;$ &lt;/span&gt;git status
On branch master
Changes to be committed:
  modified:   path/to/file.md
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;But what about the changes in those files? We still don&amp;#39;t know exactly what changes are being staged and will end up getting committed. We need a more detailed staging process.&lt;/p&gt;
&lt;h3&gt;&lt;span class="anchor" id="interactively-stage-changes"&gt;&lt;/span&gt;&lt;a href="#interactively-stage-changes"&gt;Interactively stage changes&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To review and select changes &lt;em&gt;within a file&lt;/em&gt; we want to stage we can use &lt;code&gt;git add --patch&lt;/code&gt;. This opens a basic selection interface in the terminal with which we can select &amp;quot;hunks&amp;quot; to be staged. Hunks are groups of changed lines in a file that git can easily stage and unstage.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="err"&gt;$&lt;/span&gt; git add --patch
&lt;span class="gh"&gt;diff --git a/app/models/blog_post.rb b/app/models/blog_post.rb
index 8b13789..257cc56 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- a/app/models/blog_post.rb
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/app/models/blog_post.rb
&lt;/span&gt;&lt;span class="p"&gt;@@ -154,6 +154,12 @@&lt;/span&gt; class BlogPost &amp;lt; ActiveRecord::Base
   end
&lt;span class="gi"&gt;+
+  def published?
+    binding.pry
+    Time.now &amp;gt;= published_at
+  end
&lt;/span&gt;
   def active?
&lt;span class="err"&gt;(1/10)&lt;/span&gt; Stage this hunk [y,n,q,a,d,e,?]?
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In the example above we run &lt;code&gt;git add --patch&lt;/code&gt; and it prompts us with the question if we want to &amp;quot;Stage this hunk&amp;quot;. What&amp;#39;s useful here is that we can see the &amp;quot;hunk&amp;quot; it&amp;#39;s referring to in the preview above it, and review if it&amp;#39;s any code we actually want to commit.&lt;/p&gt;

&lt;p&gt;Choosing &amp;quot;y&amp;quot; for &amp;quot;yes&amp;quot;, or &amp;quot;n&amp;quot; for &amp;quot;no&amp;quot;, we can step by step stage changes or skip them. Git will take us through all the changes one &amp;quot;hunk&amp;quot; at a time until we&amp;#39;ve reviewed them all.&lt;/p&gt;

&lt;p&gt;But maybe we spot an issue with the hunk. We can &amp;quot;q&amp;quot; for &amp;quot;quit&amp;quot; and open the file in our editor to fix the issue and then restart the process by running &lt;code&gt;git add --patch&lt;/code&gt; again.&lt;/p&gt;

&lt;p&gt;This interface is quite limiting. If we don&amp;#39;t agree with git&amp;#39;s hunk boundaries we can&amp;#39;t easily change them. It&amp;#39;s not very easy to only stage one line or omit a line. If we stage the hunk in the example above we also commit some debugging code that will break things for us later.&lt;/p&gt;
&lt;h3&gt;&lt;span class="anchor" id="git-gui"&gt;&lt;/span&gt;&lt;a href="#git-gui"&gt;Git GUI&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;A git &lt;abbr title="Graphical User Interface"&gt;GUI&lt;/abbr&gt; can help give us more control over what we commit. Apps that give us humans a more easy to digest interface for git and may even help with more advanced tools such as merge conflict resolution and rebasing.&lt;/p&gt;

&lt;p&gt;Git GUIs are great! Don&amp;#39;t let anyone tell you otherwise.&lt;/p&gt;

&lt;p&gt;When making commits it&amp;#39;s important to have a view of the current changes, stage them interactively, and commit knowing we only commit those changes that were selected. A GUI can display very detailed changes in a concise way, and the selection process for staging changes is more precise.&lt;/p&gt;

&lt;p&gt;Personally I use &lt;a href="https://jonas.github.io/tig/"&gt;tig&lt;/a&gt; most of the time in the terminal, and &lt;a href="https://fork.dev/"&gt;Fork&lt;/a&gt; any time I run into tig&amp;#39;s limitations. To get started I recommend &lt;a href="https://fork.dev/"&gt;Fork&lt;/a&gt;, or a similar GUI app, as it&amp;#39;s a very complete Git tool. Both tools give great overviews of git history and staged and unstaged files. It&amp;#39;s possible to stage entire files in one go, or deep dive and only add the &lt;em&gt;one specific line&lt;/em&gt; we want to stage and nothing else.&lt;/p&gt;

&lt;p&gt;There are &lt;a href="https://git-scm.com/downloads/guis"&gt;many other git GUIs&lt;/a&gt;, and no one tool is the best or only one you&amp;#39;re allowed to use. Experiment, try them out, and use the tool that works best for you.&lt;/p&gt;
&lt;h2&gt;&lt;span class="anchor" id="an-example-commit-flow"&gt;&lt;/span&gt;&lt;a href="#an-example-commit-flow"&gt;An example commit flow&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;When I am making a commit I often use tig in the terminal, but for convenience I&amp;#39;ll use &lt;a href="https://fork.dev/"&gt;Fork&lt;/a&gt; in this example as it will look the same for everyone.&lt;/p&gt;

&lt;p&gt;First I open the app, then I&amp;#39;m presented with the git history. After opening the &amp;quot;Local Changes&amp;quot; view I get an overview of all uncommitted changes.&lt;/p&gt;

&lt;p&gt;In this example I want to stage the &lt;code&gt;env_helpers.rb&lt;/code&gt; file, but not all changes. There are changes in the same file for another feature I&amp;#39;m working on. If I stage the entire hunk I also stage the code I don&amp;#39;t want to include. I could open the file in my editor again and remove the line temporarily, but then I should remember to restore it again later. (I&amp;#39;m going to forget that, aren&amp;#39;t I?)&lt;/p&gt;

&lt;iframe class="video" src="https://player.vimeo.com/video/462944661" width="640" height="180" frameborder="0" allow="autoplay; fullscreen" allowfullscreen&gt;&lt;/iframe&gt;

&lt;p&gt;This is the process I go through in the video:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;In the left panel&amp;#39;s &amp;quot;Unstaged Changes&amp;quot; box I look for the file I&amp;#39;ve just made changes to and want to commit.&lt;/li&gt;
&lt;li&gt;I open the file by clicking it and see the file changes in the right panel.&lt;/li&gt;
&lt;li&gt;There&amp;#39;s a change on a line I do not want to commit, as indicated by the &amp;quot;TODO&amp;quot; comment.&lt;/li&gt;
&lt;li&gt;By selecting the line I do want to commit the Fork app gives me context specific actions to &amp;quot;Stage&amp;quot; the changes.&lt;/li&gt;
&lt;li&gt;I press the &amp;quot;Stage&amp;quot; button and only the selected change gets staged. The remaining unstaged file change is the line with the &amp;quot;TODO&amp;quot; comment I do not want to commit.&lt;/li&gt;
&lt;li&gt;I click on the file in the &amp;quot;Staged Changes&amp;quot; view in the left panel. Here I see the changes the one line change I just staged and nothing else. If these are all the changes I need, I can now commit them without also committing the unstaged changes.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;&lt;span class="anchor" id="conclusion"&gt;&lt;/span&gt;&lt;a href="#conclusion"&gt;Conclusion&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I hope this peek into how I review my changes before committing helps you make better commits. It&amp;#39;s a process to keep being aware of. When I haven&amp;#39;t kept a close eye on what I commit, I have included and pushed things I shouldn&amp;#39;t have. So keep an eye on it and let&amp;#39;s make better commits together!&lt;/p&gt;

&lt;p&gt;Review the changes you stage so you don&amp;#39;t accidentally include any app secrets, debugging code, personal information, or anything else that shouldn&amp;#39;t be committed.&lt;/p&gt;

&lt;p&gt;Try out a GUI or any of the other tools I&amp;#39;ve listed in this post and &lt;a href="https://twitter.com/tombruijn"&gt;let me know&lt;/a&gt; which is your favorite!&lt;/p&gt;
</content>
  </entry>
</feed>
