<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Kartikey Tanna</title>
    <description>The latest articles on DEV Community by Kartikey Tanna (@tannakartikey).</description>
    <link>https://dev.to/tannakartikey</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F121092%2F047a3825-91f6-4201-b68d-6c3890a90fc4.jpg</url>
      <title>DEV Community: Kartikey Tanna</title>
      <link>https://dev.to/tannakartikey</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tannakartikey"/>
    <language>en</language>
    <item>
      <title>How to deploy a React app with Kamal (formerly known as MRSK) &amp; GitHub Action</title>
      <dc:creator>Kartikey Tanna</dc:creator>
      <pubDate>Mon, 17 Jun 2024 05:49:34 +0000</pubDate>
      <link>https://dev.to/tannakartikey/how-to-deploy-a-react-app-with-kamal-formerly-known-as-mrsk-github-action-32i8</link>
      <guid>https://dev.to/tannakartikey/how-to-deploy-a-react-app-with-kamal-formerly-known-as-mrsk-github-action-32i8</guid>
      <description>&lt;p&gt;I recently helped &lt;a href="https://sidecarlearning.com"&gt;Sidecar Learning&lt;/a&gt;{:target="_blank"} move their legacy application built with React front-end and Rails back-end migrate from Heroku and AWS to VPS over at DigitalOcean.&lt;/p&gt;

&lt;p&gt;This guide is outcome of that migration. React front-end was quite simple and do not have much moving parts so it's quite simple to do this. If you are looking to deploy monolith application then you can read the following posts I have written:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="///2023/04/05/how-to-deploy-rails-app-and-postgres-with-mrsk-on-single-server.html"&gt;Deploy Rails app and Postgres with Kamal(previously MRSK) on single DigitalOcean server&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="///2023/04/09/how-to-deploy-multi-environment-staging-production-application-using-mrsk.html"&gt;How to deploy multi-environment(staging, production) application using Kamal(previously MRSK)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="///2023/04/10/how-to-deploy-a-nodejs-application-using-mrsk.html"&gt;How to deploy a NodeJS application using Kamal (previously MRSK)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Kamal Config
&lt;/h2&gt;

&lt;p&gt;First, let's create a simple &lt;code&gt;deploy.yml&lt;/code&gt; for Kamal. The configuration is mostly boilerplate. I am using &lt;a href="https://docs.docker.com/build/cache/backends/gha/"&gt;GHA cache&lt;/a&gt;{:target="_blank"} to speed up the deploys.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/deploy.yml&lt;/span&gt;

&lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;react-app&lt;/span&gt;
&lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;username/image-name&lt;/span&gt;

&lt;span class="na"&gt;servers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;web&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;123.456.789.012&lt;/span&gt;

&lt;span class="na"&gt;registry&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker_username&lt;/span&gt;
  &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;KAMAL_REGISTRY_PASSWORD&lt;/span&gt;

&lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;clear&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;FOO&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;BAR&lt;/span&gt;
    &lt;span class="na"&gt;NPM_CONFIG_PRODUCTION&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;

&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;multiarch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gha&lt;/span&gt;
    &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mode=max&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you wish to, you can use "registry" cache as well. I have noticed that it's faster by 5-10 seconds but if you are using free Docker Hub account then you only get one free image. That's why I prefer to use "gha" as cache back-end. Kamal only &lt;a href="https://kamal-deploy.org/docs/configuration/builders/#using-multistage-builder-cache"&gt;supports&lt;/a&gt;{:target="_blank"} "gha" and "registry". If you would like to use the "registry" cache, use the following builder config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;multiarch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;registry&lt;/span&gt;
    &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mode=max,image-manifest=true,oci-mediatypes=true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Dockerfile
&lt;/h2&gt;

&lt;p&gt;Our app is using quite old version of NodeJS. This is why I like Kamal more than anything. No platform dependency or requirement to fulfil. You create your own environment and Kamal provides thin wrapper around the Docker commands. As simple as it could be.&lt;/p&gt;

&lt;p&gt;Here, we first create a build step. Once the build is ready, no need to include all those node_modules in our final application. We keep it as small as possible so that it's easier to perform operations.&lt;/p&gt;

&lt;p&gt;In our app, we have a small Express app that serves the static file. That's why I have created a separate &lt;code&gt;package.runtime.json&lt;/code&gt; file. You can modify that part as per your own need. The next session will explain how the express server works.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Dockerfile&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:11.10.1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;build&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; NODE_ENV="production" \&lt;/span&gt;
    NPM_CONFIG_PRODUCTION="false"

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci

&lt;span class="k"&gt;RUN &lt;/span&gt;npm run build

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:11.10.1&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; NODE_ENV="production" \&lt;/span&gt;
    NPM_CONFIG_PRODUCTION="false"

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=build /app/build ./build&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=build /app/server.js ./server.js&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=build /app/package.runtime.json ./package.json&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--production&lt;/span&gt;

&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 3000&lt;/span&gt;

&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["node", "server.js"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Express Server
&lt;/h2&gt;

&lt;p&gt;We have a tiny express server that serves the static files with appropriate headers. We have configured Cloudflare to provide SSL support and cache the static assets. Only &lt;code&gt;index.html&lt;/code&gt; file is not cached, and bundled assets will be served by this Express server only once. Then Cloudflare will take the load. All of this is deployed on our $4 DigitalOcean droplet and it is handling our moderately busy app very well.&lt;/p&gt;

&lt;p&gt;I have &lt;a href="https://webpack.js.org/guides/caching/"&gt;configured&lt;/a&gt;{:target="_blank"} the Webpack to generate bulid with hash, so with every new build, the new hash will rename the file - same as &lt;a href="https://github.com/rails/propshaft/"&gt;Propshaft&lt;/a&gt;{:target="_blank"}.&lt;/p&gt;

&lt;p&gt;I have added a route for Kamal healthcheck as well even though the health-check step has been &lt;a href="https://github.com/basecamp/kamal/pull/740"&gt;removed&lt;/a&gt;{:target="_blank"} in Kamal 1.6.0.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// server.js&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;compression&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;compression&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;path&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Use compression middleware&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;compression&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

&lt;span class="c1"&gt;// Serve static files with cache control headers&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;static&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;build&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;maxAge&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1d&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Cache for 1 day&lt;/span&gt;
  &lt;span class="na"&gt;setHeaders&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Set cache-control headers based on file types&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;endsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Cache-Control&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;public, max-age=31536000&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 1 year&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;endsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Cache-Control&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;public, max-age=31536000&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 1 year&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;endsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Cache-Control&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;public, max-age=0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// No cache&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Cache-Control&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;public, max-age=86400&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 1 day&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}));&lt;/span&gt;

&lt;span class="c1"&gt;// Kamal health check route&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/up&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&amp;lt;!DOCTYPE html&amp;gt;&amp;lt;html&amp;gt;&amp;lt;body style="background-color: green"&amp;gt;&amp;lt;/body&amp;gt;&amp;lt;/html&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Send all requests to index.html to handle routing in React Router&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;*&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;build&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;index.html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Start the server&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Server is running on port &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We only need ExpressJS and the compression packages to run this small server. That's why we are including only these two packages in the runtime package file.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;package.runtime.json&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"your-app"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.0.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"main"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"server.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"dependencies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"compression"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^1.7.4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"express"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^4.16.4"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"node server.js"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  GitHub Action
&lt;/h2&gt;

&lt;p&gt;It's a headache to run the deployment from your own machine. The following GitHub Actions config will solve it for you. I have added a concurrency config too that will let only one deploy run at a time. It's useful when you make multiple commits in a short time.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# .github/workflows/deploy.yml&lt;/span&gt;

&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy to production&lt;/span&gt;

&lt;span class="c1"&gt;# To make sure that only one deploy runs at a time. Deploy lock will not let simultaneous deployments.&lt;/span&gt;
&lt;span class="na"&gt;concurrency&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;group&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.workflow }}&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;master&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;KAMAL_REGISTRY_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.KAMAL_REGISTRY_PASSWORD }}&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Set up Docker Buildx for cache&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker/setup-buildx-action@v3&lt;/span&gt;

      &lt;span class="c1"&gt;# Since we are using GHA cache, we need to expose the cache to the runtime&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Expose GitHub Runtime for cache&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;crazy-max/ghaction-github-runtime@v3&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout code&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v3&lt;/span&gt;

      &lt;span class="c1"&gt;# Ruby is only needed to install Kamal&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Set up Ruby&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ruby/setup-ruby@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;ruby-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.3'&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install dependencies&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gem install kamal&lt;/span&gt;

      &lt;span class="c1"&gt;# This is to facilitate Kamal with the private key to access server(s)&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;webfactory/ssh-agent@v0.7.0&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;ssh-private-key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SSH_PRIVATE_KEY }}&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run deploy command&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kamal deploy&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>react</category>
      <category>kamal</category>
      <category>github</category>
    </item>
    <item>
      <title>Understanding the role of the “schema.rb” file in Ruby on Rails development</title>
      <dc:creator>Kartikey Tanna</dc:creator>
      <pubDate>Sun, 06 Aug 2023 00:00:00 +0000</pubDate>
      <link>https://dev.to/tannakartikey/understanding-the-role-of-the-schemarb-file-in-ruby-on-rails-development-8l</link>
      <guid>https://dev.to/tannakartikey/understanding-the-role-of-the-schemarb-file-in-ruby-on-rails-development-8l</guid>
      <description>&lt;p&gt;I recently published a &lt;a href="https://www.linkedin.com/feed/update/urn:li:activity:7093289792351707136/" rel="noopener noreferrer"&gt;post in a Ruby on Rails group on LinkedIn&lt;/a&gt; about a rake task I bring to most of my projects. This Rake task deletes the &lt;code&gt;schema.rb&lt;/code&gt; file and regenerates it. I got many reactions from fellow developers and that motivated me to write this post.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding the Purpose of Rails’ schema.rb File
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;schema.rb&lt;/code&gt; file is essentially a blueprint of your Rails application’s database. Imagine your app as a house - the schema.rb is the floor plan you’d show your architect. It outlines your database’s structure, detailing tables, columns, their types, primary keys, and even relationships between them. Migrations update and generate this file automatically.&lt;/p&gt;

