<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
  <title>Felipe Moyano - English Blog</title>
  <subtitle>This site will serve both as a learning experience and as a playground to try out new things and share my thoughts along the way</subtitle>
  <link href="https://gafemoyano.com/en/feed.xml" rel="self" />
  <link href="https://gafemoyano.com/" />
  <updated>2023-09-26T01:32:39Z</updated>
  <id>https://gafemoyano.com/</id>
  <author>
    <name>Felipe Moyano</name>
  </author>
  <entry>
    <title>Integration Testing Remix Routes with Vitest</title>
    <link href="https://gafemoyano.com/en/posts/integration-testing-remix-routes-with-vitest/" />
    <updated>2023-09-26T01:32:39Z</updated>
    <id>https://gafemoyano.com/en/posts/integration-testing-remix-routes-with-vitest/</id>
    <content type="html">&lt;p&gt;&lt;em&gt;In this article we&#39;ll go through the necessary steps to setup the tooling around your remix App so you can run integration tests for your Route components, loaders and actions.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Disclaimer: When I first started writing this post, the official Remix as a Vite plugin hadn&#39;t been released. I haven&#39;t tried it myself but I imagine most of this setup is still relevant.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id=&quot;full-stack-react&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://gafemoyano.com/en/posts/integration-testing-remix-routes-with-vitest/#full-stack-react&quot;&gt;Full Stack React&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I&#39;ve really been enjoying building Apps with Remix. Or rather, I&#39;ve always enjoyed building apps with full stack frameworks, even though I&#39;ve spent a significant amount of my development hours in React land for the past 4 years. Since I usually work on small to medium sized teams responsible for the whole stack, I&#39;ve always found it a burden having to work on a separate layer when a frontend framework was necessary. That includes a handful of different setups, from monorepos, glue libraries (like react-rails), separate pipelines, and such.&lt;/p&gt;
&lt;p&gt;With Remix I no longer feel the dread of having to maintain to different projects when it comes down to using React. Everything can live nicely under the same repository, share types, build pipeline, deploy to whatever cloud you want to, and access your database from a long running Nodejs server where you can bring in the whole ecosystem of libraries as needed.&lt;/p&gt;
&lt;p&gt;Still, Remix is far from a full-stack framework nor is it trying to become one: they&#39;ve clearly stated that they don&#39;t want to dictate the architecture of people&#39;s applications. That&#39;s a perfectly fine choice for the maintainers, they&#39;re already solving some really hard problems to bring us the magic experience of seamless frontend and backend integration, but it does mean that you&#39;re on your own when it comes to setting up certain core features that we&#39;ve grown accustomed to in frameworks such as Rails, Laravel or Phoenix such as testing.&lt;/p&gt;
&lt;h2 id=&quot;about-testing-remix-apps&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://gafemoyano.com/en/posts/integration-testing-remix-routes-with-vitest/#about-testing-remix-apps&quot;&gt;About Testing Remix Apps&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;It&#39;s worth mentioning that the common advice on the Remix community is to &lt;a href=&quot;https://github.com/remix-run/remix/discussions/993#discussioncomment-1792350&quot;&gt;use e2e testing&lt;/a&gt; via &lt;a href=&quot;https://playwright.dev/&quot;&gt;Playwright&lt;/a&gt; or &lt;a href=&quot;https://www.cypress.io/&quot;&gt;Cypress&lt;/a&gt;. I&#39;ve uses both of these libraries and they work really well with Remix. After all, if you have full stack react you might as well test your whole app with e2e escenarios. However I needed to get test coverage reports for this project and since Playwright and Cypress both rely on &lt;a href=&quot;https://istanbul.js.org/&quot;&gt;Instanbul&lt;/a&gt; to setup instrumentation, which itself relies on babel plugins to work and those won&#39;t work since Remix uses Esbuild under the hood.&lt;/p&gt;
&lt;p&gt;So, I had to drop down one level and do integration testing with &lt;a href=&quot;https://vitest.dev/&quot;&gt;Vitest&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;setting-up-the-tools&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://gafemoyano.com/en/posts/integration-testing-remix-routes-with-vitest/#setting-up-the-tools&quot;&gt;Setting up the Tools&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;First of all we want to install all our dependencies&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-D&lt;/span&gt; vitest vitest-dom vite-tsconfig-paths @vitejs/plugin-react @remix-run/testing&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then we add a few helper files to setup Vitest environment: &lt;em&gt;huge shoutout to &lt;a href=&quot;https://twitter.com/kentcdodds&quot;&gt;Kent C. Dodds&#39;&lt;/a&gt; Epic Stack github &lt;a href=&quot;https://github.com/epicweb-dev/epic-stack&quot;&gt;repo&lt;/a&gt;, which has been a constant source of knowledge and inspiration for my Remix journey&lt;/em&gt;&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;// tests/setup-test-env.ts

&lt;span class=&quot;token function&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; beforeEach, expect, &lt;span class=&quot;token function&quot;&gt;vi&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; from &lt;span class=&quot;token string&quot;&gt;&quot;vitest&quot;&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; installGlobals &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; from &lt;span class=&quot;token string&quot;&gt;&quot;@remix-run/node&quot;&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;dotenv/config&quot;&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; truncateDB &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; from &lt;span class=&quot;token string&quot;&gt;&quot;./truncate-db.js&quot;&lt;/span&gt;

&lt;span class=&quot;token function&quot;&gt;import&lt;/span&gt; * as matchers from &lt;span class=&quot;token string&quot;&gt;&quot;vitest-dom/matchers&quot;&lt;/span&gt;
expect.extend&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;matchers&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

// console.log&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;matchers&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
const ResizeObserverMock &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; vi.fn&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;{
  observe&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; vi.fn&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  unobserve&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; vi.fn&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  disconnect&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; vi.fn&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
}&lt;span class=&quot;token punctuation&quot;&gt;))&lt;/span&gt;&lt;/span&gt;

// Stub the global ResizeObserver
vi.stubGlobal&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;ResizeObserver&quot;&lt;/span&gt;, ResizeObserverMock&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

installGlobals&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

beforeEach&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;async &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  await truncateDB&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;setting-up-a-test-database&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://gafemoyano.com/en/posts/integration-testing-remix-routes-with-vitest/#setting-up-a-test-database&quot;&gt;Setting up a test database&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This step is optional, but I prefer having a separate database to run tets against that can be fully setup and wipe out after each test run. This way you don&#39;t even have to mock your loaders and actions and can hit your stack all the way to the database (which is what I&#39;m looking for since these are integration tests). If you want to setup a test database with docker, I&#39;d recommend following the instructions in &lt;a href=&quot;https://www.simplethread.com/isolated-integration-testing-with-remix-vitest-and-prisma/&quot;&gt;this article&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Since I&#39;m using Drizzle instead of Postgres, there&#39;s a few things we need to do differently in our setup scripts:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;// package.json
&lt;span class=&quot;token string&quot;&gt;&quot;scripts&quot;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&quot;db:migrate:dev&quot;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ts-node ./drizzle/migrate.ts&quot;&lt;/span&gt;,
    &lt;span class=&quot;token string&quot;&gt;&quot;db:migrate:test&quot;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;dotenv -e .env.test -- npx drizzle-kit push:pg&quot;&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&quot;test&quot;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;run-s db:migrate:test test:unit&quot;&lt;/span&gt;,
    &lt;span class=&quot;token string&quot;&gt;&quot;test:unit&quot;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;dotenv -e .env.test -- vitest --no-threads&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;setting-up-vitest-with-istanbul-for-coverage-reports&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://gafemoyano.com/en/posts/integration-testing-remix-routes-with-vitest/#setting-up-vitest-with-istanbul-for-coverage-reports&quot;&gt;Setting up Vitest with Istanbul for coverage reports&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Now that everything is hooked up, we have to setup up a few additional configurations to get coverage reports. Start by installing the vitest/coverage-istanbul package from npm&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-D&lt;/span&gt; @vitest/coverage-istanbul&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then in our &lt;code&gt;vitest.config.ts&lt;/code&gt; file we can add the following configurations:&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;    environment&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;jsdom&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    setupFiles&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;./tests/setup/setup-test-env.ts&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    coverage&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      reporter&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;text&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;lcov&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      provider&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;istanbul&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      include&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;app/**/*.{ts,tsx}&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      exclude&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;app/entry.server.tsx&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;app/root.tsx&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      all&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Don&#39;t forget to add the &lt;code&gt;include&lt;/code&gt; and &lt;code&gt;exclude&lt;/code&gt; according to your app and the files you&#39;ll wan&#39;t to see on the coverage report. You&#39;ll also need to tell vitest about any special aliases you might have setup in your project such as &lt;code&gt;~&lt;/code&gt;, &lt;code&gt;@&lt;/code&gt; or &lt;code&gt;#&lt;/code&gt; as root for your &lt;code&gt;app&lt;/code&gt; folder.&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;  resolve&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    alias&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token string-property property&quot;&gt;&quot;~&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;__dirname&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;app&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;writing-a-test&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://gafemoyano.com/en/posts/integration-testing-remix-routes-with-vitest/#writing-a-test&quot;&gt;Writing a test&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Since this are integration tests we&#39;re going to be focusing on testing our Routes, Loaders and Actions.&lt;/p&gt;
&lt;p&gt;The server part of Remix is not really special, since it&#39;s just a function running on a Nodejs server. We can test those directly by importing them in our test file and calling the route function directly assuming our app is running on &lt;code&gt;localhost:3000&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;For example, let&#39;s say we have a route that allows an authenticated user to see an Area and edit it at &lt;code&gt;localhost:3000/areas/:areaId &lt;/code&gt; and we want to test the loader.&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// area-loader.server.test.ts&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;describe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Edit area loader&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;returns the area&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// Get a signed cookie to clear authentication&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; cookie &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;setupAdmin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// Some function that creates an area on the test db.&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; newArea &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; AreaFactory&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; url &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;http://localhost:3000/areas/&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; newArea&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// Create a request object and pass it the cookie&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; request &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Request&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      method&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;GET&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      headers&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; cookie &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;loader&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      request&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      params&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; areaId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; newArea&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      context&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;status&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toBe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; data &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// We would expect the loader to return the area with the id we passed&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;area&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toBe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;newArea&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That&#39;s all there is to it. You might want to check additional properties on the response data, but that&#39;s good enough for now.&lt;/p&gt;
&lt;p&gt;What about Actions? We can test those in a similar way with the knowledge that on a successful request, the action will redirect to a new route.&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// area-action.server.test.ts&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;describe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Edit area action&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;returns success with valid parameters&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; cookie &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;setupAdmin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; newArea &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; AreaFactory&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// We use FormData to simulate a request&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; formData &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;FormData&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    formData&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Test name&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; url &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;http://localhost:3000/areas/&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;newArea&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// Create a request object and pass it the authentication cookie and FormData&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; request &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Request&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      method&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;POST&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      headers&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; cookie &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      body&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; formData&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// We call the action with the request&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;action&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      request&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      params&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; areaId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; newArea&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      context&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;//  We expect the response to be a redirect to the areas list&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;status&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toBe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;302&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;headers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Location&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toBe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/areas&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And that&#39;s all for the server part! There&#39;s obviously more escenarios we can test, including failed submissions, invalid requests, etc. But the important bit is that we can import our loaders and actions and call them directly passing them a Request object.&lt;/p&gt;
&lt;h2 id=&quot;testing-the-view-layer&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://gafemoyano.com/en/posts/integration-testing-remix-routes-with-vitest/#testing-the-view-layer&quot;&gt;Testing the view layer&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Since this is a React app a lot of the code we&#39;ll be writing is Components and we want to test those as well and include them in our coverage report.&lt;/p&gt;
&lt;p&gt;For this we&#39;re using an special package called &lt;code&gt;@remix-run/testing&lt;/code&gt; which provides a set of utilities to help us test Remix apps.&lt;br&gt;
The idea is that we&#39;ll use a special &lt;code&gt;createRemixStub&lt;/code&gt; function to create a RemixApp which will include the internal &lt;code&gt;contexts&lt;/code&gt; and &lt;code&gt;hooks&lt;/code&gt; so that we can render our routes by passing them as children in it.&lt;/p&gt;
&lt;p&gt;We&#39;ll also use React Testing Library to render and interact with our route components.&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// area-client.test.tsx&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; test&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; describe &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;vitest&quot;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; AdminEditAreaRoute&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; loader &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;./route&quot;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; render&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; screen&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; waitFor &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;@testing-library/react&quot;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; createRemixStub &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;@remix-run/testing&quot;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; AreaFactory &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;tests/factories/area&quot;&lt;/span&gt;

