Jekyll2024-01-28T16:04:55+00:00https://www.kartikey.dev/feed.xmlKartikey’s BlogA blog by Kartikey Tanna on programming and freelancing. I am a programmer and an avid meditator. Enjoy cooking and eating vegetarian food. Hate traveling but would go to the end of the world for a good pizza! I have two years of experience in being a good husband! 😃Kartikey Tannatannakartikey@gmail.comUnderstanding the role of the “schema.rb” file in Ruby on Rails development2023-08-06T00:00:00+00:002023-08-06T00:00:00+00:00https://www.kartikey.dev/2023/08/06/understanding-the-role-of-the-schema-rb-file-in-ruby-on-rails-development<p>I recently published a <a href="https://www.linkedin.com/feed/update/urn:li:activity:7093289792351707136/" target="_blank">post in a Ruby on Rails group on LinkedIn</a> about a rake task I bring to most of my projects. This Rake task deletes the <code class="language-plaintext highlighter-rouge">schema.rb</code> file and regenerates it. I got many reactions from fellow developers and that motivated me to write this post.</p>
<h2 id="understanding-the-purpose-of-rails-schemarb-file">Understanding the Purpose of Rails’ schema.rb File</h2>
<p>The <code class="language-plaintext highlighter-rouge">schema.rb</code> 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.</p>
<p>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.</p>
<p>The <code class="language-plaintext highlighter-rouge">schema.rb</code> file in a new Rails application contains the following comment:</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># This file is auto-generated from the current state of the database. Instead </span>
<span class="c1"># of editing this file, please use the migrations feature of Active Record to </span>
<span class="c1"># incrementally modify your database, and then regenerate this schema definition. </span>
<span class="c1"># </span>
<span class="c1"># This file is the source Rails uses to define your schema when running \`bin/rails </span>
<span class="c1"># db:schema:load\`. When creating a new database, \`bin/rails db:schema:load\` tends to </span>
<span class="c1"># be faster and is potentially less error prone than running all of your </span>
<span class="c1"># migrations from scratch. Old migrations may fail to apply correctly if those </span>
<span class="c1"># migrations use external dependencies or application code. </span>
<span class="c1"># </span>
<span class="c1"># It's strongly recommended that you check this file into your version control system.</span>
</code></pre></div></div>
<h2 id="decoding-the-authority-over-the-database-in-rails">Decoding the Authority Over the Database in Rails</h2>
<p>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.</p>
<p>The <a href="https://github.com/rails/rails/blob/9eeb00976d4b0a963c58117b46b7a5c6edcacc31/guides/source/migrations.md#what-are-schema-files-for" target="_blank">first version</a> of the docs from 2012 claimed that the <code class="language-plaintext highlighter-rouge">db/schema.rb</code> or an SQL file generated by Active Record was the authoritative source for your database schema.</p>
<blockquote>
<p><strong>What are Schema Files for?</strong></p>
<p>Migrations, mighty as they may be, are not the authoritative source for your database schema. <strong>That role falls to either <code class="language-plaintext highlighter-rouge">db/schema.rb</code> or an SQL file which Active Record generates by examining the database.</strong> They are not designed to be edited, they just represent the current state of the database.</p>
<p><strong>There is no need (and it is error prone) to deploy a new instance of an app by replaying the entire migration history.</strong> It is much simpler and faster to just load into the database a description of the current schema.</p>
</blockquote>
<p>However, the <a href="https://github.com/rails/rails/blob/84718df86097442f85999d6f2e6f6b8b59724c3f/guides/source/active_record_migrations.md#what-are-schema-files-for" target="_blank">follow-up version</a> from 2018 which we read <a href="https://guides.rubyonrails.org/active_record_migrations.html#what-are-schema-files-for-questionmark" target="_blank">today</a> clearly states that your actual database remains the authoritative source.</p>
<blockquote>
<p><strong>What are Schema Files for?</strong></p>
<p>Migrations, mighty as they may be, are not the authoritative source for your database schema. <strong>Your database remains the authoritative source.</strong> By default, Rails generates <code class="language-plaintext highlighter-rouge">db/schema.rb</code> which attempts to capture the current state of your database schema.</p>
<p>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 <code class="language-plaintext highlighter-rouge">rails db:schema:load</code> than it is to replay the entire migration history. <strong>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.</strong></p>
</blockquote>
<p>The commit was made with the following comment from the contributors:</p>
<blockquote>
<p> <strong>Update <code class="language-plaintext highlighter-rouge">schema.rb</code> documentation [CI SKIP]</strong></p>
<p> The documentation previously claimed that `db/schema.rb` was “the<br />
authoritative source for your database schema” while simultaneously<br />
also acknowledging that the file is generated. These two statements are<br />
incongruous and the guides accurately call out that many database<br />
constructs are unsupported by `schema.rb`. <strong>This change updates the</strong><br />
<strong>comment at the top of `schema.rb` to remove the assertion that the file</strong><br />
<strong>is authoritative.</strong></p>
<p> The documentation also previously referred vaguely to “issues” when<br />
re-running old migrations. This has been updated slightly to hint at the<br />
types of problems that one can encounter with old migrations.</p>
<p> In sum, this change attempts to more accurately capture the pros, cons,<br />
and shortcomings of the two schema formats in the guides and in the<br />
comment at the top of `schema.rb`.</p>
<p> [Derek Prior & Sean Griffin]</p>
</blockquote>
<p>While <code class="language-plaintext highlighter-rouge">db/schema.rb</code> and migrations play vital roles in managing your database structure, neither are the definitive descriptors of your schema. The <code class="language-plaintext highlighter-rouge">db/schema.rb</code> 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.</p>
<p>As developers, ensuring your migrations are up-to-date and error-free is paramount. These migrations should be able to recreate <code class="language-plaintext highlighter-rouge">db/schema.rb</code> 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.</p>
<h3 id="working-with-schemarb">Working with schema.rb </h3>
<p>While it might be tempting to manually modify <em>schema.rb</em>, resist the urge! Any changes should be made through migrations. This ensures that the <em>schema.rb</em> file can be regenerated correctly.</p>
<p>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.</p>
<p>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:</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># lib/tasks/complete_reset.rake</span>
<span class="n">namespace</span> <span class="ss">:db</span> <span class="k">do</span>
<span class="n">desc</span> <span class="s1">'reset the database by dropping the schema'</span>
<span class="n">task</span> <span class="ss">complete_reset: :environment</span> <span class="k">do</span>
<span class="k">raise</span> <span class="k">unless</span> <span class="no">Rails</span><span class="p">.</span><span class="nf">env</span><span class="p">.</span><span class="nf">local?</span>
<span class="no">FileUtils</span><span class="p">.</span><span class="nf">rm_f</span><span class="p">(</span><span class="s1">'db/schema.rb'</span><span class="p">)</span>
<span class="no">Rake</span><span class="o">::</span><span class="no">Task</span><span class="p">[</span><span class="s1">'db:drop'</span><span class="p">].</span><span class="nf">invoke</span>
<span class="no">Rake</span><span class="o">::</span><span class="no">Task</span><span class="p">[</span><span class="s1">'db:create'</span><span class="p">].</span><span class="nf">invoke</span>
<span class="no">Rake</span><span class="o">::</span><span class="no">Task</span><span class="p">[</span><span class="s1">'db:migrate'</span><span class="p">].</span><span class="nf">invoke</span>
<span class="no">Rake</span><span class="o">::</span><span class="no">Task</span><span class="p">[</span><span class="s1">'db:seed'</span><span class="p">].</span><span class="nf">invoke</span>
<span class="no">Rake</span><span class="o">::</span><span class="no">Task</span><span class="p">[</span><span class="s1">'dev:prime'</span><span class="p">].</span><span class="nf">invoke</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<h2 id="faqs">FAQs</h2>
<h3 id="should-i-modify-the-schemarb-file-manually">Should I modify the <code class="language-plaintext highlighter-rouge">schema.rb</code> file manually?</h3>
<p>As a rule of thumb, you <strong>should not</strong> manually modify the <code class="language-plaintext highlighter-rouge">schema.rb</code> file. Instead, you should use <a href="https://guides.rubyonrails.org/active_record_migrations.html#what-are-schema-files-for-questionmark" target="_blank">ActiveRecord Migrations</a> to alter your database schema. This ensures that changes are properly tracked and the <code class="language-plaintext highlighter-rouge">schema.rb</code> 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.</p>
<h3 id="what-are-some-best-practices-for-managing-the-schemarb-file-in-a-team-environment">What are some best practices for managing the <code class="language-plaintext highlighter-rouge">schema.rb</code> file in a team environment?</h3>
<ol>
<li><strong>Version Control</strong>: Always keep the <code class="language-plaintext highlighter-rouge">schema.rb</code> file in your version control system to keep track of its changes over time.</li>
<li><strong>Don’t Modify Manually</strong>: Avoid making manual modifications to the <code class="language-plaintext highlighter-rouge">schema.rb</code> file. Use ActiveRecord migrations instead.</li>
<li><strong>Review Changes</strong>: Before committing changes to the <code class="language-plaintext highlighter-rouge">schema.rb</code> file, review them to make sure they align with the changes made in your migrations.</li>
<li><strong>Sync with Database</strong>: Always make sure your <code class="language-plaintext highlighter-rouge">schema.rb</code> file is synchronized with the current state of your database schema.</li>
</ol>
<h3 id="what-happens-if-the-schemarb-file-is-deleted-or-modified">What happens if the <code class="language-plaintext highlighter-rouge">schema.rb</code> file is deleted or modified?</h3>
<p>If the <code class="language-plaintext highlighter-rouge">schema.rb</code> 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 <code class="language-plaintext highlighter-rouge">schema.rb</code> file.</p>Kartikey Tannatannakartikey@gmail.comI recently published a post in a Ruby on Rails group on LinkedIn about a rake task I bring to most of my projects. This Rake task deletes the schema.rb file and regenerates it. I got many reactions from fellow developers and that motivated me to write this post.How to enable Traefik dashboard with Kamal (previously MRSK)2023-04-12T13:10:00+00:002023-04-12T13:10:00+00:00https://www.kartikey.dev/2023/04/12/how-to-enable-traefik-dashboard-with-mrsk<p><a href="https://kamal-deploy.org/" target="_blank"> Kamal </a> uses Traefik as a dynamic reverse-proxy. Traefik has a beautiful <a href="https://doc.traefik.io/traefik/operations/dashboard/" target="_blank"> dashboard </a> to visually display the configuration. Kamal is not configured to show the dashboard by default.</p>
<p>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.</p>
<p>First, let’s see the unsecure mode. Unsecure mode is very easy to configure. It is unsecure because it exposes Traefix API on the <a href="https://doc.traefik.io/traefik/routing/entrypoints/" target="_blank"> entrypoint </a>. It means that after configuring the dashboard in unsecure mode, the dashboard will be available on the port 8080 of the host.</p>
<p>Adding the following snippet in the <code class="language-plaintext highlighter-rouge">config/deploy.yml</code> file will enable the unsecure dashboard.</p>
<div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># config/deploy.yml</span>
<span class="na">traefik</span><span class="pi">:</span>
<span class="na">options</span><span class="pi">:</span>
<span class="na">publish</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">8080:8080</span>
<span class="na">args</span><span class="pi">:</span>
<span class="s">api.dashboard</span><span class="pi">:</span> <span class="no">true</span>
<span class="s">api.insecure</span><span class="pi">:</span> <span class="no">true</span>
</code></pre></div></div>
<p>After adding the configuration, run the <code class="language-plaintext highlighter-rouge">kamal traefik reboot</code> 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 <code class="language-plaintext highlighter-rouge">8080</code> of the host server now e.g. http://99.99.99.99:8080</p>
<p>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 <code class="language-plaintext highlighter-rouge">api@internal</code> service. Let’s look at the configuration for the secure model.</p>
<div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">traefik</span><span class="pi">:</span>
<span class="na">options</span><span class="pi">:</span>
<span class="na">publish</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">8080:8080</span>
<span class="na">args</span><span class="pi">:</span>
<span class="s">api.dashboard</span><span class="pi">:</span> <span class="no">true</span>
<span class="na">labels</span><span class="pi">:</span>
<span class="s">traefik.enable</span><span class="pi">:</span> <span class="s2">"</span><span class="s">true"</span>
<span class="s">traefik.http.routers.dashboard.rule</span><span class="pi">:</span> <span class="s">Host(`traefik.example.com`) && (PathPrefix(`/api`) || PathPrefix(`/dashboard`))</span>
<span class="s">traefik.http.routers.dashboard.service</span><span class="pi">:</span> <span class="s2">"</span><span class="s">api@internal"</span>
<span class="s">traefik.http.routers.dashboard.middlewares</span><span class="pi">:</span> <span class="s2">"</span><span class="s">auth"</span>
<span class="s">traefik.http.middlewares.auth.basicauth.users</span><span class="pi">:</span> <span class="s">test:$2y$05$H2o72tMaO.TwY1wNQUV1K.fhjRgLHRDWohFvUZOJHBEtUXNKrqUKi</span>
</code></pre></div></div>
<p>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 <code class="language-plaintext highlighter-rouge">api@internal</code> 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 <code class="language-plaintext highlighter-rouge">auth</code> middleware to use <a href="https://doc.traefik.io/traefik/middlewares/http/basicauth/" target="_blank">HTTP Basic Authentication</a> and provided it with the credentials. You can read more about the rules in the details on the Traefik <a href="https://doc.traefik.io/traefik/routing/routers/" target="_blank"> docs </a>.</p>
<p>The credentials are in the “username:hashed_password” format. The credentials are generated with the <code class="language-plaintext highlighter-rouge">htpasswd</code> 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:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>htpasswd <span class="nt">-nb</span> admin super_strong_password
<span class="c"># output: admin:$apr1$2FGO09Gu$PSZdmmJqyrXWYvidWAm6p0</span>
</code></pre></div></div>
<p>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 <code class="language-plaintext highlighter-rouge">$</code> character but you don’t need if you are using Kamal but Kamal <a href="https://github.com/mrsked/mrsk#using-shell-expansion" target="_blank"> escapes </a> the <code class="language-plaintext highlighter-rouge">$</code> sign for these labels.</p>
<p>That’s it! Don’t forget to reboot the Trafeik container with the <code class="language-plaintext highlighter-rouge">kamal traefik reboot</code> comamnd. After that, the dashboard should be accessible on the http://traefik.example.com/dashboard endpoint.</p>
<p>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.</p>Kartikey Tannatannakartikey@gmail.comKamal uses Traefik as a dynamic reverse-proxy. Traefik has a beautiful dashboard to visually display the configuration. Kamal is not configured to show the dashboard by default.How to deploy a NodeJS application using Kamal (previously MRSK)2023-04-10T08:25:00+00:002023-04-10T08:25:00+00:00https://www.kartikey.dev/2023/04/10/how-to-deploy-a-nodejs-application-using-mrsk<p><a href="https://kamal-deploy.org/">Kamal</a> 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.</p>
<p>First of all, let’s generate an express app. I installed <a href="https://expressjs.com/en/starter/generator.html" target="_blank"><code class="language-plaintext highlighter-rouge">express-generator</code></a> and generated a new app using the <code class="language-plaintext highlighter-rouge">express express-app</code> command.</p>
<p>Then let’s create a Dockerfile:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>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" ]
</code></pre></div></div>
<p>Now, let’s use Kamal to generate the config files. Make sure you have Kamal installed on your machine. If not, you can <a href="https://kamal-deploy.org/docs/installation"> follow these instructions </a> on README.</p>
<p>Let’s init the configuration with <code class="language-plaintext highlighter-rouge">kamal init</code>. It will create <code class="language-plaintext highlighter-rouge">config/deploy.yml</code> and <code class="language-plaintext highlighter-rouge">.env</code> files in your root directory. My config file looks like this:</p>
<div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">service</span><span class="pi">:</span> <span class="s">express-app</span>
<span class="na">image</span><span class="pi">:</span> <span class="s">user/express-app</span>
<span class="na">servers</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">146.190.86.77</span>
<span class="na">registry</span><span class="pi">:</span>
<span class="na">username</span><span class="pi">:</span> <span class="s">user</span>
<span class="na">password</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">KAMAL_REGISTRY_PASSWORD</span>
<span class="na">healthcheck</span><span class="pi">:</span>
<span class="na">path</span><span class="pi">:</span> <span class="s">/</span>
<span class="na">port</span><span class="pi">:</span> <span class="m">3000</span>
</code></pre></div></div>
<p>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.</p>
<p>Now, let’s deploy the app with the <code class="language-plaintext highlighter-rouge">kamal deploy</code> 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 <a href="https://www.kartikey.dev/tag/mrsk/"> previous posts </a>. They are platform agnostic and applicable on NodeJS apps as well.</p>Kartikey Tannatannakartikey@gmail.comKamal 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.How to deploy multi-environment(staging, production) application using Kamal(previously MRSK)2023-04-09T08:45:00+00:002023-04-09T08:45:00+00:00https://www.kartikey.dev/2023/04/09/how-to-deploy-multi-environment-staging-production-application-using-mrsk<p>In the <a href="https://www.kartikey.dev/2023/04/05/how-to-deploy-rails-app-and-postgres-with-mrsk-on-single-server.html" target="_blank">previous post</a>, 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 <a href="https://kamal-deploy.org/" target="_blank">Kamal</a>.</p>
<p>Kamal uses <a href="https://traefik.io/" target="_blank">Traefik</a> 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.</p>
<p>Kamal uses Traefik with “docker” as a <a href="https://doc.traefik.io/traefik/providers/overview/" target="_blank">provider</a>. Traefik <a href="https://doc.traefik.io/traefik/routing/routers/#rule" taget="_blank">rules</a> are configured by providing certain lables to Docker containers. You can read more about it in their <a href="https://doc.traefik.io/traefik/providers/docker/" target="_blank">docs</a>.</p>
<p>Let’s start configuring Kamal. Kamal has a feature called <a href="https://github.com/mrsked/mrsk/pull/71" target="_blank">“destination”</a>, we will use that to create two separate destinations - “staging” and “production”. First, we will create the <code class="language-plaintext highlighter-rouge">config/deploy.yml</code> file with common options like service name, image name, registry, common environment variables, and Postgresql database as acessory.</p>
<div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># config/deploy.yml</span>
<span class="na">service</span><span class="pi">:</span> <span class="s">myapp</span>
<span class="na">image</span><span class="pi">:</span> <span class="s">user/myapp</span>
<span class="na">registry</span><span class="pi">:</span>
<span class="na">username</span><span class="pi">:</span> <span class="s">user</span>
<span class="na">password</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">KAMAL_REGISTRY_PASSWORD</span>
<span class="na">env</span><span class="pi">:</span>
<span class="na">clear</span><span class="pi">:</span>
<span class="na">DB_HOST</span><span class="pi">:</span> <span class="s">99.99.99.99</span>
<span class="na">secret</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">RAILS_MASTER_KEY</span>
<span class="pi">-</span> <span class="s">POSTGRES_PASSWORD</span>
<span class="na">accessories</span><span class="pi">:</span>
<span class="na">db</span><span class="pi">:</span>
<span class="na">image</span><span class="pi">:</span> <span class="s">postgres:15</span>
<span class="na">host</span><span class="pi">:</span> <span class="s">99.99.99.99</span>
<span class="na">port</span><span class="pi">:</span> <span class="m">5432</span>
<span class="na">env</span><span class="pi">:</span>
<span class="na">clear</span><span class="pi">:</span>
<span class="na">POSTGRES_USER</span><span class="pi">:</span> <span class="s1">'</span><span class="s">myapp_db_user'</span>
<span class="na">secret</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">POSTGRES_PASSWORD</span>
<span class="na">directories</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">data:/var/lib/postgresql/data</span>
</code></pre></div></div>
<h4 id="staging-config">Staging config</h4>
<p>Now, let’s create a destination named “staging” with the <code class="language-plaintext highlighter-rouge">config/deploy.staging.yml</code> file.</p>
<div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># config/deploy.staging.yml</span>
<span class="na">servers</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">99.99.99.99</span>
<span class="na">labels</span><span class="pi">:</span>
<span class="s">traefik.http.routers.myapp-web-staging.rule</span><span class="pi">:</span> <span class="s">Host(`staging.myapp.com`)</span>
<span class="na">env</span><span class="pi">:</span>
<span class="na">clear</span><span class="pi">:</span>
<span class="na">POSTGRES_DB</span><span class="pi">:</span> <span class="s">myapp_staging</span>
<span class="na">IS_STAGING</span><span class="pi">:</span> <span class="no">true</span>
</code></pre></div></div>
<p>In this file we have declared the server for the staging environment, environment variable spcific to the environment and we have used the <code class="language-plaintext highlighter-rouge">labels</code> key that will attach the labels to the staging Docker container.</p>
<p>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 <a href="https://doc.traefik.io/traefik/routing/services/" target="_blank">service</a> (not to be confused with Kamal service). With the rule mentioned in the <code class="language-plaintext highlighter-rouge">deploy.staging.yml</code> 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.</p>
<p>Don’t forget to replace “staging.myapp.com” with your domain. The domain should be configured to point to the IP of the server.</p>
<h4 id="production-config">Production config</h4>
<div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># config/deploy.production.yml</span>
<span class="na">servers</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">99.99.99.99</span>
<span class="na">labels</span><span class="pi">:</span>
<span class="s">traefik.http.routers.myapp-web-production.rule</span><span class="pi">:</span> <span class="s">Host(`rails.myapp.com`)</span>
<span class="na">env</span><span class="pi">:</span>
<span class="na">clear</span><span class="pi">:</span>
<span class="na">POSTGRES_DB</span><span class="pi">:</span> <span class="s">myapp_production</span>
</code></pre></div></div>
<p>The <code class="language-plaintext highlighter-rouge">deploy.production.yml</code> 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 <code class="language-plaintext highlighter-rouge">hosts</code> key. Again, don’t forget to configure your domain.</p>
<p>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!</p>Kartikey Tannatannakartikey@gmail.comIn the previous post, 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 Kamal.Deploy Rails app and Postgres with Kamal(previously MRSK) on single DigitalOcean server2023-04-05T13:08:00+00:002023-04-05T13:08:00+00:00https://www.kartikey.dev/2023/04/05/how-to-deploy-rails-app-and-postgres-with-mrsk-on-single-server<p>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 <a href="https://kamal-deploy.org/">Kamal</a> was launched by DHH recently.</p>
<p>In this post, I am not getting into how Kamal works and the benefits of using it. DHH has created this fantastic <a href="https://www.youtube.com/watch?v=LL1cV2FXZ5I">introduction video</a> for that.</p>
<p>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.</p>
<p>First of all, install and init Kamal on your local machine with instructions on the <a href="https://github.com/basecamp/kamal#readme">README</a>.</p>
<p>The key to the single server setup is to use the same hosts for the <code class="language-plaintext highlighter-rouge">web</code> as well as <code class="language-plaintext highlighter-rouge">accessories</code>. Here is what the <code class="language-plaintext highlighter-rouge">deploy.yml</code> file looks like for that setup:</p>
<div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">service</span><span class="pi">:</span> <span class="s">my-app</span>
<span class="na">image</span><span class="pi">:</span> <span class="s">user/my-app</span>
<span class="na">servers</span><span class="pi">:</span>
<span class="na">web</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">134.209.111.91</span>
<span class="na">registry</span><span class="pi">:</span>
<span class="na">username</span><span class="pi">:</span> <span class="s">tannakartikey</span>
<span class="na">password</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">KAMAL_REGISTRY_PASSWORD</span>
<span class="na">env</span><span class="pi">:</span>
<span class="na">clear</span><span class="pi">:</span>
<span class="na">DB_HOST</span><span class="pi">:</span> <span class="s">134.209.111.91</span>
<span class="na">secret</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">RAILS_MASTER_KEY</span>
<span class="pi">-</span> <span class="s">POSTGRES_PASSWORD</span>
<span class="na">accessories</span><span class="pi">:</span>
<span class="na">db</span><span class="pi">:</span>
<span class="na">image</span><span class="pi">:</span> <span class="s">postgres:15</span>
<span class="na">host</span><span class="pi">:</span> <span class="s">134.209.111.91</span>
<span class="na">port</span><span class="pi">:</span> <span class="m">5432</span>
<span class="na">env</span><span class="pi">:</span>
<span class="na">clear</span><span class="pi">:</span>
<span class="na">POSTGRES_USER</span><span class="pi">:</span> <span class="s1">'</span><span class="s">my_app'</span>
<span class="na">POSTGRES_DB</span><span class="pi">:</span> <span class="s1">'</span><span class="s">my_app_production'</span>
<span class="na">secret</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">POSTGRES_PASSWORD</span>
<span class="na">directories</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">data:/var/lib/postgresql/data</span>
</code></pre></div></div>
<p>I am using the default <a href="https://github.com/tannakartikey/rails_71_mrsk_deploy/blob/2d138e10b2c87ec0bccc382ae06ce6e71c6f7187/Dockerfile"><code class="language-plaintext highlighter-rouge">Dockerfile</code></a> 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 <a href="https://github.com/tannakartikey/rails_71_mrsk_deploy/commit/2d138e10b2c87ec0bccc382ae06ce6e71c6f7187#diff-e9cbb0224c4a3d23a6019ba557e0cd568c1ad5e1582ff1e335fb7d99b7a1055d"><code class="language-plaintext highlighter-rouge">.env</code></a> file in the root of the repository and also need to make minor changes in your <a href="https://github.com/tannakartikey/rails_71_mrsk_deploy/commit/2d138e10b2c87ec0bccc382ae06ce6e71c6f7187#diff-5a674c769541a71f2471a45c0e9dde911b4455344e3131bddc5a363701ba6325"><code class="language-plaintext highlighter-rouge">config/database.yml</code></a> file.</p>
<p>I have created this sample <a href="https://github.com/tannakartikey/rails_71_mrsk_deploy">repository</a> that has all the code described in this post. This <a href="https://github.com/tannakartikey/rails_71_mrsk_deploy/commit/2d138e10b2c87ec0bccc382ae06ce6e71c6f7187">commit</a> holds all the Kamal setup-related changes.</p>
<p>Be sure NOT to include your <code class="language-plaintext highlighter-rouge">.env</code> file in Git or your <code class="language-plaintext highlighter-rouge">Dockerfile</code>. 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.</p>
<p>Next, we need to set up the server since we are deploying to the server for the first time. We can do it with:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>bin/kamal setup
</code></pre></div></div>
<p>The <code class="language-plaintext highlighter-rouge">setup</code> command will set up all the accessories and deploy the app on the server.</p>
<p>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 <code class="language-plaintext highlighter-rouge">kamal deploy</code> or <code class="language-plaintext highlighter-rouge">kamal redeploy</code> based on your needs.</p>
<p>I have been using the USD4 droplet on DigitalOcean. If you are using a similar configuration I would recommend creating a <a href="https://www.digitalocean.com/community/tutorials/how-to-add-swap-space-on-ubuntu-20-04">swap</a> 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.</p>
<p>It can also be possible to host the same code multiple times for different environments i.e. staging, production etc. For that, the <a href="https://github.com/mrsked/mrsk/pull/99">“role”</a> and <a href="https://github.com/mrsked/mrsk/pull/71">“destination”</a> functionality of Kamal can be used. I might cover that in a separate post.</p>Kartikey Tannatannakartikey@gmail.comFor 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 Kamal was launched by DHH recently.Memory efficient way of reading and downloading a large file in Ruby2020-10-02T00:00:00+00:002020-10-02T00:00:00+00:00https://www.kartikey.dev/2020/10/02/memory-efficient-way-reading-and-downloading-file-in-ruby<h3 id="to-read-a-large-file-from-the-disk">To read a large file from the disk</h3>
<p><a href="https://ruby-doc.org/core-2.7.1/IO.html#method-c-foreach" target="\_blank"><code class="language-plaintext highlighter-rouge">File.foreach</code></a> method reads the file line by line; that is why it is safe to use for large files.<br />
It can accept the block to execute each line of a file.</p>
<p>Example:</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="no">File</span><span class="p">.</span><span class="nf">foreach</span><span class="p">(</span><span class="s1">'example.jsonl'</span><span class="p">)</span> <span class="p">{</span> <span class="o">|</span><span class="n">line</span><span class="o">|</span> <span class="no">JSON</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="n">line</span><span class="p">)</span> <span class="p">}</span>
</code></pre></div></div>
<p>However, in the Ruby documentation, you will not find this method defined on the <a href="https://ruby-doc.org/core-2.7.1/File.html" target="\_blank"><code class="language-plaintext highlighter-rouge">File</code></a> class. It is defined on <a href="https://ruby-doc.org/core-2.7.1/IO.html" target="\_blank"><code class="language-plaintext highlighter-rouge">IO</code></a>, which is a superclass of <a href="https://ruby-doc.org/core-2.7.1/File.html" target="\_blank"><code class="language-plaintext highlighter-rouge">File</code></a>.</p>
<h3 id="to-download-the-large-files-from-the-internet">To download the large files from the Internet</h3>
<p>We can use <a href="https://ruby-doc.org/core-2.7.1/IO.html#method-c-copy_stream" target="\_blank"><code class="language-plaintext highlighter-rouge">IO.copy_stream</code></a> to download the large files from the Internet. It will create a stream instead of loading the whole file into memory before writing.</p>
<p>Example:</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="no">IO</span><span class="p">.</span><span class="nf">copy_stream</span><span class="p">(</span><span class="nb">open</span><span class="p">(</span><span class="s1">'http://example.com/file.jsonl'</span><span class="p">),</span> <span class="s1">'file.jsonl'</span><span class="p">)</span>
</code></pre></div></div>
<p>To sum it up…</p>
<p>Memory friendly methods:<br />
<a href="https://ruby-doc.org/core-2.7.1/IO.html#method-c-foreach" target="\_blank"><code class="language-plaintext highlighter-rouge">File.foreach</code></a>, <a href="https://ruby-doc.org/core-2.7.1/IO.html#method-c-copy_stream"><code class="language-plaintext highlighter-rouge">IO.copy_stream</code></a></p>
<p>Other methods to read a file:<br />
<a href="https://ruby-doc.org/core-2.7.1/IO.html#method-c-read"><code class="language-plaintext highlighter-rouge">File.read</code></a>, <a href="https://ruby-doc.org/core-2.7.1/IO.html#method-c-readlines"><code class="language-plaintext highlighter-rouge">File.readlines</code></a></p>Kartikey Tannatannakartikey@gmail.comTo read a large file from the diskConvert API Response Into a Model in Rails2020-09-30T00:00:00+00:002020-09-30T00:00:00+00:00https://www.kartikey.dev/2020/09/30/convert-api-response-into-a-model-in-rails<p>Let’s get down straight to the business.</p>
<p>Let’s take an example of showing the invoices to the user from Stripe.</p>
<p>First, let’s write <strong>what we would like to have</strong> without worrying about the implementation.<br />
<br /></p>
<p>I like <strong>short and clean</strong> controllers. Something like the following would do.</p>
<p><code class="language-plaintext highlighter-rouge">app/controllers/invoices_controller.rb</code></p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">InvoicesController</span> <span class="o"><</span> <span class="no">ApplicationController</span>
<span class="k">def</span> <span class="nf">index</span>
<span class="vi">@invoices</span> <span class="o">=</span> <span class="no">Invoice</span><span class="p">.</span><span class="nf">find_all_by_user</span><span class="p">(</span><span class="n">current_user</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">show</span>
<span class="vi">@invoice</span> <span class="o">=</span> <span class="no">Invoice</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="n">params</span><span class="p">[</span><span class="ss">:id</span><span class="p">])</span>
<span class="n">render</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>
<p><br /></p>
<p>Below is what I would like in my view: <code class="language-plaintext highlighter-rouge">app/views/invoices/_invoice.html.erb</code></p>
<div class="language-erb highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp"><%</span> <span class="k">unless</span> <span class="p">(</span><span class="n">invoice</span><span class="p">.</span><span class="nf">total</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="cp">%></span>
<span class="nt"><tr></span>
<span class="nt"><td></span><span class="cp"><%=</span> <span class="n">link_to</span> <span class="n">invoice</span><span class="p">.</span><span class="nf">number</span><span class="p">,</span> <span class="n">invoice_path</span><span class="p">(</span><span class="n">invoice</span><span class="p">.</span><span class="nf">id</span><span class="p">)</span> <span class="cp">%></span><span class="nt"></td></span>
<span class="nt"><td></span><span class="cp"><%=</span> <span class="n">invoice</span><span class="p">.</span><span class="nf">date</span> <span class="cp">%></span><span class="nt"></td></span>
<span class="nt"><td></span><span class="cp"><%=</span> <span class="n">number_to_currency</span><span class="p">(</span><span class="n">invoice</span><span class="p">.</span><span class="nf">total</span><span class="p">,</span> <span class="ss">negative_format: </span><span class="s2">"(%u%n)"</span><span class="p">)</span> <span class="cp">%></span><span class="nt"></td></span>
<span class="nt"><td></span><span class="cp"><%=</span> <span class="n">invoice</span><span class="p">.</span><span class="nf">period_start</span> <span class="cp">%></span> to <span class="cp"><%=</span> <span class="n">invoice</span><span class="p">.</span><span class="nf">period_end</span> <span class="cp">%></span><span class="nt"></td></span>
<span class="nt"><td></span><span class="cp"><%=</span> <span class="n">invoice</span><span class="p">.</span><span class="nf">paid?</span> <span class="p">?</span> <span class="s1">'Paid'</span> <span class="p">:</span> <span class="s1">'Unpaid'</span> <span class="cp">%></span><span class="nt"></td></span>
<span class="nt"></tr></span>
<span class="cp"><%</span> <span class="k">end</span> <span class="cp">%></span>
</code></pre></div></div>
<p><br /></p>
<p>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.</p>
<p><code class="language-plaintext highlighter-rouge">app/models/invoice.rb</code></p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">Invoice</span>
<span class="nb">attr_reader</span> <span class="ss">:stripe_invoice</span>
<span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">find_all_by_user</span><span class="p">(</span><span class="n">user</span><span class="p">)</span>
<span class="k">if</span> <span class="n">user</span><span class="p">.</span><span class="nf">present?</span>
<span class="n">stripe_invoices_for_user</span><span class="p">(</span><span class="n">user</span><span class="p">).</span><span class="nf">map</span> <span class="k">do</span> <span class="o">|</span><span class="n">invoice</span><span class="o">|</span>
<span class="n">new</span><span class="p">(</span><span class="n">invoice</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">else</span>
<span class="p">[]</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">invoice_id_or_object</span><span class="p">)</span>
<span class="k">if</span> <span class="n">invoice_id_or_object</span><span class="p">.</span><span class="nf">is_a?</span> <span class="no">String</span>
<span class="vi">@stripe_invoice</span> <span class="o">=</span> <span class="n">retrieve</span><span class="p">(</span><span class="n">invoice_id_or_object</span><span class="p">)</span>
<span class="k">else</span>
<span class="vi">@stripe_invoice</span> <span class="o">=</span> <span class="n">invoice_id_or_object</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">to_partial_path</span>
<span class="s2">"invoices/</span><span class="si">#{</span><span class="nb">self</span><span class="p">.</span><span class="nf">class</span><span class="p">.</span><span class="nf">name</span><span class="p">.</span><span class="nf">underscore</span><span class="si">}</span><span class="s2">"</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">id</span>
<span class="n">stripe_invoice</span><span class="p">.</span><span class="nf">id</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">number</span>
<span class="n">stripe_invoice</span><span class="p">.</span><span class="nf">number</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">total</span>
<span class="n">cents_to_dollars</span><span class="p">(</span><span class="n">stripe_invoice</span><span class="p">.</span><span class="nf">total</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">date</span>
<span class="n">convert_stripe_time</span><span class="p">(</span><span class="n">stripe_invoice</span><span class="p">.</span><span class="nf">date</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">paid?</span>
<span class="n">stripe_invoice</span><span class="p">.</span><span class="nf">paid</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">subscription</span>
<span class="n">stripe_invoice</span><span class="p">.</span><span class="nf">subscription</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">period_start</span>
<span class="n">convert_stripe_time</span><span class="p">(</span><span class="n">stripe_invoice</span><span class="p">.</span><span class="nf">period_start</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">period_end</span>
<span class="n">convert_stripe_time</span><span class="p">(</span><span class="n">stripe_invoice</span><span class="p">.</span><span class="nf">period_end</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">user</span>
<span class="vi">@user</span> <span class="o">||=</span> <span class="no">User</span><span class="p">.</span><span class="nf">find_by</span><span class="p">(</span><span class="ss">stripe_customer_id: </span><span class="n">stripe_invoice</span><span class="p">.</span><span class="nf">customer</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">balance</span>
<span class="k">if</span> <span class="n">paid?</span>
<span class="mf">0.00</span>
<span class="k">else</span>
<span class="n">amount_due</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">amount_due</span>
<span class="n">cents_to_dollars</span><span class="p">(</span><span class="n">stripe_invoice</span><span class="p">.</span><span class="nf">amount_due</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">subtotal</span>
<span class="n">cents_to_dollars</span><span class="p">(</span><span class="n">stripe_invoice</span><span class="p">.</span><span class="nf">subtotal</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">amount_paid</span>
<span class="k">if</span> <span class="n">paid?</span>
<span class="n">amount_due</span>
<span class="k">else</span>
<span class="mf">0.00</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">plan</span>
<span class="n">stripe_invoice</span><span class="p">.</span><span class="nf">lines</span><span class="p">.</span><span class="nf">data</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nf">plan</span><span class="p">.</span><span class="nf">name</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">plan_amount</span>
<span class="n">cents_to_dollars</span> <span class="n">stripe_invoice</span><span class="p">.</span><span class="nf">lines</span><span class="p">.</span><span class="nf">data</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nf">plan</span><span class="p">.</span><span class="nf">amount</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">pay</span>
<span class="n">stripe_invoice</span><span class="p">.</span><span class="nf">pay</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">pay_if_pending</span><span class="p">(</span><span class="n">user</span><span class="p">)</span>
<span class="n">invoices</span> <span class="o">=</span> <span class="n">find_all_by_user</span><span class="p">(</span><span class="n">user</span><span class="p">)</span>
<span class="k">unless</span> <span class="n">invoices</span><span class="p">.</span><span class="nf">empty?</span> <span class="o">||</span> <span class="n">invoices</span><span class="p">.</span><span class="nf">first</span><span class="p">.</span><span class="nf">paid?</span>
<span class="n">invoices</span><span class="p">.</span><span class="nf">first</span><span class="p">.</span><span class="nf">pay</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">upcoming</span><span class="p">(</span><span class="n">user</span><span class="p">)</span>
<span class="n">new</span><span class="p">(</span><span class="no">Stripe</span><span class="o">::</span><span class="no">Invoice</span><span class="p">.</span><span class="nf">upcoming</span><span class="p">(</span><span class="ss">customer: </span><span class="n">user</span><span class="p">.</span><span class="nf">stripe_customer_id</span><span class="p">)</span> <span class="o">||</span> <span class="kp">nil</span> <span class="p">)</span>
<span class="k">end</span>
<span class="kp">private</span>
<span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">stripe_invoices_for_user</span><span class="p">(</span><span class="n">user</span><span class="p">)</span>
<span class="no">Stripe</span><span class="o">::</span><span class="no">Invoice</span><span class="p">.</span><span class="nf">all</span><span class="p">(</span><span class="ss">customer: </span><span class="n">user</span><span class="p">.</span><span class="nf">stripe_customer_id</span><span class="p">).</span><span class="nf">data</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">retrieve</span><span class="p">(</span><span class="n">invoice_id</span><span class="p">)</span>
<span class="no">Stripe</span><span class="o">::</span><span class="no">Invoice</span><span class="p">.</span><span class="nf">retrieve</span><span class="p">(</span><span class="n">invoice_id</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">convert_stripe_time</span><span class="p">(</span><span class="n">time</span><span class="p">)</span>
<span class="no">Time</span><span class="p">.</span><span class="nf">zone</span><span class="p">.</span><span class="nf">at</span><span class="p">(</span><span class="n">time</span><span class="p">).</span><span class="nf">strftime</span><span class="p">(</span><span class="s1">'%D'</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">cents_to_dollars</span><span class="p">(</span><span class="n">amount</span><span class="p">)</span>
<span class="n">amount</span> <span class="o">/</span> <span class="mf">100.0</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>
<p>Here, the <a href="https://github.com/stripe/stripe-ruby" target="blank"> Stripe </a> gem takes exposes many methods on the response. But we can take any vanilla JSON response any do the same.</p>
<p>Many years ago, I learned this pattern form <a href="https://thoughtbot.com/upcase" target="blank">Upcase</a>. Since then I am always parsing API responses like this. Upcase is free now and definitely worth a look.</p>
<p>Download the code as <a href="https://gist.github.com/tannakartikey/d9f2b7cb8a473319f65fa325790c52dd" target="blank">gist</a>.</p>Kartikey Tannatannakartikey@gmail.comLet’s get down straight to the business.Many-to-many self join in Rails2020-09-29T00:00:00+00:002020-09-29T00:00:00+00:00https://www.kartikey.dev/2020/09/29/many-to-many-self-joins-in-rails<p>Let’s say we have “products” and we want to prepare “kits” of those products. Kits are nothing but the group of the products.</p>
<p>We can use many-to-many relationship here because products can be in many kits, and kits can be associated with many products.</p>
<p>Also, since the kits are just grouping the products, we can use self-joins. There are multiple ways we can implement self-joins.</p>
<h3 id="using-has_and_belongs_to_many">Using has_and_belongs_to_many</h3>
<p>You can read more about <a href="https://guides.rubyonrails.org/association_basics.html#the-has-and-belongs-to-many-association" target="_blank">has_and_belongs_to_many</a> on Rails docs.</p>
<h4 id="migration">Migration</h4>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">CreateJoinTableProductKits</span> <span class="o"><</span> <span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Migration</span><span class="p">[</span><span class="mf">6.0</span><span class="p">]</span>
<span class="k">def</span> <span class="nf">change</span>
<span class="n">create_table</span> <span class="ss">:product_kits</span><span class="p">,</span> <span class="ss">id: </span><span class="kp">false</span> <span class="k">do</span> <span class="o">|</span><span class="n">t</span><span class="o">|</span>
<span class="n">t</span><span class="p">.</span><span class="nf">references</span> <span class="ss">:product</span><span class="p">,</span> <span class="ss">null: </span><span class="kp">false</span><span class="p">,</span> <span class="ss">foreign_key: </span><span class="kp">true</span><span class="p">,</span> <span class="ss">index: </span><span class="kp">false</span>
<span class="n">t</span><span class="p">.</span><span class="nf">references</span> <span class="ss">:kit</span><span class="p">,</span> <span class="ss">null: </span><span class="kp">false</span><span class="p">,</span> <span class="ss">foreign_key: </span><span class="p">{</span> <span class="ss">to_table: :products</span> <span class="p">},</span> <span class="ss">index: </span><span class="kp">false</span>
<span class="n">t</span><span class="p">.</span><span class="nf">index</span> <span class="p">[</span><span class="ss">:product_id</span><span class="p">,</span> <span class="ss">:kit_id</span><span class="p">],</span> <span class="ss">unique: </span><span class="kp">true</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>
<h4 id="model">Model</h4>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">Product</span> <span class="o"><</span> <span class="no">ApplicationRecord</span>
<span class="n">has_and_belongs_to_many</span> <span class="ss">:kits</span><span class="p">,</span>
<span class="ss">join_table: :product_kits</span><span class="p">,</span>
<span class="ss">class_name: </span><span class="s1">'Product'</span><span class="p">,</span>
<span class="ss">association_foreign_key: </span><span class="s1">'kit_id'</span>
<span class="k">end</span>
</code></pre></div></div>
<h3 id="using-has_many-through">Using has_many through</h3>
<p>This approach is better because later on in your project you can add more fields and validations in <code class="language-plaintext highlighter-rouge">ProductKit</code> 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 is
better to be prepared and use <code class="language-plaintext highlighter-rouge">has_many :through</code> from the beginning.</p>
<p>More on, <a href="https://guides.rubyonrails.org/association_basics.html#the-has-many-through-association" target="_blank"><code class="language-plaintext highlighter-rouge">has_many :through</code></a> on Rails docs.</p>
<h4 id="migration-1">Migration</h4>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">CreateJoinTableProductKits</span> <span class="o"><</span> <span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Migration</span><span class="p">[</span><span class="mf">6.0</span><span class="p">]</span>
<span class="k">def</span> <span class="nf">change</span>
<span class="n">create_table</span> <span class="ss">:product_kits</span> <span class="k">do</span> <span class="o">|</span><span class="n">t</span><span class="o">|</span>
<span class="n">t</span><span class="p">.</span><span class="nf">references</span> <span class="ss">:product</span><span class="p">,</span> <span class="ss">null: </span><span class="kp">false</span><span class="p">,</span> <span class="ss">foreign_key: </span><span class="kp">true</span><span class="p">,</span> <span class="ss">index: </span><span class="kp">false</span>
<span class="n">t</span><span class="p">.</span><span class="nf">references</span> <span class="ss">:kit</span><span class="p">,</span> <span class="ss">null: </span><span class="kp">false</span><span class="p">,</span> <span class="ss">foreign_key: </span><span class="p">{</span> <span class="ss">to_table: :products</span> <span class="p">},</span> <span class="ss">index: </span><span class="kp">false</span>
<span class="n">t</span><span class="p">.</span><span class="nf">index</span> <span class="p">[</span><span class="ss">:product_id</span><span class="p">,</span> <span class="ss">:kit_id</span><span class="p">],</span> <span class="ss">unique: </span><span class="kp">true</span>
<span class="n">t</span><span class="p">.</span><span class="nf">timestamps</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>
<h4 id="model-appmodelsproductrb">Model <code class="language-plaintext highlighter-rouge">app/models/product.rb</code></h4>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">Product</span> <span class="o"><</span> <span class="no">ApplicationRecord</span>
<span class="n">has_many</span> <span class="ss">:product_kits</span><span class="p">,</span> <span class="ss">dependent: :destroy</span>
<span class="n">has_many</span> <span class="ss">:kits</span><span class="p">,</span> <span class="ss">through: :product_kits</span>
<span class="k">end</span>
</code></pre></div></div>
<h4 id="model-appmodelsproduct_kitrb">Model <code class="language-plaintext highlighter-rouge">app/models/product_kit.rb</code></h4>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">ProductKit</span> <span class="o"><</span> <span class="no">ApplicationRecord</span>
<span class="n">belongs_to</span> <span class="ss">:product</span>
<span class="n">belongs_to</span> <span class="ss">:kit</span><span class="p">,</span> <span class="ss">class_name: </span><span class="s1">'Product'</span>
<span class="k">end</span>
</code></pre></div></div>Kartikey Tannatannakartikey@gmail.comLet’s say we have “products” and we want to prepare “kits” of those products. Kits are nothing but the group of the products.Challenges I face as a freelancer and how I overcome them2020-09-20T00:00:00+00:002020-09-20T00:00:00+00:00https://www.kartikey.dev/2020/09/20/Challenges-I-face-as-a-freelancer-and-how-I-overcome-them<p>My initial days as a freelancer were rather difficult and confusing. There were multiple challenges that I learned to overcome the hard-way.</p>
<p>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.</p>
<h3 id="finding-work-finding-good-work"><s>Finding work</s> Finding good work</h3>
<p>Initially, as a freelancer, when I would receive an offer, I used to be apprehensive about things like:<br />
What if I get a better offer later?<br />
Should I go for a short-term contract with higher hourly rates or a long-term contract with lesser hourly rates?<br />
How long will the project go on?<br />
What if the client went out of funds earlier than they had foreseen?</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<h3 id="in-between-projects-personality-update-available"><s>In-between projects</s> Personality update available</h3>
<p>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. <a href="https://www.goodreads.com/book/show/106835.The_Intelligent_Investor" target="_blank">The Intelligent Investor</a> turned out to be a good start.</p>
<p>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.</p>
<h3 id="exploiting-appreciating-flexible-timings"><s>Exploiting</s> Appreciating flexible timings</h3>
<p>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.</p>
<h3 id="being-outdated-updated">Being <s>outdated</s> updated</h3>
<p>As a freelancer, it is also vital that we keep learning. If you are not updated, then you are outdated.</p>
<p>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 <a href="https://frontendmasters.com/" target="_blank">Frontend Masters</a>, <a href="https://egghead.io/" target="_blank">Egghead</a>, <a href="https://testingjavascript.com/" target="_blank">TestingJavascript</a>, <a href="https://www.pirple.com/" target="_blank">Pirple</a>, <a href="https://codestool.coding-gnome.com/" target="_blank">Coding-Gnome</a>, and regularly take courses there (links are not affiliated/sponsored).</p>
<p>If you liked this article, check out my other articles on freelancing.</p>
<ul>
<li><a href="/2020/08/30/why-do-i-freelance-and-why-should-you-too.html">Why do I freelance and why should you too</a></li>
<li><a href="/2020/09/06/the-3-roles-of-a-freelance-developer.html">The three roles of a freelance developer</a></li>
</ul>Kartikey Tannatannakartikey@gmail.comMy initial days as a freelancer were rather difficult and confusing. There were multiple challenges that I learned to overcome the hard-way.The 3 Roles of a Freelance Developer2020-09-06T00:00:00+00:002020-09-06T00:00:00+00:00https://www.kartikey.dev/2020/09/06/the-3-roles-of-a-freelance-developer<p>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.</p>
<h3 id="being-brahma-the-creator">Being Brahma: the creator</h3>
<p>Creating need not necessarily refer to just building web apps. There are many other tasks that you have to look into, like:</p>
<ul>
<li>Building a remarkable online presence to get new clients</li>
<li>Creating a network of clients</li>
<li>Drafting proposals</li>
<li>Coming up with creative ideas to improve the products.</li>
<li>A strategy to follow throughout the project.</li>
</ul>
<h3 id="being-vishnu-the-sustainer">Being Vishnu: the sustainer</h3>
<p>Once new projects start rolling, there is quite an amount of maintenance required, viz.:</p>
<ul>
<li>Maintaining a healthy relationship with the clients, including the previous ones.</li>
<li>Stick to the strategy decided and maintain the pace of the project.</li>
<li>Updating documentation, fixing bugs, upgrading dependencies, keeping an eye on the performance.</li>
<li>Managing your finances and maintaining good health. (Do not underestimate this!)</li>
</ul>
<h3 id="being-shiva-the-transformerdestroyer">Being Shiva: the transformer/destroyer</h3>
<p>Update to improvise!</p>
<ul>
<li>Transform and upgrade your work-style and strategy if needed.</li>
<li>Troubleshooting technical challenges</li>
<li>Refactor the code and get rid of the clutter.</li>
</ul>
<p>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.<br />
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.</p>Kartikey Tannatannakartikey@gmail.comAccording 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.