&lt;p&gt;Why is it important? Think of it this way: a new team member joins and needs to understand your database structure. Rather than making them trawl through countless migrations (a process as thrilling as watching paint dry), they can reference the schema.rb file. This file provides a concise summary of your database structure, the Cliff Notes version, if you will.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;schema.rb&lt;/code&gt; file in a new Rails application contains the following comment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# This file is auto-generated from the current state of the database. Instead  
# of editing this file, please use the migrations feature of Active Record to  
# incrementally modify your database, and then regenerate this schema definition.  
#  
# This file is the source Rails uses to define your schema when running \`bin/rails  
# db:schema:load\`. When creating a new database, \`bin/rails db:schema:load\` tends to  
# be faster and is potentially less error prone than running all of your  
# migrations from scratch. Old migrations may fail to apply correctly if those  
# migrations use external dependencies or application code.  
#  
# It's strongly recommended that you check this file into your version control system.

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Decoding the Authority Over the Database in Rails
&lt;/h2&gt;

&lt;p&gt;The Rails docs’ interpretation of the authority over the database schema has seen notable changes over the years. To illustrate this, let’s look at two significant commits on the Rails GitHub.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/rails/rails/blob/9eeb00976d4b0a963c58117b46b7a5c6edcacc31/guides/source/migrations.md#what-are-schema-files-for" rel="noopener noreferrer"&gt;first version&lt;/a&gt; of the docs from 2012 claimed that the &lt;code&gt;db/schema.rb&lt;/code&gt; or an SQL file generated by Active Record was the authoritative source for your database schema.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What are Schema Files for?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Migrations, mighty as they may be, are not the authoritative source for your database schema. &lt;strong&gt;That role falls to either &lt;code&gt;db/schema.rb&lt;/code&gt; or an SQL file which Active Record generates by examining the database.&lt;/strong&gt; They are not designed to be edited, they just represent the current state of the database.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;There is no need (and it is error prone) to deploy a new instance of an app by replaying the entire migration history.&lt;/strong&gt; It is much simpler and faster to just load into the database a description of the current schema.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;However, the &lt;a href="https://github.com/rails/rails/blob/84718df86097442f85999d6f2e6f6b8b59724c3f/guides/source/active_record_migrations.md#what-are-schema-files-for" rel="noopener noreferrer"&gt;follow-up version&lt;/a&gt; from 2018 which we read &lt;a href="https://guides.rubyonrails.org/active_record_migrations.html#what-are-schema-files-for-questionmark" rel="noopener noreferrer"&gt;today&lt;/a&gt; clearly states that your actual database remains the authoritative source.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What are Schema Files for?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Migrations, mighty as they may be, are not the authoritative source for your database schema. &lt;strong&gt;Your database remains the authoritative source.&lt;/strong&gt; By default, Rails generates &lt;code&gt;db/schema.rb&lt;/code&gt; which attempts to capture the current state of your database schema.&lt;/p&gt;

&lt;p&gt;It tends to be faster and less error prone to create a new instance of your application’s database by loading the schema file via &lt;code&gt;rails db:schema:load&lt;/code&gt; than it is to replay the entire migration history. &lt;strong&gt;Old migrations may fail to apply correctly if those migrations use changing external dependencies or rely on application code which evolves separately from your migrations.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The commit was made with the following comment from the contributors:&lt;/p&gt;

&lt;blockquote&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;**Update `schema.rb` documentation [CI SKIP]**

The documentation previously claimed that `db/schema.rb` was “the  
authoritative source for your database schema” while simultaneously  
also acknowledging that the file is generated. These two statements are  
incongruous and the guides accurately call out that many database  
constructs are unsupported by `schema.rb`. **This change updates the**  
**comment at the top of `schema.rb` to remove the assertion that the file**  
**is authoritative.**

The documentation also previously referred vaguely to “issues” when  
re-running old migrations. This has been updated slightly to hint at the  
types of problems that one can encounter with old migrations.

In sum, this change attempts to more accurately capture the pros, cons,  
and shortcomings of the two schema formats in the guides and in the  
comment at the top of `schema.rb`.

[Derek Prior &amp;amp; Sean Griffin]
&lt;/code&gt;&lt;/pre&gt;
&lt;/blockquote&gt;

&lt;p&gt;While &lt;code&gt;db/schema.rb&lt;/code&gt; and migrations play vital roles in managing your database structure, neither are the definitive descriptors of your schema. The &lt;code&gt;db/schema.rb&lt;/code&gt; file is a Rails-generated snapshot of your database structure, and is useful for setting up new instances of your database quickly. Migrations, however, are used to implement incremental changes to your database over time.&lt;/p&gt;

&lt;p&gt;As developers, ensuring your migrations are up-to-date and error-free is paramount. These migrations should be able to recreate &lt;code&gt;db/schema.rb&lt;/code&gt; accurately. In the event of any breaking changes, such as class name alterations, best practices must be followed to ensure your migrations remain reversible and unaffected by such changes. Understanding these principles will help manage your database schema effectively in Rails.&lt;/p&gt;

&lt;h3&gt;
  
  
  Working with schema.rb
&lt;/h3&gt;

&lt;p&gt;While it might be tempting to manually modify &lt;em&gt;schema.rb&lt;/em&gt;, resist the urge! Any changes should be made through migrations. This ensures that the &lt;em&gt;schema.rb&lt;/em&gt; file can be regenerated correctly.&lt;/p&gt;