&lt;span class=&quot;token function&quot;&gt;describe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;New template route&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;it renders correctly&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; area &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; AreaFactory&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; RemixStub &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createRemixStub&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        id&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;root&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        path&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// We want to pass the cookie to check for authentication.&lt;/span&gt;
        &lt;span class=&quot;token function-variable function&quot;&gt;loader&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;args&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          args&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;headers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;cookie&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; cookie&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;token comment&quot;&gt;// This loader can actually just be a Mock or a fake&lt;/span&gt;
          &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;loader&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;args&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        children&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            path&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/areas/:areaId&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// Here we&#39;re passing the Route component as a child.&lt;/span&gt;
            Component&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; AdminEditAreaRoute&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token function-variable function&quot;&gt;loader&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;args&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
              args&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;headers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;cookie&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; cookie&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

              &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;loader&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;args&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// Initial entries we&#39;ll make it so that we don&#39;t have to navigate&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// to this route to test it.&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;RemixStub initialEntries&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;/admin/areas/&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;area&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// We test that the page is rendering correctly&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;waitFor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;
      screen&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findByRole&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;heading&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; name&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Edit area &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;area&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This works fine for Route tests since they depend on Remix internals and contexts, for UI components I&#39;d avoid this setup and just render them directly with React Testing Library.&lt;/p&gt;
&lt;p&gt;Now that we have everything setup, we can run our test suite with the following command:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; run testdotenv &lt;span class=&quot;token parameter variable&quot;&gt;-e&lt;/span&gt; .env.test -- vitest --no-threads
&lt;span class=&quot;token comment&quot;&gt;# Or the shorthand defined in package.json&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; run test:unit&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You might experiment with the &lt;code&gt;--no-threads&lt;/code&gt; flag to see if it makes a difference in the speed of the test suite, but I&#39;ve found it lead to flakyness and I&#39;d rather wait a little bit longer than have false positives on my test runs.&lt;/p&gt;
&lt;h2 id=&quot;generating-a-coverage-report&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://gafemoyano.com/en/posts/integration-testing-remix-routes-with-vitest/#generating-a-coverage-report&quot;&gt;Generating a coverage report&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If everytyhing goes well, you we can run the following command to generate a coverage report:&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token property&quot;&gt;&quot;scripts&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;coverage&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;dotenv -e .env.test -- vitest  run --coverage --no-threads&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will run the test suite and generate a coverage report in the &lt;code&gt;coverage&lt;/code&gt; folder.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://gafemoyano.com/assets/img/articles/coverage.png&quot; alt=&quot;Vitest Coverage Report&quot;&gt;&lt;/p&gt;
&lt;p&gt;As a bonus, since we also defined &lt;code&gt;lcov&lt;/code&gt; as a reporter, we get a really nice html report that we can open in a browser to view an interactive version of the coverage report in our projets root folder.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://gafemoyano.com/assets/img/articles/coverage-html.png&quot; alt=&quot;Vitest Coverage Report&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://gafemoyano.com/en/posts/integration-testing-remix-routes-with-vitest/#conclusion&quot;&gt;Conclusion&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In this article we covered how to setup integration tests for a Remix app using Vitest, React Testing Library and Istanbul for coverage reports. It took me a while to figure out how to go about this, specially rendering the Routes with &lt;code&gt;createRemixStub&lt;/code&gt;. Now that Remix works as a Vite plugin I&#39;d imagine that setting up Playwright or Cypress with coverage is going to be more straightforward, but I haven&#39;t tried it myself and I still think there&#39;s good value in focusing on integration tests rather than e2e for some apps.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Isla Sigma</title>
    <link href="https://gafemoyano.com/en/posts/isla-sigma/" />
    <updated>2022-09-19T15:24:22Z</updated>
    <id>https://gafemoyano.com/en/posts/isla-sigma/</id>
    <content type="html">&lt;p&gt;I&#39;ll probably write this translation &lt;em&gt;some day&lt;/em&gt;. In the meantime, this article is only available in &lt;a href=&quot;https://gafemoyano.com/es/posts/isla-sigma/&quot;&gt;spanish&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>From Tailwind to Vanilla Extract: the right CSS tool for the Design System job</title>
    <link href="https://gafemoyano.com/en/posts/from-tailwind-to-vanilla-extract/" />
    <updated>2021-12-22T00:00:00Z</updated>
    <id>https://gafemoyano.com/en/posts/from-tailwind-to-vanilla-extract/</id>
    <content type="html">&lt;h2 id=&quot;intro&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://gafemoyano.com/en/posts/from-tailwind-to-vanilla-extract/#intro&quot;&gt;Intro&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Recently I came into a new organization that is a good fit for developing it&#39;s own Design System. Although we&#39;re still early on the product development journey and we&#39;re just getting a couple of MVPs of the ground with a small team, we decided it would be worth to extract base components early on that would eventually become the foundation for a full featured Design System down the line.&lt;/p&gt;
&lt;p&gt;The plan was to build the feature screens first and then extract common UI components as they revealed themselves. I&#39;ve found this approach of &amp;quot;build then extract&amp;quot; to be a good balance of extracting base components while allowing the team to move forward with feature work. It does require some willingness to &amp;quot;let some things go&amp;quot;, since there isn&#39;t always time to extract a component right then and there but the added benefit is that you can design the component&#39;s API that&#39;s, ideally, used in a couple different places.&lt;/p&gt;
&lt;h2 id=&quot;extracting-tailwind-components&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://gafemoyano.com/en/posts/from-tailwind-to-vanilla-extract/#extracting-tailwind-components&quot;&gt;Extracting Tailwind Components&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;For our tech stack we chose React and Nextjs with Tailwind as a styling solution. I&#39;d found tailwind to work really well with component UI libraries since it creates a natural boundary for style reuse instead of going down the road of using &lt;code&gt;@apply&lt;/code&gt;. However building components for a design system is quite a different story from building application components. The former tend to need more flexibility: they are often times themable and include lower level utilities for layouts and spacing based on design tokens. While we had some doubts about being able to pull out this transition we felt confident enough to keep using tailwind to get things of the ground and figure it out down the road. Some encouraging &lt;a href=&quot;https://www.netlify.com/blog/2021/03/23/from-semantic-css-to-tailwind-refactoring-the-netlify-ui-codebase/&quot;&gt;words&lt;/a&gt; from the &lt;a href=&quot;https://www.netlify.com/&quot;&gt;Netlify&lt;/a&gt; team also reassured us that the idea wasn&#39;t too crazy after all.&lt;/p&gt;
&lt;p&gt;However as we started extracting out some components we started bumping into some difficulties with the utility classes approach. As a disclaimer, I think tailwind is fine if you&#39;re extracting components inside your own app and are keeping the API of each component fairly restricted but since we were looking to extract a &lt;strong&gt;design system&lt;/strong&gt; the constraints are a bit different. That said, let&#39;s dive into some of the problems we found.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It&#39;s awkward to map tailwind classes to props&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The first decision that we bumped into is how we should expose styling props for some components. For example imagine a component that exposes background property that accepts a color from your palette. Do consumers of the component pass in the class that contains the style or a string with the color value?. The former feels a bit odd, since you&#39;d be repeating yourself by using the class &lt;code&gt;background=&amp;quot;bg-blue-400&amp;quot;&lt;/code&gt;. The latter would take in the value, such as &lt;code&gt;blue-400&lt;/code&gt; and the component would rebuild the appropriate class dynamically which brings it&#39;s own set of issues which we&#39;ll discuss later.&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// This feels odd&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Card&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; background &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;bg-white&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;rest &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;div className&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;background&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;rest&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// This needs more work for every property&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Card&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; background &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;white&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;rest &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; backgroundClass &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;bg-&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;background&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// No type safety&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;div className&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;topSpace&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;rest&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is also the case for any other property that the component might expose and can get overwhelming pretty quickly.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;You have to keep the Tailwind config in sync with component props&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;There&#39;s no real integration between React and Tailwind, meaning that they&#39;re not &lt;strong&gt;aware&lt;/strong&gt; of each other, so every time you add or remove a value from Tailwind&#39;s config you have to remember to update or expose the corresponding props in your components. It&#39;s easy to forget this step and end up with out of date props and styles.&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// tailwind.config.js&lt;/span&gt;
module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;exports &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;extend&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;spacing&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// New values&lt;/span&gt;
      &lt;span class=&quot;token number&quot;&gt;72&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;18rem&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token number&quot;&gt;84&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;21rem&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Must update types that map to the new values&lt;/span&gt;
type SpacingProps &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// New values&lt;/span&gt;
  &lt;span class=&quot;token number&quot;&gt;72&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; number&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token number&quot;&gt;84&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; number&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Spacer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; top &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;top&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; SpacingProps &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;div className&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;topSpace&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;rest&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Dynamically generating classNames don&#39;t get generated with JIT&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Previously we discussed the awkwardness of interpolating values into strings to generate tailwind classes. Another issue is that there&#39;s no way for the JIT to know if a certain class needs to be generated or not, so you might get production bundles that don&#39;t include the classes that you&#39;re expecting. This might be confusing at first but the easy way to get around it is to whitelist the classes you want to be included in the bundle.&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Spacer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; top &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;16&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;rest &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; topSpace &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;mt-&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;top&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// mt-16 will be absent on the prod bundle since PurgeCSS couldn&#39;t find it&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;div className&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;topSpace&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;rest&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Deduping and CSS specificity issues&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;When it comes to overriding base styles for a component or ensuring that you don&#39;t apply 2 different classes that target the same CSS property you&#39;re on your own. Generally components in a Design System will have some default classes and allow expose some props to modify their appearance/behaviour. It&#39;s ultimately up to you to write the conditional logic that makes this work and it can get a bit hairy in some cases.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Sometimes you need to break out of tailwind&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;There&#39;s a good chance that you&#39;ll get to a point where you need to implement something outside of tailwind&#39;s utilities. The alternatives here are to use plain old CSS with &lt;code&gt;@apply&lt;/code&gt; so you can keep using your tokens. This works fine but I&#39;ve found that since &lt;code&gt;className&lt;/code&gt; is already overloaded with utilities it&#39;s easy to miss when and where custom CSS was used which is something I&#39;d rather see stand out in the code as an implementor.&lt;/p&gt;