&lt;p&gt;When working on a new or unreleased project, it might make sense to adjust existing migrations rather than creating new ones. This way, you keep the migration history lean and focused. However, if a project is already in production, creating new migrations for each database change is the standard practice. This ensures that your database changes are properly tracked, and each team member can understand when and why the database structure was changed.&lt;/p&gt;

&lt;p&gt;In new and unreleased projects, I often opt to modify existing migrations rather than creating new ones. In older projects where migrations are being rectified, I use a custom rake task to streamline the process. Here’s a snippet of the task for resetting the database by dropping the schema:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# lib/tasks/complete_reset.rake

namespace :db do
  desc 'reset the database by dropping the schema'
  task complete_reset: :environment do
   raise unless Rails.env.local?

    FileUtils.rm_f('db/schema.rb')
    Rake::Task['db:drop'].invoke
    Rake::Task['db:create'].invoke
    Rake::Task['db:migrate'].invoke
    Rake::Task['db:seed'].invoke
    Rake::Task['dev:prime'].invoke
  end
end

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

&lt;/div&gt;



&lt;p&gt;This task handles the removal of db/schema.rb, drops the database, creates it anew, migrates and seeds it, and primes it for development. Be extremely cautious when choosing the environment this task is applied to; it should not be used on deployed servers once your app has hit staging or production stages.&lt;/p&gt;

&lt;p&gt;This task becomes particularly useful in the early stages of a project when specifications are frequently changing. When switching between branches, conflicts in the schema.rb file can often arise due to these rapid changes. This task helps you avoid creating excessive migrations and ensures a smooth, efficient development process by resolving such conflicts.&lt;/p&gt;

&lt;p&gt;It’s important to note that this task also invokes another task, dev:prime. This primes the development database with test data, providing a consistent starting state after a reset. So, firing complete_reset on the development environment not only clears any schema conflicts, but also populates your database with test data for a fresh start.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQs
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Should I modify the &lt;code&gt;schema.rb&lt;/code&gt; file manually?
&lt;/h3&gt;

&lt;p&gt;As a rule of thumb, you &lt;strong&gt;should not&lt;/strong&gt; manually modify the &lt;code&gt;schema.rb&lt;/code&gt; file. Instead, you should use &lt;a href="https://guides.rubyonrails.org/active_record_migrations.html#what-are-schema-files-for-questionmark" rel="noopener noreferrer"&gt;ActiveRecord Migrations&lt;/a&gt; to alter your database schema. This ensures that changes are properly tracked and the &lt;code&gt;schema.rb&lt;/code&gt; is updated correctly. In rare, exceptional cases with legacy code or failed migrations, direct modification could be considered but it’s a risky approach and should only be done with extreme caution.&lt;/p&gt;

&lt;h3&gt;
  
  
  What are some best practices for managing the &lt;code&gt;schema.rb&lt;/code&gt; file in a team environment?
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Version Control&lt;/strong&gt; : Always keep the &lt;code&gt;schema.rb&lt;/code&gt; file in your version control system to keep track of its changes over time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don’t Modify Manually&lt;/strong&gt; : Avoid making manual modifications to the &lt;code&gt;schema.rb&lt;/code&gt; file. Use ActiveRecord migrations instead.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Review Changes&lt;/strong&gt; : Before committing changes to the &lt;code&gt;schema.rb&lt;/code&gt; file, review them to make sure they align with the changes made in your migrations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sync with Database&lt;/strong&gt; : Always make sure your &lt;code&gt;schema.rb&lt;/code&gt; file is synchronized with the current state of your database schema.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  What happens if the &lt;code&gt;schema.rb&lt;/code&gt; file is deleted or modified?
&lt;/h3&gt;

&lt;p&gt;If the &lt;code&gt;schema.rb&lt;/code&gt; file is deleted or modified manually, it may lead to inconsistencies between the actual state of your database and the Rails application’s understanding of the database schema. This can lead to unexpected errors or bugs in your application. Therefore, it is advised not to delete or manually modify the &lt;code&gt;schema.rb&lt;/code&gt; file.&lt;/p&gt;

</description>
      <category>rails</category>
      <category>postgres</category>
    </item>
    <item>
      <title>How to enable Traefik dashboard with Kamal (formerly known as MRSK)</title>
      <dc:creator>Kartikey Tanna</dc:creator>
      <pubDate>Wed, 12 Apr 2023 13:10:00 +0000</pubDate>
      <link>https://dev.to/tannakartikey/how-to-enable-traefik-dashboard-with-mrsk-48ef</link>
      <guid>https://dev.to/tannakartikey/how-to-enable-traefik-dashboard-with-mrsk-48ef</guid>
      <description>&lt;p&gt;&lt;a href="https://kamal-deploy.org/"&gt;Kamal&lt;/a&gt;, formerly known as MRSK uses Traefik as a dynamic reverse-proxy. Traefik has a beautiful &lt;a href="https://doc.traefik.io/traefik/operations/dashboard/"&gt;dashboard&lt;/a&gt; to visually display the configuration. Kamal is not configured to show the dashboard by default.&lt;/p&gt;

&lt;p&gt;Traefik dashboard can run in two modes - secure and insecure. Traefik recommends secure mode but this post wil cover how to enable both the modes.&lt;/p&gt;

&lt;p&gt;First, let’s see the unsecure mode. Unsecure mode is very easy to configure. It is unsecure because it exposes Traefix API on the &lt;a href="https://doc.traefik.io/traefik/routing/entrypoints/"&gt;entrypoint&lt;/a&gt;. It means that after configuring the dashboard in unsecure mode, the dashboard will be available on the port 8080 of the host.&lt;/p&gt;

&lt;p&gt;Adding the following snippet in the &lt;code&gt;config/deploy.yml&lt;/code&gt; file will enable the unsecure dashboard.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# config/deploy.yml
traefik:
  options:
    publish:
      - 8080:8080
  args:
    api.dashboard: true
    api.insecure: true

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

&lt;/div&gt;



&lt;p&gt;After adding the configuration, run the &lt;code&gt;kamal traefik reboot&lt;/code&gt; command to apply the configuration. This command will stop, remove and start new container again with the latest configuration. The dashboard should be visible on the port &lt;code&gt;8080&lt;/code&gt; of the host server now e.g. &lt;a href="http://99.99.99.99:8080"&gt;http://99.99.99.99:8080&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, let’s see about the secure mode. It’s called secure mode because the API is not expose on the entrypoint. We need to create a router rule that uses &lt;code&gt;api@internal&lt;/code&gt; service. Let’s look at the configuration for the secure model.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;traefik:
  options:
    publish:
      - 8080:8080
  args:
    api.dashboard: true
  labels:
    traefik.enable: "true"
    traefik.http.routers.dashboard.rule: Host(`traefik.example.com`) &amp;amp;&amp;amp; (PathPrefix(`/api`) || PathPrefix(`/dashboard`))
    traefik.http.routers.dashboard.service: "api@internal"
    traefik.http.routers.dashboard.middlewares: "auth"
    traefik.http.middlewares.auth.basicauth.users: test:$2y$05$H2o72tMaO.TwY1wNQUV1K.fhjRgLHRDWohFvUZOJHBEtUXNKrqUKi

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

&lt;/div&gt;



&lt;p&gt;Here, we have configured Traefik dynamically with help of Docker labels. First of we have created a router. Then we attached the router with the &lt;code&gt;api@internal&lt;/code&gt; service because in the secure mode we have to do this manually. After that, we added the auth middleware to Trafeik. In the last, we conifgured this &lt;code&gt;auth&lt;/code&gt; middleware to use &lt;a href="https://doc.traefik.io/traefik/middlewares/http/basicauth/"&gt;HTTP Basic Authentication&lt;/a&gt; and provided it with the credentials. You can read more about the rules in the details on the Traefik &lt;a href="https://doc.traefik.io/traefik/routing/routers/"&gt;docs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The credentials are in the “username:hashed_password” format. The credentials are generated with the &lt;code&gt;htpasswd&lt;/code&gt; command. Let’s say you want to create a user with the username “admin” and the password “super_strong_password” then you can use the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;htpasswd -nb admin super_strong_password
# output: admin:$apr1$2FGO09Gu$PSZdmmJqyrXWYvidWAm6p0

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

&lt;/div&gt;



&lt;p&gt;You will get the password hash in the output. Just copy paste the output with the username:password in the labels. The official Traefik docs mention that you need to escape the &lt;code&gt;$&lt;/code&gt; character but you don’t need if you are using Kamal but Kamal &lt;a href="https://kamal-deploy.org/docs/configuration#using-shell-expansion"&gt;escapes&lt;/a&gt; the &lt;code&gt;$&lt;/code&gt; sign for these labels.&lt;/p&gt;

&lt;p&gt;That’s it! Don’t forget to reboot the Trafeik container with the &lt;code&gt;kamal traefik reboot&lt;/code&gt; comamnd. After that, the dashboard should be accessible on the &lt;a href="http://traefik.example.com/dashboard"&gt;http://traefik.example.com/dashboard&lt;/a&gt; endpoint.&lt;/p&gt;

&lt;p&gt;P.S. I would not recommend running the dashboard permanently if you are using a small single server with multple apps. Because that would eat up the valuable server resources.&lt;/p&gt;

</description>
      <category>kamal</category>
    </item>
    <item>
      <title>How to deploy a NodeJS application using MRSK</title>
      <dc:creator>Kartikey Tanna</dc:creator>
      <pubDate>Mon, 10 Apr 2023 08:25:00 +0000</pubDate>
      <link>https://dev.to/tannakartikey/how-to-deploy-a-nodejs-application-using-mrsk-5311</link>
      <guid>https://dev.to/tannakartikey/how-to-deploy-a-nodejs-application-using-mrsk-5311</guid>
      <description>&lt;p&gt;&lt;a href="https://kamal-deploy.org/"&gt;Kamal&lt;/a&gt;, formerly known as MRSK, is created by DHH - founder of Rails, but the tool is not limited to deploy only Rails applications. Kamal is a simple tool that automates some Docker related commands. That is why it can support any platform/language. In this post, let’s see how to deploy a NodeJS application on a small DigitalOcean VPS using Kamal.&lt;/p&gt;

&lt;p&gt;First of all, let’s generate an express app. I installed &lt;a href="https://expressjs.com/en/starter/generator.html"&gt;&lt;code&gt;express-generator&lt;/code&gt;&lt;/a&gt; and generated a new app using the &lt;code&gt;express express-app&lt;/code&gt; command.&lt;/p&gt;

&lt;p&gt;Then let’s create a Dockerfile:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FROM node:16

# Create app directory
WORKDIR /usr/src/app

# Install app dependencies
# A wildcard is used to ensure both package.json AND package-lock.json are copied
# where available (npm@5+)
COPY package*.json ./

RUN npm install
# If you are building your code for production
# RUN npm ci --omit=dev

# Bundle app source
COPY . .

EXPOSE 3000
CMD ["node", "bin/www"]

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

&lt;/div&gt;



&lt;p&gt;Now, let’s use Kamal to generate the config files. Make sure you have Kamal installed on your machine. If not, you can &lt;a href="https://kamal-deploy.org/docs/installation"&gt;follow these instructions&lt;/a&gt; on README.&lt;/p&gt;

&lt;p&gt;Let’s init the configuration with &lt;code&gt;kamal init&lt;/code&gt;. It will create &lt;code&gt;config/deploy.yml&lt;/code&gt; and &lt;code&gt;.env&lt;/code&gt; files in your root directory. My config file looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;service: express-app

image: user/express-app
servers:
  - 146.190.86.77

registry:
  username: user
  password:
    - KAMAL_REGISTRY_PASSWORD

healthcheck:
  path: /
  port: 3000

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

&lt;/div&gt;



&lt;p&gt;The config file I have created here is minimal. It defines a service, provides the server IP, Docker Hub registry info and healthcheck information. After successful deploy, Kamal will ping the “/” path on port “3000” and will expect the “200 OK” response from the server.&lt;/p&gt;

&lt;p&gt;Now, let’s deploy the app with the &lt;code&gt;kamal deploy&lt;/code&gt; command. Kamal will deploy your app and you can check if it is live or not by entering the server IP address in the browser. If you want to connect your app with the database, or host multiple environment of the application like staging, production then you can check out my &lt;a href="https://www.kartikey.dev/tag/mrsk/"&gt;previous posts&lt;/a&gt;. They are platform agnostic and applicable on NodeJS apps as well.&lt;/p&gt;

</description>
      <category>kamal</category>
      <category>node</category>
    </item>
    <item>
      <title>How to deploy multi-environment(staging, production) application using Kamal (preivously MRSK)</title>
      <dc:creator>Kartikey Tanna</dc:creator>
      <pubDate>Sun, 09 Apr 2023 08:45:00 +0000</pubDate>
      <link>https://dev.to/tannakartikey/how-to-deploy-multi-environmentstaging-production-application-using-mrsk-5dae</link>
      <guid>https://dev.to/tannakartikey/how-to-deploy-multi-environmentstaging-production-application-using-mrsk-5dae</guid>
      <description>&lt;p&gt;In the &lt;a href="https://dev.to/tannakartikey/deploy-rails-app-and-postgres-with-mrsk-on-single-digitalocean-server-7e3"&gt;previous post&lt;/a&gt;, I described how to host a Rails app and a database on a single server. This post will describe what if you want to host multiple environments i.e. staging, production of the same application using &lt;a href="https://kamal-deploy.org/"&gt;Kamal&lt;/a&gt; - previously known as MRSK.&lt;/p&gt;

&lt;p&gt;Kamal uses &lt;a href="https://traefik.io/"&gt;Traefik&lt;/a&gt; as a reverse proxy. It means that any incoming request will be handled by Traefik. Traefik will handover the request to appropriate server and Docker container based on the configuration. For example, let’s assume that we have deployed the staging and the production version of our apps on the server. Then we have to configure Traefik in such a way that it points “staging.myapp.com” and “production.myapp.com” to the staging and the production Docker containers respectively.&lt;/p&gt;

&lt;p&gt;Kamal uses Traefik with “docker” as a &lt;a href="https://doc.traefik.io/traefik/providers/overview/"&gt;provider&lt;/a&gt;. Traefik &lt;a href="https://doc.traefik.io/traefik/routing/routers/#rule"&gt;rules&lt;/a&gt; are configured by providing certain lables to Docker containers. You can read more about it in their &lt;a href="https://doc.traefik.io/traefik/providers/docker/"&gt;docs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Let’s start configuring Kamal. Kamal has a feature called &lt;a href="https://github.com/basecamp/kamal/pull/71"&gt;“destination”&lt;/a&gt;, we will use that to create two separate destinations - “staging” and “production”. First, we will create the &lt;code&gt;config/deploy.yml&lt;/code&gt; file with common options like service name, image name, registry, common environment variables, and Postgresql database as acessory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# config/deploy.yml
service: myapp
image: user/myapp

registry:
  username: user
  password:
    - KAMAL_REGISTRY_PASSWORD

env:
  clear:
    DB_HOST: 99.99.99.99
  secret:
    - RAILS_MASTER_KEY
    - POSTGRES_PASSWORD