&lt;h2 id=&quot;switching-to-vanilla-extract&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://gafemoyano.com/en/posts/from-tailwind-to-vanilla-extract/#switching-to-vanilla-extract&quot;&gt;Switching to Vanilla Extract&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Given the previous points we felt that we needed to stop bending tailwind backwards and use a different underlying tool to power our growing component library. Ideally we&#39;d go for something that allows the use of design tokens directly as props. Vanilla Extract provides just with it&#39;s Sprinkles addon plust it comes with good Typescript support.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The switch&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We decided to do the switch progressively, converting one component at a time as we needed to add functionality. Setting up the library was somewhat easy but it was helpful to see a couple reference implementations from &lt;a href=&quot;https://github.com/Shopify/foundational-design-system-proto&quot;&gt;Shopify&#39;s Polaris&lt;/a&gt; and &lt;a href=&quot;https://github.com/seek-oss/braid-design-system&quot;&gt;Seek&#39;s Braid&lt;/a&gt; given that you need to do some plumbing in order to expose your atomic classes as props.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The good things&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;First class Typescript support gives you autocomplete for consumers and type errors for authors. This added an extra layer of confidence for the team to continue growing the Design System while avoiding unintentionally breaking application components.&lt;/p&gt;
&lt;p&gt;The mental model while working with Vanilla Extract is a bit different than Tailwind. While it provides atomic classes it doesn&#39;t expect you to write your whole application with it. Thus, it feels more naturally to drop down to &lt;code&gt;css in ts&lt;/code&gt; and finish the job. You still get to mix and match regular CSS properties with your atomic classes and it all gets extracted to static CSS at buildtime.&lt;/p&gt;
&lt;p&gt;Defining low level components proved relatively simple, which in turn could be composed into more complex ones. &lt;a href=&quot;https://github.com/TheMightyPenguin/dessert-box&quot;&gt;Dessert Box&lt;/a&gt; was a great starting point to map your theme variables to a &lt;code&gt;Box&lt;/code&gt; component that could later be used as basis for components such as &lt;code&gt;Flex&lt;/code&gt;, &lt;code&gt;Center&lt;/code&gt;, &lt;code&gt;Grid&lt;/code&gt;, &lt;code&gt;Cards&lt;/code&gt;, &lt;code&gt;Button&lt;/code&gt; and more.&lt;/p&gt;
&lt;p&gt;These components could be used to develop new features or to reimplement existing ones extracted with Tailwind. Since Vanilla Extract has first class variant support we could simplify a lot of conditional code to apply classes according to props. If a component get&#39;s particularly convoluted &#39;css.ts&#39; files provide an obvious place to write styling logic.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The not so good things&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Previously I listed dealing PurgeCSS when creating classNames dynamically as a problem. That&#39;s not really fair in this comparison since Vanilla Extract doesn&#39;t do any purging at all. But Vanilla Extract doesn&#39;t &lt;strong&gt;want you&lt;/strong&gt; to map as much of CSS to utilities, so while in theory you could end up with unused CSS it&#39;s unlikely this will become a problem in practice.&lt;/p&gt;
&lt;p&gt;While one of the initial value propositions of Vanilla Extract is that CSS in TS is closer to CSS than other approaches it&#39;s early to tell if this will be the case. So far I find that &lt;code&gt;css.ts&lt;/code&gt; files tend to feel more like typescript files than style sheets. This is fine for developers and you get to have the full power of typescript at your disposal, but ideally you&#39;d want designers to share ownership of this files and they might feel a bit daunting at first:&lt;/p&gt;
&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// A sample css.ts file.&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; step &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;styleVariants&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  active&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    color&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;white&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    background&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; vars&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;colors&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;purple-500&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  disabled&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    color&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; vars&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;colors&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;gray-400&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    background&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; vars&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;colors&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;gray-50&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; icon &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    transition&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;transform 75ms ease-in&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;atoms&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    height&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;6&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    width&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;6&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; openAnimation &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  transform&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;rotate(180deg)&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You&#39;re restricted to one kind of variant per property. In tailwind you can get multiple kind&#39;s of variants for a single property. Think of how you can have &lt;code&gt;hover:text-sm&lt;/code&gt; and &lt;code&gt;md:text-lg&lt;/code&gt; at the same time. On Vanilla Extract + Sprinkles, as far as I can tell, you can only have one type of variant per property. This is fine 90% of the time and, as I mentioned before, since Vanilla Extract doesn&#39;t encourage to have every single style as an atomic property, it&#39;s best to pull out a &lt;code&gt;css.ts&lt;/code&gt; if you&#39;re looking for multiple modifiers, but I do miss it some times.&lt;/p&gt;
&lt;h2 id=&quot;takeaways&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://gafemoyano.com/en/posts/from-tailwind-to-vanilla-extract/#takeaways&quot;&gt;Takeaways&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This article isn&#39;t by any means meant to bash on Tailwind. While it was a very real possibility that it wasn&#39;t going to be the right fit to build a Component Library it was still a good way to get the ball rolling given the team&#39;s size and constraints . I think it&#39;s valuable to write about the experience of hitting some of the walls that you might encounter with these libraries in the context of building a production application since I find that most articles focus on shallow use cases that don&#39;t give you the full picture in order to understand some of the trade offs of a library.&lt;/p&gt;
&lt;p&gt;On a personal note, I am very impressed by Vanilla Extract and I feel like I&#39;ll be reaching for it more often on projects that require a component based approach, while keeping tailwind as my preferred solution for server side templates. I&#39;m particularly excited to see how Web Components continue to mature and I think Vanilla Extract, being statically extracted at build time, fits into that ecosystem.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Tailwind: more than a CSS library</title>
    <link href="https://gafemoyano.com/en/posts/tailwind-more-than-a-css-library/" />
    <updated>2021-12-20T00:00:00Z</updated>
    <id>https://gafemoyano.com/en/posts/tailwind-more-than-a-css-library/</id>
    <content type="html">&lt;h2 id=&quot;intro&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://gafemoyano.com/en/posts/tailwind-more-than-a-css-library/#intro&quot;&gt;Intro&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I&#39;ve been a fan of &lt;a href=&quot;https://tailwindcss.com&quot;&gt;Tailwind&lt;/a&gt; for a while now and it seems that it has recently exploded in popularity. It&#39;s a great framework and I&#39;m happy to see it succeed given how much value I&#39;ve gotten out of it. Plenty of articles have been written about the good parts (and downsides) of using Tailwind from the technical point of view, however, some of the reasons I hold Tailwind in such a high regard go beyond the technical aspect of it, I&#39;d say they&#39;re even emotional and I&#39;d like to share them in this article.&lt;/p&gt;
&lt;h2 id=&quot;how-it-first-came-to-me&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://gafemoyano.com/en/posts/tailwind-more-than-a-css-library/#how-it-first-came-to-me&quot;&gt;How it first came to me&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;On 2016 I started developing a new consumer product along with my partners at &lt;a href=&quot;https://savy.co&quot;&gt;Savy&lt;/a&gt;. At the time I didn&#39;t really &lt;em&gt;know&lt;/em&gt; how to make a site look nice, but I knew that Bootstrap was a good starting point for typography, colors and a few components. Naturally, I quickly bumped into use cases where I wanted to change break out a bit from the default styles, specially with components such as the navbar, cards, etc. To my delight, bootstrap included helper utilities that you could throw in the HTML like &lt;code&gt;mb-5&lt;/code&gt;, &lt;code&gt;px-4&lt;/code&gt;, &lt;code&gt;bg-primary&lt;/code&gt; to personalize the default components. In fact, just with a few utilities for padding, positioning, display, and color I realized that &lt;code&gt;80%&lt;/code&gt; of what I needed to do was achievable with these utilities, right in my HTML.&lt;/p&gt;
&lt;p&gt;I was hooked on utility classes from that point on, but &lt;code&gt;80%&lt;/code&gt; wasn&#39;t enough, I wanted to have more of them which eventually lead me to Tailwind v0.7(IIRC). Thus it began one of my longest lasting relationship&#39;s with a library. But there was something bigger in store for me behind that library, something that changed my career for the better, I just didn&#39;t know it at the time. It was also the beginning of my love for Web Design.&lt;/p&gt;
&lt;h2 id=&quot;beyond-a-css-library&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://gafemoyano.com/en/posts/tailwind-more-than-a-css-library/#beyond-a-css-library&quot;&gt;Beyond a CSS library&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;There&#39;s a lot of work that was put behind Tailwind as a library but there&#39;s just as much, if not more, work put around it&#39;s ecosystem and development. I&#39;m talking about &lt;a href=&quot;https://tailwindcss.com/&quot;&gt;the awesome docs&lt;/a&gt;, &lt;a href=&quot;https://www.youtube.com/c/AdamWathan&quot;&gt;the streams&lt;/a&gt; implementing UIs with the library, &lt;a href=&quot;https://twitter.com/i/events/879086180909764608?lang=en&quot;&gt;the design tips&lt;/a&gt;, &lt;a href=&quot;https://www.refactoringui.com/book&quot;&gt;the book&lt;/a&gt; on design for developers, and more. All of these added up to an ecosystem that made it easy to not only succeed with the library but also learn Web Design as a whole at the same time.&lt;/p&gt;
&lt;h3 id=&quot;it-taught-me-css-basics&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://gafemoyano.com/en/posts/tailwind-more-than-a-css-library/#it-taught-me-css-basics&quot;&gt;It taught me CSS basics&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I&#39;d been using CSS for years but I don&#39;t think I&#39;d ever truly &lt;em&gt;understood&lt;/em&gt; it up to that point. Seriously, I have to thank the Tailwind docs for finally making &lt;code&gt;flexbox&lt;/code&gt; click in my brain (and I don&#39;t think I&#39;m alone here). The pictures along with the code samples were so helpful in understanding the different properties at play in a flex container. It wasn&#39;t an overnight process, but the way tailwind forces you to think in primitives made it so that I had to learn the properties in order to implement a layout.&lt;/p&gt;
&lt;p&gt;This was a turning point for me. I could see a design and decompose in my mind the individual properties that would need to be implemented. Suddenly a whole new world of creative exploration was available to me.&lt;/p&gt;
&lt;h3 id=&quot;it-helped-me-take-ownership-of-application-styles&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://gafemoyano.com/en/posts/tailwind-more-than-a-css-library/#it-helped-me-take-ownership-of-application-styles&quot;&gt;It helped me take ownership of application styles&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I used to feel very insecure when editing the stylesheets of an existing application. It wasn&#39;t just lack of knowledge but also that the way styles were written changing a property could lead to very unexpected results. So I&#39;d generally feel like I was guessing and then hoping things wouldn&#39;t break. With Tailwind, even though elements with lots of classes might be difficult to parse specially at first, you &lt;em&gt;know&lt;/em&gt; that it&#39;s all scoped to that single element. You&#39;re free to change everything up without fear of breaking other parts of the layout.&lt;/p&gt;
&lt;h3 id=&quot;it-made-me-more-productive&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://gafemoyano.com/en/posts/tailwind-more-than-a-css-library/#it-made-me-more-productive&quot;&gt;It made me more productive&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I greatly value how the Tailwind workflow enables you to implement designs without having to jump between files. The quick feedback loop is great for iteration, if you&#39;re experimenting, or for implementing an existing design from a tool like figma. It also removes the step of having to come up with names for every HTML element and lets you focus just on the styling. While the markup may get pretty crowded and might be difficult to navigate you&#39;re encouraged to extract components or partials which let you focus on one piece of your design at a time.&lt;/p&gt;
&lt;h3 id=&quot;it-taught-me-(web)-design-fundamentals&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://gafemoyano.com/en/posts/tailwind-more-than-a-css-library/#it-taught-me-(web)-design-fundamentals&quot;&gt;It taught me (web) design fundamentals&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;This is part of Tailwind&#39;s ecosystem rather than the library itself, but following Steve&#39;s design tips on &lt;a href=&quot;https://twitter.com/i/events/994601867987619840&quot;&gt;twitter&lt;/a&gt; helped me understand design basics for building UIs. On top of that Adam&#39;s screencasts on &lt;a href=&quot;https://www.youtube.com/c/AdamWathan&quot;&gt;youtube&lt;/a&gt; were a terrific walk-through on how to implement a designer&#39;s vision with CSS. By making design fundamentals more approachable a new world was opened up to me, which ended up playing a big role in my career as a web developer and eventually as an entrepreneur.&lt;/p&gt;
&lt;h3 id=&quot;it-showed-me-the-value-of-a-design-system&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://gafemoyano.com/en/posts/tailwind-more-than-a-css-library/#it-showed-me-the-value-of-a-design-system&quot;&gt;It showed me the value of a design system&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Okay kind of, this one is a bit of a stretch. A Design System goes beyond tokens and the way the framework has grown since the 0.7 days when you could call the limiting set of utilities an advantage to enforce consistency. Yet, Tailwind does point you in the right direction: tokenizing spacing scales, colors, borders, shadows and typography set you up for success specially with the great defaults given by the library itself. I don&#39;t consider Tailwind to be a Design System by itself (and it&#39;s not one of the libraries objectives) but it is a stepping stone for creating one.&lt;/p&gt;
&lt;h2 id=&quot;the-future&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://gafemoyano.com/en/posts/tailwind-more-than-a-css-library/#the-future&quot;&gt;The future&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I&#39;ll probably keep using Tailwind on my personal projects. It keeps pushing it&#39;s own boundaries and it&#39;s been an interesting journey to discover what&#39;s possible beyond the idea of utility classes and atomic CSS. But this space is in constant flux, and new paradigms emerge everyday as the web evolves. CSS variables have already unlocked some new approaches such as &lt;a href=&quot;https://vanilla-extract.style&quot;&gt;Vanilla Extract&lt;/a&gt; and &lt;a href=&quot;https://stitches.dev&quot;&gt;Stitches&lt;/a&gt; which I&#39;m excited to explore for building Design Systems. It is, after all, still an exciting time for being a web developer.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>UUIDs with Action Text and Active Storage</title>
    <link href="https://gafemoyano.com/en/posts/uuids-with-action-text-and-active-storage/" />
    <updated>2021-04-11T00:00:00Z</updated>
    <id>https://gafemoyano.com/en/posts/uuids-with-action-text-and-active-storage/</id>
    <content type="html">&lt;p&gt;In this article we&#39;ll learn how to use UUIDs as default primary key for a new Rails application using Postgres, Active Storage and Action Text.&lt;/p&gt;