accessories:
  db:
    image: postgres:15
    host: 99.99.99.99
    port: 5432
    env:
      clear:
        POSTGRES_USER: 'myapp_db_user'
      secret:
        - POSTGRES_PASSWORD
    directories:
      - data:/var/lib/postgresql/data

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

&lt;/div&gt;



&lt;h4&gt;
  
  
  Staging config
&lt;/h4&gt;

&lt;p&gt;Now, let’s create a destination named “staging” with the &lt;code&gt;config/deploy.staging.yml&lt;/code&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# config/deploy.staging.yml
servers:
  - 99.99.99.99

labels:
  traefik.http.routers.myapp-web-staging.rule: Host(`staging.myapp.com`)

env:
  clear:
    POSTGRES_DB: myapp_staging
    IS_STAGING: true

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

&lt;/div&gt;



&lt;p&gt;In this file we have declared the server for the staging environment, environment variable spcific to the environment and we have used the &lt;code&gt;labels&lt;/code&gt; key that will attach the labels to the staging Docker container.&lt;/p&gt;

&lt;p&gt;Let’s understand these rules with little more details. Kamal applies some default Traefik labels to each container in the “service-role-destination” format. With the help of these labels Traefik defines a &lt;a href="https://doc.traefik.io/traefik/routing/services/"&gt;service&lt;/a&gt; (not to be confused with Kamal service). With the rule mentioned in the &lt;code&gt;deploy.staging.yml&lt;/code&gt; file above, we are overriding the default labels. Since we have not specified any role, Kamal will assign the web role to the service. Anyways, there has to have one “web” role if we are specifying roles.&lt;/p&gt;

&lt;p&gt;Don’t forget to replace “staging.myapp.com” with your domain. The domain should be configured to point to the IP of the server.&lt;/p&gt;

&lt;h4&gt;
  
  
  Production config
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# config/deploy.production.yml
servers:
  - 99.99.99.99

labels:
  traefik.http.routers.myapp-web-production.rule: Host(`rails.myapp.com`)

env:
  clear:
    POSTGRES_DB: myapp_production

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

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;deploy.production.yml&lt;/code&gt; is similar to the staging configuration. Since we are going to deploy both the staging and the production as well as Postgresql(the database) on the same server, the same IP is configured under the &lt;code&gt;hosts&lt;/code&gt; key. Again, don’t forget to configure your domain.&lt;/p&gt;

&lt;p&gt;That’s it! You should have both the staging and the production as well as the database deployed on the same or different server(s) and accessible from the domains you have set for each environment!&lt;/p&gt;

</description>
      <category>kamal</category>
      <category>rails</category>
      <category>postgres</category>
    </item>
    <item>
      <title>Deploy Rails app and Postgres with Kamal(previously MRSK) on single DigitalOcean server</title>
      <dc:creator>Kartikey Tanna</dc:creator>
      <pubDate>Wed, 05 Apr 2023 13:08:00 +0000</pubDate>
      <link>https://dev.to/tannakartikey/deploy-rails-app-and-postgres-with-mrsk-on-single-digitalocean-server-7e3</link>
      <guid>https://dev.to/tannakartikey/deploy-rails-app-and-postgres-with-mrsk-on-single-digitalocean-server-7e3</guid>
      <description>&lt;p&gt;For me, it’s been always cumbersome to host a Rails side project or personal application. Sure, Heroku is straightforward but it’s not as cheap as I would like it to be. And I don’t like the uptime limitation their free plan has. I have been making my way through Dokku and Capistrano. Luckily, a new door opened when &lt;a href="https://kamal-deploy.org/"&gt;Kamal&lt;/a&gt;, previously known as MRSK was launched by DHH recently.&lt;/p&gt;

&lt;p&gt;In this post, I am not getting into how Kamal works and the benefits of using it. DHH has created this fantastic &lt;a href="https://www.youtube.com/watch?v=LL1cV2FXZ5I"&gt;introduction video&lt;/a&gt; for that.&lt;/p&gt;

&lt;p&gt;Kamal is capable to deploy applications on multiple servers as well as a single server. In this post, I will describe how we can deploy a Rails application and Postgresql on a single VPS.&lt;/p&gt;

&lt;p&gt;First of all, install and init Kamal on your local machine with instructions on the &lt;a href="https://github.com/basecamp/kamal#readme"&gt;README&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The key to the single server setup is to use the same hosts for the &lt;code&gt;web&lt;/code&gt; as well as &lt;code&gt;accessories&lt;/code&gt;. Here is what the &lt;code&gt;deploy.yml&lt;/code&gt; file looks like for that setup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-app&lt;/span&gt;
&lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;user/my-app&lt;/span&gt;

&lt;span class="na"&gt;servers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;web&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;134.209.111.91&lt;/span&gt;

&lt;span class="na"&gt;registry&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tannakartikey&lt;/span&gt;
  &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;KAMAL_REGISTRY_PASSWORD&lt;/span&gt;

&lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;clear&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;DB_HOST&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;134.209.111.91&lt;/span&gt;
  &lt;span class="na"&gt;secret&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;RAILS_MASTER_KEY&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_PASSWORD&lt;/span&gt;

&lt;span class="na"&gt;accessories&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:15&lt;/span&gt;
    &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;134.209.111.91&lt;/span&gt;
    &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5432&lt;/span&gt;
    &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;clear&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;POSTGRES_USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;my_app'&lt;/span&gt;
        &lt;span class="na"&gt;POSTGRES_DB&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;my_app_production'&lt;/span&gt;
      &lt;span class="na"&gt;secret&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_PASSWORD&lt;/span&gt;
    &lt;span class="na"&gt;directories&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;data:/var/lib/postgresql/data&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;I am using the default &lt;a href="https://github.com/tannakartikey/rails_71_mrsk_deploy/blob/2d138e10b2c87ec0bccc382ae06ce6e71c6f7187/Dockerfile"&gt;&lt;code&gt;Dockerfile&lt;/code&gt;&lt;/a&gt; that is generated with Rails 7.1. Create and place the Dockerfile in your root directory if it does not exist already. You also need to create a &lt;a href="https://github.com/tannakartikey/rails_71_mrsk_deploy/commit/2d138e10b2c87ec0bccc382ae06ce6e71c6f7187#diff-e9cbb0224c4a3d23a6019ba557e0cd568c1ad5e1582ff1e335fb7d99b7a1055d"&gt;&lt;code&gt;.env&lt;/code&gt;&lt;/a&gt; file in the root of the repository and also need to make minor changes in your &lt;a href="https://github.com/tannakartikey/rails_71_mrsk_deploy/commit/2d138e10b2c87ec0bccc382ae06ce6e71c6f7187#diff-5a674c769541a71f2471a45c0e9dde911b4455344e3131bddc5a363701ba6325"&gt;&lt;code&gt;config/database.yml&lt;/code&gt;&lt;/a&gt; file.&lt;/p&gt;

&lt;p&gt;I have created this sample &lt;a href="https://github.com/tannakartikey/rails_71_mrsk_deploy"&gt;repository&lt;/a&gt; that has all the code described in this post. This &lt;a href="https://github.com/tannakartikey/rails_71_mrsk_deploy/commit/2d138e10b2c87ec0bccc382ae06ce6e71c6f7187"&gt;commit&lt;/a&gt; holds all the Kamal setup-related changes.&lt;/p&gt;

&lt;p&gt;Be sure NOT to include your &lt;code&gt;.env&lt;/code&gt; file in Git or your &lt;code&gt;Dockerfile&lt;/code&gt;. If you are using Docker Hub or any other registry, make sure to make the image private because it holds a copy of the application code.&lt;/p&gt;

&lt;p&gt;Next, we need to set up the server since we are deploying to the server for the first time. We can do it with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bin/kamal setup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;setup&lt;/code&gt; command will set up all the accessories and deploy the app on the server.&lt;/p&gt;