&lt;p&gt;Some time ago Rails shook the web development world by showing us the power of relying on convention over configuration. One of these conventions was that every table would have an ID column as primary key, and it would be an auto incremental integer. At the time, it was a very welcomed change. We no longer had to spend time thinking about what to name our primary key columns, if our tables should be named in singular or plural form or if we should use natural or surrogate keys on our tables.&lt;/p&gt;
&lt;p&gt;At the time, Rails chose the default to give a surrogate key to every model and make it an incremental ID. Back then, this was mostly taken as a good practice by the community and it&#39;s still probably a good enough default for new apps, but nowadays you could make an argument for UUIDs as a better default given:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;They don&#39;t expose information about your system&lt;/li&gt;
&lt;li&gt;Accidentally accessing the wrong model by passing it another model&#39;s ID&lt;/li&gt;
&lt;li&gt;Frontend independence to create new IDs without a database roundtrip&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It&#39;s also worth noting the things that we&#39;d be giving up by going all in on UUIDs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Convenience to access a particular model in production&lt;/li&gt;
&lt;li&gt;Logic for handling special cases (which you probably shouldn&#39;t be doing in your apps but it does happen often enough)&lt;/li&gt;
&lt;li&gt;A very small (but not negligible) chance of key clashes.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Taking into account the previous considerations, I think it&#39;s worth it for me to go with UUIDs as default for my future Rails applications. I&#39;m currently working on a job board app and I&#39;d like to allow users to edit job listings by clicking on a magic link sent to their E-mail instead of having to create an account and login. Using the default edit action with a sequential ID was out of the question since it could be guessed easily. With a UUID, I&#39;d practically get this feature for free without having to write any extra code.&lt;/p&gt;
&lt;h2 id=&quot;making-uuids-the-new-default&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://gafemoyano.com/en/posts/uuids-with-action-text-and-active-storage/#making-uuids-the-new-default&quot;&gt;Making UUIDs the new default&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;There&#39;s already a &lt;a href=&quot;https://pawelurbanek.com/uuid-order-rails&quot;&gt;great article&lt;/a&gt; covering the steps to setup your models with UUIDs on a Rails 6 app and PostgreSQL. I recommend checking it out if you want to know in depth what&#39;s going on to make this work, or if you&#39;re trying to migrate an existing table with data. For completeness, I&#39;ll add the steps here to get us going before moving onto the ActionText and ActiveStorage bits.&lt;/p&gt;
&lt;p&gt;Let&#39;s start by enabling the &lt;code&gt;pgcrypto&lt;/code&gt; extension on Postgres. Create a new migration with&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;bin/rails g migration enable_pgcrypto&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;EnablePgcypto&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; ActiveRecord&lt;span class=&quot;token double-colon punctuation&quot;&gt;::&lt;/span&gt;Migration&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;6.0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;change&lt;/span&gt;&lt;/span&gt;
    enable_extension &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;pgcrypto&#39;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, we configure the rails generators to use UUIDs by default.&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;Rails&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;application&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;config&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;generators &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;g&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;
  g&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;orm &lt;span class=&quot;token symbol&quot;&gt;:active_record&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;primary_key_type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:uuid&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;From Rails 6 a new configuration option was introduced to control the default ordering column in ActiveRecord. Normally it uses IDs for ordering which would be inconsistent with random UUIDs and could break methods like &lt;code&gt;last&lt;/code&gt; and &lt;code&gt;first&lt;/code&gt;. Given that we want to make all tables use UUIDs by default, we can add the following to &lt;code&gt;ApplicationRecord&lt;/code&gt; to make the change global.&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ApplicationRecord&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; ActiveRecord&lt;span class=&quot;token double-colon punctuation&quot;&gt;::&lt;/span&gt;Base
  &lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;abstract_class &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;implicit_order_column &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;created_at&quot;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If everything is wired up correctly, your generators should now pickup the new default. For example, after running the following command to create a new Listing model for my Jobs:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;bin/rails generate model Listing job:references published:boolean&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The resulting migration should look like this (notice the &lt;code&gt;:uuid&lt;/code&gt; tags on the table and the reference)&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CreateListings&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; ActiveRecord&lt;span class=&quot;token double-colon punctuation&quot;&gt;::&lt;/span&gt;Migration&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;6.1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;highlight-line&quot;&gt;  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;change&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;highlight-line&quot;&gt;    create_table &lt;span class=&quot;token symbol&quot;&gt;:listings&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:uuid&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;t&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;&lt;/span&gt;
&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;      t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;references &lt;span class=&quot;token symbol&quot;&gt;:job&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;foreign_key&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:uuid&lt;/span&gt;&lt;/mark&gt;
&lt;span class=&quot;highlight-line&quot;&gt;      t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;boolean &lt;span class=&quot;token symbol&quot;&gt;:published&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;highlight-line&quot;&gt;      t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;timestamps&lt;/span&gt;
&lt;span class=&quot;highlight-line&quot;&gt;    &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;highlight-line&quot;&gt;  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can see, the migration adapters need to explicitly define that they&#39;ll be using &lt;code&gt;uuid&lt;/code&gt; as primary key. It&#39;s easy to forget to add the extra arguments if you&#39;re doing migrations manually, so I generally try to always run the generators to avoid these kind of manual errors.&lt;/p&gt;
&lt;h2 id=&quot;installing-active-storage&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://gafemoyano.com/en/posts/uuids-with-action-text-and-active-storage/#installing-active-storage&quot;&gt;Installing Active Storage&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Now that we&#39;ve changed our defaults, we can move on to installing Active Storage. If you want to read the full details of the setup, I recommend checking out the official rails guides &lt;a href=&quot;https://edgeguides.rubyonrails.org/active_storage_overview.html&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The first step is to run the following command:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;bin/rails active_storage:install&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will generate a couple of migration tables. We now need to adjust the ActiveStorage migrations to look like the one on our listing model, making sure we adjust the table&#39;s &lt;code&gt;id&lt;/code&gt; type and the references as well:&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CreateActiveStorageTables&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; ActiveRecord&lt;span class=&quot;token double-colon punctuation&quot;&gt;::&lt;/span&gt;Migration&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;5.2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;highlight-line&quot;&gt;  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;change&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;highlight-line&quot;&gt;    create_table &lt;span class=&quot;token symbol&quot;&gt;:active_storage_blobs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:uuid&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;t&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;&lt;/span&gt;
&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;      t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;string   &lt;span class=&quot;token symbol&quot;&gt;:key&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;        &lt;span class=&quot;token symbol&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;/mark&gt;
&lt;span class=&quot;highlight-line&quot;&gt;      t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;string   &lt;span class=&quot;token symbol&quot;&gt;:filename&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;   &lt;span class=&quot;token symbol&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;highlight-line&quot;&gt;      t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;string   &lt;span class=&quot;token symbol&quot;&gt;:content_type&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;highlight-line&quot;&gt;      t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;text     &lt;span class=&quot;token symbol&quot;&gt;:metadata&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;highlight-line&quot;&gt;      t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;bigint   &lt;span class=&quot;token symbol&quot;&gt;:byte_size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;  &lt;span class=&quot;token symbol&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;highlight-line&quot;&gt;      t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;string   &lt;span class=&quot;token symbol&quot;&gt;:checksum&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;   &lt;span class=&quot;token symbol&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;highlight-line&quot;&gt;      t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;datetime &lt;span class=&quot;token symbol&quot;&gt;:created_at&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;highlight-line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;highlight-line&quot;&gt;      t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;index &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:key&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;unique&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;highlight-line&quot;&gt;    &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;highlight-line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;highlight-line&quot;&gt;    create_table &lt;span class=&quot;token symbol&quot;&gt;:active_storage_attachments&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:uuid&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;t&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;&lt;/span&gt;
&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;      t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;string     &lt;span class=&quot;token symbol&quot;&gt;:name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;     &lt;span class=&quot;token symbol&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;/mark&gt;
&lt;span class=&quot;highlight-line&quot;&gt;      t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;references &lt;span class=&quot;token symbol&quot;&gt;:record&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;   &lt;span class=&quot;token symbol&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;polymorphic&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;index&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:uuid&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;highlight-line&quot;&gt;      t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;references &lt;span class=&quot;token symbol&quot;&gt;:blob&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;     &lt;span class=&quot;token symbol&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:uuid&lt;/span&gt;&lt;/span&gt;
&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;&lt;/mark&gt;
&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;      t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;datetime &lt;span class=&quot;token symbol&quot;&gt;:created_at&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;/mark&gt;
&lt;span class=&quot;highlight-line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;highlight-line&quot;&gt;      t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;index &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:record_type&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:record_id&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:blob_id&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;index_active_storage_attachments_uniqueness&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;unique&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;highlight-line&quot;&gt;      t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;foreign_key &lt;span class=&quot;token symbol&quot;&gt;:active_storage_blobs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;column&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:blob_id&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;highlight-line&quot;&gt;    &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;highlight-line&quot;&gt;  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;installing-action-text&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://gafemoyano.com/en/posts/uuids-with-action-text-and-active-storage/#installing-action-text&quot;&gt;Installing Action Text&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Same story for Action Text. Let&#39;s run the installation script and modify the corresponding migration.&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;bin/rails action_text:install&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CreateActionTextTables&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; ActiveRecord&lt;span class=&quot;token double-colon punctuation&quot;&gt;::&lt;/span&gt;Migration&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;6.0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;highlight-line&quot;&gt;  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;change&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;highlight-line&quot;&gt;    create_table &lt;span class=&quot;token symbol&quot;&gt;:action_text_rich_texts&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:uuid&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;t&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;highlight-line&quot;&gt;      t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;string     &lt;span class=&quot;token symbol&quot;&gt;:name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;highlight-line&quot;&gt;      t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;text       &lt;span class=&quot;token symbol&quot;&gt;:body&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:long&lt;/span&gt;&lt;/span&gt;
&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;      t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;references &lt;span class=&quot;token symbol&quot;&gt;:record&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;polymorphic&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;index&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:uuid&lt;/span&gt;&lt;/mark&gt;
&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;&lt;/mark&gt;
&lt;span class=&quot;highlight-line&quot;&gt;      t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;timestamps&lt;/span&gt;
&lt;span class=&quot;highlight-line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;highlight-line&quot;&gt;      t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;index &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:record_type&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:record_id&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:name&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;index_action_text_rich_texts_uniqueness&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;unique&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;highlight-line&quot;&gt;    &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;highlight-line&quot;&gt;  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Don&#39;t forget to follow the rest of the installation steps for Action Text &lt;a href=&quot;https://edgeguides.rubyonrails.org/action_text_overview.html#installation&quot;&gt;here&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;closing-thoughts&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://gafemoyano.com/en/posts/uuids-with-action-text-and-active-storage/#closing-thoughts&quot;&gt;Closing thoughts&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I hope this will help you get up and running on your new Rails app with UUIDs, Action Text and Active Storage. It&#39;s never been a better time to spin off new Rails apps and I&#39;ve never felt more productive using the framework. It might have some opinions you don&#39;t agree with, but they&#39;re fairly easy to overwrite and replace with your own. As always, let me know if you try this out and how it went for you on (twitter)[&lt;a href=&quot;https://twitter.com/gafemoyano&quot;&gt;https://twitter.com/gafemoyano&lt;/a&gt;] or writing to &lt;a href=&quot;mailto:felipe@gafemoyano.com&quot;&gt;felipe@gafemoyano.com&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Not Every Bug is Worth Fixing</title>
    <link href="https://gafemoyano.com/en/posts/not-every-bug-is-worth-fixing/" />
    <updated>2021-02-27T00:00:00Z</updated>
    <id>https://gafemoyano.com/en/posts/not-every-bug-is-worth-fixing/</id>
    <content type="html">&lt;p&gt;On the early days of Savy, as we were building up the technology team and I did most of the coding, I took a very aggressive stance against fixing bugs: whenever someone asked me if I could fix it, my default answer would be &lt;strong&gt;no&lt;/strong&gt;. This came as a shock to my co-founders&#39;.&lt;/p&gt;
&lt;p&gt;The conversation would go something like this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;What do you mean you&#39;re not going to fix it? SOMEBODY just sent me a screenshot of an error, it mu st be fixed! We can&#39;t let this happen again!&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Why?&lt;/em&gt; I would respond, &lt;em&gt;it&#39;s just that one person. Let&#39;s wait and see if it happens again to a couple more people then we can see if it&#39;s &lt;strong&gt;worth&lt;/strong&gt; fixing.&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I&#39;d say that 90% of the time it wasn&#39;t. The bug did exist, but it probably wasn&#39;t on a hot path on the application. It just wasn&#39;t &lt;strong&gt;that&lt;/strong&gt; bad. Maybe it would come up later and get fixed down the line if it, but I certainly didn&#39;t need to look at it right away.&lt;/p&gt;
&lt;p&gt;As a founder, it was easy for me to say &lt;strong&gt;no&lt;/strong&gt; to bugs and challenge the status quo. But as the team grew, I wanted them to have the same benefit. Sure, we established a process around it, but as a general rule &lt;strong&gt;no bug was urgent&lt;/strong&gt;. I believe this allowed us to focus on bigger, more ambitious projects even if it meant that bugs had to wait a bit more to get fixed, or maybe didn&#39;t get fixed at all.&lt;/p&gt;
&lt;p&gt;In my experience, people tend to treat all bugs as emergencies. But really, few of them are and even fewer require an immediate response. I believe there&#39;s two arguments to support that claim. The first one is that fixing bugs is &lt;em&gt;expensive&lt;/em&gt; for you and your team, probably much more than you realize. The second one, is that &lt;strong&gt;it&#39;s probably not that big of a deal&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id=&quot;a-general-misunderstanding-of-bugs&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://gafemoyano.com/en/posts/not-every-bug-is-worth-fixing/#a-general-misunderstanding-of-bugs&quot;&gt;A general misunderstanding of bugs&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;When you&#39;re developing a digital product there&#39;s going to be bugs. It&#39;s just a &lt;a href=&quot;https://m.signalvnoise.com/software-has-bugs-this-is-normal/&quot;&gt;fact about software&lt;/a&gt;. But somehow, we&#39;ve set unreal expectations to software quality. We act like if every bug is outrageous, and that it should be fixed as soon as possible if not sooner.&lt;/p&gt;
&lt;p&gt;But what is a bug anyways? I think the definition is not so straight forward and to make matters worse, it might depend on your position. The same definition might not apply if you&#39;re a developer, a QA engineer or a Product Manager. I believe this general misunderstanding comes from the current industry trend to create specialized silos of expertise, instead of having multidisciplinary groups responsible for a part of the product or feature. This lack of a general definition makes it hard to agree on the severeness of a bug and the corresponding tradeoffs every role views them differently and measuring a different metric as KPI. Even worse, sometimes that metric has nothing to do with the scope of the project.&lt;/p&gt;
&lt;p&gt;If you&#39;re building a new product then you&#39;re likely putting out new features to complete your vision, or trying out different ideas while your company finds market fit. This means you&#39;re writing more code which means you&#39;re introducing more bugs. And that&#39;s okay! it&#39;s part of the process, there&#39;s no point in achieving a perfectly correct piece of engineering if you haven&#39;t validated your product&#39;s market. What matters is that you&#39;re making progress on solving your user&#39;s needs and providing them value. But no one on the team should be losing sleep over an ever growing backlog of bugs.&lt;/p&gt;
&lt;p&gt;There will come times, when a particularly bad bug breaks an important part of the application and it will be a real emergency that calls for a &lt;strong&gt;code red all hands on&lt;/strong&gt; type of response. But this emergencies are &lt;strong&gt;rare&lt;/strong&gt;. You can&#39;t go into panic response every time a piece of javascript code crashes a user&#39;s application.&lt;/p&gt;
&lt;p&gt;In short not all bugs are equal. Some of them occur on very specific circumstances or to a very small set of users. Other&#39;s live in poorly frequented workflows or low impact features. So instead of panicking, wait. Check your dashboards, review your data. Chances are most of your users are getting by with their day just fine.&lt;/p&gt;
&lt;h2 id=&quot;the-real-cost-of-fixing-a-bug&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://gafemoyano.com/en/posts/not-every-bug-is-worth-fixing/#the-real-cost-of-fixing-a-bug&quot;&gt;The real cost of fixing a bug&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In general, people tend to underestimate &lt;strong&gt;the cost of interruption and context switching&lt;/strong&gt;. This is true for meetings, calls, instant messaging, and incidentally bug tickets. Reflect on how much it takes you to build up context and concentration when you&#39;re working on a task that requires deep focus. Writing a sensitive email, coding, designing a user interface. For most people, I&#39;d say it takes at least 30 minutes to build up context on your task and pick up on where you left off, and at least another 30 minutes to get into your flow and start making progress. If by the time you manage to start being productive someone pokes you on the shoulder or over slack to go pay attention to something else, then that concentration build up didn&#39;t amount to much. Next time, you&#39;ll have to do it again. Imagine this happens a couple of times per week, if not more. If this is the case, then it wouldn&#39;t seem very surprising that you or your team are constantly behind schedule.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Why should bugs cut the line?&lt;/strong&gt; If you spend time carefully considering on what next big project comes next, prioritizing work and setting ambitious objectives, why throw it all out of the window because of bugs? What makes them more important than the other work you&#39;ve lined up? Bugs are work that need to be done by someone. As such, they should be weighted in with other tasks instead of cutting the queue by default.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Bugs can lead down to rabbit holes.&lt;/strong&gt; A lot of times it&#39;s not obvious to pin point the root cause of the problem let alone a way to fix it. There might not even be a quick fix available once you manage to track down the problem. Keep this in mind next time you have an urge to go down the whole and chace a bug down. At the very least, I&#39;d recommend setting strong time boundaries for diagnostics work.&lt;/p&gt;
&lt;h2 id=&quot;it&#39;s-about-company-culture&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://gafemoyano.com/en/posts/not-every-bug-is-worth-fixing/#it&#39;s-about-company-culture&quot;&gt;It&#39;s about company culture&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;When it comes to having good team dynamics and organizationonal culture, there&#39;s &lt;a href=&quot;http://worrydream.com/refs/Brooks-NoSilverBullet.pdf&quot;&gt;no silver bullet&lt;/a&gt;. In my opinion, teams should experiment with different methodologies and practices and find what works best. Consequently, most of the learning in this article are derived from my own experience on start up product companies so that&#39;s the environment where they&#39;re likely to make most sense. However, I would dare to say these applies to broader engineering teams. If we want teams to be productive and deliver on time, they must be able to execute without being constantly distracted. A team that is working on challenging problems and making progress towards solving them is likely to stay a motivated team.&lt;/p&gt;
&lt;p&gt;Having a company culture where every bug or ticket has a timer on it and must be solved ASAP ultimately drive&#39;s down a team&#39;s productivity and is detrimental to an organization. Let&#39;s explore some of the reasons why.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It sets the unrealistic expectations for engineering teams.&lt;/strong&gt; At the beginning, it&#39;ll seem pretty harmless to deal with bugs right away. When the code base still fits in one person&#39;s head, it&#39;s a reasonable thing to do without getting lost in the weeds. But it won&#39;t stay that way for long and you&#39;ve now set a precedent of responding immediately. As the code grows in size and complexity and new dependencies are introduced tasks that used to take a couple of minutes now take a couple of hours. Yet the expectation of immediate response will still be there and the team will constantly feel like it&#39;s treading water. The best way to prevent this scenario, is to kindly but firmly say &lt;strong&gt;no&lt;/strong&gt; to this kind of work that&#39;s labeled as urgent. You&#39;ll soon realize, most of the time it wasn&#39;t that urgent after all.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It encourages poor communication&lt;/strong&gt;. Just like there&#39;s a trend for bugs to cut in line on the priority queue they also tend to cut through the proper channels of communication. As a team lead, I&#39;ve encountered more than one time a member of my team working on a bug fix just cause someone from another team asked him to. Founders are particularly guilty of this. Or maybe a person from support asked nicely via direct message to check on something out quickly. I&#39;m not one to advocates for processes over good communication and common sense, but I&#39;ve seen these practices add up to the point were they become a problem, like someone getting &lt;em&gt;abused&lt;/em&gt; because they&#39;re &lt;em&gt;too nice&lt;/em&gt; and never say no. I&#39;m not saying that bugs shouldn&#39;t be reported, on the contrary I&#39;d encourage everyone at a company to report bugs and take the time to do it properly. But let&#39;s get rid of the expectation that reporting it means assigning it to someone and putting a deadline on it. They&#39;ll be dealt with at the right time, which is, as I&#39;ve repeated a few times already, &lt;em&gt;not now&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It&#39;s demoralizing for the team&lt;/strong&gt;. Let&#39;s consider for a secound the anxiety caused by constantly getting notifications for every error in a production environment. In this moderne age we&#39;ve already started to see the psicological effects of consantly having reminders from our inboxes or direct messaging applications. This can lead to feelings of guilt and maybe even depression. I&#39;ve seen organizations where it gets so overwhelming that people have to ignore it in order to keep their focus. Too little signal to noise ratio. Even if your team is able to handle the stream of incoming tickets while getting their schedule tasks done, it&#39;s probably slowing them down tremendously. And even if it isn&#39;t, for how long will they be able to keep it up? It&#39;s not a sustainable strategy on the mid term. There&#39;s a strong correlation between a team&#39;s &lt;a href=&quot;https://www.isixsigma.com/implementation/teams/high-performance-teams-understanding-team-cohesiveness/&quot;&gt;morale&lt;/a&gt; and it&#39;s capacity to &lt;a href=&quot;https://journals.sagepub.com/doi/10.1177/0730888406290054&quot;&gt;deliver&lt;/a&gt;. If the equation breaks down at some point, you&#39;ll end up with an unhappy, unmotivated team.&lt;/p&gt;
&lt;h2 id=&quot;what-about-quality%3F&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://gafemoyano.com/en/posts/not-every-bug-is-worth-fixing/#what-about-quality%3F&quot;&gt;What about quality?&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;By now, I hope I was able to convince you that handling bugs as an emergency that should be addressed ASAP is a &lt;strong&gt;bad idea&lt;/strong&gt;. Saying no to bugs will give you and your team the freedom to focus on delivering value and solving challenges during a product cycle, but they still need to be dealt with at some point. This is not an excuse to ignore them, however, I do believe there&#39;s a smarter way to deal with them. So let&#39;s go through a couple of techniques that are effective when it come&#39;s to bugs. I&#39;d split them into two groups. &lt;strong&gt;Technical safeguards and tactical organizational practices.&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;technical-safeguards&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://gafemoyano.com/en/posts/not-every-bug-is-worth-fixing/#technical-safeguards&quot;&gt;Technical Safeguards&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;These recommendations are mainly concerned with making sure that most of the things are fine for most of the users. This will give you the confidence to say no, when dealing with a bug that doesn&#39;t trip any alarms and peace of mind to keep doing what you&#39;re doing.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Implement end to end tests for your most valuable user flows&lt;/strong&gt;. If I could mention one investment of time that pays itself over tenfold, it would be this one. End to end tests are, generally, not easy to setup nor to maintain. But being able to deploy confidently is worth it and it will become your team&#39;s last line of defense to prevent an error from getting to production in the first place. It may seem obvious, but preventing bugs from getting through in the first place is the cheapest way to deal with them.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Use monitoring tools on your production environment&lt;/strong&gt;. There&#39;s multiple kinds of monitoring tools and dashboards that give you a sense of how things are running. From exception trackers, performance metrics, user reports, among others. These are all useful on their own way so it&#39;s up to each team to decide which ones to adopt. The important part here is not to obsess over every metric or crash report, but rather to look at the big picture and know how your application behaves during normal conditions so you can quickly identify when something is amiss . Furthermore, you&#39;ll have historical data to make decisions. Exception tracking tools, for example, will also keep records of how many users or times each error is being hit, which is very valuable when deciding if action must be taken immediately or it can wait a few days.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Set automatic alarms for when things go really wrong&lt;/strong&gt;. This will get you on your feet when an emergency does happen. Having dealt with emergencies successfully will give you and your team the confidence to not panic when they arrive, and to handle future incidents calmly and promptly. Their also quite easy to set up, so that&#39;s a lot of value for little effort.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Set automatic alarms for when things go really wrong&lt;/strong&gt;. They generally require little effort to set up and they serve a dual purpose. For one, they might prevent an issue from escalating into an emergency such as a server going down due to extreme load. If not, they&#39;ll make sure that your team can react on a timely manner and be the &lt;em&gt;first&lt;/em&gt; ones to know when an emergency arises. This will create trust amongst business areas and give the team the space it&#39;ll need in order to deal with the situation without added preassure.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;organizational-practices-and-processes&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://gafemoyano.com/en/posts/not-every-bug-is-worth-fixing/#organizational-practices-and-processes&quot;&gt;Organizational Practices and Processes&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The following practices are not about technology but rather about processes and how work is organized and expectations set internally as a company. They intend to create a sustainable environment for product teams and establishing clear communication lines.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Define what an emergency is and it&#39;s response.&lt;/strong&gt; As I said before, 95% of the time a bug shouldn&#39;t make you drop what you&#39;re doing. But then there&#39;s the remaining 5%. What does this percentage mean for your product? If you&#39;re upfront about the things that are &lt;em&gt;critical&lt;/em&gt; and will be treated as a &lt;em&gt;code red&lt;/em&gt; in case they fail you&#39;ll get reduce a lot of uncertainty for everyone at the company. Tech teams won&#39;t be troubled by non-emergencies, and management can have the ensurance to know that, when things go wrong, there&#39;s a plan for it.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Let the dust settle&lt;/strong&gt; after releasing new features. Honestly, I blame the adoption of Scrum and two week cycles for this. They&#39;re so short that you barely get anything and then there&#39;s the promise to have a release by the end of it. And right away, another cycle starts on the third week with a new batch of tickets. I think a healthier approach is to have longer and more ambicious cycles, say 4-6 weeks and then leave one or two weeks of &lt;strong&gt;unscheduled work&lt;/strong&gt;. This is a good time to schedule a release, address immediate feedback and &lt;em&gt;settle down&lt;/em&gt;. Let the team catch their breath and the new features to settle in.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Have bug cycles to clean up technical debt.&lt;/strong&gt; I&#39;ve seen this one catching on popularity and it&#39;s quite effective in my experience. After two or three intense cycles working on new features, teams generally welcome the chance to work on bugs or maybe pending tasks that they wouldn&#39;t get to do otherwise. This way you can change up the pace and avoid monotony and it&#39;s a good way to trim the backlogs every now and then.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Promote bugs to scheduled work.&lt;/strong&gt; This is a continuation of the idea that bugs aren&#39;t really special or different from other kinds of work. Some bugs have root causes that are a symptom of a bigger issue like a bad architectural choice a while back, or an external service that&#39;s not quite working as expected. This is usually not a fix for one person to take in the form of a bug ticket, and should rather be scheduled as a project in it&#39;s own right and be put on the table against other ideas for the decision makers to consider the trade-offs and opportunity costs of working on it.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;closing-thoughts&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://gafemoyano.com/en/posts/not-every-bug-is-worth-fixing/#closing-thoughts&quot;&gt;Closing thoughts&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I hope these post will make you consider your view on what a bug is and the best way to deal with them. In the end, it&#39;s all about making great products that our users love and solving real problems for them. So if you feel like you&#39;re already fulfilling that mission, awesome! keep doing what&#39;s working for you. But if you&#39;re feeling like your product is not moving forward, please consider some of the ideas and practices written here. Maybe the way you&#39;re dealing with bugs is slowing you down. So next time you get a bug report, instead of saying &lt;em&gt;give me 15 minutes&lt;/em&gt; you could reply, let&#39;s wait a few hours or until next week. Let&#39;s make sure that this bug is &lt;strong&gt;worth fixing&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;acknowledgements&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://gafemoyano.com/en/posts/not-every-bug-is-worth-fixing/#acknowledgements&quot;&gt;Acknowledgements&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I&#39;d like to thank my great friend Felipe for always being my first reader and critic and my brother David for his timely and accurate feedback. Without them I would hardly be able to organize my ramblings into a coherent article.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>DRY Input Errors in Rails</title>
    <link href="https://gafemoyano.com/en/posts/dry-input-errors-in-rails/" />
    <updated>2021-01-31T00:00:00Z</updated>
    <id>https://gafemoyano.com/en/posts/dry-input-errors-in-rails/</id>
    <content type="html">&lt;p&gt;Spinning up a new Rails app has always been a joy to me. You can quickly scaffold your screens, setup your initial models and go straight to creating value and business logic. However, there&#39;s always a part where I feel like the framework doesn&#39;t quite do the entire job: form validation and error handling. There&#39;s one thing that I find specially difficult, and that is showing validation errors.&lt;/p&gt;
&lt;p&gt;If you run Rails scaffold generator (which is great way to get going fast but not really intended for &lt;em&gt;production&lt;/em&gt; code), the form partial includes this snippet of code:&lt;/p&gt;
&lt;pre class=&quot;language-erb&quot;&gt;&lt;code class=&quot;language-erb&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;error_explanation&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;h2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token erb language-erb&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;&amp;lt;%=&lt;/span&gt;&lt;span class=&quot;token ruby language-ruby&quot;&gt; pluralize&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;model&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;errors&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;count&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;error&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;/span&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt; prohibited this model from being saved:&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;h2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;

  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;ul&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token erb language-erb&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;&amp;lt;%&lt;/span&gt;&lt;span class=&quot;token ruby language-ruby&quot;&gt; model&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;errors&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;error&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;/span&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;li&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token erb language-erb&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;&amp;lt;%=&lt;/span&gt;&lt;span class=&quot;token ruby language-ruby&quot;&gt; error&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;full_message &lt;/span&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;li&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token erb language-erb&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;&amp;lt;%&lt;/span&gt;&lt;span class=&quot;token ruby language-ruby&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt; &lt;/span&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;ul&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What it does is render an alert on top of the form wich contains the field name followed by the error message. Furthermore, since this bit of logic is fairly generic we could consider extracting it into it&#39;s own partial:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://gafemoyano.com/assets/img/articles/lM8pK46.png&quot; alt=&quot;description&quot;&gt;&lt;/p&gt;
&lt;p&gt;This technique is fine if your forms contain two or three inputs, but more than that it transfers the burden of mapping each error message to it&#39;s corresponding field to the user.&lt;/p&gt;
&lt;p&gt;It would be clearer to show each error next to the input that produced it. To achieve this we&#39;d have to delete this block of code generated by the scaffold with something else:&lt;/p&gt;
&lt;pre class=&quot;language-erb&quot;&gt;&lt;code class=&quot;language-erb&quot;&gt;&lt;span class=&quot;token erb language-erb&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;&amp;lt;%=&lt;/span&gt;&lt;span class=&quot;token ruby language-ruby&quot;&gt; form&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;label &lt;span class=&quot;token symbol&quot;&gt;:email&lt;/span&gt; &lt;/span&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token erb language-erb&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;&amp;lt;%=&lt;/span&gt;&lt;span class=&quot;token ruby language-ruby&quot;&gt; form&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;text_field &lt;span class=&quot;token symbol&quot;&gt;:email&lt;/span&gt; &lt;/span&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- Show Email validation errors --&gt;&lt;/span&gt;
&lt;span class=&quot;token erb language-erb&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;&amp;lt;%=&lt;/span&gt;&lt;span class=&quot;token ruby language-ruby&quot;&gt; tag&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;p user&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;errors&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;email&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;join&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;, &quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; user&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;errors&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;email&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;any&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;/span&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;While this seems good enough for start, it quickly becomes a very repetitive task to add the error fields to every input. Furthermore, if you&#39;ve ever used something like &lt;a href=&quot;https://github.com/heartcombo/simple_form&quot;&gt;Simple Form&lt;/a&gt; or &lt;a href=&quot;https://github.com/formtastic/formtastic&quot;&gt;Formtastic&lt;/a&gt; it feels like we&#39;re writing so much more code compared to:&lt;/p&gt;
&lt;pre class=&quot;language-erb&quot;&gt;&lt;code class=&quot;language-erb&quot;&gt;&lt;span class=&quot;token erb language-erb&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;&amp;lt;%=&lt;/span&gt;&lt;span class=&quot;token ruby language-ruby&quot;&gt; simple_form_for &lt;span class=&quot;token variable&quot;&gt;@user&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;f&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;/span&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token erb language-erb&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;&amp;lt;%=&lt;/span&gt;&lt;span class=&quot;token ruby language-ruby&quot;&gt; f&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;input &lt;span class=&quot;token symbol&quot;&gt;:email&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Your email please&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Email is mandatory, please specify one&#39;&lt;/span&gt;&lt;/span&gt; &lt;/span&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token erb language-erb&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;&amp;lt;%&lt;/span&gt;&lt;span class=&quot;token ruby language-ruby&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt; &lt;/span&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This snippet is doing a lot behind the scenes. It creates a label for our input, correctly infers the input type , conditionally renders errors below the input an even wraps it all up inside a dive so that your CSS can target the whole group.&lt;/p&gt;
&lt;p&gt;However I did not want to bring in Simple Form into this project for two reasons.&lt;/p&gt;
&lt;p&gt;The first one is that Simple Form plays better with a full fledged css framework like Bootstrap or Bulma and I&#39;m using tailwind in this project. Since I&#39;m using &lt;a href=&quot;https://tailwindcss.com&quot;&gt;tailwind&lt;/a&gt; here I don&#39;t think utility css plays well with passing options through &lt;code&gt;keyword_args&lt;/code&gt;. It makes it harder to understand the output HTML and I have&#39;t been able to make the intellisense + autocomplete work in my editor:&lt;/p&gt;
&lt;pre class=&quot;language-erb&quot;&gt;&lt;code class=&quot;language-erb&quot;&gt;&lt;span class=&quot;token erb language-erb&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;&amp;lt;%=&lt;/span&gt;&lt;span class=&quot;token ruby language-ruby&quot;&gt; simple_form_for &lt;span class=&quot;token variable&quot;&gt;@user&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;f&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;/span&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token erb language-erb&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;&amp;lt;%=&lt;/span&gt;&lt;span class=&quot;token ruby language-ruby&quot;&gt; f&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;input &lt;span class=&quot;token symbol&quot;&gt;:email&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;token symbol&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Your email please&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;token symbol&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Email is mandatory, please specify one&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;token symbol&quot;&gt;wrapper_html&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;mt-2&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;token symbol&quot;&gt;label_html&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;font-semibold text-gray-800&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;token symbol&quot;&gt;input_html&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;border border-gray-800 rounded&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
   &lt;/span&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token erb language-erb&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;&amp;lt;%&lt;/span&gt;&lt;span class=&quot;token ruby language-ruby&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt; &lt;/span&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- vs --&gt;&lt;/span&gt;

&lt;span class=&quot;token erb language-erb&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;&amp;lt;%=&lt;/span&gt;&lt;span class=&quot;token ruby language-ruby&quot;&gt; form_for &lt;span class=&quot;token variable&quot;&gt;@user&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;f&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;/span&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;mt-2&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;label&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;for&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;user_email&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;font-semibold text-gray-800&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Email&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;label&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token erb language-erb&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;&amp;lt;%=&lt;/span&gt;&lt;span class=&quot;token ruby language-ruby&quot;&gt; f&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;input &lt;span class=&quot;token symbol&quot;&gt;:email&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;border border-gray-800 rounded&quot;&lt;/span&gt;&lt;/span&gt; &lt;/span&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token erb language-erb&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;&amp;lt;%&lt;/span&gt;&lt;span class=&quot;token ruby language-ruby&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt; &lt;/span&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The second reason is a self-imposed rule to avoid bringing in gems if I only need a &lt;em&gt;small&lt;/em&gt; part of it&#39;s functionality and I could write it myself in a &lt;em&gt;reasonable&lt;/em&gt; amount of time.&lt;/p&gt;
&lt;h2 id=&quot;drying-up-error-rendering-with-a-hook&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://gafemoyano.com/en/posts/dry-input-errors-in-rails/#drying-up-error-rendering-with-a-hook&quot;&gt;DRYing up error rendering with a hook&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Even if I didn&#39;t want all of Simple Form&#39;s functionality, conditionally rendering the model&#39;s validation errors seemed pretty useful to me so we could try to bring just that bit into our apps. Some googling around lead me to this great &lt;a href=&quot;https://www.jorgemanrubia.com/2019/02/16/form-validations-with-html5-and-modern-rails/&quot;&gt;blog post&lt;/a&gt; by Jorge Manrubia. From there, we could modify his initializer to render errors next to each field instead of at the top of the form.&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Place this code in a initializer. E.g: config/initializers/field_error.rb&lt;/span&gt;

ActionView&lt;span class=&quot;token double-colon punctuation&quot;&gt;::&lt;/span&gt;Base&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;field_error_proc &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Proc&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;html_tag&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; instance_tag&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;
  fragment &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Nokogiri&lt;span class=&quot;token double-colon punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;HTML&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;fragment&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;html_tag&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  field &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; fragment&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;at&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;input,select,textarea&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  html &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; field
           error_message &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; instance_tag&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;error_message&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;join&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;, &#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
           field&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;class&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;token content&quot;&gt;field&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;class&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; border-red-600 border focus:outline-none&quot;&lt;/span&gt;&lt;/span&gt;
           html &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string-literal heredoc-string&quot;&gt;&lt;span class=&quot;token delimiter&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&amp;lt;-&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;HTML&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;
              &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;token content&quot;&gt;fragment&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;to_s&lt;/span&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;
              &amp;lt;p class=&quot;mt-1 text-sm text-red-600&quot;&gt;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;token content&quot;&gt;error_message&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;upcase_first&lt;/span&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&amp;lt;/p&gt;
           &lt;/span&gt;&lt;span class=&quot;token delimiter&quot;&gt;&lt;span class=&quot;token symbol&quot;&gt;HTML&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
           html
         &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt;
           html_tag
         &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

  html&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;html_safe
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I&#39;ll walk through this code to see what&#39;s going on. While Rails is rendering the form elements it will call a &lt;code&gt;Proc&lt;/code&gt; every time it detects the model has an error. You can hook into that and replace the default behaviour (which wraps the input in a div with a &lt;code&gt;field_error&lt;/code&gt; css class) with something else.&lt;/p&gt;
&lt;p&gt;Since The &lt;code&gt;Proc&lt;/code&gt; gets called with all sort of elements, the first step is to check if we&#39;re dealing with an input element, and if that&#39;s the case append a &lt;code&gt;&amp;lt;p /&amp;gt;&lt;/code&gt; tag with our error and styling on it. Thanks to the &lt;a href=&quot;https://api.rubyonrails.org/classes/ActionView/Helpers/ActiveModelInstanceTag.html#method-i-error_message&quot;&gt;error_message&lt;/a&gt;) method on &lt;code&gt;instance_tag&lt;/code&gt; we can get the corresponding errors on that field. For all other elements we return their html unchanged.&lt;/p&gt;
&lt;h2 id=&quot;closing-thoughts&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://gafemoyano.com/en/posts/dry-input-errors-in-rails/#closing-thoughts&quot;&gt;Closing thoughts&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;And there you go! Now we have a way to show validation errors on all of our forms without having to write conditional checks againts every field on our views. I got to admit it felt a bit hacky at first, but so far I haven&#39;t encountered any issues with this technique it works great with &lt;code&gt;ActiveModel&lt;/code&gt; validations and &lt;a href=&quot;https://gafemoyano.com/en/posts/dry-input-errors-in-rails/a-simple-strategy-for-translated-routes-in-rails.md&quot;&gt;Form Objects&lt;/a&gt; server side validations. So what do you think? Is this a great or terrible idea? Let me know via email if you experiment with it and find any issues.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>My happy place</title>
    <link href="https://gafemoyano.com/en/posts/my-happy-place/" />
    <updated>2020-12-08T00:00:00Z</updated>
    <id>https://gafemoyano.com/en/posts/my-happy-place/</id>
    <content type="html">&lt;p&gt;A few days ago I was reflecting about the sentence &amp;quot;Go to your happy place&amp;quot;. It&#39;s supposed to be some place you go to in your &lt;a href=&quot;https://youtu.be/fzoXalgG7oY&quot;&gt;imagination when you close your eyes to the rest of the world&lt;/a&gt;, just when you don&#39;t want to take a little bit more of it, and instead of losing your mind you close your eyes and go somewhere else.&lt;/p&gt;
&lt;p&gt;Or at least that&#39;s how I think that&#39;s supposed to work, since I haven&#39;t really felt the need to take a mind trip over there in a while.&lt;/p&gt;
&lt;p&gt;It&#39;s been long overdue for me to write some closing thoughts about my previous entrepreneurship at &lt;a href=&quot;https://savy.co&quot;&gt;Savy&lt;/a&gt;, after all, it was over so quickly and unceremoniously that there never was a good time for it. Then life happened, COVID kept going strong and... I just never got around to it. Until a timely apple algorithm decided, last week, to show me the cover picture on this post.&lt;/p&gt;
&lt;p&gt;The thing that hit me the most about it, is how blissfully happy everyone looks in it. I know, it&#39;s a picture. You are supposed to smile. But there&#39;s something genuine about it that captured the joy of that particular moment, present in the air and on everyone&#39;s face when the picture was taken.&lt;/p&gt;
&lt;p&gt;Because, in spite of all the challenges we faced, for the most part I believe that&#39;s the environment we lived in.&lt;/p&gt;
&lt;p&gt;And that&#39;s what finally motivated me to write this piece: For nearly three years, I had the privilege of living in my happy place. That&#39;s what it always felt like, no matter how difficult things where, if a project was out of track, if the numbers weren&#39;t meeting our expectations, or even when I felt I had to let someone go from the team (which fucking &lt;em&gt;sucks&lt;/em&gt;), I didn&#39;t feel like closing my eyes and going somewhere else. Not. Even. Once.&lt;/p&gt;
&lt;p&gt;Entrepreneurship is funny like that, you get to experience so many feelings: joy and sadness, success and failure, clarity and despair, pride and shame and so much more. That you &lt;strong&gt;get&lt;/strong&gt; to experience &lt;strong&gt;all&lt;/strong&gt; of this is, in my eyes, reason enough to do it.&lt;/p&gt;
&lt;p&gt;Unfortunately, everything must come to an end and 2020 was quite ruthless at ending things abruptly. Going back to the picture that prompted this post, I cannot predict the future but I do hope, sincerely, that everyone from the picture (and from our team) finds themselves, once again, in kind of environment. That once again, we get to experience this electric feeling of being surrounded by a team that&#39;s so much more than the sum of it&#39;s parts and that we&#39;re able to hold on to it for a bit longer than we were able to.&lt;/p&gt;
&lt;p&gt;I&#39;m incredibly lucky for having had that opportunity and I&#39;m even more thankful to all the people that took part in those feelings and experiences. I loved being part of that team and having a small part in creating something that brought joy and happiness to the world and lives of many people.&lt;/p&gt;
&lt;p&gt;To everyone in that picture (and the ones that aren&#39;t, you know who you are), for having shared this experience with me.&lt;/p&gt;
&lt;p&gt;To my cofounders, for putting up with me during all these years.&lt;/p&gt;
&lt;p&gt;Thank you for being part of my happy place.&lt;/p&gt;
&lt;p&gt;And to my family, friends and girlfriend who dealt with my absence during the longest hours I&#39;ve ever worked.&lt;/p&gt;
&lt;p&gt;Felipe.&lt;/p&gt;
&lt;br&gt;
&lt;br&gt;
&lt;p&gt;&lt;em&gt;P.S. Some other memories&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Allow me, for posterity, to share some other moments that come to memory from looking at this picture:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Playing ping pong matches on the afternoon and how Chris&#39; competitive nature drove him to become the best at it&lt;/li&gt;
&lt;li&gt;The after lunch Mario Kart game, to decide who would do the dishes.&lt;/li&gt;
&lt;li&gt;Being perpetually late to every meeting we had, to the annoyance of my very punctual and very french co-founders.&lt;/li&gt;
&lt;li&gt;The moment we released our first app to the app store, and the positive reviews started flowing in.&lt;/li&gt;
&lt;li&gt;Watching Camilo arrive to the office every day with a huge smile on his face.&lt;/li&gt;
&lt;li&gt;How sad everyone was when Sebastián left the team.&lt;/li&gt;
&lt;li&gt;Luis&#39; unconcious habit of singing when he was deeply focused.&lt;/li&gt;
&lt;li&gt;Nathalia, Juanita and Abraham squished on top of each other working together on the office&#39;s iMac.&lt;/li&gt;
&lt;li&gt;Mathias 🐕.&lt;/li&gt;
&lt;li&gt;Camila teaching me how to dance properly, she did not succeed.&lt;/li&gt;
&lt;li&gt;Ruby Conf, where Luis and I drank half the beer at the hostal.&lt;/li&gt;
&lt;li&gt;My occasional gossip session with Sandra.&lt;/li&gt;
&lt;li&gt;Las charlas con el jefe de jefes&lt;/li&gt;
&lt;li&gt;The co founder late night meetings, regretting our choice of food for the evening 9 out of 10 times.&lt;/li&gt;
&lt;li&gt;That fancy dinner we had after closing our first investment round.&lt;/li&gt;
&lt;li&gt;Going out to party and having 1 bottle of guaro for every two people in the group.&lt;/li&gt;
&lt;li&gt;The tejo, the parties, the after work beers, the cocktail evenings, the speeches and so many more.&lt;/li&gt;
&lt;li&gt;Telling Gaetan he was right about the gamification project.&lt;/li&gt;
&lt;li&gt;Crashing Chris&#39;s bike for running out late. My hand still hurts.&lt;/li&gt;
&lt;li&gt;Practicing the Shark Tank pitch over and over with the team.&lt;/li&gt;
&lt;li&gt;Our first YC application video that took 3 hours to record.&lt;/li&gt;
&lt;li&gt;Our last YC application video that took 3 minutes.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So many more... I&#39;m sorry if I missed some important ones, I might keep adding to the list just for the sake of memory.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>A Simple Strategy for Translated Routes in Rails</title>
    <link href="https://gafemoyano.com/en/posts/a-simple-strategy-for-translated-routes-in-rails/" />
    <updated>2020-11-02T00:00:00Z</updated>
    <id>https://gafemoyano.com/en/posts/a-simple-strategy-for-translated-routes-in-rails/</id>
    <content type="html">&lt;p&gt;Recently I&#39;ve been working on a new project, a job board focused on remote offerings. Admittedly this is not a novel idea, however I haven&#39;t seen anything like this targeted to the local job market in Colombia. My guess is that remote offerings are not that popular here (yet) and that most of the existing boards out there are not in spanish so I decided to give it a try.&lt;/p&gt;
&lt;p&gt;I decided to go with Rails for the stack since it would allow me to leverage &lt;a href=&quot;https://edgeguides.rubyonrails.org/active_storage_overview.html&quot;&gt;ActiveStorage&lt;/a&gt; for file uploads and &lt;a href=&quot;https://edgeguides.rubyonrails.org/action_text_overview.html&quot;&gt;ActionText&lt;/a&gt; for rich text editing, both of which I was going to need at some point for this.&lt;/p&gt;
&lt;p&gt;My hypothesis is that current remote job boards haven&#39;t caught on due to them being on a foreign language, so I want this app to be completely in spanish. Rails comes with built in &lt;a href=&quot;https://guides.rubyonrails.org/i18n.html&quot;&gt;i18n&lt;/a&gt;, however it is targetted at content and does not cover one piece of the app, the URL. Also, I&#39;m not really looking to internationalize this app I just want the public routes not to be in english.&lt;/p&gt;
&lt;p&gt;Also, I&#39;m not really looking to support multiple languages or internationalize this app, I just want the public facing routes not to be in english.&lt;/p&gt;
&lt;p&gt;In the past, I&#39;ve dealt with this in the following ways:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Create your domain models in the target language&lt;/strong&gt; Since Rails is convention based, you can just create your models in spanish and by convention the routes themselves will be in spanish too. The downside, is that you&#39;d have to start adding exceptions to Rails&#39; &lt;a href=&quot;https://api.rubyonrails.org/classes/ActiveSupport/Inflector.html&quot;&gt;inflector&lt;/a&gt; so that it pluralizes your models correctly and even if you do there are still edge cases where it doesn&#39;t work as expected. Also, the rest of Rails is still in english, so you end up reading code with words mixed up in two languages. I&#39;d rather keep all the code in english.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Bring in an external gem&lt;/strong&gt;. There&#39;s some great gems out there for handling route translations. Most notable of them is &lt;a href=&quot;https://github.com/enriclluelles/route_translator&quot;&gt;route_translator&lt;/a&gt; which can handle multiple use cases for multilingual sites. This would allow me to set spanish as the default locale and wrap all my routes in a &lt;code&gt;localized&lt;/code&gt; block. This seems like a good option, but it also feels like this gem does way more than what I actually need. I&#39;d like a simpler solution, if possible.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Ignore the URLs and let them be in english&lt;/strong&gt; This is what I&#39;ve done most of the time. Despite common SEO advice I haven&#39;t seen much impact on having two routes be in english and user&#39;s don&#39;t really notice the URL or what language it&#39;s in.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;None of this options seemed very satisfying to me. There &lt;strong&gt;must&lt;/strong&gt; be a simpler way to achieve this. After all, Rails is known for being a modular and flexible framework, although some people might argue with this statement.&lt;/p&gt;
&lt;p&gt;Turns out there &lt;strong&gt;is&lt;/strong&gt; a simpler way and it had been in front of me all along. I just needed to take a closer look into how rails routing works. When you define a resource or route in rails, it can take some keyword arguments. One of them is &lt;code&gt;path&lt;/code&gt;, which according to the &lt;a href=&quot;https://api.rubyonrails.org/classes/ActionDispatch/Routing/Mapper/Resources.html#method-i-resources&quot;&gt;docs&lt;/a&gt; it allows you to change the path prefix of the resource. So we could do something like:&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;resources &lt;span class=&quot;token symbol&quot;&gt;:jobs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;trabajos&#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# Generates the following routes:&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# jobs	   GET    /trabajos(.:format)&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;#		   POST   /trabajos(.:format)&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# new_job  GET    /trabajos/new(.:format)&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# edit_job GET    /trabajos/:id/edit(.:format)&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# job 	   GET    /trabajos/:id(.:format)&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;#	       PATCH  /trabajos/:id(.:format)&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;#          PUT    /trabajos/:id(.:format)&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;#	       DELETE /trabajos/:id(.:format)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That&#39;s great, on the one hand we have now have our user facing routes (mostly) in Spanish and on the other hand we get to keep our code consistent by referring to the models in English in our route helpers.&lt;/p&gt;
&lt;p&gt;What about &lt;code&gt;new&lt;/code&gt; and &lt;code&gt;edit&lt;/code&gt;? Well it turns out that Rails also has an option to override those with the &lt;code&gt;path_names&lt;/code&gt; keyword:&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;resources &lt;span class=&quot;token symbol&quot;&gt;:jobs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;trabajos&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;path_names&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;nuevo&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;edit&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;editar&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Furthermore, we could avoid having to pass &lt;code&gt;path_names&lt;/code&gt; to every resource by defining a scope at the top of our routes:&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;scope&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:path_names&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:new&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;nuevo&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:edit&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;editar&#39;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt;
    resources &lt;span class=&quot;token symbol&quot;&gt;:jobs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;trabajos&#39;&lt;/span&gt;&lt;/span&gt;
    resources &lt;span class=&quot;token symbol&quot;&gt;:tags&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;etiquetas&#39;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# new_job  GET    /trabajos/nuevo(.:format)&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# edit_job GET    /trabajos/:id/editar(.:format)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And there you go! now every part of the URL should be in your language of choice, withouot having to include complex external dependencies or sacrificing hypothetical SEO points.&lt;/p&gt;
&lt;p&gt;I hope this simple example is useful enough to illustrate that sometimes a simple solution might be enough and you sometimes have to look &lt;strong&gt;into&lt;/strong&gt; the framework and the options it provides, instead of &lt;strong&gt;outside&lt;/strong&gt; for dependencies that often include broader use cases.&lt;/p&gt;
</content>
  </entry>
</feed>