&lt;p&gt;That’s it! You should have a running Rails app on the server which is connected to Postgresql on the same server. For subsequent deploy you can use &lt;code&gt;kamal deploy&lt;/code&gt; or &lt;code&gt;kamal redeploy&lt;/code&gt; based on your needs.&lt;/p&gt;

&lt;p&gt;I have been using the USD4 droplet on DigitalOcean. If you are using a similar configuration I would recommend creating a &lt;a href="https://www.digitalocean.com/community/tutorials/how-to-add-swap-space-on-ubuntu-20-04"&gt;swap&lt;/a&gt; partition on the server. I am assuming that if you are doing this, you are not expecting loads of traffic on day one. But without a swap partition, some simple operations like migrating the database may also fail.&lt;/p&gt;

&lt;p&gt;It can also be possible to host the same code multiple times for different environments i.e. staging, production etc. For that, the &lt;a href="https://github.com/basecamp/kamal/pull/99"&gt;“role”&lt;/a&gt; and &lt;a href="https://github.com/basecamp/kamal/pull/71"&gt;“destination”&lt;/a&gt; functionality of Kamal can be used. I might cover that in a separate post.&lt;/p&gt;

</description>
      <category>kamal</category>
      <category>rails</category>
      <category>postgres</category>
    </item>
    <item>
      <title>The surpising behaviour of Rails' default_scope method</title>
      <dc:creator>Kartikey Tanna</dc:creator>
      <pubDate>Sun, 10 Jul 2022 14:45:06 +0000</pubDate>
      <link>https://dev.to/tannakartikey/an-interesting-fact-about-rails-defaultscope-1ij2</link>
      <guid>https://dev.to/tannakartikey/an-interesting-fact-about-rails-defaultscope-1ij2</guid>
      <description>&lt;p&gt;It is very convinient to declare &lt;code&gt;default_scope&lt;/code&gt; in the Rails models. Specially for something like &lt;code&gt;deleted_at&lt;/code&gt;. But it has an interesting side effect that has to be kept in mind.&lt;/p&gt;

&lt;p&gt;Let's take an example model &lt;code&gt;Book&lt;/code&gt;with an attribute &lt;code&gt;approved&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is how the column is defined in the migration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;boolean&lt;/span&gt; &lt;span class="ss"&gt;:approved&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;null: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;default: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is how the &lt;code&gt;default_scope&lt;/code&gt; method is declared in the model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Book&lt;/span&gt;
  &lt;span class="n"&gt;default_scope&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;approved: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Whenever a new record is created, the &lt;code&gt;approved&lt;/code&gt; is going to be &lt;code&gt;true&lt;/code&gt; for the record even though the migration has the &lt;code&gt;default&lt;/code&gt; value set as &lt;code&gt;false&lt;/code&gt;. Let's see it in action:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;book&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Book&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
&lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;approved&lt;/span&gt; &lt;span class="c1"&gt;# true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To conclude, the &lt;code&gt;default_scope&lt;/code&gt; overrides the default value set at the migration level. That has to be kept in mind while declaring &lt;code&gt;default_scope&lt;/code&gt; in a model.&lt;/p&gt;

</description>
      <category>rails</category>
      <category>activerecord</category>
    </item>
    <item>
      <title>Memory efficient way of reading and downloading a large file in Ruby</title>
      <dc:creator>Kartikey Tanna</dc:creator>
      <pubDate>Fri, 02 Oct 2020 00:00:00 +0000</pubDate>
      <link>https://dev.to/tannakartikey/memory-efficient-way-of-reading-and-downloading-a-large-file-in-ruby-4b8i</link>
      <guid>https://dev.to/tannakartikey/memory-efficient-way-of-reading-and-downloading-a-large-file-in-ruby-4b8i</guid>
      <description>&lt;h3&gt;
  
  
  To read a large file from the disk
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://ruby-doc.org/core-2.7.1/IO.html#method-c-foreach"&gt;&lt;code&gt;File.foreach&lt;/code&gt;&lt;/a&gt; method reads the file line by line; that is why it is safe to use for large files.&lt;br&gt;&lt;br&gt;
It can accept the block to execute each line of a file.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;foreach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'example.jsonl'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="no"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, in the Ruby documentation, you will not find this method defined on the &lt;a href="https://ruby-doc.org/core-2.7.1/File.html"&gt;&lt;code&gt;File&lt;/code&gt;&lt;/a&gt; class. It is defined on &lt;a href="https://ruby-doc.org/core-2.7.1/IO.html"&gt;&lt;code&gt;IO&lt;/code&gt;&lt;/a&gt;, which is a superclass of &lt;a href="https://ruby-doc.org/core-2.7.1/File.html"&gt;&lt;code&gt;File&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  To download the large files from the Internet
&lt;/h3&gt;

&lt;p&gt;We can use &lt;a href="https://ruby-doc.org/core-2.7.1/IO.html#method-c-copy_stream"&gt;&lt;code&gt;IO.copy_stream&lt;/code&gt;&lt;/a&gt; to download the large files from the Internet. It will create a stream instead of loading the whole file into memory before writing.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;IO&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;copy_stream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'http://example.com/file.jsonl'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s1"&gt;'file.jsonl'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To sum it up…&lt;/p&gt;

&lt;p&gt;Memory friendly methods:&lt;br&gt;&lt;br&gt;
&lt;a href="https://ruby-doc.org/core-2.7.1/IO.html#method-c-foreach"&gt;&lt;code&gt;File.foreach&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://ruby-doc.org/core-2.7.1/IO.html#method-c-copy_stream"&gt;&lt;code&gt;IO.copy_stream&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Other methods to read a file:&lt;br&gt;&lt;br&gt;
&lt;a href="https://ruby-doc.org/core-2.7.1/IO.html#method-c-read"&gt;&lt;code&gt;File.read&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://ruby-doc.org/core-2.7.1/IO.html#method-c-readlines"&gt;&lt;code&gt;File.readlines&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ruby</category>
    </item>
    <item>
      <title>Convert API Response Into a Model in Rails</title>
      <dc:creator>Kartikey Tanna</dc:creator>
      <pubDate>Wed, 30 Sep 2020 00:00:00 +0000</pubDate>
      <link>https://dev.to/tannakartikey/convert-api-response-into-a-model-in-rails-4j6l</link>
      <guid>https://dev.to/tannakartikey/convert-api-response-into-a-model-in-rails-4j6l</guid>
      <description>&lt;p&gt;Let’s get down straight to the business.&lt;/p&gt;

&lt;p&gt;Let’s take an example of showing the invoices to the user from Stripe.&lt;/p&gt;

&lt;p&gt;First, let’s write &lt;strong&gt;what we would like to have&lt;/strong&gt; without worrying about the implementation.&lt;/p&gt;

&lt;p&gt;I like &lt;strong&gt;short and clean&lt;/strong&gt; controllers. Something like the following would do.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;app/controllers/invoices_controller.rb&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class InvoicesController &amp;lt; ApplicationController
  def index
    @invoices = Invoice.find_all_by_user(current_user)
  end

  def show
    @invoice = Invoice.new(params[:id])
    render
  end
end

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

&lt;/div&gt;



&lt;p&gt;Below is what I would like in my view: &lt;code&gt;app/views/invoices/_invoice.html.erb&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;% unless (invoice.total == 0) %&amp;gt;
  &amp;lt;tr&amp;gt;
    &amp;lt;td&amp;gt;&amp;lt;%= link_to invoice.number, invoice_path(invoice.id) %&amp;gt;&amp;lt;/td&amp;gt;
    &amp;lt;td&amp;gt;&amp;lt;%= invoice.date %&amp;gt;&amp;lt;/td&amp;gt;
    &amp;lt;td&amp;gt;&amp;lt;%= number_to_currency(invoice.total, negative_format: "(%u%n)") %&amp;gt;&amp;lt;/td&amp;gt;
    &amp;lt;td&amp;gt;&amp;lt;%= invoice.period_start %&amp;gt; to &amp;lt;%= invoice.period_end %&amp;gt;&amp;lt;/td&amp;gt;
    &amp;lt;td&amp;gt;&amp;lt;%= invoice.paid? ? 'Paid' : 'Unpaid' %&amp;gt;&amp;lt;/td&amp;gt;
  &amp;lt;/tr&amp;gt;
&amp;lt;% end %&amp;gt;

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

&lt;/div&gt;



&lt;p&gt;I have experienced that writing the interface first, as we did above, gives me a lot of clarity during implementation. Now, let’s start implementing it in a model.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;app/models/invoice.rb&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class Invoice

  attr_reader :stripe_invoice

  def self.find_all_by_user(user)
    if user.present?
      stripe_invoices_for_user(user).map do |invoice|
        new(invoice)
      end
    else
      []
    end
  end

  def initialize(invoice_id_or_object)
    if invoice_id_or_object.is_a? String
      @stripe_invoice = retrieve(invoice_id_or_object)
    else
      @stripe_invoice = invoice_id_or_object
    end
  end

  def to_partial_path
    "invoices/#{self.class.name.underscore}"
  end

  def id
    stripe_invoice.id
  end

  def number
    stripe_invoice.number
  end

  def total
    cents_to_dollars(stripe_invoice.total)
  end

  def date
    convert_stripe_time(stripe_invoice.date)
  end

  def paid?
    stripe_invoice.paid
  end

  def subscription
    stripe_invoice.subscription
  end

  def period_start
    convert_stripe_time(stripe_invoice.period_start)
  end

  def period_end
    convert_stripe_time(stripe_invoice.period_end)
  end

  def user
    @user ||= User.find_by(stripe_customer_id: stripe_invoice.customer)
  end

  def balance
    if paid?
      0.00
    else
      amount_due
    end
  end

  def amount_due
    cents_to_dollars(stripe_invoice.amount_due)
  end

  def subtotal
    cents_to_dollars(stripe_invoice.subtotal)
  end

  def amount_paid
    if paid?
      amount_due
    else
      0.00
    end
  end

  def plan
    stripe_invoice.lines.data[0].plan.name
  end

  def plan_amount
    cents_to_dollars stripe_invoice.lines.data[0].plan.amount
  end

  def pay
    stripe_invoice.pay
  end

  def self.pay_if_pending(user)
    invoices = find_all_by_user(user)
    unless invoices.empty? || invoices.first.paid?
      invoices.first.pay
    end
  end

  def self.upcoming(user)
    new(Stripe::Invoice.upcoming(customer: user.stripe_customer_id) || nil )
  end

  private

  def self.stripe_invoices_for_user(user)
    Stripe::Invoice.all(customer: user.stripe_customer_id).data
  end

  def retrieve(invoice_id)
    Stripe::Invoice.retrieve(invoice_id)
  end

  def convert_stripe_time(time)
    Time.zone.at(time).strftime('%D')
  end

  def cents_to_dollars(amount)
    amount / 100.0
  end
end

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

&lt;/div&gt;



&lt;p&gt;Here, the &lt;a href="https://github.com/stripe/stripe-ruby"&gt;Stripe&lt;/a&gt; gem takes exposes many methods on the response. But we can take any vanilla JSON response any do the same.&lt;/p&gt;

&lt;p&gt;Many years ago, I learned this pattern form &lt;a href="https://thoughtbot.com/upcase"&gt;Upcase&lt;/a&gt;. Since then I am always parsing API responses like this. Upcase is free now and definitely worth a look.&lt;/p&gt;

&lt;p&gt;Download the code as &lt;a href="https://gist.github.com/tannakartikey/d9f2b7cb8a473319f65fa325790c52dd"&gt;gist&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>rails</category>
      <category>ruby</category>
    </item>
    <item>
      <title>Many-to-many self join in Rails</title>
      <dc:creator>Kartikey Tanna</dc:creator>
      <pubDate>Tue, 29 Sep 2020 00:00:00 +0000</pubDate>
      <link>https://dev.to/tannakartikey/many-to-many-self-join-in-rails-3m64</link>
      <guid>https://dev.to/tannakartikey/many-to-many-self-join-in-rails-3m64</guid>
      <description>&lt;p&gt;Let’s say we have “products” and we want to prepare “kits” of those products. Kits are nothing but the group of the products.&lt;/p&gt;

&lt;p&gt;We can use many-to-many relationship here because products can be in many kits, and kits can be associated with many products.&lt;/p&gt;

&lt;p&gt;Also, since the kits are just grouping the products, we can use self-joins. There are multiple ways we can implement self-joins.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using has_and_belongs_to_many
&lt;/h3&gt;

&lt;p&gt;You can read more about &lt;a href="https://guides.rubyonrails.org/association_basics.html#the-has-and-belongs-to-many-association"&gt;has_and_belongs_to_many&lt;/a&gt; on Rails docs.&lt;/p&gt;

&lt;h4&gt;
  
  
  Migration
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CreateJoinTableProductKits&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;6.0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;change&lt;/span&gt;
    &lt;span class="n"&gt;create_table&lt;/span&gt; &lt;span class="ss"&gt;:product_kits&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;id: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;references&lt;/span&gt; &lt;span class="ss"&gt;:product&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;null: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;foreign_key: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;index: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;references&lt;/span&gt; &lt;span class="ss"&gt;:kit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;null: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;foreign_key: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;to_table: :products&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="ss"&gt;index: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;index&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:product_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:kit_id&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="ss"&gt;unique: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

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

&lt;/div&gt;



&lt;h4&gt;
  
  
  Model
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="n"&gt;has_and_belongs_to_many&lt;/span&gt; &lt;span class="ss"&gt;:kits&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;join_table: :product_kits&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;class_name: &lt;/span&gt;&lt;span class="s1"&gt;'Product'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;association_foreign_key: &lt;/span&gt;&lt;span class="s1"&gt;'kit_id'&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Using has_many through
&lt;/h3&gt;

&lt;p&gt;This approach is better because later on in your project you can add more fields and validations in &lt;code&gt;ProductKit&lt;/code&gt; model.As you know, our projects are always dynamic and most of the time(all the time) we end up modifying the flow. So, it isbetter to be prepared and use &lt;code&gt;has_many :through&lt;/code&gt; from the beginning.&lt;/p&gt;

&lt;p&gt;More on, &lt;a href="https://guides.rubyonrails.org/association_basics.html#the-has-many-through-association"&gt;&lt;code&gt;has_many :through&lt;/code&gt;&lt;/a&gt; on Rails docs.&lt;/p&gt;

&lt;h4&gt;
  
  
  Migration
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CreateJoinTableProductKits&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;6.0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;change&lt;/span&gt;
    &lt;span class="n"&gt;create_table&lt;/span&gt; &lt;span class="ss"&gt;:product_kits&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;references&lt;/span&gt; &lt;span class="ss"&gt;:product&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;null: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;foreign_key: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;index: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;references&lt;/span&gt; &lt;span class="ss"&gt;:kit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;null: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;foreign_key: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;to_table: :products&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="ss"&gt;index: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;index&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:product_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:kit_id&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="ss"&gt;unique: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;

      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;timestamps&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

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

&lt;/div&gt;



&lt;h4&gt;
  
  
  Model &lt;code&gt;app/models/product.rb&lt;/code&gt;
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="ss"&gt;:product_kits&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;dependent: :destroy&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="ss"&gt;:kits&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;through: :product_kits&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

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

&lt;/div&gt;



&lt;h4&gt;
  
  
  Model &lt;code&gt;app/models/product_kit.rb&lt;/code&gt;
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProductKit&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="ss"&gt;:product&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="ss"&gt;:kit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;class_name: &lt;/span&gt;&lt;span class="s1"&gt;'Product'&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

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

&lt;/div&gt;



</description>
      <category>rails</category>
      <category>ruby</category>
    </item>
    <item>
      <title>Challenges I face as a freelancer and how I overcome them</title>
      <dc:creator>Kartikey Tanna</dc:creator>
      <pubDate>Sun, 20 Sep 2020 00:00:00 +0000</pubDate>
      <link>https://dev.to/tannakartikey/challenges-i-face-as-a-freelancer-and-how-i-overcome-them-kho</link>
      <guid>https://dev.to/tannakartikey/challenges-i-face-as-a-freelancer-and-how-i-overcome-them-kho</guid>
      <description>&lt;p&gt;My initial days as a freelancer were rather difficult and confusing. There were multiple challenges that I learned to overcome the hard-way.&lt;/p&gt;

&lt;p&gt;Many people assume that the biggest challenge while freelancing is about finding work. Once the ball starts rolling, selecting, managing, and planning the work becomes a bigger concern.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;del&gt;Finding work&lt;/del&gt; Finding good work
&lt;/h3&gt;

&lt;p&gt;Initially, as a freelancer, when I would receive an offer, I used to be apprehensive about things like:&lt;br&gt;&lt;br&gt;
What if I get a better offer later?&lt;br&gt;&lt;br&gt;
Should I go for a short-term contract with higher hourly rates or a long-term contract with lesser hourly rates?&lt;br&gt;&lt;br&gt;
How long will the project go on?&lt;br&gt;&lt;br&gt;
What if the client went out of funds earlier than they had foreseen?&lt;/p&gt;

&lt;p&gt;I’d either not accept any offer and lose time and money or I’d accept multiple offers and deliver inferior work quality according to my standards.&lt;/p&gt;

&lt;p&gt;So in the past few years, I have learned to overcome these concerns by not accepting a less paying job out of desperation. I keep my hourly rates at par with the market and my skills. Being realistic, neither underestimating nor overestimating myself.&lt;/p&gt;

&lt;p&gt;Additionally, communication is the key! I outrightly started asking the client regarding the project’s duration and financial status. I convey to them how these aspects of the project can affect me.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;del&gt;In-between projects&lt;/del&gt; Personality update available
&lt;/h3&gt;

&lt;p&gt;No matter how careful I am, sometimes, there is no full-time project in hand for a few days. This initially bothered me as the cash flow would take a hit. Saving and having knowledge about investment came into play after such incidents. &lt;a href="https://www.goodreads.com/book/show/106835.The_Intelligent_Investor"&gt;The Intelligent Investor&lt;/a&gt; turned out to be a good start.&lt;/p&gt;

&lt;p&gt;I also realized that this is the time to improve my online presence and resume, contribute to open source, work on personal projects, and learn new things. These things help me in acquiring the next job sooner and at a higher hourly rate.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;del&gt;Exploiting&lt;/del&gt; Appreciating flexible timings
&lt;/h3&gt;

&lt;p&gt;Another challenge is prioritizing! As a freelancer, flexible working hours come as a boon, but the same can be a curse if you keep procrastinating. I observed that when I didn’t set a target then I’d either hardly work or would keep working endlessly. I’d either make the entire week a weekend or keep scrolling through assigned tickets, responding to messages even while having dinner with family, and reading feature suggestions till late night. All of this only decreased my work efficiency. To enjoy the perks of flexible timings, I don’t let important tasks become urgent, and I have a goal that I have to achieve by Friday so that I can have a good weekend with my family.&lt;/p&gt;

&lt;h3&gt;
  
  
  Being &lt;del&gt;outdated&lt;/del&gt; updated
&lt;/h3&gt;

&lt;p&gt;As a freelancer, it is also vital that we keep learning. If you are not updated, then you are outdated.&lt;/p&gt;

&lt;p&gt;I upgrade my knowledge by contributing to open-source, lots of reading, participating in conferences, online courses, and working on personal projects. I have subscribed to portals like &lt;a href="https://frontendmasters.com/"&gt;Frontend Masters&lt;/a&gt;, &lt;a href="https://egghead.io/"&gt;Egghead&lt;/a&gt;, &lt;a href="https://testingjavascript.com/"&gt;TestingJavascript&lt;/a&gt;, &lt;a href="https://www.pirple.com/"&gt;Pirple&lt;/a&gt;, &lt;a href="https://codestool.coding-gnome.com/"&gt;Coding-Gnome&lt;/a&gt;, and regularly take courses there (links are not affiliated/sponsored).&lt;/p&gt;

&lt;p&gt;If you liked this article, check out my other articles on freelancing.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.kartikey.dev/2020/08/30/why-do-i-freelance-and-why-should-you-too.html"&gt;Why do I freelance and why should you too&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.kartikey.dev/2020/09/06/the-3-roles-of-a-freelance-developer.html"&gt;The three roles of a freelance developer&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>freelancing</category>
      <category>career</category>
      <category>motivation</category>
    </item>
    <item>
      <title>The 3 Roles of a Freelance Developer</title>
      <dc:creator>Kartikey Tanna</dc:creator>
      <pubDate>Sun, 06 Sep 2020 00:00:00 +0000</pubDate>
      <link>https://dev.to/tannakartikey/the-3-roles-of-a-freelance-developer-5d3g</link>
      <guid>https://dev.to/tannakartikey/the-3-roles-of-a-freelance-developer-5d3g</guid>
      <description>&lt;p&gt;According to Hindu mythology, the three cosmic functions of creation, sustaining, and transformation/destruction of the universe are performed by Lord Brahma, Lord Vishnu, and Lord Shiva, respectively. Similarly, to stand apart from others, we have to perform these three Godlike functions in our human profession! Let’s look into it from a freelance developer’s point of view.&lt;/p&gt;

&lt;h3&gt;
  
  
  Being Brahma: the creator
&lt;/h3&gt;

&lt;p&gt;Creating need not necessarily refer to just building web apps. There are many other tasks that you have to look into, like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Building a remarkable online presence to get new clients&lt;/li&gt;
&lt;li&gt;Creating a network of clients&lt;/li&gt;
&lt;li&gt;Drafting proposals&lt;/li&gt;
&lt;li&gt;Coming up with creative ideas to improve the products.&lt;/li&gt;
&lt;li&gt;A strategy to follow throughout the project.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Being Vishnu: the sustainer
&lt;/h3&gt;

&lt;p&gt;Once new projects start rolling, there is quite an amount of maintenance required, viz.:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Maintaining a healthy relationship with the clients, including the previous ones.&lt;/li&gt;
&lt;li&gt;Stick to the strategy decided and maintain the pace of the project.&lt;/li&gt;
&lt;li&gt;Updating documentation, fixing bugs, upgrading dependencies, keeping an eye on the performance.&lt;/li&gt;
&lt;li&gt;Managing your finances and maintaining good health. (Do not underestimate this!)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Being Shiva: the transformer/destroyer
&lt;/h3&gt;

&lt;p&gt;Update to improvise!&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Transform and upgrade your work-style and strategy if needed.&lt;/li&gt;
&lt;li&gt;Troubleshooting technical challenges&lt;/li&gt;
&lt;li&gt;Refactor the code and get rid of the clutter.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There might be various other things apart from the above that a freelance developer is required to do, but, interestingly, all of it falls under these three categories.&lt;br&gt;&lt;br&gt;
If you are a freelance developer, its a good idea to regularly introspect which of these three categories you expertise at and which you need to work upon.&lt;/p&gt;

</description>
      <category>freelancing</category>
      <category>career</category>
    </item>
  </channel>
</rss>
