<?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>Forem: Kevin Luo</title>
    <description>The latest articles on Forem by Kevin Luo (@kevinluo201).</description>
    <link>https://forem.com/kevinluo201</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%2F283252%2F07012131-5bb5-4aff-b73c-0dbd85e2439f.png</url>
      <title>Forem: Kevin Luo</title>
      <link>https://forem.com/kevinluo201</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/kevinluo201"/>
    <language>en</language>
    <item>
      <title>Before Kamal: How to Set Up a VM, Domain, and Dockerfile the Easy Way</title>
      <dc:creator>Kevin Luo</dc:creator>
      <pubDate>Sat, 06 Sep 2025 02:23:50 +0000</pubDate>
      <link>https://forem.com/kevinluo201/before-kamal-how-to-set-up-a-vm-domain-and-dockerfile-the-easy-way-3ih7</link>
      <guid>https://forem.com/kevinluo201/before-kamal-how-to-set-up-a-vm-domain-and-dockerfile-the-easy-way-3ih7</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Deploying an application on your own machine is never a simple task. Nonetheless, I found using  &lt;a href="https://kamal-deploy.org/" rel="noopener noreferrer"&gt;Kamal&lt;/a&gt; to deploy apps on my own VM is pretty easy. It allows me to run multiple apps on a very basic and cheap VM with Docker. &lt;/p&gt;

&lt;p&gt;Though I really enjoy what Kamal brings to me, I want to write a tutorial for it. However, while I was writing the article about Kamal, I noticed I was always stuck at some points and thought: how can a beginner know how to rent a VM if they have never done that before? &lt;/p&gt;

&lt;p&gt;That's why I want to write this article. There are some preparations for Kamal's deployment which are very trivial and simple, but they could intimidate software newbies. In this article, I'm going to introduce how to do the following three things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Rent a virtual machine&lt;/li&gt;
&lt;li&gt;Buy a domain name&lt;/li&gt;
&lt;li&gt;Set up a Dockerfile&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These are prerequisites of deployment of Kamal (or even any deployment nowadays). To be honest, these three tasks are "boring" part of web app development. However, we will have a little bit of help from modern AI in each step. I hope that makes them no longer dreadful, and you may even enjoy the process 😎&lt;/p&gt;

&lt;h2&gt;
  
  
  Rent a Virtual Machine (VM)
&lt;/h2&gt;

&lt;p&gt;Since our goal is to deploy things on your own VM, you have to have a VM first. In fact, you can get a VM on cloud providers' websites easily. You can pick any one from those big cloud providers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Amazon AWS EC2 &lt;/li&gt;
&lt;li&gt;Google GCP Compute Engine&lt;/li&gt;
&lt;li&gt;Microsoft Azure Virtual Machine&lt;/li&gt;
&lt;li&gt;DigitalOcean Droplet&lt;/li&gt;
&lt;li&gt;Akamai Cloud Compute&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I completely don't understand why their names are so random, but they all mean the same thing---their VM service. There are also many smaller providers. If you want, you can ask AI&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;What VPS options do I have in my region? (VPS, Virtual Private Server)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;What cloud providers can I choose to rent a VM?&lt;/code&gt;
and you should get a suggestion list.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I eventually chose &lt;strong&gt;DigitalOcean Droplet&lt;/strong&gt;. I live in Toronto, and one of their data centers is here. Although I will only show how to enable and connect to a VM on Digital Ocean, the same workflow can be easily applied to all other cloud providers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create a new VM
&lt;/h3&gt;

&lt;p&gt;First, go to &lt;a href="https://www.digitalocean.com/" rel="noopener noreferrer"&gt;DigitalOcean&lt;/a&gt;. Sign up an account if you don't have one and then log in. Go to the "Droplets" page and click "Create Droplet". You will see many different settings you can configure for the new VM. &lt;/p&gt;

&lt;p&gt;For the OS, if you don't have any clue which to install, I suggest choosing &lt;strong&gt;Ubuntu&lt;/strong&gt; or &lt;strong&gt;Debian&lt;/strong&gt;. For the specification of the machine, since we are just practicing and learning, we chose the minimum option. At this moment in September 2025, I get a machine with &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1 CPU&lt;/li&gt;
&lt;li&gt;1 GB RAM&lt;/li&gt;
&lt;li&gt;25 GB SSD Disk&lt;/li&gt;
&lt;li&gt;1TB network transfer
It costs only $6/month, which is cheaper than Starbucks coffee ☕.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgqmcfwauoycnbvqjtd7h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgqmcfwauoycnbvqjtd7h.png" alt="list of VM" width="800" height="672"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;(A DigitalOcean exclusive feature) A good part of Droplet creation is that you can create a SSH key to log in. You can also choose Password to set a root password and set the SSH key authentication later. Up to you.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fezgikxjh5koi8is8p5ap.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fezgikxjh5koi8is8p5ap.png" alt="set ssh key" width="800" height="314"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After the droplet is created successfully. You should see your new VM appear in the Droplets list.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F787z0erxdex1fkuttdr8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F787z0erxdex1fkuttdr8.png" alt="droplets list" width="800" height="514"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Copy its IP address and use &lt;code&gt;ssh&lt;/code&gt; to connect to it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh root@IP_OF_THE_DROPLET
&lt;span class="c"&gt;# or if you use a different ssh key&lt;/span&gt;
ssh &lt;span class="nt"&gt;-i&lt;/span&gt; ~/.ssh/you_ssh_private_key root@IP_OF_THE_VM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If it connects successfully, then you're good.&lt;/p&gt;

&lt;h3&gt;
  
  
  Add ssh-key to log in
&lt;/h3&gt;

&lt;p&gt;(If you already added the ssh-key, you can skip this section.) The step is to add ssh-key to log in the VM. Why don't we just stick to the password? The answer is that it will make the deployment easier in the future, and it's also more secure.&lt;/p&gt;

&lt;p&gt;You can ask AI if you really don't have a clue how to do that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;How to generate a ssh-key pair and use it to log into my VM?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, the workflow should be similar like below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# generate ssh key pair in ~/.ssh, it will create &lt;/span&gt;
&lt;span class="c"&gt;# 1. Private key: ~/.ssh/id_ed25519&lt;/span&gt;
&lt;span class="c"&gt;# 2. Public key: ~/.ssh/id_ed25519.pub&lt;/span&gt;
ssh-keygen &lt;span class="nt"&gt;-t&lt;/span&gt; ed25519 &lt;span class="nt"&gt;-C&lt;/span&gt; &lt;span class="s2"&gt;"your_email@example.com"&lt;/span&gt;

&lt;span class="c"&gt;# show the content of the public key, and then copy it&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; ~/.ssh/id_ed25519.pub

&lt;span class="c"&gt;# login to your VM with your password&lt;/span&gt;
ssh root@IP_OF_THE_VM

&lt;span class="c"&gt;# edit ~/.ssh/authorized_keys&lt;/span&gt;
&lt;span class="c"&gt;# paste the public key's content in this file&lt;/span&gt;
vim ~/.ssh/authorized_keys
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point, you should be able to login the VM directly like below and without entering password&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ssh root@IP_OF_THE_VM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Buy a domain
&lt;/h2&gt;

&lt;p&gt;You need a domain name to direct to your VM, so your users can use &lt;code&gt;https://your-domain.com&lt;/code&gt; instead of a meaningless IP, like 123.123.0.1. You can get a very cheap domain from any domain name provider.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.godaddy.com/" rel="noopener noreferrer"&gt;GoDaddy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://porkbun.com/" rel="noopener noreferrer"&gt;porkbun&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dash.cloudflare.com/" rel="noopener noreferrer"&gt;cloudflare&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Again, you can ask AI to give you a list of providers if you want. Especially you want a special Top-level domain (the last part of the domain name, like .com, .net), as far as I know, some top-level domains are only sold on specific sites.&lt;/p&gt;

&lt;p&gt;You just need to come up with a good domain name you'd like, and search it on those providers' websites. Below is the example that I tried to search &lt;code&gt;get-food-in-gta&lt;/code&gt;, and it gave me a long list of available domain names, each one also has a rent price on it&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsz888gtv6ef6c9sbs0dl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsz888gtv6ef6c9sbs0dl.png" alt="search domains" width="800" height="462"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It is just like online shopping (but for geeks...). Whichever creative domain name you get, you need to configure the DNS A record to point to your VM's IP. (Please note that you MUST use IPv4, since IPv6 is not supported by Kamal yet at the moment when I write this article). &lt;/p&gt;

&lt;p&gt;Find the entrance to the DNS management page for the domain name you just bought:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fepz8dcdn5zlheq5aq2u9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fepz8dcdn5zlheq5aq2u9.png" alt="enter dns" width="800" height="141"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Add a new A record. If there is an existing A record, you can delete it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fosov4otfn7iy54gkp4hj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fosov4otfn7iy54gkp4hj.png" alt="add A" width="800" height="537"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After the A record is added, create a CNAME record for &lt;a href="http://www" rel="noopener noreferrer"&gt;www&lt;/a&gt;. It usually points to the same place, for example: &lt;strong&gt;&lt;a href="http://www.google.com" rel="noopener noreferrer"&gt;www.google.com&lt;/a&gt;&lt;/strong&gt; and &lt;strong&gt;google.com&lt;/strong&gt; should be the same.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqra0mx69sfge6zw98yfz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqra0mx69sfge6zw98yfz.png" alt="add CNAME" width="800" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;DNS needs a period of time up to 1 day to be activated. Be patient, you can use &lt;code&gt;ssh&lt;/code&gt; to connect to the VM by the domain name to check if the settings are active.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh root@your-domain.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you connect to the VM by the command above, the VM is all set.&lt;/p&gt;

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

&lt;p&gt;I think this is the most interesting part in this AI era, and why I think AI really boost productivity. In the past: Yes, I knew Docker. Yes, I knew Linux. And I know every detail of my application. However, writing a Dockerfile from scratch isn't a joyful task. Recall how you set up your new computer; writing the Dockerfile is exactly like that process.&lt;/p&gt;

&lt;p&gt;The situation has changed now. Since a Dockerfile follows a strict format, which is a very reproducible pattern for LLM. AI can generate a well-structured, well-written, good-practice Dockerfile for you if you provide it with the whole project as the context.&lt;/p&gt;

&lt;p&gt;Below, I created a small web app by using Nuxt and tried to use Gemini CLI to create a suitable Dockerfile for it. Again, you can use any AI assisting tools: Claude Code, Copilot, Cursor, etc. I just want to present the idea.&lt;/p&gt;

&lt;h3&gt;
  
  
  Install Gemini CLI
&lt;/h3&gt;

&lt;p&gt;First, go to &lt;a href="https://github.com/google-gemini/gemini-cli" rel="noopener noreferrer"&gt;https://github.com/google-gemini/gemini-cli&lt;/a&gt; and follow the instructions to install Gemini CLI on your machine. Then open your project with your editor. If you happen to use VSCode, you can also install the Gemini CLI Companion extension, which can let Gemini CLI know which files are opened in VSCode.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwee9og8cq915ufvkg8es.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwee9og8cq915ufvkg8es.png" alt="gemini CLI" width="800" height="421"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Open the terminal in VSCode and start Gemini CLI. Although we can start telling the Gemini to generate the juicy Dockerfile for us right away, it will be better if you execute the command &lt;code&gt;/init&lt;/code&gt; first. &lt;code&gt;/init&lt;/code&gt; will analyze the whole project and create a &lt;code&gt;Gemini.md&lt;/code&gt; in your project, which will help Gemini CLI have a basic context of your project.&lt;/p&gt;

&lt;p&gt;!gemini cli ext](&lt;a href="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/50ow23illxauabsgh5jn.png" rel="noopener noreferrer"&gt;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/50ow23illxauabsgh5jn.png&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;When the next step is just to tell Gemini CLI to generate the Dockerfile, there are infinite options there:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Generate a Dockerfile for this project&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Generate a Dockerfile for this Nuxt project&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Generate a Dockerfile for this Nuxt project. Use Node 22 Alpine as the base image&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;...
You can be more specific and detailed as you would like.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Okay...I admit I oversimplify this process (especially the Dockerfile part) and make AI seem like black magic. A big project which couples with many services like database, cache, storage, etc., may not be as easy as it is demonstrated above. However, I guarantee it will give you a very nice template as the entry point.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Maintaining Linux servers, DNS records and Dockerfile are all skills that deserve thorough studying and let you master them. AI makes the process faster; however, it doesn't mean that we don't need to learn them anymore. Because the current AI doesn't truly "automate" those things, it can still contain many mistakes in it. It just makes them easier to start with. &lt;/p&gt;

&lt;p&gt;On the other hand, these things are just the foundation, the starting point of the goal. Our final goal is to deploy our applications, so I think getting the results by AI first is acceptable.😃&lt;/p&gt;

</description>
      <category>kamal</category>
      <category>docker</category>
      <category>devops</category>
    </item>
    <item>
      <title>Running Cron Jobs by Kamal with the Whenever Gem (The Simple Way)</title>
      <dc:creator>Kevin Luo</dc:creator>
      <pubDate>Wed, 09 Jul 2025 02:25:59 +0000</pubDate>
      <link>https://forem.com/kevinluo201/running-cron-jobs-in-kamal-with-the-whenever-gem-the-simple-way-1bb7</link>
      <guid>https://forem.com/kevinluo201/running-cron-jobs-in-kamal-with-the-whenever-gem-the-simple-way-1bb7</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;I just started learning and practicing Kamal. While I found it's a joy to finally be able to deploy Docker images on my own machine, there are some challenges. One of the biggest challenges is that it's unreasonably difficult to run cron jobs 🥲 On top of that, I like to use the &lt;a href="https://github.com/javan/whenever" rel="noopener noreferrer"&gt;whenever&lt;/a&gt; gem to manage cron jobs, which makes it even harder.&lt;/p&gt;

&lt;p&gt;I came across a helpful &lt;a href="https://world.hey.com/alan.m/cronjobs-using-kamal-and-whenever-gem-ddd901f9" rel="noopener noreferrer"&gt;blog post&lt;/a&gt; by &lt;strong&gt;Alan Morales&lt;/strong&gt;, which already solves this issue. However, after some research and experiments, I came up with a simpler version, and I'd like to share it here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Some terms
&lt;/h2&gt;

&lt;p&gt;We know &lt;code&gt;cron&lt;/code&gt; is the program that runs routine jobs—like running a script every 5 minutes. &lt;code&gt;cronjobs&lt;/code&gt; just means the jobs that will be run by &lt;code&gt;cron&lt;/code&gt;. &lt;code&gt;crontab&lt;/code&gt; is the command used to edit the &lt;code&gt;cronjobs&lt;/code&gt;. (I guess &lt;code&gt;crontab&lt;/code&gt; stands for &lt;strong&gt;cron table&lt;/strong&gt;, but I don't have proof for that 😄)&lt;/p&gt;

&lt;h2&gt;
  
  
  Official Tutorial
&lt;/h2&gt;

&lt;p&gt;The official tutorial suggests running "cron" jobs as a separate server in Kamal. If you check out the tutorial at &lt;a href="https://kamal-deploy.org/docs/configuration/cron/" rel="noopener noreferrer"&gt;https://kamal-deploy.org/docs/configuration/cron/&lt;/a&gt;, it gives a very concise (but actually clever) example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bash &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"(env &amp;amp;&amp;amp; cat config/crontab) | crontab - &amp;amp;&amp;amp; cron -f"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you follow this exactly, I bet 99% of the time it won’t work for you. Let’s have a close look at this command:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;bash -c&lt;/code&gt;: starts a new Bash session to execute the commands.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;(env &amp;amp;&amp;amp; cat config/crontab)&lt;/code&gt;: &lt;code&gt;env&lt;/code&gt; prints all current environment variables. &lt;code&gt;cat config/crontab&lt;/code&gt; outputs the contents of the crontab file, which should have cronjob entries like &lt;code&gt;0 5 * * * /path/to/script.sh&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;| crontab -&lt;/code&gt;: pipes the combined output into &lt;code&gt;crontab -&lt;/code&gt;, which updates the cron jobs.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cron -f&lt;/code&gt;: runs &lt;code&gt;cron&lt;/code&gt; in the foreground so the Docker container stays alive.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;From this, we know we need:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The &lt;code&gt;cron&lt;/code&gt; program installed in the Docker image.&lt;/li&gt;
&lt;li&gt;An existing cronjobs file to set up with &lt;code&gt;crontab&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Environment variables (&lt;code&gt;env&lt;/code&gt;) at the top of the crontab file because &lt;code&gt;cron&lt;/code&gt; doesn’t source profile files like &lt;code&gt;.bashrc&lt;/code&gt; or &lt;code&gt;.zshrc&lt;/code&gt;. Rails commands need variables like &lt;code&gt;PATH&lt;/code&gt;, &lt;code&gt;BUNDLE_PATH&lt;/code&gt;, &lt;code&gt;RAILS_ENV&lt;/code&gt;, etc.&lt;/li&gt;
&lt;li&gt;One more thing: &lt;code&gt;cron&lt;/code&gt; is designed to run as the root user. Running it as a non-root user often causes "Permission Denied" errors.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Proposed Method
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Install &lt;code&gt;cron&lt;/code&gt; in the Dockerfile
&lt;/h3&gt;

&lt;p&gt;Just like the &lt;a href="https://world.hey.com/alan.m/cronjobs-using-kamal-and-whenever-gem-ddd901f9" rel="noopener noreferrer"&gt;blog post&lt;/a&gt; suggests, you need to have &lt;code&gt;cron&lt;/code&gt; installed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Install cron
RUN apt-get update -qq &amp;amp;&amp;amp; \ 
    apt-get install --no-install-recommends -y cron &amp;amp;&amp;amp; \ 
    rm -rf /var/lib/apt/lists /var/cache/apt/archives &amp;amp;&amp;amp; \ 
    rm -rf /etc/cron.*/*
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Modify the cmd
&lt;/h3&gt;

&lt;p&gt;In your &lt;code&gt;deploy.yml&lt;/code&gt;, set up the cron server like this:&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;servers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;cron&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;hosts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;www.xxx.yyy.zzz&lt;/span&gt; &lt;span class="c1"&gt;# your server IP&lt;/span&gt;
    &lt;span class="na"&gt;cmd&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bash -c "bundle exec whenever --update-crontab &amp;amp;&amp;amp; (env &amp;amp;&amp;amp; crontab -l) | crontab - &amp;amp;&amp;amp; cron -f"&lt;/span&gt;
    &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;root&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Explanation:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;bundle exec whenever --update-crontab&lt;/code&gt;: updates the crontab using &lt;code&gt;whenever&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;(env &amp;amp;&amp;amp; crontab -l)&lt;/code&gt;: prints environment variables and current crontab.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;| crontab -&lt;/code&gt;: sets the new crontab.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cron -f&lt;/code&gt;: runs &lt;code&gt;cron&lt;/code&gt; in the foreground.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;options.user: root&lt;/code&gt;: indicates to use &lt;code&gt;root&lt;/code&gt; to run the server&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The key part is this single line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bash &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"bundle exec whenever --update-crontab &amp;amp;&amp;amp; (env &amp;amp;&amp;amp; crontab -l) | crontab - &amp;amp;&amp;amp; cron -f"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. (Optional) Print cron output to Docker’s stdout
&lt;/h3&gt;

&lt;p&gt;Debugging cron in Docker is hard because you can't see the logs. Here's a simple trick: redirect the cron output to Docker’s stdout. Add this line to the top of your &lt;code&gt;config/schedule.rb&lt;/code&gt;:&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;set&lt;/span&gt; &lt;span class="ss"&gt;:output&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;standard: &lt;/span&gt;&lt;span class="s1"&gt;'/proc/1/fd/1'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;error: &lt;/span&gt;&lt;span class="s1"&gt;'/proc/1/fd/2'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The mysterious &lt;code&gt;/proc/1/fd/1&lt;/code&gt; and &lt;code&gt;/proc/1/fd/2&lt;/code&gt; point to the Docker container logs. You can then check the cron output with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kamal app logs &lt;span class="nt"&gt;-r&lt;/span&gt; cron &lt;span class="nt"&gt;-f&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In every stage of my career, I’ve found that running cron jobs on production servers is never easy. Running them in Docker makes it even trickier 😆. I hope this changes in the future, but until then, we just have to figure out our own ways.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://world.hey.com/alan.m/cronjobs-using-kamal-and-whenever-gem-ddd901f9" rel="noopener noreferrer"&gt;https://world.hey.com/alan.m/cronjobs-using-kamal-and-whenever-gem-ddd901f9&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/basecamp/kamal/issues/47#issuecomment-1865975293" rel="noopener noreferrer"&gt;https://github.com/basecamp/kamal/issues/47#issuecomment-1865975293&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>kamal</category>
      <category>rails</category>
      <category>docker</category>
      <category>ruby</category>
    </item>
    <item>
      <title>Limit your function calls with debounce</title>
      <dc:creator>Kevin Luo</dc:creator>
      <pubDate>Sun, 13 Apr 2025 17:22:21 +0000</pubDate>
      <link>https://forem.com/kevinluo201/limit-your-function-calls-with-debounce-1nec</link>
      <guid>https://forem.com/kevinluo201/limit-your-function-calls-with-debounce-1nec</guid>
      <description>&lt;h2&gt;
  
  
  What is &lt;strong&gt;debounce&lt;/strong&gt;?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Debounce&lt;/strong&gt; is a very interesting and important concept in programming. It triggers a function only once when it is called multiple times in a short period. The most common example is the search input field. Assuming we have an input which triggers a database query whenever the user types anything:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"search"&lt;/span&gt; &lt;span class="na"&gt;oninput=&lt;/span&gt;&lt;span class="s"&gt;"searchProduct(event)"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;searchProduct&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&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;// Simulate a search query&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Query for:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When a user wants to search "Avocado", it will be like:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;types &lt;code&gt;A&lt;/code&gt; -&amp;gt; it queries the database for &lt;code&gt;A&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;types &lt;code&gt;v&lt;/code&gt; -&amp;gt; it queries the database for &lt;code&gt;v&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;types &lt;code&gt;o&lt;/code&gt; -&amp;gt; it queries the database for &lt;code&gt;o&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;types &lt;code&gt;c&lt;/code&gt; -&amp;gt; it queries the database for &lt;code&gt;c&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;and so on.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It will eventually make seven database queries.  This behaviour is inefficient and can overwhelm the server with unnecessary requests.  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwujzsep1nlg6pf3dyyg1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwujzsep1nlg6pf3dyyg1.png" alt="Make multiple queries" width="800" height="390"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  debounce the calls
&lt;/h2&gt;

&lt;p&gt;What we want is that it makes only one query after the whole word is typed. Let's not beat around the bush and see how a debounced version searchProduct can do that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"search"&lt;/span&gt; &lt;span class="na"&gt;oninput=&lt;/span&gt;&lt;span class="s"&gt;"searchProduct(event)"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;timer&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;debouncedSearchProduct&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&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="nf"&gt;clearTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;timer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&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;// Simulate a search query&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Query for:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It will result in like below, only do the database query for one:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5rs382g4nnfwesn77afc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5rs382g4nnfwesn77afc.png" alt="debounced result" width="657" height="283"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let me explain what &lt;code&gt;debouncedSearchProduct&lt;/code&gt; does:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It delays the database query by &lt;strong&gt;500ms&lt;/strong&gt; with &lt;code&gt;setTimeout&lt;/code&gt; and store the timer's ID into the variable &lt;code&gt;timer&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;If &lt;code&gt;debouncedSearchProduct&lt;/code&gt; is called after, it calls &lt;code&gt;clearTimeout(timer)&lt;/code&gt; to cancel the timer and do &lt;code&gt;1.&lt;/code&gt; again.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;As a result, it makes the database query only when the user stops typing longer than 500ms. Of course, you can adjust 500ms to more suitable value. As you can see, debouncing is very simple to achieve.&lt;/p&gt;

&lt;h2&gt;
  
  
  debounce at the beginning
&lt;/h2&gt;

&lt;p&gt;The previous example only considers the last event. What if we want to consider the first event and ignore the rest of the events in a timeframe? We can make a few changes to it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;debounced&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&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;searchProduct&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;debounced&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Simulate a search query&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Query for:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;debounced&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;setTimeout&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;debounced&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;500&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;h2&gt;
  
  
  Reuse debounce functionality
&lt;/h2&gt;

&lt;p&gt;We can refactor the debounce function to accept a function and return its debounced version so we can reuse it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;search&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;oninput&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;debouncedSearchProduct(event)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;

&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;script&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;searchProduct&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&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;// Simulate a search query&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Query for:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;debounce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;delay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;timeoutId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;args&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="nf"&gt;clearTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timeoutId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;timeoutId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&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;callback&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;delay&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;debouncedSearchProduct&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;debounce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;searchProduct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/script&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To be honest, we don't need to write our own debounce in practice, unless it's required. For example, we can use &lt;code&gt;debounce&lt;/code&gt; method from 3rd-party libraries like &lt;a href="https://lodash.com/docs/4.17.15#debounce" rel="noopener noreferrer"&gt;lodash&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"search"&lt;/span&gt; &lt;span class="na"&gt;oninput=&lt;/span&gt;&lt;span class="s"&gt;"debouncedSearchProduct(event)"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;searchProduct&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&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;// Simulate a search query&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Query for:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&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;debouncedSearchProduct&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;debounce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;searchProduct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// If you want the leading timeout&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;debouncedSearchProduct&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;debounce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;searchProduct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;leading&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="na"&gt;trailing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; 
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why it's called debounce?
&lt;/h2&gt;

&lt;p&gt;Why is it called &lt;strong&gt;debounce&lt;/strong&gt;? You will know that if you have programmed on a hardware button. The digital world is simple, and there are only &lt;code&gt;0&lt;/code&gt; and &lt;code&gt;1&lt;/code&gt;. Assume there's a button on a screen, it only has two states, unclicked(&lt;code&gt;0&lt;/code&gt;) and clicked(&lt;code&gt;1&lt;/code&gt;). &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkdm2jb4hzhzlor3cupcv.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkdm2jb4hzhzlor3cupcv.gif" alt="clicking button" width="400" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;However, in the analog world, the real world, it is not that simple. For example, When you click a button with your finger, and you hear a "click" sound. The physical button part already vibrates multiple times (at least 20 times a human being can only hear sound bigger than 20Hz) so the sensitive sensor under it may send multiple "clicked" signals. This behaviour is called &lt;strong&gt;"bounce"&lt;/strong&gt; and to remove this effect is called &lt;strong&gt;"debounce"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh8twp4z9x9wvrd2vm1k9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh8twp4z9x9wvrd2vm1k9.png" alt="debounce chart" width="800" height="393"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Although I use Javascript for examples in this article, it can be applied on any languages. And it's not only for UI, you can apply this concept to any kind of programs. For example, don't call ChatGPT API for every changes at the backend to explode your token usage.&lt;/p&gt;

</description>
      <category>javascript</category>
    </item>
    <item>
      <title>How to do binary search for Ruby's array by #bsearch</title>
      <dc:creator>Kevin Luo</dc:creator>
      <pubDate>Mon, 24 Feb 2025 01:46:23 +0000</pubDate>
      <link>https://forem.com/kevinluo201/how-to-do-binary-search-for-rubys-array-by-bsearch-336g</link>
      <guid>https://forem.com/kevinluo201/how-to-do-binary-search-for-rubys-array-by-bsearch-336g</guid>
      <description>&lt;p&gt;We sometimes need to check if a value is included in an array. There are many methods to achieve that, e.g. &lt;code&gt;Array#include?&lt;/code&gt;, &lt;code&gt;Array#find&lt;/code&gt;, etc. But they use linear search, which time complexity is &lt;code&gt;O(n)&lt;/code&gt;. If it's a &lt;strong&gt;sorted array&lt;/strong&gt;, we know the &lt;strong&gt;binary search&lt;/strong&gt; can give you &lt;code&gt;O(logn)&lt;/code&gt; speed. It would be a shame if you didn't know that Ruby's Array can perform binary search out of the box by &lt;code&gt;Array#bsearch&lt;/code&gt;.  Unfortunately, it may not be that intuitive to use so many people don't know hot to use it. Let's see some examples first:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhqzulxxlr531cror0n5d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhqzulxxlr531cror0n5d.png" alt="Examples of Array#bsearch" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you don't know what happened there, you can continue reading this article to know that 😉&lt;/p&gt;

&lt;p&gt;There are two very important things to know when using &lt;code&gt;Array#bsearch&lt;/code&gt;. First, it can only be performed on a &lt;strong&gt;sorted array&lt;/strong&gt;. &lt;code&gt;Array#bsearch&lt;/code&gt; doesn't sort the array for you (you can call &lt;code&gt;array.sort&lt;/code&gt; first if you're not sure). Second, &lt;code&gt;Array#bsearch&lt;/code&gt; has &lt;strong&gt;two modes&lt;/strong&gt;. That means it has two different ways to use it. The first mode is called &lt;strong&gt;Find-Minimum mode&lt;/strong&gt;, and the other one is called &lt;strong&gt;Find-Any mode&lt;/strong&gt;. These two modes actually do a very similar thing, but you need to send two completely different blocks to it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Find-Minimum mode
&lt;/h2&gt;

&lt;p&gt;When using Find-Minimum mode, &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The block you send to the &lt;code&gt;#bsearch&lt;/code&gt; must return a &lt;code&gt;Boolean&lt;/code&gt; value, &lt;code&gt;false&lt;/code&gt; or &lt;code&gt;true&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;If we apply &lt;code&gt;Array.map&lt;/code&gt; with the same block, it should return a series of &lt;code&gt;false&lt;/code&gt; values followed by a series of &lt;code&gt;true&lt;/code&gt; values.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;#bsearch&lt;/code&gt; will return the first element which returns &lt;code&gt;true&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I believe it is easier to understand when seeing the real examples.&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;array&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bsearch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; 5&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It returns &lt;code&gt;5&lt;/code&gt;, because &lt;code&gt;5&lt;/code&gt; is the first element that makes the block &lt;code&gt;{ |x| x &amp;gt;= 5 }&lt;/code&gt; to return &lt;code&gt;true&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# compute x &amp;gt;= 5 for all elements&lt;/span&gt;
&lt;span class="c1"&gt;# [1,     3,     5,    7,    9 ]&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we use another to another block &lt;code&gt;{ |x| x &amp;gt;= 6 }&lt;/code&gt;&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;array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bsearch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; 7&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It returns &lt;code&gt;7&lt;/code&gt;. Why? Again, it is because &lt;code&gt;7&lt;/code&gt; is the first element make the block return &lt;code&gt;true&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;
&lt;span class="c1"&gt;# [1,     3,     5,     7,    9 ]&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If I use the block &lt;code&gt;{ |x| x &amp;gt;= 10 }&lt;/code&gt;,&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;array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bsearch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; nil&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because there is no element in the array able to make the block return &lt;code&gt;true&lt;/code&gt;, the &lt;code&gt;nil&lt;/code&gt; will be returned.&lt;/p&gt;

&lt;h2&gt;
  
  
  Find-Any mode
&lt;/h2&gt;

&lt;p&gt;When using Find-Any mode,&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The block you send to the &lt;code&gt;#bsearch&lt;/code&gt; must return a &lt;code&gt;Numeric&lt;/code&gt; value&lt;/li&gt;
&lt;li&gt;It returns a &lt;strong&gt;positive number&lt;/strong&gt; if an array element is &lt;strong&gt;smaller&lt;/strong&gt; than the values you're searching&lt;/li&gt;
&lt;li&gt;It returns a &lt;strong&gt;negative number&lt;/strong&gt; if an array element is &lt;strong&gt;greater&lt;/strong&gt; than the values you're searching&lt;/li&gt;
&lt;li&gt;It should return &lt;strong&gt;zero&lt;/strong&gt;, if the element matches what you're searching&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Even though the rules above feel much more complex than the &lt;strong&gt;Find-Minimum mode&lt;/strong&gt;, trust me, it's also simple.&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;array&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bsearch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; 5&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What is &lt;code&gt;&amp;lt;=&amp;gt;&lt;/code&gt;? This &lt;code&gt;&amp;lt;=&amp;gt;&lt;/code&gt; is called a &lt;em&gt;3-way comparison&lt;/em&gt;, a.k.a &lt;strong&gt;spaceship operator&lt;/strong&gt;. If we compare &lt;code&gt;a &amp;lt;=&amp;gt; b&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;if &lt;code&gt;a &amp;lt; b&lt;/code&gt;, then the result is &lt;code&gt;-1&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;if &lt;code&gt;a == b&lt;/code&gt;, then the result is &lt;code&gt;0&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;if &lt;code&gt;a &amp;gt; b&lt;/code&gt;, then the result is &lt;code&gt;1&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you really want to remember it, I hope the picture below can help you:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh80ejif5ikfricqy8ll6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh80ejif5ikfricqy8ll6.png" alt="easy remembering &amp;lt;=&amp;gt;" width="800" height="523"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Anyway, coming back to our example, let's see the computed results of &lt;code&gt;{ |x| 5 &amp;lt;=&amp;gt; x }&lt;/code&gt; for each element:&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;array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;# [1, 1, 0, -1, -1]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;5&lt;/code&gt; makes the block return &lt;code&gt;0&lt;/code&gt;, so &lt;code&gt;5&lt;/code&gt; is returned. If I try to search &lt;code&gt;6&lt;/code&gt;&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;array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bsearch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; nil&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It returns nil because the computed results of &lt;code&gt;{ |x| 6 &amp;lt;=&amp;gt; x }&lt;/code&gt; is&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;array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;# [1, 1, 1, -1, -1]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and it doesn't have &lt;code&gt;0&lt;/code&gt; in it.&lt;/p&gt;

&lt;p&gt;Find-Any mode is very powerful. We can search for a range instead of a single element. For 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="n"&gt;array&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bsearch&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;x&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;
    &lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="k"&gt;elsif&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;
    &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="mi"&gt;0&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;because the block's results of the array is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# [1, 2, 3, 4, 5, 6,  7,  8,  9, 10]&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;4&lt;/code&gt;, &lt;code&gt;5&lt;/code&gt; and &lt;code&gt;6&lt;/code&gt; make the block return &lt;code&gt;0&lt;/code&gt;, so &lt;code&gt;array.bsearch&lt;/code&gt; will return &lt;strong&gt;any&lt;/strong&gt; one of them as a result. That's why it is called &lt;strong&gt;Find-Any mode&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;If you scroll to the top and check the first screenshot now, I think you can understand it. No matter which mode you use, the most important thing is that the search time is &lt;code&gt;O(logn)&lt;/code&gt;. If you have a very looooong sorted array to search, and &lt;code&gt;Array.find&lt;/code&gt; feels so slow (which uses linear search), try &lt;code&gt;Array#bsearch&lt;/code&gt;, you will love it!❤️&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>programming</category>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>Kevin Luo</dc:creator>
      <pubDate>Sun, 16 Feb 2025 01:57:19 +0000</pubDate>
      <link>https://forem.com/kevinluo201/-48hn</link>
      <guid>https://forem.com/kevinluo201/-48hn</guid>
      <description>&lt;div class="ltag__link"&gt;
  &lt;a href="/kevinluo201" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F283252%2F07012131-5bb5-4aff-b73c-0dbd85e2439f.png" alt="kevinluo201"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/kevinluo201/some-tips-for-making-a-ruby-gem-42he" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Some tips for making a ruby gem&lt;/h2&gt;
      &lt;h3&gt;Kevin Luo ・ Feb 16&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#ruby&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


</description>
      <category>ruby</category>
    </item>
    <item>
      <title>Some tips for making a ruby gem</title>
      <dc:creator>Kevin Luo</dc:creator>
      <pubDate>Sun, 16 Feb 2025 01:52:09 +0000</pubDate>
      <link>https://forem.com/kevinluo201/some-tips-for-making-a-ruby-gem-42he</link>
      <guid>https://forem.com/kevinluo201/some-tips-for-making-a-ruby-gem-42he</guid>
      <description>&lt;p&gt;I recently built a gem, &lt;a href="https://github.com/kevinluo201/fx_rates" rel="noopener noreferrer"&gt;https://github.com/kevinluo201/fx_rates&lt;/a&gt;, which is an API wrapper for a good exchange rate service, &lt;a href="https://fxratesapi.com/" rel="noopener noreferrer"&gt;https://fxratesapi.com/&lt;/a&gt;. Anyway, what that gem does is not the point here. I simply want to share my experience of making a ruby gem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use bundler to build a gem
&lt;/h2&gt;

&lt;p&gt;First, I suggest using &lt;code&gt;bundler&lt;/code&gt; to build a gem. Of course, you can follow the official guide on RubyGem.org's &lt;a href="https://guides.rubygems.org/make-your-own-gem/" rel="noopener noreferrer"&gt;Make your own gem&lt;/a&gt;. However, just like other modern projects, it will be much easier to start from boilerplates. &lt;code&gt;bundler&lt;/code&gt;  can provide us the ruby gem's boilerplates. Assume the gem you're going to make is called &lt;code&gt;your_gem&lt;/code&gt;. You can initiate the gem by:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bundle gem your_gem
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should get a directory like below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tree
.
├── Gemfile
├── LICENSE.txt
├── README.md
├── Rakefile
├── bin
│   ├── console
│   └── setup
├── lib
│   ├── your_gem
│   │   └── version.rb
│   └── your_gem.rb
├── sig
│   └── your_gem.rbs
├── spec
│   ├── spec_helper.rb
│   └── your_gem_spec.rb
└── your_gem.gemspec
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Yours can be different because of different versions of bundler. Besides getting this scaffold, it also has some advantages, like:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You can use &lt;code&gt;bundler&lt;/code&gt; to handle the dependencies.&lt;/li&gt;
&lt;li&gt;You have predefined Rake tasks to test and release the gem&lt;/li&gt;
&lt;li&gt;git repository is initiated&lt;/li&gt;
&lt;li&gt;etc.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;More information can be found at &lt;a href="https://bundler.io/guides/creating_gem.html" rel="noopener noreferrer"&gt;https://bundler.io/guides/creating_gem.html&lt;/a&gt;. Now you can start editing the &lt;code&gt;your_gem_name.gemspec&lt;/code&gt; to add the basic information for your gem and the dependencies.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use Zeitwerk
&lt;/h2&gt;

&lt;p&gt;Second, I suggest using &lt;a href="https://github.com/fxn/zeitwerk" rel="noopener noreferrer"&gt;zeitwerk&lt;/a&gt; gem to manage the constants like classes and modules via organizing the files. I'm going to explain why.&lt;/p&gt;

&lt;p&gt;We need to know one thing first: What happens when we require a gem in ruby? For 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="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'a_gem'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It tells Ruby to execute the &lt;strong&gt;entry point&lt;/strong&gt; ruby file in that &lt;code&gt;a_gem&lt;/code&gt;. What is the entry point file in a gem? It is always the file with the &lt;strong&gt;same name&lt;/strong&gt; as your gem's under &lt;code&gt;lib/&lt;/code&gt;. In our case, it's &lt;code&gt;lib/your_gem.rb&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;After we create a gem by bundler, &lt;code&gt;lib/your_gem.rb&lt;/code&gt; file should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="nb"&gt;require_relative&lt;/span&gt; &lt;span class="s2"&gt;"your_gem/version"&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;YourGem&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;StandardError&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="c1"&gt;# Your code goes here...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's look at &lt;code&gt;require_relative "your_gem/version"&lt;/code&gt;. It just means it loads &lt;code&gt;lib/your_gem/version.rb&lt;/code&gt;. You will realize &lt;code&gt;require 'your_gem&lt;/code&gt; only loads the entry file. For other files in the gem, they need to be "required in the entry point file. There's no magic. They won't be loaded or connected automatically.&lt;/p&gt;

&lt;p&gt;Now, suppose we want to add a new class, &lt;code&gt;YourGem::Configuration&lt;/code&gt;, we can put it under &lt;code&gt;lib/your_gem/configuration.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;├── lib
│   ├── your_gem
│   │   ├── configuation.rb
│   │   └── version.rb
│   └── your_gem.rb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The file's content can be an empty class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# lib/your_gem/configuration.rb&lt;/span&gt;
&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;YourGem&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Configuration&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we need to require it in the entry point file:&lt;br&gt;
we need to require this new file in the entry point:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="nb"&gt;require_relative&lt;/span&gt; &lt;span class="s2"&gt;"your_gem/version"&lt;/span&gt;
&lt;span class="nb"&gt;require_relative&lt;/span&gt; &lt;span class="s2"&gt;"your_gem/configuration"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="c1"&gt;# &amp;lt;---- this line&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;YourGem&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;StandardError&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="c1"&gt;# Your code goes here...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can get &lt;code&gt;YourGem::Configuration&lt;/code&gt; after requiring the entry point file. We can repeat this process for every file in the gem. Although it is very clear, &lt;code&gt;zeitwerk&lt;/code&gt; provides another approach to automate this process.&lt;/p&gt;

&lt;p&gt;With &lt;code&gt;zeitwek&lt;/code&gt;, it can be rewritten as&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="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"zeitwerk"&lt;/span&gt;
&lt;span class="n"&gt;loader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Zeitwerk&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Loader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;for_gem&lt;/span&gt;
&lt;span class="n"&gt;loader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setup&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;YourGem&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;StandardError&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="c1"&gt;# Your code goes here...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;YourGem::Configuration&lt;/code&gt; will be loaded automatically. In fact, all files under &lt;code&gt;lib/your_gem/**/*&lt;/code&gt; will be loaded after &lt;code&gt;loader.setup&lt;/code&gt; is executed, so you don't need to add any line after adding a new file. &lt;/p&gt;

&lt;p&gt;It's a pattern of "convention over configuration", so all your files names have to follow the following pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lib/my_gem.rb         -&amp;gt; MyGem
lib/my_gem/foo.rb     -&amp;gt; MyGem::Foo
lib/my_gem/bar_baz.rb -&amp;gt; MyGem::BarBaz
lib/my_gem/woo/zoo.rb -&amp;gt; MyGem::Woo::Zoo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;More details can be found at &lt;a href="https://github.com/fxn/zeitwerk" rel="noopener noreferrer"&gt;https://github.com/fxn/zeitwerk&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Add configuration
&lt;/h2&gt;

&lt;p&gt;Some gems provide an interface for developers to define global settings, like in rails&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;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;eager_loading&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;
  &lt;span class="c1"&gt;# other settings&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It looks super cool! We can also let our gem have this ability. Let's say we want to set a global &lt;code&gt;api_key&lt;/code&gt;. First, we can create a class to hold the settings. We can use &lt;code&gt;YourGem::Configuration&lt;/code&gt; we created above to do that:&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;module&lt;/span&gt; &lt;span class="nn"&gt;YourGem&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Configuration&lt;/span&gt;
    &lt;span class="nb"&gt;attr_accessor&lt;/span&gt; &lt;span class="ss"&gt;:api_key&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;
      &lt;span class="vi"&gt;@api_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then in the entry_point &lt;code&gt;lib/your_gem.rb&lt;/code&gt;, we add a module-level accessor and method:&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;module&lt;/span&gt; &lt;span class="nn"&gt;YourGem&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;StandardError&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;
    &lt;span class="nb"&gt;attr_accessor&lt;/span&gt; &lt;span class="ss"&gt;:config&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;configure&lt;/span&gt;
      &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="no"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="c1"&gt;# &amp;lt;--- YourGem::Configuration has been loaded by zeitwerk above&lt;/span&gt;
      &lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;block_given?&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By doing this, we can write and read the &lt;code&gt;api_key&lt;/code&gt; by&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# write&lt;/span&gt;
&lt;span class="no"&gt;YourGem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; 
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;api_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"your-api-key-here"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# read&lt;/span&gt;
&lt;span class="no"&gt;YourGem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;api_key&lt;/span&gt;
&lt;span class="c1"&gt;# "your-api-key-here"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How to test the gem before releasing it?
&lt;/h2&gt;

&lt;p&gt;Of course, we want to check out how's our gem doing before releasing it. There are 2 ways to test your gem quickly.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;bin/console&lt;/code&gt;: it opens the &lt;code&gt;irb&lt;/code&gt; and loads your gem, so you can &lt;/li&gt;
&lt;li&gt;
&lt;code&gt;rake install&lt;/code&gt;: it is a rake task set up by bundler. It will build a gem package and invode &lt;code&gt;gem install pkg/your_gem&lt;/code&gt; to install it in the system. As a result, you can truly test it in your other Ruby projects by &lt;code&gt;require 'your_gem'&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  How to release it?
&lt;/h2&gt;

&lt;p&gt;All the things we've done for a gem are to release it on &lt;a href="https://rubygems.org/" rel="noopener noreferrer"&gt;https://rubygems.org/&lt;/a&gt;. To release the gem, you need an account on &lt;a href="https://rubygems.org/" rel="noopener noreferrer"&gt;https://rubygems.org/&lt;/a&gt; first. So, &lt;strong&gt;sign up for one if you haven't done that yet.&lt;/strong&gt;&lt;br&gt;
Then execute &lt;code&gt;rake build&lt;/code&gt; to build a gem file under pkg, it should look like &lt;code&gt;pkg/your_gem-0.0.1.gem&lt;/code&gt;. The version number &lt;code&gt;0.0.1&lt;/code&gt; is defined in the &lt;code&gt;lib/your_gem/version.rb&lt;/code&gt;. Finally, we call&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gem push pkg/your_gem-0.0.1.gem
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and it will push the gem to &lt;a href="https://rubygems.org/" rel="noopener noreferrer"&gt;https://rubygems.org/&lt;/a&gt; 🎉&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I think building a gem is not a daily job for most of the developers. However, gems are an essential part of the Ruby community. Why can we always find a gem for our needs? It is because people share what they build. How do we become people who can share? Knowing how to make a gem is the first step. I hope this article can make you feel making a gem is simpler than you thought. 🙏&lt;/p&gt;

</description>
      <category>ruby</category>
    </item>
    <item>
      <title>Drawing Taiwan's Flag with one &lt;div&gt; and CSS</title>
      <dc:creator>Kevin Luo</dc:creator>
      <pubDate>Fri, 22 Nov 2024 03:46:46 +0000</pubDate>
      <link>https://forem.com/kevinluo201/drawing-taiwans-flag-with-css-317a</link>
      <guid>https://forem.com/kevinluo201/drawing-taiwans-flag-with-css-317a</guid>
      <description>&lt;p&gt;I was impressed very much by &lt;a class="mentioned-user" href="https://dev.to/alvaromontoro"&gt;@alvaromontoro&lt;/a&gt;'s work&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag__link"&gt;
  &lt;a href="/alvaromontoro" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F161327%2F2ff05281-db58-4dcb-946a-4b679e4a266b.jpeg" alt="alvaromontoro"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/alvaromontoro/drawing-togos-flag-with-css-33nh" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Drawing Togo's Flag with CSS&lt;/h2&gt;
      &lt;h3&gt;Alvaro Montoro ・ Nov 19 '24&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#css&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#html&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#webdev&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;
 of drawing a Togo flag with a &lt;strong&gt;single div and bunch of magic CSS&lt;/strong&gt;. It's truly mind-blowing for me. Therefore, I want to do the same thing. Here is how I've done it.
&lt;h2&gt;
  
  
  HTML
&lt;/h2&gt;

&lt;p&gt;I add one &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; with some aria attributes. This will be the single &lt;/p&gt; to render the flag.&lt;br&gt;

&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;role=&lt;/span&gt;&lt;span class="s"&gt;"img"&lt;/span&gt; &lt;span class="na"&gt;aria-label=&lt;/span&gt;&lt;span class="s"&gt;"Flag of Taiwan"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"flag taiwan"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;
  
  
  CSS
&lt;/h2&gt;

&lt;p&gt;I used the same method as &lt;a class="mentioned-user" href="https://dev.to/alvaromontoro"&gt;@alvaromontoro&lt;/a&gt; to create a basic background of the Taiwan flag: red background color with a blue rectangle on the top-left.&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.flag.taiwan&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;aspect-ratio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;500px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;relative&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;linear-gradient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;rgb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;19&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;53&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;129&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="m"&gt;50%&lt;/span&gt; &lt;span class="m"&gt;50%&lt;/span&gt; &lt;span class="nb"&gt;no-repeat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nb"&gt;rgb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;205&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;44&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;36&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;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmlmuir3rgxz2dp6gcz42.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmlmuir3rgxz2dp6gcz42.png" alt="taiwan flag background" width="800" height="537"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Sun
&lt;/h2&gt;

&lt;p&gt;Ok, the easiest part is finished. Now it is time for the real deal. The sun on the flag.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flxl6jdxhbcthkzeuydo0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flxl6jdxhbcthkzeuydo0.png" alt="The sun on the flag" width="800" height="800"&gt;&lt;/a&gt;&lt;br&gt;
It looks pretty complicated, with 12 beams and a circle at the center. How is it possible to use the pseudo elements, ::before and ::after to draw them? It feels like only the &lt;code&gt;clip-path: path()&lt;/code&gt; is the only way to do that because &lt;code&gt;path()&lt;/code&gt; can draw any shape we want. However, &lt;code&gt;clip-path: path()&lt;/code&gt; has a fatal disadvantage: it's not responsive! That means the flag can have only one size if I choose that approach.&lt;/p&gt;

&lt;p&gt;I started googling many SVG files of Taiwan flag. And I noticed that they only use 2 elements to represent the sun.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;a white 12-beam star&lt;/li&gt;
&lt;li&gt;the white circle with a blue border&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhwwxqwuk5fziwen2hluv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhwwxqwuk5fziwen2hluv.png" alt="white 12-beam star" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxgbiv1j3ojmkv5kt4n3c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxgbiv1j3ojmkv5kt4n3c.png" alt="concentric circle" width="618" height="616"&gt;&lt;/a&gt;&lt;br&gt;
When the circle is put down on the center of the star. It looks like there are 12 beams surrounding the circle with a desired gap. So smart!. It seems like designers have already figured out this clever way to draw that sun. By taking this approach, I can use &lt;code&gt;::before&lt;/code&gt; as the circle and &lt;code&gt;::after&lt;/code&gt; as the star.&lt;/p&gt;
&lt;h2&gt;
  
  
  12-beam star
&lt;/h2&gt;

&lt;p&gt;It is very easy to find SVG files of Taiwan flag. Unfortunately, all the stars are drawn by &lt;code&gt;path()&lt;/code&gt;. It's because &lt;code&gt;path()&lt;/code&gt; is responsive when it is in a real &lt;code&gt;&amp;lt;svg&amp;gt;&lt;/code&gt; so they do not have this problem. It is only not responsive in &lt;code&gt;clip-path&lt;/code&gt;. The &lt;code&gt;polygon&lt;/code&gt; is responsive but I did not find a way to convert &lt;code&gt;path&lt;/code&gt; to &lt;code&gt;polygon&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;It is a really difficult problem. My final solution is really to calculate all the positions of all the points of the 12-beam star😂 I utilized this fantastic online SVG Path Editorhttps://yqnn.github.io/svg-path-editor/ to visualize all points in the path.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fczv9gi2go6eiw3l59kkw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fczv9gi2go6eiw3l59kkw.png" alt="online SVG Path Editor" width="800" height="678"&gt;&lt;/a&gt;&lt;br&gt;
And I asked my dear brother who is very good at math what are the positions of the rest of the points. He used &lt;strong&gt;mathematica&lt;/strong&gt; to solve  12 linear equations and got all the points! 😂&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzpzxzqeh3ihqxxkcfel9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzpzxzqeh3ihqxxkcfel9.png" alt="mathematica to solve linear equation" width="800" height="453"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then, I backed to the path editor to draw the outline of the star as the path and scaled it so it will be in a 100*100 scope.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff003m7dfd61sq03ixzr7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff003m7dfd61sq03ixzr7.png" alt="online SVG Path Editor to draw the outline of the star" width="800" height="866"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I then convert all positions to percentages since it's already in 100*100 scope. As a result, we can display the star on the flag&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nd"&gt;::after&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;content&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="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;6.25%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;12.5%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;25%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="m"&gt;8&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;white&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;clip-path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;polygon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;50%&lt;/span&gt; &lt;span class="m"&gt;0%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;56.6987%&lt;/span&gt; &lt;span class="m"&gt;25%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;75%&lt;/span&gt; &lt;span class="m"&gt;6.6987%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;68.3013%&lt;/span&gt; &lt;span class="m"&gt;31.6987%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;93.3013%&lt;/span&gt; &lt;span class="m"&gt;25%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;75%&lt;/span&gt; &lt;span class="m"&gt;43.3013%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;100%&lt;/span&gt; &lt;span class="m"&gt;50%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;75%&lt;/span&gt; &lt;span class="m"&gt;56.6987%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;93.3013%&lt;/span&gt; &lt;span class="m"&gt;75%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;68.3013%&lt;/span&gt; &lt;span class="m"&gt;68.3013%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;75%&lt;/span&gt; &lt;span class="m"&gt;93.3013%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;56.6987%&lt;/span&gt; &lt;span class="m"&gt;75%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;50%&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;43.3013%&lt;/span&gt; &lt;span class="m"&gt;75%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;25%&lt;/span&gt; &lt;span class="m"&gt;93.3013%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;31.6987%&lt;/span&gt; &lt;span class="m"&gt;68.3013%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;6.6983%&lt;/span&gt; &lt;span class="m"&gt;75%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;25%&lt;/span&gt; &lt;span class="m"&gt;56.6987%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;0%&lt;/span&gt; &lt;span class="m"&gt;50%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;25%&lt;/span&gt; &lt;span class="m"&gt;43.3013%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;6.6983%&lt;/span&gt; &lt;span class="m"&gt;25%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;31.6987%&lt;/span&gt; &lt;span class="m"&gt;31.6987%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;25%&lt;/span&gt; &lt;span class="m"&gt;6.6983%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;43.3013%&lt;/span&gt; &lt;span class="m"&gt;25%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="err"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmxdv1pigojqqjq1sw3bs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmxdv1pigojqqjq1sw3bs.png" alt="Taiwan flag with only the star" width="800" height="531"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The circle
&lt;/h2&gt;

&lt;p&gt;The circle is relatively easier. However, my first attempt was using the &lt;code&gt;border&lt;/code&gt;. It failed because the width of the border can only be &lt;code&gt;px&lt;/code&gt;. I changed to use &lt;code&gt;radial-gradient&lt;/code&gt;. The tricky part is that the percentage in &lt;code&gt;radial-gradient&lt;/code&gt; needs to be the diagonal of the element so it also needs some math but it's not that hard.&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;amp;::before {
    content: '';
    position: absolute;
    top: 14.375%;
    left: 17.92%;
    width: calc(17 / 120 * 100%);
    height: calc(17 / 80 * 100%);
    background: radial-gradient(circle, white 62.3917%, rgb(19, 53, 129) 62.3917%);
    border-radius: 50%;
    z-index: 2;
  }
&lt;/code&gt;&lt;/pre&gt;



&lt;h2&gt;
  
  
  Result
&lt;/h2&gt;

&lt;p&gt;The full CSS is&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.flag.taiwan&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;aspect-ratio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;relative&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;linear-gradient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;rgb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;19&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;53&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;129&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="m"&gt;50%&lt;/span&gt; &lt;span class="m"&gt;50%&lt;/span&gt; &lt;span class="nb"&gt;no-repeat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nb"&gt;rgb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;205&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;44&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;36&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="err"&gt;&amp;amp;::before&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;content&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="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;14.375%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;17.92%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;17&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="m"&gt;120&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;17&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;radial-gradient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;circle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;white&lt;/span&gt; &lt;span class="m"&gt;62.3917%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;rgb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;19&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;53&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;129&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="m"&gt;62.3917%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;50%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;z-index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&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;amp;&lt;/span&gt;&lt;span class="nd"&gt;::after&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;content&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="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;6.25%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;12.5%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;25%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="m"&gt;8&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;white&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;clip-path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;polygon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;50%&lt;/span&gt; &lt;span class="m"&gt;0%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;56.6987%&lt;/span&gt; &lt;span class="m"&gt;25%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;75%&lt;/span&gt; &lt;span class="m"&gt;6.6987%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;68.3013%&lt;/span&gt; &lt;span class="m"&gt;31.6987%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;93.3013%&lt;/span&gt; &lt;span class="m"&gt;25%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;75%&lt;/span&gt; &lt;span class="m"&gt;43.3013%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;100%&lt;/span&gt; &lt;span class="m"&gt;50%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;75%&lt;/span&gt; &lt;span class="m"&gt;56.6987%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;93.3013%&lt;/span&gt; &lt;span class="m"&gt;75%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;68.3013%&lt;/span&gt; &lt;span class="m"&gt;68.3013%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;75%&lt;/span&gt; &lt;span class="m"&gt;93.3013%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;56.6987%&lt;/span&gt; &lt;span class="m"&gt;75%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;50%&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;43.3013%&lt;/span&gt; &lt;span class="m"&gt;75%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;25%&lt;/span&gt; &lt;span class="m"&gt;93.3013%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;31.6987%&lt;/span&gt; &lt;span class="m"&gt;68.3013%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;6.6983%&lt;/span&gt; &lt;span class="m"&gt;75%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;25%&lt;/span&gt; &lt;span class="m"&gt;56.6987%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;0%&lt;/span&gt; &lt;span class="m"&gt;50%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;25%&lt;/span&gt; &lt;span class="m"&gt;43.3013%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;6.6983%&lt;/span&gt; &lt;span class="m"&gt;25%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;31.6987%&lt;/span&gt; &lt;span class="m"&gt;31.6987%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;25%&lt;/span&gt; &lt;span class="m"&gt;6.6983%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;43.3013%&lt;/span&gt; &lt;span class="m"&gt;25%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="err"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="err"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;You can also check out my work on codepen below&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/kevinluo201-the-bold/embed/PoMrRQy?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Hope you like it!&lt;/p&gt;

</description>
      <category>taiwan</category>
      <category>css</category>
      <category>html</category>
    </item>
    <item>
      <title>Use Vue.js SFC in Rails</title>
      <dc:creator>Kevin Luo</dc:creator>
      <pubDate>Mon, 11 Nov 2024 01:27:58 +0000</pubDate>
      <link>https://forem.com/kevinluo201/use-viterails-to-use-vue-sfcsingle-file-component-vue-in-rails7-51bn</link>
      <guid>https://forem.com/kevinluo201/use-viterails-to-use-vue-sfcsingle-file-component-vue-in-rails7-51bn</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Rails Hotwire is good but sometimes you want to build a more reactive. Vue.js is a good choice. In addition, Vue.js SFC is an awesome way to manage your components. However, you cannot just use &lt;code&gt;.vue&lt;/code&gt; files because browsers don't recognize &lt;code&gt;.vue&lt;/code&gt; files. They need to be compiled into JS codes first.&lt;/p&gt;

&lt;p&gt;If you already know Vue.js, you probably know that using &lt;code&gt;vite&lt;/code&gt; as the bundler to compile &lt;code&gt;.vue&lt;/code&gt; files into JS codes is very common in the Vue.js world. There's a gem &lt;code&gt;vite_rails&lt;/code&gt; to integrate &lt;code&gt;vite&lt;/code&gt; into &lt;code&gt;rails&lt;/code&gt;. I'll introduce how to configure &lt;code&gt;vite_rails&lt;/code&gt; so you can use &lt;code&gt;.vue&lt;/code&gt; files in Rails.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preparation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  If you want to start it from scratch(optional)
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;importmap&lt;/code&gt; is the default way to manage npm packages after Rails 7. However, we don't want to use &lt;code&gt;importmap&lt;/code&gt; now, so add &lt;code&gt;--skip-javascript&lt;/code&gt; behind the &lt;code&gt;rails new&lt;/code&gt; command to opt out of all Javascript codes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rails new you_project_name &lt;span class="nt"&gt;--skip-javascript&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Of course, you can keep it if you want. It's just trickier to balance them carefully because they have kind of the same purposes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Add a simple page
&lt;/h3&gt;

&lt;p&gt;We add a simple index page just for demo&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bundle exec rails g controller pages index
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;it will create the pages_controller an &lt;code&gt;:index&lt;/code&gt; action&lt;/p&gt;

&lt;h2&gt;
  
  
  Install Vite
&lt;/h2&gt;

&lt;p&gt;Add &lt;code&gt;vite_rails&lt;/code&gt; in to the &lt;code&gt;Gemfile&lt;/code&gt;&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;gem&lt;/span&gt; &lt;span class="s1"&gt;'vite_rails'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and run&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bundle &lt;span class="nb"&gt;install
&lt;/span&gt;bundle &lt;span class="nb"&gt;exec &lt;/span&gt;vite &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;bundle exec vite install&lt;/code&gt; will add basic config file and inject some code snippet into the current view template, e.g. in the &lt;code&gt;app/views/layouts/application.html.erb&lt;/code&gt;, it will inject these 2 lines&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;%= vite_client_tag %&amp;gt;
&amp;lt;%= vite_javascript_tag 'application' %&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;vite_client_tag&lt;/code&gt;: is for HMR (hot module replacement) so you don't need to refresh the page to the the changes&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;vite_javascript_tag&lt;/code&gt;: it will load &lt;code&gt;entrypoints/application.js&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Run vite dev server
&lt;/h3&gt;

&lt;p&gt;The vite dev server will transpile your code in real-time. It's required and also helpful for our development so that we can use features like HMR. Run this command in a new terminal session:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bin/vite dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Test
&lt;/h3&gt;

&lt;p&gt;To test if the &lt;code&gt;vite_rails&lt;/code&gt; is install correctly, you can run rails server by&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rails s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and go to &lt;a href="http://localhost:3000/pages/index" rel="noopener noreferrer"&gt;http://localhost:3000/pages/index&lt;/a&gt;.&lt;br&gt;
Open the console, if you see &lt;code&gt;Vite ⚡️ Rails&lt;/code&gt;, then it means you have installed &lt;code&gt;vite_rails&lt;/code&gt; successfully!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs1nzzaf78sv6jgjzs2sq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs1nzzaf78sv6jgjzs2sq.png" alt="See Vite ⚡️ Rails in the console" width="800" height="371"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you want to run the rails server and vite dev server at the same terminal session. You can install foreman and run &lt;code&gt;foreman start -f Procfile.dev&lt;/code&gt;. &lt;code&gt;Procfile.dev&lt;/code&gt; was also added by &lt;code&gt;bundle exec vite install&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Install Vue
&lt;/h2&gt;

&lt;p&gt;Let's install 2 essential modules:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install vue
npm install @vitejs/plugin-vue
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;vue&lt;/code&gt;: It's Vue.js, of course we need it&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@vitejs/plugin-vue&lt;/code&gt;: this module let vite recognize SFC and transpile &lt;code&gt;.vue&lt;/code&gt; into js&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To enable &lt;code&gt;@vitejs/plugin-vue&lt;/code&gt;, we need to configure vite. Open &lt;code&gt;vite.config.ts&lt;/code&gt; and edit it like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;RubyPlugin&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vite-plugin-ruby&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;vue&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@vitejs/plugin-vue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;-------- add this&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nc"&gt;RubyPlugin&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="nf"&gt;vue&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;-------- add this&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;It's done and we can try to use SFC in our app!&lt;/p&gt;

&lt;h2&gt;
  
  
  Mount your first Vue component
&lt;/h2&gt;

&lt;p&gt;Let's create our first Vue SFC, App.vue, and mount it!&lt;/p&gt;

&lt;h3&gt;
  
  
  create App.vue
&lt;/h3&gt;

&lt;p&gt;Create a file &lt;code&gt;app/frontend/components/App.vue&lt;/code&gt; and edit it like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vue"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Vue App!&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt; &lt;span class="na"&gt;setup&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;style&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"scss"&lt;/span&gt; &lt;span class="na"&gt;scoped&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;h1&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;red&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;style&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that different versions of &lt;code&gt;vite_rails&lt;/code&gt; may have a different path for the source folder. The example here is &lt;code&gt;app/frontend/components/App.vue&lt;/code&gt;, but your may be &lt;code&gt;app/javascript/components/App.vue&lt;/code&gt;. It depends on where is your &lt;code&gt;entries/appliaction.js&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Set the mounting point
&lt;/h3&gt;

&lt;p&gt;Then on our index page's erb, we replace everything with&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="na"&gt;#&lt;/span&gt; &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="na"&gt;views&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="na"&gt;pages&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="na"&gt;index.html.erb&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"app"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The SFC will be mounted on this div.&lt;/p&gt;

&lt;h3&gt;
  
  
  import App.vue in application.js
&lt;/h3&gt;

&lt;p&gt;Open &lt;code&gt;app/frontend/entrypoints/application.js&lt;/code&gt;. This is the entry point of the javascript program because we wrote &lt;code&gt;&amp;lt;%= vite_javascript_tag 'application' %&amp;gt;&lt;/code&gt; in the layout.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createApp&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../App.vue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="p"&gt;)&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;mount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Import the App.vue and mount it&lt;/p&gt;

&lt;h3&gt;
  
  
  Test
&lt;/h3&gt;

&lt;p&gt;Go to &lt;a href="http://localhost:3000/pages/index" rel="noopener noreferrer"&gt;http://localhost:3000/pages/index&lt;/a&gt;, you should see the content of our &lt;code&gt;App.vue&lt;/code&gt;!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flpx7v261vay1562drp4z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flpx7v261vay1562drp4z.png" alt="Our first Vue SFC" width="800" height="234"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Pass data from Rails to Vue components
&lt;/h2&gt;

&lt;p&gt;Rails is very good at fetching the data in the database. You can make an API endpoint returning JSON string and let the Vue component to call that. However, if you just want the Vue components have the data as props when mounting it. For example, we want the App.vue show the text passed to it as props so we modify it as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vue"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{{&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt; &lt;span class="si"&gt;}}&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt; &lt;span class="na"&gt;setup&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nf"&gt;defineProps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;style&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"scss"&lt;/span&gt; &lt;span class="na"&gt;scoped&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;h1&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;red&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;style&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now our &lt;code&gt;App.vue&lt;/code&gt; is expecting  props &lt;code&gt;msg&lt;/code&gt; to be sent in.&lt;/p&gt;

&lt;p&gt;We set a instance variable in &lt;code&gt;PagesController&lt;/code&gt;:&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;PagesController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;
    &lt;span class="vi"&gt;@msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Hello Vue on Rails!"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We then do a little trick here. Put &lt;code&gt;@msg&lt;/code&gt; content on an empty div's &lt;code&gt;data-*&lt;/code&gt; attribute, modify &lt;code&gt;app/views/pages/index.html.erb&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt;
  &lt;span class="n"&gt;content_tag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="ss"&gt;:div&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;id: &lt;/span&gt;&lt;span class="s1"&gt;'appProps'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;data: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="ss"&gt;props: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="ss"&gt;msg: &lt;/span&gt;&lt;span class="vi"&gt;@msg&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;as_json&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"app"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;it will render the div below on the page.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; 
      &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"appProps"&lt;/span&gt; 
      &lt;span class="na"&gt;data-props=&lt;/span&gt;&lt;span class="s"&gt;"{&amp;amp;quot;msg&amp;amp;quot;:&amp;amp;quot;Hello Vue on Rails!&amp;amp;quot;}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can then parse the &lt;code&gt;data-props&lt;/code&gt; as a JSON in the &lt;code&gt;application.js&lt;/code&gt; and sent the JSON to &lt;code&gt;App.vue&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createApp&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../components/App.vue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;appProps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;appProps&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;props&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;createApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&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="nx"&gt;appProps&lt;/span&gt;&lt;span class="p"&gt;))&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;mount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(This is a trick used by &lt;a href="https://github.com/gazay/gon" rel="noopener noreferrer"&gt;https://github.com/gazay/gon&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Refresh the page and you can see the &lt;code&gt;App.vue&lt;/code&gt; is showing our &lt;code&gt;@msg&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2lfz0lk180use1p4cggi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2lfz0lk180use1p4cggi.png" alt="The App.vue with our @msg" width="800" height="497"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Vue.js SFC is a very powerful tool. To be honest, I was attracted to it the first time I saw its syntax. Combining HTML, CSS, JS into a single file is so elegant. Using Vue SFC with Rails is really a joy. Hope you like it!&lt;/p&gt;

</description>
      <category>rails</category>
      <category>vue</category>
      <category>vite</category>
      <category>ruby</category>
    </item>
    <item>
      <title>How to generate self-signed certificates for localhost</title>
      <dc:creator>Kevin Luo</dc:creator>
      <pubDate>Wed, 30 Oct 2024 01:18:27 +0000</pubDate>
      <link>https://forem.com/kevinluo201/how-to-generate-self-signed-certificates-for-localhost-4lmf</link>
      <guid>https://forem.com/kevinluo201/how-to-generate-self-signed-certificates-for-localhost-4lmf</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;When we develop a website, we usually connect to our development server via  HTTP with a special port number like 3000, 5000, or 8888, e.g. &lt;code&gt;http://localhost:3000&lt;/code&gt;.  Everything works fine and you are happy 😃 One day, you need to connect the dev server via HTTPS. You follow some instructions online but no matter what you've tried, you always get the Not Secure page in Chrome:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3gd2687dkgf2jqo0wp03.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3gd2687dkgf2jqo0wp03.png" alt="Chrome's warning saying " width="654" height="441"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Have you run into this situation? In this article, I will show you how to solve this problem to generate valid certificates that can work in Chrome for the local host.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Please aware that the method in this article does not work for Firefox&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Generate a certificate for &lt;em&gt;localhost&lt;/em&gt;
&lt;/h2&gt;

&lt;p&gt;The solution is to generate a certificate that includes &lt;strong&gt;localhost&lt;/strong&gt; as the domain name and put this information in the certificate's field recognized by modern browsers, so when the browsers check the self-signed certificate and the current URL, it will know it's a valid certificate. It makes sense, right? We will use &lt;code&gt;openssl&lt;/code&gt; to generate such a certificate. I use a Macbook, so the example I show here is under macOS&lt;/p&gt;

&lt;h3&gt;
  
  
  Install openssl
&lt;/h3&gt;

&lt;p&gt;Use Homebrew or other way more suitable for you to install &lt;code&gt;openssl&lt;/code&gt; if you don't have &lt;code&gt;openssl&lt;/code&gt; installed in your system yet.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;openssl
&lt;span class="c"&gt;# or&lt;/span&gt;
brew upgrade openssl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Generate a certificate by openssl
&lt;/h3&gt;

&lt;p&gt;To generate the certificate we want, execute the command below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openssl req &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-newkey&lt;/span&gt; rsa:2048 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-x509&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-nodes&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-keyout&lt;/span&gt; localhost.key &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-new&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-out&lt;/span&gt; localhost.crt &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-subj&lt;/span&gt; /CN&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'Localhost self-signed certificate'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-addext&lt;/span&gt; &lt;span class="s2"&gt;"subjectAltName=DNS:localhost"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-sha256&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-days&lt;/span&gt; 365
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will get &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a certificate, &lt;code&gt;localhost.crt&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;a private key, &lt;code&gt;localhost.key&lt;/code&gt;
You need these two files to start a HTTPS server. Done!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's super simple, eh?  but it's definitely not straightforward unless you are already familiar with SSL certificates. To be honest, &lt;code&gt;openssl&lt;/code&gt;'s commands always feel like black magic spells. In the past, I just grabbed pieces from different answers on the internet and tried to do what I want. This time, I do want to understand. Let me (and ChatGPT😆) explain to you what this command actually mean.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;openssl req&lt;/code&gt;: tell openssl to generate CSR. Although it's the main parameter but it's not important here. I'll explain more below.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-newkey rsa:2048&lt;/code&gt;: use RSA to generate private key. If you don't know what's RSA, you only need to know that is the reason we have a private key&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-x509&lt;/code&gt;: instead of generating a CSR, generate a X.509 certificate. This mysterious &lt;strong&gt;X.509&lt;/strong&gt; is just the a standard for the certificate used by HTTPS. You can think it like a JSON with specific keys.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-nodes&lt;/code&gt;: don't use passphrase to encrypt the private key&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-keyout localhost.key&lt;/code&gt;: what's the private key's filename. We call it &lt;code&gt;localhost.key&lt;/code&gt; here&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-new&lt;/code&gt;: straightforward, we want a new certificate&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-out localhost.crt&lt;/code&gt;: we want the output certificate's filename to be &lt;code&gt;localhost.crt&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-subj /CN='Localhost self-signed certificate'&lt;/code&gt;: Set Subject's value. &lt;strong&gt;Subject&lt;/strong&gt; is a field in X.509. CN is Common Name, it can be anything like "My first self-signed certificate"&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-addext "subjectAltName=DNS:localhost"&lt;/code&gt;: We want to add an extension field, Subject Alternative Name, and put DNS:localhost. &lt;strong&gt;This is the most important line.&lt;/strong&gt; Basically, you can ignore all other parameters on this list.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-sha256&lt;/code&gt;: use SHA-256 to sign the certificate&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-days 365&lt;/code&gt;: The validity is 365 days&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Trust this certificate in your OS
&lt;/h3&gt;

&lt;p&gt;Because our self-signed certificate is not issued by a trusted CA(Certificate Authority) like GoDaddy, modern browsers will be skeptical and won't trust it. Therefore, we need to make our computer "trust" our own certificate manually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;security add-trusted-cert &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; trustRoot &lt;span class="nt"&gt;-k&lt;/span&gt; /Library/Keychains/System.keychain localhost.crt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command registers your new certificate in the system and also &lt;strong&gt;trusts&lt;/strong&gt; it. When your OS trusts it, your browser will trust it accordingly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Test
&lt;/h3&gt;

&lt;p&gt;We can use the newly generated certificate and private key to run any web server that supports HTTPS. It doesn't matter which web server you want to use. Since &lt;strong&gt;Deno2&lt;/strong&gt; is the hottest stuff recently and is also super simple, we can use Deno2 to demo.&lt;/p&gt;

&lt;p&gt;You can install Deno2 by following their website's instructions, &lt;a href="https://deno.com/" rel="noopener noreferrer"&gt;https://deno.com/&lt;/a&gt;. After installing Deno2, first we can make an empty folder. Second, we can put the &lt;code&gt;localhost.crt&lt;/code&gt; and &lt;code&gt;localhost.key&lt;/code&gt; into that folder. Then create a file in the folder, &lt;code&gt;main.ts&lt;/code&gt; like below to run a HTTPS web server using the certificate and the private key at port &lt;strong&gt;8000&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// main.ts&lt;/span&gt;
&lt;span class="nx"&gt;Deno&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;serve&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;cert&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Deno&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readTextFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./localhost.crt&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Deno&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readTextFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./localhost.key&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_req&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="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello, World!&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can run the dev server by executing&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;deno main.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open Chrome and link to &lt;code&gt;https://localhost:8000&lt;/code&gt;, you should see a nice Hello World without any Not Secure warning. Viola!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fym9agcsi6fpynjy423ac.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fym9agcsi6fpynjy423ac.png" alt="Successful Hello World wit URL localhost:8000 in the browser" width="620" height="419"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Generate a certificate for an arbitrary domain
&lt;/h2&gt;

&lt;p&gt;This method can not only be used for &lt;code&gt;localhost&lt;/code&gt;. We can also generate a certificate for an arbitrary domain pointing to localhost. Let's say we want to test &lt;code&gt;abc.dev&lt;/code&gt; on our machine.&lt;/p&gt;

&lt;h3&gt;
  
  
  Add the domain in &lt;code&gt;/etc/hosts&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;First, we need to add a new entry in /etc/hosts to make it point to 127.0.0.1, our local:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# executing `sudo vim /etc/hosts` in the terminal
127.0.0.1   abc.dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Follow the same steps as localhost's example
&lt;/h3&gt;

&lt;p&gt;I hope you get out of vim successfully. Now we only need to do the same things as we did for &lt;code&gt;localhost&lt;/code&gt;, the only difference is that we will add &lt;code&gt;abc.dev&lt;/code&gt; into &lt;code&gt;subjectAltName&lt;/code&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Generate the certificate
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openssl req &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-newkey&lt;/span&gt; rsa:2048 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-x509&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-nodes&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-keyout&lt;/span&gt; abc.dev.key &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-new&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-out&lt;/span&gt; abc.dev.crt &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-subj&lt;/span&gt; /CN&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'Localhost self-signed certificate'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-addext&lt;/span&gt; &lt;span class="s2"&gt;"subjectAltName=DNS:localhost,DNS:abc.dev"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-sha256&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-days&lt;/span&gt; 365
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;trust the newly generated certificate
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain abc.dev.crt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;remember to change the &lt;code&gt;main.ts&lt;/code&gt; so it uses abc.dev.crt and .key
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;Deno&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;serve&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;cert&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Deno&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readTextFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./abc.dev.crt&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Deno&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readTextFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./abc.dev.key&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_req&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="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello, World!&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Test
&lt;/h3&gt;

&lt;p&gt;Voila!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flx8pqviwb3nbtcbb1qgq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flx8pqviwb3nbtcbb1qgq.png" alt="Successful Hello World wit URL abc.dev:8000 in the browser" width="674" height="475"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You'll find that &lt;a href="https://localhost:8000" rel="noopener noreferrer"&gt;https://localhost:8000&lt;/a&gt; also works! It's because we also add &lt;code&gt;DNS:localhost&lt;/code&gt; in &lt;code&gt;subjectAltName&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbtqrhv2qrb33f9om9sex.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbtqrhv2qrb33f9om9sex.png" alt="Successful Hello World wit URL localhost:8000 in the browser" width="605" height="416"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  How to remove the certificate from the keychain?(MacOS)
&lt;/h3&gt;

&lt;p&gt;We have now learned how to make and trust self-signed certificates. I guess we will have lots of trusted certificates in the system. I guess most of the time, we just want to test some things for development. After we remove the certificates for the testing. We can also remove their records of being trusted from the system. To do that in MacOS, we can open &lt;strong&gt;Keychain Access&lt;/strong&gt; app, find the certificates we trusted and delete them.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftvc7ve3f4ng5fnonqhqj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftvc7ve3f4ng5fnonqhqj.png" alt="Keychain Access app in macOS" width="800" height="494"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;MacOS Sequoia removes &lt;strong&gt;Keychain Access&lt;/strong&gt; app from the launch pad, we can still find it at &lt;code&gt;/System/Library/CoreServices/Applications/Keychain Access.app&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What is subjectAltName(SAN)?
&lt;/h2&gt;

&lt;p&gt;Here comes a big question: why it works?  SAN is just an extra field inside X.509 certificate.To dig deeper, we need to expand the certificate to see what's inside. We can use the command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openssl x509 &lt;span class="nt"&gt;-in&lt;/span&gt; localhost.crt &lt;span class="nt"&gt;-text&lt;/span&gt; &lt;span class="nt"&gt;-noout&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It will print out the content of the certificate. You can see it's a YAML-like, nested structure:&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;Certificate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;Version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;3 (0x2)&lt;/span&gt;
        &lt;span class="na"&gt;Serial Number&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="s"&gt;68:5b:f9:5b:72:d2:1f:cd:fd:1b:f9:43:bb:b9:66:cc:53:ab:14:ca&lt;/span&gt;
        &lt;span class="na"&gt;Signature Algorithm&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sha256WithRSAEncryption&lt;/span&gt;
        &lt;span class="na"&gt;Issuer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;CN=Localhost self-signed certificate&lt;/span&gt;
        &lt;span class="s"&gt;Validity&lt;/span&gt;
            &lt;span class="s"&gt;Not Before&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Oct 29 03:45:30 2024 GMT&lt;/span&gt;
            &lt;span class="s"&gt;Not After&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Oct 29 03:45:30 2025 GMT&lt;/span&gt;
        &lt;span class="na"&gt;Subject&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;CN=Localhost self-signed certificate&lt;/span&gt;
        &lt;span class="na"&gt;Subject Public Key Info&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;Public Key Algorithm&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rsaEncryption&lt;/span&gt;
                &lt;span class="s"&gt;Public-Key&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;(2048 bit)&lt;/span&gt;
                &lt;span class="s"&gt;Modulus&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
                    &lt;span class="na"&gt;00:a6:84:2a:57:5c:c9:a1:69:60:66:6b:43:07:a3&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                    &lt;span class="na"&gt;4c:56:68:43:eb:10:29:65:ba:08:d8:75:e8:47:d1&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                    &lt;span class="na"&gt;7e:57:16:95:0d:e1:73:3b:69:3f:37:6f:e2:8e:c9&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                    &lt;span class="na"&gt;82:74:75:46:e4:16:0f:83:b3:f2:13:77:45:e1:3f&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                    &lt;span class="na"&gt;6f:1d:78:36:1b:56:04:65:b8:79:97:c5:15:30:7b&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                    &lt;span class="na"&gt;28:59:7a:db:c0:7e:1c:32:f3:03:26:f9:5e:4d:ea&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                    &lt;span class="na"&gt;db:76:3d:98:ec:12:ae:6b:10:b6:54:5c:68:88:47&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                    &lt;span class="na"&gt;a2:b8:2c:2d:f8:c2:ba:7b:ca:14:a3:a6:e0:ec:49&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                    &lt;span class="na"&gt;13:08:ed:bc:67:4f:ef:e7:ab:24:22:65:a8:60:45&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                    &lt;span class="na"&gt;74:87:d3:f7:ea:3d:a2:05:e3:46:07:6c:32:2c:48&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                    &lt;span class="na"&gt;4d:bd:e0:78:6f:05:dc:7e:8c:b3:4e:1a:e8:c9:0a&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                    &lt;span class="na"&gt;cd:3b:2f:cd:9f:2d:a7:7d:4c:76:d8:64:de:7f:9b&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                    &lt;span class="na"&gt;06:6c:de:10:cc:44:40:e3:c3:3e:41:85:f0:ca:96&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                    &lt;span class="na"&gt;0d:7a:ab:94:26:c9:e3:cb:9a:ca:ab:52:86:c2:28&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                    &lt;span class="na"&gt;03:7e:7a:7b:43:29:d2:aa:35:d0:69:1b:37:8f:cb&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                    &lt;span class="na"&gt;45:6a:c2:20:37:b3:6e:05:02:43:0b:d5:dd:1d:27&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                    &lt;span class="na"&gt;08:51:02:87:85:e3:75:93:03:86:b7:43:8e:b3:15&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                    &lt;span class="s"&gt;a0:81&lt;/span&gt;
                &lt;span class="na"&gt;Exponent&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;65537 (0x10001)&lt;/span&gt;
        &lt;span class="na"&gt;X509v3 extensions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;X509v3 Subject Key Identifier&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
                &lt;span class="s"&gt;2F:90:ED:0C:14:D2:64:11:F8:0C:54:29:EF:EC:55:A7:3B:B1:30:7E&lt;/span&gt;
            &lt;span class="na"&gt;X509v3 Authority Key Identifier&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
                &lt;span class="s"&gt;2F:90:ED:0C:14:D2:64:11:F8:0C:54:29:EF:EC:55:A7:3B:B1:30:7E&lt;/span&gt;
            &lt;span class="na"&gt;X509v3 Basic Constraints&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;critical&lt;/span&gt;
                &lt;span class="s"&gt;CA:TRUE&lt;/span&gt;
            &lt;span class="na"&gt;X509v3 Subject Alternative Name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
                &lt;span class="s"&gt;DNS:localhost, DNS:abc.dev&lt;/span&gt;
    &lt;span class="na"&gt;Signature Algorithm&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sha256WithRSAEncryption&lt;/span&gt;
    &lt;span class="na"&gt;Signature Value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;11:05:1f:f5:80:aa:6d:42:c1:02:9d:a3:3d:c4:0d:29:d3:5b&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;2b:d8:eb:91:7b:56:cf:de:1d:96:76:85:93:91:fa:95:df:86&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;d4:68:a3:6b:3b:dd:92:61:69:dc:05:73:51:5d:f0:2a:7c:a4&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;42:5a:dc:cf:3e:d0:92:19:7a:38:02:b1:20:9d:ce:c8:58:a2&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;3a:45:24:c6:d7:f2:bf:72:5a:bb:cf:49:9e:96:49:02:08:15&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;dd:da:5e:e4:01:40:92:e8:b9:86:7c:da:b7:1b:dc:bc:95:c9&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;6c:38:5c:b7:4c:9d:df:c2:11:49:8e:db:72:41:9f:a6:bd:8e&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;aa:4f:b1:8f:79:c4:ea:eb:03:f1:92:0c:20:3c:f1:d5:8e:9c&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;87:b5:23:66:e9:60:de:10:b9:a6:3b:29:af:f0:bd:22:1f:ec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;21:c1:10:b9:0b:63:94:9c:c3:2f:87:09:4e:56:0e:24:ae:5d&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;53:54:04:9a:38:10:cb:99:1e:ee:63:47:e3:d3:8b:4b:bd:af&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;f3:76:7f:73:98:fb:71:87:9b:d0:a1:94:d4:4c:7e:04:95:6d&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;83:57:54:93:da:81:44:51:c0:ec:2f:48:24:17:8b:58:65:58&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;08:5c:2b:13:cd:3c:85:b8:2a:2e:06:37:d5:35:90:fb:7f:14&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="s"&gt;da:11:d2:b2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is a field called &lt;strong&gt;Subject&lt;/strong&gt; and a field called &lt;strong&gt;X509v3 Subject Alternative Name&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#...
Subject: CN=Localhost self-signed certificate
#...
X509v3 extensions:
      X509v3 Subject Alternative Name: 
        DNS:localhost, DNS:abc.dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Subject's CN(Common Name)&lt;/strong&gt; used to be the domain name like &lt;code&gt;CN=abc.dev&lt;/code&gt;. However, it can have only one value.  It results in that one certificate can only have one domain name bound on it. Therefore, &lt;strong&gt;Subject Alternative Name&lt;/strong&gt; is invented as an extension field to let one certificate support multiple domain names. &lt;br&gt;
The browser used to check only the Subject field in the past, but it somehow it checks only the SAN now. That's why it cannot work if we just look up some instructions online because they often only embed the domain names in the Subject field, and that way has been outdated.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I think the mechanism of HTTPS is complex for many people. That's why it feels hard to understand. Maybe it's not that bad because it's for security. The fewer people understand it the better. However, it will always be a pain in the ass if developers like us do not have a chance to learn about it.&lt;br&gt;
This solution works well for Safari, Chrome or Chromium browsers. However, &lt;strong&gt;it does not work for Firefox,&lt;/strong&gt; which will return &lt;strong&gt;MOZILLA_PKIX_ERROR_SELF_SIGNED_CERT&lt;/strong&gt; warning. To make a self-signed certificate work for Firefox, we may need to create our own CA to sign the certificate. I may write another article showing how to do that one day 😀&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Dear AI, can you translate the Rails Guide for me?</title>
      <dc:creator>Kevin Luo</dc:creator>
      <pubDate>Sun, 30 Jul 2023 19:59:07 +0000</pubDate>
      <link>https://forem.com/kevinluo201/dear-ai-can-you-translate-the-rails-guide-for-me-3hc</link>
      <guid>https://forem.com/kevinluo201/dear-ai-can-you-translate-the-rails-guide-for-me-3hc</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR;
&lt;/h2&gt;

&lt;p&gt;I used ChatGPT API to translate the Rails Guide into different languages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Taiwan's Traditional Chinese🇹🇼 &lt;a href="https://ai.rails-guide.com/zh-TW" rel="noopener noreferrer"&gt;https://ai.rails-guide.com/zh-TW&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;French🇫🇷 &lt;a href="https://ai.rails-guide.com/fr" rel="noopener noreferrer"&gt;https://ai.rails-guide.com/fr&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Lithuanian🇱🇹 &lt;a href="https://ai.rails-guide.com/lt" rel="noopener noreferrer"&gt;https://ai.rails-guide.com/lt&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Brazilian Portuguese🇧🇷 &lt;a href="https://ai.rails-guide.com/pt-BR" rel="noopener noreferrer"&gt;https://ai.rails-guide.com/pt-BR&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Thai🇹🇭 &lt;a href="https://ai.rails-guide.com/th" rel="noopener noreferrer"&gt;https://ai.rails-guide.com/th&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Simplified Chinese🇨🇳 &lt;a href="https://ai.rails-guide.com/zh-CN" rel="noopener noreferrer"&gt;https://ai.rails-guide.com/zh-CN&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Update on 2023/08/12
&lt;/h2&gt;

&lt;p&gt;I added 3 more langauges&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Japanese🇯🇵 &lt;a href="https://ai.rails-guide.com/jp" rel="noopener noreferrer"&gt;https://ai.rails-guide.com/jp&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Korean🇰🇷 &lt;a href="https://ai.rails-guide.com/ko" rel="noopener noreferrer"&gt;https://ai.rails-guide.com/ko&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Espanõl🇪🇸 &lt;a href="https://ai.rails-guide.com/es" rel="noopener noreferrer"&gt;https://ai.rails-guide.com/es&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What's the Rails Guide?
&lt;/h2&gt;

&lt;p&gt;I guess people who read this article already know Rails, however, just in case, I'll briefly introduce Ruby on Rails and the Rails Guide. Feel free to skip this section if you already knew them.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://rubyonrails.org/" rel="noopener noreferrer"&gt;Ruby on Rails&lt;/a&gt; is a full-stack web application framework. With Rails, you can build a website that can access your database's data, return as API payload or render them on the user's browser easily and safely. &lt;a href="https://guides.rubyonrails.org/" rel="noopener noreferrer"&gt;The Rails Guide&lt;/a&gt; is the user manual for developers to learn how to use Rails. The Rails Guide is also a &lt;strong&gt;crowd-creation&lt;/strong&gt; and is in the same repository on GitHub. It has very high quality because it is reviewed and modified again and again by numerous seasoned Rails developers. For anyone who wants to learn Ruby on Rails, I will definitely recommend they read the guide first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why translate the Rails Guide?
&lt;/h2&gt;

&lt;p&gt;Translating the Rails Guide is not for &lt;em&gt;diversity&lt;/em&gt;. The Ruby on Rails guide is written exclusively in English and it is totally fine. However, there are many talented developers all around the world who just cannot read English well. It is really a pity that they don't have a chance to get in touch with this wonderful and powerful web framework, Ruby on Rails, just because it lacks the information in their languages. I believe by translating the Rails Guide, we'll have a better chance for people all over the world to learn Rails.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why use generative AI to translate Rails Guide?
&lt;/h2&gt;

&lt;p&gt;First of all, generative AI can produce &lt;em&gt;more human&lt;/em&gt; text. Moreover, with more context, it can generate more accurate and suitable translations. You must have read some articles which you could tell immediately that were translated by Google Translate because they felt very &lt;strong&gt;unnatural&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;Second, although there are already many repositories of rails guide in different languages, &lt;a href="https://guides.rubyonrails.org/contributing_to_ruby_on_rails.html#translating-rails-guides" rel="noopener noreferrer"&gt;https://guides.rubyonrails.org/contributing_to_ruby_on_rails.html#translating-rails-guides&lt;/a&gt;. However, the problem is that &lt;strong&gt;most&lt;/strong&gt; of them are &lt;strong&gt;out of date&lt;/strong&gt;. Those repositories also depend on volunteers' efforts. The Rails community used to have some enthusiastic fans who were willing to help translate the guide. Unfortunately, since the popularity of Rails plummeted, it hasn't had enough volunteers to continue the work. Using Generative AI to translate documents saves time and human effort. One person can refine the translation result by his/herself easily. It also means that we can update them more frequently. It could be a more sustainable method.&lt;/p&gt;

&lt;h2&gt;
  
  
  Proposed Workflow
&lt;/h2&gt;

&lt;p&gt;My original plan was simple. &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Write a script to read the Rails guide files and send their content to ChatGPT to translate to a specified language. &lt;/li&gt;
&lt;li&gt;Then use the existing Rails Guide script to generate HTML files just like the &lt;a href="https://guides.rubyonrails.org/contributing_to_ruby_on_rails.html#translating-rails-guides" rel="noopener noreferrer"&gt;current translation workflow&lt;/a&gt;
I may wrap the code into a class, &lt;code&gt;AiTranslator&lt;/code&gt;, so it should be like this&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpjjt3eoc3bkfwymcqg9q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpjjt3eoc3bkfwymcqg9q.png" alt="Original Idea"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;However, it was not as simple as I imagined 😅&lt;/p&gt;

&lt;h2&gt;
  
  
  Challenges
&lt;/h2&gt;

&lt;p&gt;There are many challenges in this &lt;strong&gt;simple task&lt;/strong&gt;. I picked some more significant ones here.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tokens
&lt;/h3&gt;

&lt;p&gt;ChatGPT or other generative AI models can only accept a limited number of tokens. Tokens are composed of both input and output strings. It's not the number of characters or words but only correlated. Tokens are also used for OpenAI to charge your bill.&lt;/p&gt;

&lt;p&gt;The current most popular model, &lt;code&gt;gpt-3.5-turbo&lt;/code&gt; only allows &lt;strong&gt;4097&lt;/strong&gt; tokens for one request. Remember, it's used for both input and output. That means I cannot just upload a whole file to ChatGPT but I need to process a file piece by piece.&lt;/p&gt;

&lt;p&gt;Maybe you think: it's easy, you can just send 1 to 2 phrases for a ChatGPT API call, then you'll never exceed the limit. &lt;/p&gt;

&lt;p&gt;You're right. However, each ChatGPT request is independent, they don't share any context. I can show you an exmaple of the web page's ChatGPT. If I ask ChatGPT "Do you know NBA?" then ask it "Who's the champion of 2019?&lt;br&gt;
". It will answer it's Toronto Raptors.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa7s8al91cnjmrnook2z8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa7s8al91cnjmrnook2z8.png" alt="context ex1"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;However, if I only ask "Who's the champion of 2019?" directly in a new session, ChatGPT will not be able to answer me because of lacking context.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmvr5yjo7fx0kfhmcymbd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmvr5yjo7fx0kfhmcymbd.png" alt="context ex2"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Unlike Google Translate which is like a strengthened dictionary. We'd better treat the Generative AI model like a very smart student. &lt;strong&gt;The more input you give it, the better the result it returns to you.&lt;/strong&gt; As a result, I want to feed ChatGPT text as much as possible so it can have appropriate context to translate the Rails Guide properly.&lt;/p&gt;

&lt;p&gt;My approach is like the code block below. &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;buffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;
&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readlines&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;buffer&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="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="vi"&gt;@buffer_size&lt;/span&gt;
    &lt;span class="n"&gt;translated_text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ai_translate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buffer&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="ss"&gt;:text&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;translated_text&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;buffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="n"&gt;buffer&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;line&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;ol&gt;
&lt;li&gt;I declare a &lt;code&gt;buffer = []&lt;/code&gt; at the beginning.&lt;/li&gt;
&lt;li&gt;Iterate a file line by line. For each iteration, I'll put one line into &lt;code&gt;buffer&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;When the number of words exceeds a threshold, I'll send the request to ChatGPT API with the content in the &lt;code&gt;buffer&lt;/code&gt;. The threshold, &lt;code&gt;@buffer_size&lt;/code&gt;, is defaulted as &lt;code&gt;700&lt;/code&gt;. It's just an empirical magic number &lt;/li&gt;
&lt;li&gt;Plus, we know &lt;strong&gt;paragraphs&lt;/strong&gt; in markdown are separated by &lt;strong&gt;blank lines&lt;/strong&gt;, therefore, I also want to translate a whole paragraph in one ChatGPT request.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Prompt phrase
&lt;/h3&gt;

&lt;p&gt;The prompt phrase for the Generative AI model affects the result drastically. I tried a lot of different combinations. And eventually, I made it this way:&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;LANGUAGES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="s1"&gt;'zh-TW'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"Traditional Chinese used in Taiwan(台灣繁體中文)."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'lt'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Lithuanian'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'fr'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'French'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'pt-BR'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Brazilian Portuguese'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'th'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Thai'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'zh-CN'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Simplified Chinese'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;system_prompt&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="s2"&gt;"Translate the technical document to &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;LANGUAGES&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="vi"&gt;@target_language&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; without adding any new content."&lt;/span&gt;


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

&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Translate the technical document&lt;/code&gt;: pointing out that we are translating a technical document excerpt so it will know it does not need to translate some elements like code blocks.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;LANGUAGES[@target_language]&lt;/code&gt;: I don't know whether it is a unique problem for Traditional Chinese. Although they're both Chinese words, the terminologies, writing style and intonation of Traditional Chinese in Taiwan are very different from what Simplified Chinese has. I need to specify it more clearly so I can get the desired result.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;without adding any new content.&lt;/code&gt;: It is also important to tell ChatGPT not to add extra information because we're translating an article. Otherwise, it will just be like some annoying students in your classroom, who keep talking and add much needless knowledge.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Markdown parsing
&lt;/h3&gt;

&lt;p&gt;The Rails Guide is full of code blocks for showing code examples. It's reasonable not to send a code block separately. I made the line reader a simple state machine. It will change the state to &lt;code&gt;:codeblock&lt;/code&gt; when it starts parsing a codeblock and it won't call ChatGPT API until it finishes that block.&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;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;:readline&lt;/span&gt;
&lt;span class="n"&gt;buffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;
&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readlines&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;include?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"` ` `"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# I need to add spaces between the backtick(`), or Dev.to will have problem&lt;/span&gt;
    &lt;span class="n"&gt;buffer&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;
    &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="ss"&gt;:codeblock&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="ss"&gt;:readline&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="ss"&gt;:codeblock&lt;/span&gt;
  &lt;span class="k"&gt;elsif&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="ss"&gt;:readline&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;buffer&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="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;buffer_size&lt;/span&gt;
    &lt;span class="n"&gt;translated_text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ai_translate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buffer&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="ss"&gt;:text&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;translated_text&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;buffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="n"&gt;buffer&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;line&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;h3&gt;
  
  
  Anchors
&lt;/h3&gt;

&lt;p&gt;When you open any rails guide's page, you can see there's a &lt;strong&gt;Chapters&lt;/strong&gt; block on the right serving as a table of content.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftsfr0zs5odz8gcxu44bl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftsfr0zs5odz8gcxu44bl.png" alt="Chapters"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That table is generated automatically by a script. The titles, such as &lt;code&gt;&amp;lt;h1&amp;gt;, &amp;lt;h2&amp;gt;, &amp;lt;h3&amp;gt;&lt;/code&gt;, etc. will be assigned &lt;code&gt;id&lt;/code&gt; with the title's text. For example, if the title is "Guide Assumption" in the markdown, &lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;

&lt;span class="gu"&gt;### Guide Assumption&lt;/span&gt;&lt;span class="sb"&gt;


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

&lt;/div&gt;

&lt;p&gt;it will be rendered as in the final HTML&lt;/p&gt;

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

&lt;span class="nt"&gt;&amp;lt;h3&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"guide-assumptions"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;...&lt;span class="nt"&gt;&amp;lt;/h3&amp;gt;&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;The link in the table of content can then be referred to the elements with that id value.&lt;/p&gt;

&lt;p&gt;It works fine in the original Rails Guide. When you click a link in the Chapters, the browser will jump to the corresponding section. However, a problem happens once all titles are translated. After some investigation, I found that it's related to Turbo. I guess it's a Turbo's bug. My current solution is disabling Turbo for the links in the Chapters block.&lt;/p&gt;

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

&lt;span class="nt"&gt;&amp;lt;ol&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"chapters"&lt;/span&gt; &lt;span class="na"&gt;data-turbo=&lt;/span&gt;&lt;span class="s"&gt;"false"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
...
&lt;span class="nt"&gt;&amp;lt;/ol&amp;gt;&lt;/span&gt;


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

&lt;/div&gt;
&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;Repository: &lt;a href="https://github.com/kevinluo201/rails-guide-ai" rel="noopener noreferrer"&gt;https://github.com/kevinluo201/rails-guide-ai&lt;/a&gt;&lt;br&gt;
This repo is forked from the Rails repo so that it can pull the updates of the guide's files. It only has 2 new files:&lt;/p&gt;

&lt;p&gt;It only has 2 new files.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;guides/rails_guides/ai_translator.rb&lt;/code&gt;: it's the main program.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;guides/ai_translate.rb&lt;/code&gt;: it's the starting point&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can do the following steps if you want to play around with it.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Set a new environment variable call &lt;code&gt;OPENAI_ACCESS_TOKEN&lt;/code&gt; and set its value to your personal access token on OpenAI.&lt;/li&gt;
&lt;li&gt;add a new language in &lt;code&gt;RailsGuide::AiTranslator&lt;/code&gt;, for example, &lt;code&gt;'jp' =&amp;gt; 'Japanese'&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Open the terminal, go to &lt;code&gt;guides/&lt;/code&gt; and start translating by executing
```bash
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;ruby ./ai_translate.rb jp&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;4. You can also translate a single file, just add a filename after the command
```bash


ruby ./ai_translate.rb jp getting_started.md


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

&lt;/div&gt;

&lt;ol&gt;
&lt;li&gt;After all files are translated, you can just execute the rails existing script to generate HTML, CSS and JS. Unfortunately, &lt;strong&gt;it is likely to fail when you do that.&lt;/strong&gt; Usually, it is because there are duplicated titles which lead to duplicated &lt;code&gt;id&lt;/code&gt; in the HTML. You can fix it by finding out which title has the problem and can change that title a bit to avoid the problem. It can also have different problems when translating into different languages. Just try solving them so the process can finish.&lt;/li&gt;
&lt;/ol&gt;


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

&lt;p&gt;bundle exec rake guides:generate:html GUIDES_LANGUAGE=jp&lt;/p&gt;

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

&lt;/div&gt;
&lt;h2&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Help Wanted&lt;br&gt;
&lt;/h2&gt;

&lt;p&gt;It is just an experimental project now. There are several issues that can be improved. If you think it is an interesting topic, feel free to discuss it with me.&lt;/p&gt;

&lt;h3&gt;
  
  
  Current Issues
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Anchor links
&lt;/h4&gt;

&lt;p&gt;The table of content is solved by disabling Turbo. However, there are anchor links spread among the articles. They cannot be converted to the correct URL smoothly, especially when it refers to an anchor on another page.&lt;/p&gt;

&lt;h4&gt;
  
  
  Versioning
&lt;/h4&gt;

&lt;p&gt;The Rails Guide has versions. A version is kind of a snapshot of the guide at a particular time. I haven't thought of a good way to manage them.&lt;/p&gt;

&lt;h4&gt;
  
  
  Different models
&lt;/h4&gt;

&lt;p&gt;I'm now using &lt;code&gt;gpt-3.5-turbo&lt;/code&gt;. I live in Canada so I cannot use Google's &lt;code&gt;Bard&lt;/code&gt;. Feel free to change the code to be able to switch different models, like &lt;code&gt;gpt4&lt;/code&gt; or &lt;code&gt;llamas 2&lt;/code&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  EPUB
&lt;/h4&gt;

&lt;p&gt;Epub files can be generated by the Rails guide script. However, it has errors when I want to import them into the Epub reader software, such as "Books" on OSX. I think it may related to the broken anchor links.&lt;/p&gt;

&lt;h4&gt;
  
  
  Other stuff
&lt;/h4&gt;

&lt;p&gt;If you have any ideas that can make this project more sustainable, please discuss it with me. For example, it's a guide for Rails, why not build it as a Rails app?&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The quality of AI translation is not perfect but acceptable. I'm not concerned about the quality. As far I can see, the limitation of tokens and the trained model are the most significant factors. I believe this problem will be solved by swapping the current model (&lt;code&gt;gpt-3.5-turbo&lt;/code&gt;) with a more advanced model in the future. The result shows that this workflow really works and that's the most important lesson for me.&lt;/p&gt;

&lt;p&gt;About the cost, I have done many experiments for this idea and I translated the Rails Guides into 6 different languages. It costs me about $27 so each version of the translation costs less than $5 on average. The actual price should be less than that because many experiments just failed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyir8pc2f7txyag0uab1h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyir8pc2f7txyag0uab1h.png" alt="usage chart"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;Due to its good quality and low cost, Generative AI might be a good solution for technical documents of open-source projects. *&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Buy me a coffee
&lt;/h2&gt;

&lt;p&gt;At last, if you like what i'm doing, you can buy me a coffee 😉☕️&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftwve1hh3j8ewl5aowo7r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftwve1hh3j8ewl5aowo7r.png" alt="Buy Me A Coffee"&gt;&lt;/a&gt;](&lt;a href="https://www.buymeacoffee.com/kevinluo" rel="noopener noreferrer"&gt;https://www.buymeacoffee.com/kevinluo&lt;/a&gt;)&lt;/p&gt;

</description>
      <category>rails</category>
      <category>ruby</category>
      <category>chatgpt</category>
      <category>ai</category>
    </item>
    <item>
      <title>Delve deeply into Devise - Introduce all the Modules</title>
      <dc:creator>Kevin Luo</dc:creator>
      <pubDate>Sat, 24 Jun 2023 14:29:55 +0000</pubDate>
      <link>https://forem.com/kevinluo201/introduction-to-devise-modules-and-enable-all-of-them-4p25</link>
      <guid>https://forem.com/kevinluo201/introduction-to-devise-modules-and-enable-all-of-them-4p25</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;If you're a Rails developer, I bet you have already heard of or used &lt;code&gt;devise&lt;/code&gt;. If you really don't know it, I guess you're reading this article in the far future 😅. &lt;code&gt;devise&lt;/code&gt; is a very comprehensive &lt;strong&gt;user authentication&lt;/strong&gt; library for Rails and it's still true in 2023.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;devise&lt;/code&gt; is very powerful but it's also notorious for being difficult to learn. It has too many features and there isn't a entry point to cut in. Despite that, I'd like to introduce &lt;code&gt;devise&lt;/code&gt; starting from its 10 modules. These modules encapsulate 10 common features an authentication system may have. In addition, almost all functionalities of &lt;code&gt;devise&lt;/code&gt;  are built around these 10 modules. I believe by knowing more about them, it will easier for anyone to harness &lt;code&gt;devise&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In this article, I will enable all 10 modules on the &lt;code&gt;User&lt;/code&gt; model one by one. I suggest reading this article sequentially and also following the codes in each chapter. You will build an application with the 100% released &lt;code&gt;devise&lt;/code&gt;. If you're totally new to &lt;code&gt;devise&lt;/code&gt; or even Rails, you can take a look at my other article to setup a simplest environment and play around with it first. &lt;/p&gt;
&lt;div class="ltag__link"&gt;
  &lt;a href="/kevinluo201" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F283252%2F07012131-5bb5-4aff-b73c-0dbd85e2439f.png" alt="kevinluo201"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="/kevinluo201/how-to-setup-very-basic-devise-in-rails-7-55ia" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Setup very basic authentication with Devise in Rails 7&lt;/h2&gt;
      &lt;h3&gt;Kevin Luo ・ May 15 '23&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#rails&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#authentication&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#ruby&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#tutorial&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;
.
&lt;h2&gt;
  
  
  Devise Modules
&lt;/h2&gt;

&lt;p&gt;Let's have a brief view of all 10 modules.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
Database Authenticatable: storing user's password as hash digest and allowing users to sign in&lt;/li&gt;
&lt;li&gt;
Registerable: allowing new users to register&lt;/li&gt;
&lt;li&gt;
Confirmable: new users need to click the confirmation link in a confirmation email to activate their accounts.&lt;/li&gt;
&lt;li&gt;
Validatable: check the username and password validity&lt;/li&gt;
&lt;li&gt;
Recoverable: "Forgot Password?" feature&lt;/li&gt;
&lt;li&gt;
Rememberable: the user session will be remembered so the users don't need to log in again after restarting the browser&lt;/li&gt;
&lt;li&gt;
Timeoutable: the user session will expire after a period of time&lt;/li&gt;
&lt;li&gt;
Lockable: lock/unlock users&lt;/li&gt;
&lt;li&gt;
Trackable: track how users use the system, such as how many times a user has signed in&lt;/li&gt;
&lt;li&gt;
Omniauthable: add Omniauth support&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  Preparation
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Environment
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Ruby 3.1.2p20&lt;/li&gt;
&lt;li&gt;Rails 7.0.5&lt;/li&gt;
&lt;li&gt;devise 4.9.2&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Installation
&lt;/h3&gt;

&lt;p&gt;Create a new rails project and install &lt;code&gt;devise&lt;/code&gt;. Then execute the command below to initialize &lt;code&gt;devise&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

rails generate devise:install


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

&lt;/div&gt;
&lt;h3&gt;
  
  
  Add Mailer's default_url_options
&lt;/h3&gt;

&lt;p&gt;Many features will send mails out and this is a required configuration.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;

&lt;span class="c1"&gt;# config/environments/development.rb&lt;/span&gt;
&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;action_mailer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;default_url_options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;host: &lt;/span&gt;&lt;span class="s1"&gt;'localhost'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;port: &lt;/span&gt;&lt;span class="mi"&gt;3000&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;
&lt;h3&gt;
  
  
  Add User model
&lt;/h3&gt;

&lt;p&gt;We will use &lt;code&gt;User&lt;/code&gt; model for authentication and we want to use the &lt;code&gt;email&lt;/code&gt; field as the &lt;strong&gt;identifier&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;To create the table in the database, execute:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

rails g migration create_users email:index


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

&lt;/div&gt;
&lt;p&gt;It will create a migration file in &lt;code&gt;db/migrations&lt;/code&gt;, please open that file and edit it like below to add some extra constraints like &lt;code&gt;NOT NULL&lt;/code&gt; and &lt;code&gt;UNIQUE&lt;/code&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;CreateUsers&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;7.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;:users&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;string&lt;/span&gt; &lt;span class="ss"&gt;:email&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="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="n"&gt;add_index&lt;/span&gt; &lt;span class="ss"&gt;:users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:email&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Run &lt;code&gt;rails db:migrate&lt;/code&gt; to make the changes.&lt;/p&gt;

&lt;p&gt;Add the &lt;code&gt;User&lt;/code&gt; model:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;

&lt;span class="c1"&gt;# app/models/user.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&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="k"&gt;end&lt;/span&gt;


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

&lt;/div&gt;
&lt;h4&gt;
  
  
  Warning
&lt;/h4&gt;

&lt;p&gt;Please be aware that in practice you should use&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

rails generate devise User


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

&lt;/div&gt;
&lt;p&gt;to create &lt;code&gt;User&lt;/code&gt; with a basic devise configuration. However, this article's purpose is to introduce &lt;code&gt;devise&lt;/code&gt;'s modules and enable them progressively.&lt;/p&gt;
&lt;h3&gt;
  
  
  Add a root page
&lt;/h3&gt;

&lt;p&gt;Let's add a root page so it can make testing more smooth.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Add a &lt;code&gt;PagesController&lt;/code&gt; with an &lt;code&gt;index&lt;/code&gt; action
```ruby
&lt;/li&gt;
&lt;/ol&gt;
&lt;h1&gt;
  
  
  app/controllers/pages_controller.rb
&lt;/h1&gt;

&lt;p&gt;class PagesController &amp;lt; ApplicationController&lt;br&gt;
  def index&lt;br&gt;
  end&lt;br&gt;
end&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2. Add the view for `index`
```html


&amp;lt;!-- app/views/pages/index.html.erb --&amp;gt;
&amp;lt;h1&amp;gt;Pages#index&amp;lt;/h1&amp;gt;
&amp;lt;% if user_signed_in? %&amp;gt;
  &amp;lt;%= link_to 'Sign out', destroy_user_session_path, data: { turbo_method: :delete }
&amp;lt;% else %&amp;gt;
  &amp;lt;%= link_to 'Sign in', new_user_session_path %&amp;gt;
&amp;lt;% end %&amp;gt;


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

&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;Modify the &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; of the layout so it can show flash messages
```html
&lt;/li&gt;
&lt;/ol&gt;




&lt;br&gt;
  &lt;p&gt;&amp;lt;%= notice %&amp;gt;&lt;/p&gt;
&lt;br&gt;
  &lt;p&gt;&amp;lt;%= alert %&amp;gt;&lt;/p&gt;

&lt;p&gt;&amp;lt;%= yield %&amp;gt;&lt;br&gt;
&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
### Routes
Modify the routes like below.
```ruby


# config/routes.rb
Rails.application.routes.draw do
  root "pages#index"
  devise_for :users
end


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

&lt;/div&gt;
&lt;p&gt;&lt;code&gt;devise_for :users&lt;/code&gt; will generate route paths for &lt;code&gt;devise&lt;/code&gt; in the scope of &lt;code&gt;users&lt;/code&gt;. It doen't do anything right now because we haven't enabled any module yet. We'll see more examples while introducing each module.&lt;/p&gt;
&lt;h3&gt;
  
  
  Export Devise views
&lt;/h3&gt;

&lt;p&gt;The last step is to export devise views. This is usually for customization.  In fact, we don't have to do this now because we're not going to modify them. It could be helpful to see those pages in code for better understanding.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

rails generate devise:views


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

&lt;/div&gt;
&lt;p&gt;We have done all the prerequisites. At this point, if you start the rails server and go to &lt;code&gt;http://localhost:3000&lt;/code&gt;, it should be a crashed page 😆. There's nothing to concern about. Let's start enabling those modules on &lt;code&gt;User&lt;/code&gt;!&lt;/p&gt;
&lt;h2&gt;
  
  
  Database Authenticatable &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;:database_authenticatable&lt;/code&gt; module provides 2 main functionalities to the &lt;code&gt;User&lt;/code&gt; model:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Store the users' passwords in a &lt;strong&gt;hash digest&lt;/strong&gt; format in the column, &lt;code&gt;encrypted_password&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Verify whether a user's input matches the password digest stored in the database.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It should be a &lt;strong&gt;required&lt;/strong&gt; module. To be honest, I don't know what will happen if you don't include this module but others. Why do you want to do that? If you really need to not include this module, you'd better think twice before using &lt;code&gt;devise&lt;/code&gt;. &lt;/p&gt;
&lt;h3&gt;
  
  
  Enable the module
&lt;/h3&gt;

&lt;p&gt;To enable &lt;code&gt;:database_authenticatable&lt;/code&gt;, we need to add one column to the &lt;code&gt;users&lt;/code&gt; table, &lt;code&gt;encrypted_password&lt;/code&gt;. Execute&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

rails g migration add_encrypted_password_to_users encrypted_password


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

&lt;/div&gt;
&lt;p&gt;It will create a new migration file in &lt;code&gt;db/migrations&lt;/code&gt;. Although it's a magic command, it's not magical enough😅 Please open the file and add &lt;code&gt;null: false&lt;/code&gt; for the database &lt;code&gt;NOT NULL&lt;/code&gt; constraint.&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;AddEncryptedPasswordToUsers&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;7.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;add_column&lt;/span&gt; &lt;span class="ss"&gt;:users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:encrypted_password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:string&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="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;


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

&lt;/div&gt;
&lt;p&gt;Run &lt;code&gt;rails db:migrate&lt;/code&gt; to make the changes&lt;/p&gt;

&lt;p&gt;We can enable our first module in &lt;code&gt;User&lt;/code&gt; by using the &lt;code&gt;devise&lt;/code&gt; method:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;

&lt;span class="c1"&gt;# app/models/user.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&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;devise&lt;/span&gt; &lt;span class="ss"&gt;:database_authenticatable&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;


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

&lt;/div&gt;
&lt;h3&gt;
  
  
  Usage
&lt;/h3&gt;
&lt;h4&gt;
  
  
  Headless usage
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;:database_authenticatable&lt;/code&gt; will introduce a method &lt;code&gt;#password=&lt;/code&gt; for setting users password. Open the rails console:&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;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;email: &lt;/span&gt;&lt;span class="s1"&gt;'kevin@taiwan.com'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'123456'&lt;/span&gt;
&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save!&lt;/span&gt;


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

&lt;/div&gt;
&lt;p&gt;If you check &lt;code&gt;user.encrypted_password&lt;/code&gt;, it'll be a gibberish string which is exactly the &lt;strong&gt;hash digest&lt;/strong&gt; of &lt;code&gt;bcrypt&lt;/code&gt; so no one knows the true password except you.&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;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;last&lt;/span&gt;
&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encrypted_password&lt;/span&gt;
&lt;span class="c1"&gt;# "$2a$12$j.tv091dn9OQPV4seF74Z.PIlohxesFxMGuQh0l39hH4mFS5XyDTi"&lt;/span&gt;


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

&lt;/div&gt;
&lt;p&gt;We can use the method &lt;code&gt;#valid_password?&lt;/code&gt; to verify if a password is correct.&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;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;valid_password?&lt;/span&gt; &lt;span class="s1"&gt;'123456'&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; true&lt;/span&gt;
&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;valid_password?&lt;/span&gt; &lt;span class="s1"&gt;'abcde'&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; false&lt;/span&gt;


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

&lt;/div&gt;
&lt;h4&gt;
  
  
  Testing in browsers
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;:database_authenticatable&lt;/code&gt; module will &lt;strong&gt;unlock&lt;/strong&gt; 3 paths for users to log in and log out. (It really feels like playing a game🎮) You can check them by executing &lt;code&gt;rails routes&lt;/code&gt;&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

    new_user_session GET    /users/sign_in(.:format)  devise/sessions#new
        user_session POST   /users/sign_in(.:format)  devise/sessions#create
destroy_user_session DELETE /users/sign_out(.:format) devise/sessions#destroy


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

&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;devise/sessions#new&lt;/code&gt; is for the sign-in page&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;devise/sessions#create&lt;/code&gt; is create the login session&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;devise/sessions#destroy&lt;/code&gt; is for logging out&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;Now, you can start the rails server by &lt;code&gt;rails server&lt;/code&gt; and go to &lt;code&gt;http://localhost:3000/&lt;/code&gt; on a browser. You'll find the page is fixed now and can display successfully.
&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foo5lqjcnkv1f1umvh8nw.png" alt="home page"&gt;
&lt;/li&gt;
&lt;li&gt;Click the &lt;strong&gt;Sign In&lt;/strong&gt; link and you will be led to &lt;code&gt;http://localhost:3000/users/sign_in&lt;/code&gt; and you should see a Login page provided by &lt;code&gt;devise&lt;/code&gt;
&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmvt9mjwtt8iy8bf86arn.png" alt="Sign-in page"&gt;
&lt;/li&gt;
&lt;li&gt;You can then input the user's credentials you just created in the rails console. You should log in successfully and be redirected to the root page.
&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fldmbt971crl2blgv16za.png" alt="signed in"&gt;
&lt;/li&gt;
&lt;li&gt;You can try to click &lt;strong&gt;Sign Out&lt;/strong&gt; and sign out.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  Customization
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;modify settings in &lt;code&gt;config/initializers/devise.rb&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;modify the exported &lt;code&gt;app/views/devise/sessions/new.html.erb&lt;/code&gt; which is for displaying the login page.&lt;/li&gt;
&lt;li&gt;execute &lt;code&gt;rails generate devise:controllers users -c sessions&lt;/code&gt; to export &lt;code&gt;SessionsController&lt;/code&gt;. &lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  Checkpoint - encrypted_password
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;encrypted_password&lt;/code&gt; is a misleading name because it actually uses &lt;code&gt;bcrypt&lt;/code&gt; to calculate the &lt;strong&gt;hash digest&lt;/strong&gt; of the password and stores that digest. I didn't check the git log but I guess it's a historical result. &lt;/p&gt;

&lt;p&gt;Again, it's okay if you don't understand what the previous paragraph means. I don't think a web developer has to know how the hash algorithm is implemented. However, knowing the difference between an encrypted password and a hashed password is more important. A very easy way to understand them is: you can decrypt an encrypted password to the original password but you cannot revert a hashed digest of a password. As a result, we usually think hashing a password is more secure.&lt;/p&gt;
&lt;h2&gt;
  
  
  Registerable &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;:registerable&lt;/code&gt; module is for new users to sign up and manage their data. I think this one is the most confusing one. It sounds like you need to include this module to create users but the fact is that you don't need this module to do that. We just created a new user in the previous chapter, right?&lt;/p&gt;

&lt;p&gt;&lt;code&gt;:registerable&lt;/code&gt; only adds some extra &lt;strong&gt;routing endpoints&lt;/strong&gt; and makes some views show the links to go to the &lt;strong&gt;signup page&lt;/strong&gt;. Even though we &lt;strong&gt;include&lt;/strong&gt; this module in &lt;code&gt;User&lt;/code&gt;, it doesn't add any feature to the model itself. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;:registerable&lt;/code&gt; can save you some time by preventing you from building a similar workflow again.&lt;/p&gt;
&lt;h3&gt;
  
  
  Enable the module
&lt;/h3&gt;

&lt;p&gt;Unlike &lt;code&gt;:database_authenticatable&lt;/code&gt;, we can just enable &lt;code&gt;:registerable&lt;/code&gt; module directly.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;

&lt;span class="c1"&gt;# app/models/user.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&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;devise&lt;/span&gt; &lt;span class="ss"&gt;:database_authenticatable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:registerable&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;


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

&lt;/div&gt;
&lt;h3&gt;
  
  
  Usage
&lt;/h3&gt;

&lt;p&gt;After enabling &lt;code&gt;:registerable&lt;/code&gt;, a series of &lt;code&gt;user_registration&lt;/code&gt; paths will be generated:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

cancel_user_registration GET    /users/cancel(.:format)  devise/registrations#cancel
   new_user_registration GET    /users/sign_up(.:format) devise/registrations#new
  edit_user_registration GET    /users/edit(.:format)    devise/registrations#edit
       user_registration PATCH  /users(.:format)         devise/registrations#update
                         PUT    /users(.:format)         devise/registrations#update
                         DELETE /users(.:format)         devise/registrations#destroy
                         POST   /users(.:format)         devise/registrations#create


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

&lt;/div&gt;
&lt;p&gt;I think showing examples is easier for you to understand what these paths do.&lt;/p&gt;
&lt;h4&gt;
  
  
  Create a user
&lt;/h4&gt;

&lt;p&gt;If you go to &lt;code&gt;http://localhost:3000/users/sign_up&lt;/code&gt;, you can see a Sign-Up page provided by &lt;code&gt;devise&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1f9d028gtmcd4kq3ct51.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1f9d028gtmcd4kq3ct51.png" alt="Sign-up page"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can create a new user if you submit the form with valid input values.&lt;/p&gt;
&lt;h4&gt;
  
  
  Update or Delete a user
&lt;/h4&gt;

&lt;p&gt;If you &lt;strong&gt;logged in&lt;/strong&gt;, you can go to &lt;code&gt;http://localhost:3000/users/edit&lt;/code&gt; to edit your information.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx5errzmh4uj0ldym3rwv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx5errzmh4uj0ldym3rwv.png" alt="Edit user info"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Customization
&lt;/h3&gt;

&lt;p&gt;If you want to customize it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;modify settings in &lt;code&gt;config/initializers/devise.rb&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;modify the exported views of &lt;code&gt;app/views/devise/registration/*.html.erb&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;execute &lt;code&gt;rails generate devise:controllers users -c registrations&lt;/code&gt; to export &lt;code&gt;RegistrationsController&lt;/code&gt;. &lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  Checkpoint - links on pages
&lt;/h3&gt;

&lt;p&gt;If you go to the Log-in page &lt;code&gt;http://localhost:3000/users/sign_in&lt;/code&gt; now, you'll find a &lt;strong&gt;Sign up&lt;/strong&gt; link that wasn't there.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7vbgoula8sxqdnf0dhak.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7vbgoula8sxqdnf0dhak.png" alt="Sign-in page"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It feels like magic. What's happening? This is because the partial, &lt;code&gt;app/views/devise/shared/_links.html.erb&lt;/code&gt; checks which modules are enabled and displays the required links. You can check it out to see the details.&lt;/p&gt;
&lt;h2&gt;
  
  
  Confirmable &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;I think &lt;code&gt;:confirmable&lt;/code&gt; is the second most frequently used module of &lt;code&gt;devise&lt;/code&gt;. It provides a very common behaviour: users need to validate their email addresses by clicking a confirmation &lt;strong&gt;link&lt;/strong&gt; sent with a confirmation mail. It can prevent malicious users from signing up with fake emails.&lt;/p&gt;
&lt;h3&gt;
  
  
  Enable the module
&lt;/h3&gt;

&lt;p&gt;We need to add some required columns on &lt;code&gt;users&lt;/code&gt; table for this module.&lt;br&gt;
Execute&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

rails g migration add_confirmation_to_users confirmation_token:index confirmed_at:datetime confirmation_sent_at:datetime unconfirmed_email


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

&lt;/div&gt;
&lt;p&gt;and it should create a migration file like below&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;AddConfirmationToUsers&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;7.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;add_column&lt;/span&gt; &lt;span class="ss"&gt;:users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:confirmation_token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:string&lt;/span&gt;
    &lt;span class="n"&gt;add_index&lt;/span&gt; &lt;span class="ss"&gt;:users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:confirmation_token&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;add_column&lt;/span&gt; &lt;span class="ss"&gt;:users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:confirmed_at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:datetime&lt;/span&gt;
    &lt;span class="n"&gt;add_column&lt;/span&gt; &lt;span class="ss"&gt;:users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:confirmation_sent_at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:datetime&lt;/span&gt;
    &lt;span class="n"&gt;add_column&lt;/span&gt; &lt;span class="ss"&gt;:users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:unconfirmed_email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:string&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;


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

&lt;/div&gt;
&lt;p&gt;It will add 4 columns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;confirmation_token&lt;/code&gt;: A randomly generated token will be stored in this column and it will be embedded in the confirmation link sent to the user.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;confirmation_sent_at&lt;/code&gt;: it records the time the confirmation mail was sent&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;confirmed_at&lt;/code&gt;: it records the time after a user clicks the confirmation link and is confirmed. &lt;strong&gt;A user has been confirmed if this column is not NULL&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;unconfirmed_email&lt;/code&gt;: If a user wants to update his/her email address, it will store the new email address.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Run &lt;code&gt;rails db:migrate&lt;/code&gt; to make the changes. Then add &lt;code&gt;:confirmable&lt;/code&gt; in &lt;code&gt;User&lt;/code&gt; to enable the module:&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;User&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;devise&lt;/span&gt; &lt;span class="ss"&gt;:database_authenticatable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:registerable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:confirmable&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;


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

&lt;/div&gt;
&lt;h3&gt;
  
  
  Usage
&lt;/h3&gt;
&lt;h4&gt;
  
  
  Headless
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;:confirmable&lt;/code&gt; adds an array of methods. We're going to try some of them. Open the console:&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;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;
&lt;span class="c1"&gt;# check if the user is confirmed&lt;/span&gt;
&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;confirmed?&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; false&lt;/span&gt;


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

&lt;/div&gt;
&lt;p&gt;You can call &lt;code&gt;#send_confirmation_instructions&lt;/code&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;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send_confirmation_instructions&lt;/span&gt;


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

&lt;/div&gt;
&lt;p&gt;A confirmation mail with the confirmation link will be sent to the user's email address:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

Date: Mon, 19 Jun 2023 12:37:09 -0400
From: please-change-me-at-config-initializers-devise@example.com
Reply-To: please-change-me-at-config-initializers-devise@example.com
To: kevin@taiwan.com
Message-ID: &amp;lt;6490843547bc2_f396c1c90348@F2XWD4WR0C.mail&amp;gt;
Subject: Confirmation instructions
Mime-Version: 1.0
Content-Type: text/html;
 charset=UTF-8
Content-Transfer-Encoding: 7bit

&amp;lt;p&amp;gt;Welcome kevin@taiwan.com!&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;You can confirm your account email through the link below:&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;&amp;lt;a href="http://localhost:3000/users/confirmation?confirmation_token=uyMSehFApDiMs_-yjcT7"&amp;gt;Confirm my account&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;


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

&lt;/div&gt;
&lt;p&gt;Check the user's attributes now:&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;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;confirmation_token&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; "uyMSehFApDiMs_-yjcT7"&lt;/span&gt;
&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;confirmation_sent_at&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; Mon, 19 Jun 2023 16:37:09.185202000 UTC +00:00&lt;/span&gt;
&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;confirmed_at&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; nil&lt;/span&gt;
&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unconfirmed_email&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; nil&lt;/span&gt;


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

&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;user.confirmation_token&lt;/code&gt; matches the &lt;code&gt;confirmation_token&lt;/code&gt; embedded in the email's confirmation link.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;user.confirmation_sent_at&lt;/code&gt; was filled&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;user.confirmed_at&lt;/code&gt; is &lt;code&gt;nil&lt;/code&gt; because the user hasn't clicked the confirmation link yet&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;user.unconfirmed_email&lt;/code&gt; is &lt;code&gt;nil&lt;/code&gt; because we're not doing a reconfirmation&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can confirm the user by calling &lt;code&gt;#confirm&lt;/code&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;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;confirm&lt;/span&gt;
&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;confirmed?&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; true&lt;/span&gt;


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

&lt;/div&gt;
&lt;p&gt;If you want to do the reconfirmation for the user, you can save the new email in the &lt;code&gt;unconfirmed_email&lt;/code&gt; first.&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;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;email&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; "kevin@taiwan.com"&lt;/span&gt;
&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update!&lt;/span&gt; &lt;span class="ss"&gt;unconfirmed_email: &lt;/span&gt;&lt;span class="s1"&gt;'kevin@taipei.tw'&lt;/span&gt;
&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pending_reconfirmation?&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; true&lt;/span&gt;
&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send_reconfirmation_instructions&lt;/span&gt;
&lt;span class="c1"&gt;# an email with the reconfirmation link will be sent&lt;/span&gt;
&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;confirm&lt;/span&gt;
&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;email&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; 'kevin@taipei.tw'&lt;/span&gt;
&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unconfirmed_email&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; nil&lt;/span&gt;


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

&lt;/div&gt;
&lt;h4&gt;
  
  
  Use in browsers
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;You will not be able to log in as an unconfirmed user. You'll be rejected with a message saying you need to confirm the email first.
&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1elfa6zi2k4wwvfg954g.png" alt="Sign-in page"&gt;
&lt;/li&gt;
&lt;li&gt;If the user wants the system to send the confirmation mail again, they can click &lt;strong&gt;"Didn't receive confirmation instruction?"&lt;/strong&gt; which will lead them to &lt;code&gt;http://localhost:3000/users/confirmation/new&lt;/code&gt; to resend the mail.
&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbpkvfrsizqby8adoxxhl.png" alt="Resend confirmation mail"&gt;
&lt;/li&gt;
&lt;li&gt;You can log in as normal after you click the confirmation link in the mail &lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;
  
  
  Customization
&lt;/h4&gt;

&lt;p&gt;You can customize &lt;code&gt;:confirmable&lt;/code&gt; by&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;modify settings in &lt;code&gt;config/initializers/devise.rb&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;modify the mail template &lt;code&gt;app/views/devise/mailer/confirmation_instructions.html.erb&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;modify the resend mail page, &lt;code&gt;app/views/devise/confirmations/new.html.erb&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;run &lt;code&gt;rails generate devise:controllers users -c confirmations&lt;/code&gt; to export &lt;code&gt;ConfirmationsController&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  Validatable &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;:validatable&lt;/code&gt; module provides validations for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;email&lt;/code&gt;: check if the input email complies with the email's regex&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;password&lt;/code&gt;: check the length of the input password is 6~128&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's it. It's a very simple module.&lt;/p&gt;
&lt;h3&gt;
  
  
  Enable the module
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;:validatable&lt;/code&gt;  adds model validations for &lt;code&gt;email&lt;/code&gt; and &lt;code&gt;password&lt;/code&gt; so &lt;code&gt;users&lt;/code&gt; needs to have both &lt;strong&gt;methods&lt;/strong&gt;. We've already done that when enabling &lt;code&gt;:database_authenticatable&lt;/code&gt; so we can just enable the module in &lt;code&gt;User&lt;/code&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;User&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;devise&lt;/span&gt; &lt;span class="ss"&gt;:database_authenticatable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:registerable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:confirmable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:validatable&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;


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

&lt;/div&gt;
&lt;h3&gt;
  
  
  Usage
&lt;/h3&gt;

&lt;p&gt;This is a very straightforward module. Please check the example below:&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;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="ss"&gt;email: &lt;/span&gt;&lt;span class="s1"&gt;'test-user'&lt;/span&gt;
&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'1234'&lt;/span&gt;
&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; false&lt;/span&gt;
&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; 2&lt;/span&gt;
&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;full_message&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;# Email is invalid&lt;/span&gt;
&lt;span class="c1"&gt;# Password is too short (minimum is 6 characters)&lt;/span&gt;


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

&lt;/div&gt;
&lt;h3&gt;
  
  
  Customization
&lt;/h3&gt;

&lt;p&gt;Please forgive me but I personally think it's &lt;strong&gt;meaningless&lt;/strong&gt; to customize this module 😆. Adding validations is way too easy in Rails. For example, the code below can do exactly the same thing as &lt;code&gt;:validatable&lt;/code&gt; does.&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;validates&lt;/span&gt; &lt;span class="ss"&gt;:email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;format: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;with: &lt;/span&gt;&lt;span class="no"&gt;URI&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;MailTo&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;EMAIL_REGEXP&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;validates&lt;/span&gt; &lt;span class="ss"&gt;:password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;length: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;in: &lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;128&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;
&lt;p&gt;If you want to customize the &lt;code&gt;:validatable&lt;/code&gt;, I suggest you just stop using it. Use Rails validation instead and customize it by yourself.&lt;/p&gt;
&lt;h2&gt;
  
  
  Recoverable &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;:recoverable&lt;/code&gt; is another module that I use frequently in different projects. In a nutshell, it's the &lt;strong&gt;"Forget Password?"&lt;/strong&gt; feature. Users can reset their passwords via a reset link sent to their email addresses.&lt;/p&gt;
&lt;h3&gt;
  
  
  Enable the module
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;:recoverable&lt;/code&gt; needs some required columns. Execute&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

rails g migration add_recoverable_to_users reset_password_token:index reset_password_sent_at:datetime


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

&lt;/div&gt;
&lt;p&gt;to create a migration file:&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;AddRecoverableToUsers&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;7.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;add_column&lt;/span&gt; &lt;span class="ss"&gt;:users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:reset_password_token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:string&lt;/span&gt;
    &lt;span class="n"&gt;add_index&lt;/span&gt; &lt;span class="ss"&gt;:users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:reset_password_token&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;add_column&lt;/span&gt; &lt;span class="ss"&gt;:users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:reset_password_sent_at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:datetime&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;


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

&lt;/div&gt;
&lt;p&gt;It's pretty similar with &lt;code&gt;:confirmable&lt;/code&gt;. When a user wants to reset a password, a randomly generated token will be stored in &lt;code&gt;reset_password_token&lt;/code&gt; and it will be embedded in the link in the reset password mail sent to the user's email address. &lt;code&gt;reset_password_sent_at&lt;/code&gt; will record the time the mail is sent.&lt;/p&gt;

&lt;p&gt;We can enable &lt;code&gt;:recoverable&lt;/code&gt; now:&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;User&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;devise&lt;/span&gt; &lt;span class="ss"&gt;:database_authenticatable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:registerable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:confirmable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:validatable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
         &lt;span class="ss"&gt;:recoverable&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;


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

&lt;/div&gt;
&lt;h3&gt;
  
  
  Usage
&lt;/h3&gt;
&lt;h4&gt;
  
  
  Headless
&lt;/h4&gt;

&lt;p&gt;You can call &lt;code&gt;#send_reset_password_instructions&lt;/code&gt; to create a &lt;code&gt;reset_password_token&lt;/code&gt; and it will be embedded in the reset password mail.&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;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send_reset_password_instructions&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; 's5ZkHwPHQGAc7DSGCpR_'&lt;/span&gt;


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

&lt;/div&gt;
&lt;p&gt;Below is a sample of reset password mail. You can find the &lt;code&gt;reset_password_token&lt;/code&gt; embedded in the link.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

Date: Mon, 19 Jun 2023 21:21:18 -0400
From: please-change-me-at-config-initializers-devise@example.com
Reply-To: please-change-me-at-config-initializers-devise@example.com
To: kevin@taipei.tw
Message-ID: &amp;lt;6490ff0e7179_df83c1c193c8@F2XWD4WR0C.mail&amp;gt;
Subject: Reset password instructions
Mime-Version: 1.0
Content-Type: text/html;
 charset=UTF-8
Content-Transfer-Encoding: 7bit

&amp;lt;p&amp;gt;Hello kevin@taipei.tw!&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;Someone has requested a link to change your password. You can do this through the link below.&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;&amp;lt;a href="http://localhost:3000/users/password/edit?reset_password_token=s5ZkHwPHQGAc7DSGCpR_"&amp;gt;Change my password&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;If you didn't request this, please ignore this email.&amp;lt;/p&amp;gt;
&amp;lt;p&amp;gt;Your password won't change until you access the link above and create a new one.&amp;lt;/p&amp;gt;


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

&lt;/div&gt;
&lt;h4&gt;
  
  
  Use in browsers
&lt;/h4&gt;

&lt;p&gt;After enabling &lt;code&gt;:recoverable&lt;/code&gt;, you'll get new series of routes endpoints for &lt;code&gt;/passwords&lt;/code&gt;&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

new_user_password  GET    /users/password/new(.:format)  devise/passwords#new
edit_user_password GET    /users/password/edit(.:format) devise/passwords#edit
     user_password PATCH  /users/password(.:format)      devise/passwords#update
                   PUT    /users/password(.:format)      devise/passwords#update
                   POST   /users/password(.:format)      devise/passwords#create


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

&lt;/div&gt;
&lt;p&gt;We can open a browser to check what these paths do. &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;code&gt;http://localhost:3000/users/sign_in&lt;/code&gt;, and you'll find a new link shows up, &lt;strong&gt;Forgot your password?&lt;/strong&gt;
&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7m2fowrwdlevb9p1kk2u.png" alt="Sign-in page"&gt;
&lt;/li&gt;
&lt;li&gt;Click that link and you'll be led to &lt;code&gt;http://localhost:3000/users/password/new&lt;/code&gt;
&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzu8dgigmt8vy9lixqh5o.png" alt="Forgot password"&gt;
&lt;/li&gt;
&lt;li&gt;If you input a valid email of the users you created in the system and submit the form. A &lt;code&gt;POST&lt;/code&gt; request to &lt;code&gt;http://localhost:3000/users/password&lt;/code&gt; will be made. Then a &lt;strong&gt;reset password mail&lt;/strong&gt; as described in the previous section will be sent out. You can find that mail in the terminal running &lt;code&gt;rails server&lt;/code&gt;. If you have a gem like &lt;a href="https://github.com/ryanb/letter_opener" rel="noopener noreferrer"&gt;letter_opener&lt;/a&gt;, you can see that mail shown in your browser. &lt;/li&gt;
&lt;li&gt;Click or copy-paste the reset password link to open the reset password page &lt;code&gt;http://localhost:3000/users/password/edit?reset_password_token=xxxxxxxxxx&lt;/code&gt;
&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmz0b8zv85mi6ykyhbdqx.png" alt="Reset Password"&gt;
&lt;/li&gt;
&lt;li&gt;Input a new password and submit the form. A &lt;code&gt;PATCH/PUT&lt;/code&gt; request will be made toward &lt;code&gt;http://localhost:3000/users/password&lt;/code&gt;. It will then check the &lt;code&gt;reset_password_token&lt;/code&gt; and then update the user's password.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  Customization
&lt;/h3&gt;

&lt;p&gt;To customize the pages and workflow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;modify settings in &lt;code&gt;config/initializers/devise.rb&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Modify the views in &lt;code&gt;app/views/devise/passwords/*.html.erb&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Modify &lt;code&gt;app/views/devise/mailer/reset_password_instructions.html.erb&lt;/code&gt; for the reset password mail&lt;/li&gt;
&lt;li&gt;Execute &lt;code&gt;rails generate devise:controllers users -c passwords&lt;/code&gt; to export &lt;code&gt;PasswordsController&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  Checkpoint - reset password token
&lt;/h3&gt;

&lt;p&gt;If you're a scrupulous person, you might find one interesting thing. I mentioned above that the token will be stored in &lt;code&gt;reset_password_token&lt;/code&gt; and will be embedded in the reset password link. However, if you really check their values, you'll find they're different. Take the example we used in this section, &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the token embedded in the reset password link is &lt;code&gt;s5ZkHwPHQGAc7DSGCpR_&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;but if you check &lt;code&gt;user.reset_password_token&lt;/code&gt;, it'll be &lt;code&gt;aab3acb2a7dc13e835394c85a14c37417bbd5837692ee67853961bbc6863ee01&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Why? How can &lt;code&gt;devise&lt;/code&gt; verify the token if they are totally different? &lt;/p&gt;

&lt;p&gt;The answer is that the value stored in &lt;code&gt;reset_password_token&lt;/code&gt; is also a &lt;strong&gt;hash digest&lt;/strong&gt;, like how &lt;code&gt;devise&lt;/code&gt; stores passwords. Furthermore, it will use &lt;strong&gt;HMAC-SHA256&lt;/strong&gt; hash algorithm to calculate the hash digest. You can run the commands below to check the result.&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;salt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Devise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;token_generator&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="ss"&gt;:key_for&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:reset_password_token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="no"&gt;OpenSSL&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;HMAC&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hexdigest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'SHA256'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;salt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'s5ZkHwPHQGAc7DSGCpR_'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; "aab3acb2a7dc13e835394c85a14c37417bbd5837692ee67853961bbc6863ee01"&lt;/span&gt;


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

&lt;/div&gt;
&lt;p&gt;Why does &lt;code&gt;:database_authenticatable&lt;/code&gt; use &lt;strong&gt;bcrypt&lt;/strong&gt; and &lt;code&gt;:recoverable&lt;/code&gt; use &lt;strong&gt;HMAC-SHA256&lt;/strong&gt;? The usage is pretty similar. It may be because of the speed, &lt;code&gt;bcrypt&lt;/code&gt; is the most secure way to do hashing. &lt;/p&gt;

&lt;p&gt;To be frank, the true answer is I don't know. I don't think speed is that critical for resetting passwords. I guess this inconsistency may be common in an open-source project. Different groups of volunteers chose different approaches.&lt;/p&gt;
&lt;h2&gt;
  
  
  Rememberable &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;If you've logged in to the system, try restarting your browser. You'll be logged out. It's inconvenient. To solve this issue, &lt;code&gt;:rememberable&lt;/code&gt; is introduced. This module provides the ability to keep the user session even if the browser is closed.&lt;/p&gt;

&lt;p&gt;How does it do that? &lt;strong&gt;Cookies&lt;/strong&gt;! Generally speaking, &lt;code&gt;:rememberable&lt;/code&gt; records the current user's id in a cookie field when a user logs into the system. For instance, if a user with id &lt;code&gt;10&lt;/code&gt; is logging in, it will record &lt;code&gt;user_id =&amp;gt; '10'&lt;/code&gt; in the cookie. When the system detects a cookie with &lt;code&gt;user_id =&amp;gt; '10'&lt;/code&gt;, it will assume that the user with id &lt;code&gt;10&lt;/code&gt; is logged in. How long can it last? It depends on the cookie's &lt;strong&gt;expiration time&lt;/strong&gt;. If the cookie expires after 1 week, then the user's session can be kept for 1 week.&lt;/p&gt;

&lt;p&gt;Of course, we don't use a cookie just like that because anyone can modify a cookie's value. You don't want a user to change that &lt;code&gt;user_id&lt;/code&gt; to an admin's id and suddenly become an admin. Don't worry. &lt;code&gt;:rememberable&lt;/code&gt; also consider how to make it &lt;strong&gt;tamperproof&lt;/strong&gt; and &lt;strong&gt;verify&lt;/strong&gt; the cookie's value.&lt;/p&gt;
&lt;h3&gt;
  
  
  Enable the module
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;:recoverable&lt;/code&gt; requires one more column. Execute&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

rails g migration add_rememberable_to_users remember_created_at:datetime


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

&lt;/div&gt;
&lt;p&gt;to create a migration file:&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;AddRememberableToUsers&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;7.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;add_column&lt;/span&gt; &lt;span class="ss"&gt;:users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:remember_created_at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:datetime&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;


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

&lt;/div&gt;
&lt;p&gt;&lt;code&gt;remember_created_at&lt;/code&gt; is used for storing the time a user logging with the "&lt;strong&gt;Remember me&lt;/strong&gt;" checkbox checked.&lt;/p&gt;

&lt;p&gt;We can enable &lt;code&gt;:rememberable&lt;/code&gt; now:&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;User&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;devise&lt;/span&gt; &lt;span class="ss"&gt;:database_authenticatable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:registerable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:confirmable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:validatable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
         &lt;span class="ss"&gt;:recoverable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:rememberable&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;


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

&lt;/div&gt;
&lt;h3&gt;
  
  
  Usage
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;:rememberable&lt;/code&gt; utilizes cookies to realize its functionality. Although it does add many methods for the &lt;code&gt;User&lt;/code&gt; model, it doesn't make sense if we only call those methods in the rails console. Therefore, I only demonstrate how it works in browsers in this article. &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;code&gt;http://localhost:3000/users/sign_in&lt;/code&gt;, you'll find there's a new &lt;strong&gt;Remember me&lt;/strong&gt; checkbox
&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxy110dqb3sveukzjtlbh.png" alt="sign-in page"&gt;
&lt;/li&gt;
&lt;li&gt;Logging in with the checkbox checked, a parameter &lt;code&gt;rememeber_me&lt;/code&gt; will be sent with the form and that will trigger &lt;code&gt;:rememberable&lt;/code&gt; mechanism to remember your session in the cookie.
```ruby
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;"remember_me"=&amp;gt;"1"&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;3. After logging in, open your browser's devtools (I'm using Chrome) and you can find there a new key, `remember_user_token`, is added to the cookie. It should expire in 2 weeks because that's the default setting. The cookie value seems gibberish. It's actually a **signed message** and it's the **key** of the whole `:rememberable` functionality. It records the currently logged-in user's id and other helpful information. The rails system can take advantage of that information to **remember** the user's session.
  ![Cookie](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p225dhjvmb5upl3ubc1h.png)
4. With this key-value pair, you can try close and start your browser again. You'll find your session is kept. Nice! 🎉

### Customization
You can change the configuration of `:rememberable` in the initializer, for example, changing the longevity of the cookie to another time. The default is 2 weeks and you can change it to any length you want.
```ruby


# config/initializers/devise.rb
config.remember_for = 1.weeks


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

&lt;/div&gt;
&lt;p&gt;Even though there is some stuff &lt;code&gt;devise&lt;/code&gt; allows us to customize &lt;code&gt;:rememberable&lt;/code&gt;, I don't think it's meaningful to do that. The whole logic of &lt;code&gt;:rememberable&lt;/code&gt; is encapsulated well and I can't see the need to change them. &lt;/p&gt;
&lt;h3&gt;
  
  
  Checkpoint1 - Signed message
&lt;/h3&gt;

&lt;p&gt;For a curious mind like you, I think you must want to know what kind of message is really stored in &lt;code&gt;remember_user_token&lt;/code&gt;. First, we copy-paste the gibberish cookie value from our previous example:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

eyJfcmFpbHMiOnsibWVzc2FnZSI6Ilcxc3hYU3dpSkRKaEpERXlKRVpWT0VwWVRFOWxPVXh6TG5KRU1qbFBkME5HVFdVaUxDSXhOamczTXpVMU56VXlMakEzTXpBM05TSmQiLCJleHAiOiIyMDIzLTA3LTA1VDEzOjU1OjUyLjA3M1oiLCJwdXIiOiJjb29raWUucmVtZW1iZXJfdXNlcl90b2tlbiJ9fQ==--1c344cce076954bc63f16c7ae6213be0d39ceb6d


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

&lt;/div&gt;
&lt;p&gt;Let's demystify it. This message can be split into 2 parts, &lt;strong&gt;the true message&lt;/strong&gt; and &lt;strong&gt;the signature&lt;/strong&gt;, and they're separated by &lt;code&gt;--&lt;/code&gt; in the string. The string before &lt;code&gt;--&lt;/code&gt; is the message itself and the string after &lt;code&gt;--&lt;/code&gt; is the signature. You can try to stop the program by &lt;code&gt;binding.break&lt;/code&gt; (this is a functionality from &lt;code&gt;debug&lt;/code&gt; gem)&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;def&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;
  &lt;span class="nb"&gt;binding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;break&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;


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

&lt;/div&gt;
&lt;p&gt;The message is not encrypted, it's just encoded by base64 &lt;strong&gt;twice&lt;/strong&gt; so we can decode it easily.&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;cookie_value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cookies&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'remember_user_token'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="c1"&gt;#=&amp;gt; eyJfcmFpbHMiOnsibWVzc2FnZSI6Ilcxc3hYU3dpSkRKaEpERXlKRVpWT0VwWVRFOWxPVXh6TG5KRU1qbFBkME5HVFdVaUxDSXhOamczTXpVMU56VXlMakEzTXpBM05TSmQiLCJleHAiOiIyMDIzLTA3LTA1VDEzOjU1OjUyLjA3M1oiLCJwdXIiOiJjb29raWUucmVtZW1iZXJfdXNlcl90b2tlbiJ9fQ==--1c344cce076954bc63f16c7ae6213be0d39ceb6d&lt;/span&gt;
&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;signature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cookie_value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'--'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;rememberable_msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; "{\"_rails\":{\"message\":\"W1sxXSwiJDJhJDEyJEZVOEpYTE9lOUxzLnJEMjlPd0NGTWUiLCIxNjg3MzU1NzUyLjA3MzA3NSJd\",\"exp\":\"2023-07-05T13:55:52.073Z\",\"pur\":\"cookie.remember_user_token\"}}"&lt;/span&gt;
&lt;span class="no"&gt;Base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode64&lt;/span&gt;&lt;span class="p"&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;rememberable_msg&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="s1"&gt;'_rails'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;'message'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; "[[1],\"$2a$12$FU8JXLOe9Ls.rD29OwCFMe\",\"1687355752.073075\"]"&lt;/span&gt;


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

&lt;/div&gt;
&lt;p&gt;The result &lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;

&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="s2"&gt;"$2a$12$FU8JXLOe9Ls.rD29OwCFMe"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"1687355752.073075"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;


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

&lt;/div&gt;
&lt;p&gt;is exact the&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;

&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;salt_of_password_hash_digest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;


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

&lt;/div&gt;
&lt;p&gt;My user's id was &lt;code&gt;1&lt;/code&gt; so it recorded &lt;code&gt;1&lt;/code&gt; in the first element of the message.&lt;/p&gt;

&lt;p&gt;In fact, this signed cookie message is a functionality provided by Rails. You can get the value directly by &lt;code&gt;cookies.signed[]&lt;/code&gt;. It parses the message correctly and also verifies the signature.&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;cookies&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;signed&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'remember_user_token'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; [[1], "$2a$12$FU8JXLOe9Ls.rD29OwCFMe", "1687355752.073075"]&lt;/span&gt;


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

&lt;/div&gt;
&lt;p&gt;The signature is generated based on the message's content. If anyone tries to change the message but doesn't know how to create the corresponding signature, the verification will be failed and it will return &lt;code&gt;nil&lt;/code&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;cookies&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'remember_user_token'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cookies&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'remember_user_token'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;..-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;cookies&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;signed&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'remember_user_token'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; nil&lt;/span&gt;


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

&lt;/div&gt;
&lt;h3&gt;
  
  
  Checkpoint2 - SessionCookie
&lt;/h3&gt;

&lt;p&gt;Did you notice there's also another cookie value, &lt;code&gt;_devise_modules_session&lt;/code&gt;? That's used for storing the &lt;strong&gt;session data&lt;/strong&gt;. It feels strange. Why do we need another cookie value to remember the session? Isn't the session already kept by that cookie value?&lt;/p&gt;

&lt;p&gt;You can check the &lt;strong&gt;Expires&lt;/strong&gt; column and it says &lt;code&gt;_devise_modules_session&lt;/code&gt; is a &lt;strong&gt;Session Cookie&lt;/strong&gt;. A session cookie will be destroyed after you close the browser.&lt;br&gt;
&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#session_cookie" rel="noopener noreferrer"&gt;https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#session_cookie&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's designed to be like this so developers can choose to enable &lt;code&gt;:rememeberable&lt;/code&gt; depending on the requirements.&lt;/p&gt;
&lt;h2&gt;
  
  
  Timeoutable &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Users can keep their session forever if they don't close their browsers. That's a problem for some systems. &lt;code&gt;:timeoutable&lt;/code&gt; will revoke a user's session if the user doesn't do anything for quite a while. For example, if a user doesn't do anything in the application for 30 minutes, he'll be signed out and redirected to the sign-in page if it's needed.&lt;/p&gt;

&lt;p&gt;I felt difficult to imagine how to realize this feature. Surprisingly, it turned out to be simple to do. For every request, &lt;code&gt;devise&lt;/code&gt;  records the current time in cookies. Therefore, when Rails gets a new request, it can compare the last request's time and the current time to see if the difference is over the threshold. If it's over, then just revoke the user's session.&lt;/p&gt;
&lt;h3&gt;
  
  
  Enable the module
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;:timeoutable&lt;/code&gt;  doesn't need any database migration either because it only uses cookies to realize the feature. Thus, we can enable it directly.&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;User&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;devise&lt;/span&gt; &lt;span class="ss"&gt;:database_authenticatable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:registerable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:confirmable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:validatable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
         &lt;span class="ss"&gt;:recoverable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:rememberable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:timeoutable&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;


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

&lt;/div&gt;
&lt;p&gt;There aren't many things you can customize for this module. The only one may be the &lt;strong&gt;timeout time&lt;/strong&gt;. The default timeout time is 30 minutes. You can change it in the initializer. Later for testing, I'm going to change it to 1 minute.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;

&lt;span class="c1"&gt;# config/initializers/devise.rb&lt;/span&gt;
&lt;span class="c1"&gt;# config.timeout_in = 30.minutes&lt;/span&gt;
&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;timeout_in&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;minute&lt;/span&gt;


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

&lt;/div&gt;
&lt;h3&gt;
  
  
  Usage
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Log into the system in a browser. &lt;/li&gt;
&lt;li&gt;You can print &lt;code&gt;session&lt;/code&gt; and you should see there's a new key called &lt;code&gt;warden.user.user.session&lt;/code&gt;  with a nested JSON  &lt;code&gt;last_request_at&lt;/code&gt; recording a timestamp of the request's time.
```ruby
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;puts session.to_json&lt;br&gt;
{&lt;br&gt;
  # ignore other keys&lt;br&gt;
  "warden.user.user.session": {&lt;br&gt;
    "last_request_at": 1687446153&lt;br&gt;
  }&lt;br&gt;
}&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;3. Wait for 1 minute and refresh the page, you will be redirected to the root page.

In my opinion, this module conflicts with `:rememberable`, for example, even if the "remember me time" is 2 weeks long, if the "timeout time" is 30 minutes, you'll lose your session after 30 minutes. The only benefit it has when you enable both modules is that **you can keep the session if you restart your browser within the timeout time** but it's not practical 😅.

## Lockable &amp;lt;a name="Lockable"&amp;gt;&amp;lt;/a&amp;gt;
`:lockable` implements methods for you to lock and unlock users. A locked user cannot log into the system. There are some reasons for you to want to lock users:
- You can lock a user if he/she enters the wrong password several times. It could be a malicious user trying to hack the account
- You want to lock a user because that person already left your organization
- ~~based on your mood~~

### Lockable strategies
`:lockable` has already designed some strategies. You can choose under what kind of situation should you lock or unlock a user. You can configure them in the `config/initializers/devise.rb`

#### Locking strategies
1. `:failed_attempts`: a user will be locked if the wrong password was entered several times
2. `:none`: you have to lock the user manually
The default is `:failed_attempts`

#### Unlocking strategies
1. `:email`: Send an unlock link to the user's email
2. `:time`: A locked user will be unlocked automatically after a certain amount of time
3. `:both`: Enables both strategies
4. `:none`: You have to unlock a user manually
The default is `:both`

### Eanble the module
`:lockable` requires some more columns. Execute
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;rails g migration add_lockable_to_users failed_attempts:integer unlock_token locked_at:datetime&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;to create a migration file:
```ruby


class AddLockableToUsers &amp;lt; ActiveRecord::Migration[7.0]
  def change
    add_column :users, :failed_attempts, :integer, default: 0
    add_column :users, :unlock_token, :string
    add_column :users, :locked_at, :datetime
  end
end


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

&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;locked_at&lt;/code&gt; stores the time a user is locked. A user isn't locked when it's NULL.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;failed_attempts&lt;/code&gt; default value is 0, it will increase 1 whenever a user try to log in with wrong password&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;unlock_token&lt;/code&gt; is a token embedded in the unlock link sent to the user if you allow users to unlock by themself.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We can enable &lt;code&gt;:lockable&lt;/code&gt; now:&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;User&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;devise&lt;/span&gt; &lt;span class="ss"&gt;:database_authenticatable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:registerable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:confirmable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:validatable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
         &lt;span class="ss"&gt;:recoverable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:rememberable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:lockable&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;


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

&lt;/div&gt;
&lt;h3&gt;
  
  
  Usage
&lt;/h3&gt;
&lt;h4&gt;
  
  
  Headless
&lt;/h4&gt;

&lt;p&gt;We can check how to lock a user manually in the rails console.&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;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;
&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lock_access!&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; 'ZxmxUFVX579zNgpBJftN' This is the unlock_token&lt;/span&gt;
&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;access_locked?&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; true&lt;/span&gt;


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

&lt;/div&gt;
&lt;p&gt;When you do that, an unlock mail with a unlock link will be sent to the user's email&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

Date: Thu, 22 Jun 2023 13:14:42 -0400
From: please-change-me-at-config-initializers-devise@example.com
Reply-To: please-change-me-at-config-initializers-devise@example.com
To: kevin@taipei.tw
Message-ID: &amp;lt;64948182289b0_eb89c1c317fd@F2XWD4WR0C.mail&amp;gt;
Subject: Unlock instructions
Mime-Version: 1.0
Content-Type: text/html;
 charset=UTF-8
Content-Transfer-Encoding: 7bit

&amp;lt;p&amp;gt;Hello kevin@taipei.tw!&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;Your account has been locked due to an excessive number of unsuccessful sign in attempts.&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;Click the link below to unlock your account:&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;&amp;lt;a href="http://localhost:3000/users/unlock?unlock_token=ZxmxUFVX579zNgpBJftN"&amp;gt;Unlock my account&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;


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

&lt;/div&gt;
&lt;p&gt;&lt;code&gt;unlock_token&lt;/code&gt; will be embedded in the link. By the way, just like &lt;code&gt;:recoverable&lt;/code&gt;, the token in the link is the real token and &lt;code&gt;user.unlock_token&lt;/code&gt; stores its HMAC digest.&lt;/p&gt;

&lt;p&gt;You can unlock the user by&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;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unlock_access!&lt;/span&gt;
&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;access_locked?&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; true&lt;/span&gt;
&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;locked_at&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; nil&lt;/span&gt;
&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unlock_token&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; nil&lt;/span&gt;


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

&lt;/div&gt;
&lt;h4&gt;
  
  
  Use in browsers
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;:lockable&lt;/code&gt; will add 3 more paths:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

new_user_unlock GET    /users/unlock/new(.:format) devise/unlocks#new
user_unlock     GET    /users/unlock(.:format)     devise/unlocks#show
                POST   /users/unlock(.:format)     devise/unlocks#create


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

&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;devise/unlocks#show&lt;/code&gt; is for the unlock link&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;devise/unlocks#new&lt;/code&gt; and &lt;code&gt;devise/unlock#create&lt;/code&gt; are for asking for resending the unlock mail&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We can try entering the password several times to see how this really works. Before the test, let's modify the threshold to 3 times so we don't need to do that too many times.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;

&lt;span class="c1"&gt;# config/initializers/devise.rb&lt;/span&gt;
&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;maximum_attempts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;


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

&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;Go to &lt;code&gt;http://localhost:3000/users/sign_in&lt;/code&gt; and try to log in with the wrong password for 3 times&lt;/li&gt;
&lt;li&gt;Your user account will be locked and you cannot log in even if you enter the correct password.
&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F466rr3itee7pxmxs9hmp.png" alt="locked"&gt;
&lt;/li&gt;
&lt;li&gt;You can find the unlock link in the unlock mail displayed in your rails server's logs. If you can't find it. You can also click the new link, &lt;strong&gt;"Didn't receive unlock instructions?"&lt;/strong&gt;, which leads you to &lt;code&gt;/users/unlock/new&lt;/code&gt; to ask for a new unlock mail.
&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F84b4ug9xxlafdcm15ah2.png" alt="Sign-in page"&gt;
&lt;/li&gt;
&lt;li&gt;Before you unlock your user account, you can go to the rails console and you'll find &lt;code&gt;user.failed_attempts&lt;/code&gt; has increased.
```ruby
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;user.failed_attempts&lt;/p&gt;
&lt;h1&gt;
  
  
  =&amp;gt; 4
&lt;/h1&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;5. After you click the unlock link, your user will be unlocked. All related columns will be reset
```ruby


user.locked_at
# =&amp;gt; nil
user.unlock_token
# =&amp;gt; nil
user.failed_attempts
# =&amp;gt; 0


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

&lt;/div&gt;
&lt;h3&gt;
  
  
  Customization
&lt;/h3&gt;

&lt;p&gt;You can customize &lt;code&gt;:lockable&lt;/code&gt; by&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You can choose lock/unlock strategies and customize other options in &lt;code&gt;config/initializers/devise.rb&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;modifying the mail template &lt;code&gt;app/views/devise/mailer/unlock_instructions.html.erb&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;modify the resend instructions page, &lt;code&gt;app/views/devise/unlocks/new.html.erb&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;run &lt;code&gt;rails generate devise:controllers users -c unlocks&lt;/code&gt; to export &lt;code&gt;UnlocksController&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  Trackable &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;:trackable&lt;/code&gt; provides you the ability to track some information related to the use of &lt;code&gt;devise&lt;/code&gt;. I think it'll be very straightforward if you just see the columns required by this module which are also the information that will be tracked. There are 5 of them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;sign_in_count&lt;/code&gt;: how many times this user has signed in&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;current_sign_in_at&lt;/code&gt;: the time the &lt;strong&gt;current&lt;/strong&gt; session began&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;current_sign_in_ip&lt;/code&gt;: the IP address of the user when the &lt;strong&gt;current&lt;/strong&gt; session created&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;last_sign_in_at&lt;/code&gt;: the time the &lt;strong&gt;last&lt;/strong&gt; session began&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;last_sign_in_ip&lt;/code&gt;: the IP address of the user when the &lt;strong&gt;last&lt;/strong&gt; session created&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can use that information to detect suspicious actions or see statistics for the use of the system.&lt;/p&gt;
&lt;h3&gt;
  
  
  Enable the module
&lt;/h3&gt;

&lt;p&gt;We need to add those columns first for &lt;code&gt;:trackable&lt;/code&gt;. Execute&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

rails g migration add_trackable_to_users sign_in_count:integer current_sign_in_at:datetime current_sign_in_ip last_sign_in_at:datetime last_sign_in_ip


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

&lt;/div&gt;
&lt;p&gt;and&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;AddTrackableToUsers&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;7.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;add_column&lt;/span&gt; &lt;span class="ss"&gt;:users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:sign_in_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:integer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;default: &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="n"&gt;add_column&lt;/span&gt; &lt;span class="ss"&gt;:users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:current_sign_in_at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:datetime&lt;/span&gt;
    &lt;span class="n"&gt;add_column&lt;/span&gt; &lt;span class="ss"&gt;:users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:current_sign_in_ip&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:string&lt;/span&gt;
    &lt;span class="n"&gt;add_column&lt;/span&gt; &lt;span class="ss"&gt;:users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:last_sign_in_at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:datetime&lt;/span&gt;
    &lt;span class="n"&gt;add_column&lt;/span&gt; &lt;span class="ss"&gt;:users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:last_sign_in_ip&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:string&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;


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

&lt;/div&gt;
&lt;p&gt;After &lt;code&gt;db:migrate&lt;/code&gt;, we can enable &lt;code&gt;:trackable&lt;/code&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;User&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;devise&lt;/span&gt; &lt;span class="ss"&gt;:database_authenticatable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:registerable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:confirmable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:validatable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
         &lt;span class="ss"&gt;:recoverable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:rememberable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:timeoutable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:lockable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:trackable&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;


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

&lt;/div&gt;
&lt;h3&gt;
  
  
  Usage
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Before opening your browser, open the rails console and check the current information
```ruby
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;user.sign_in_count&lt;/p&gt;
&lt;h1&gt;
  
  
  =&amp;gt; 0
&lt;/h1&gt;

&lt;p&gt;user.current_sign_in_at&lt;/p&gt;
&lt;h1&gt;
  
  
  =&amp;gt; nil
&lt;/h1&gt;

&lt;p&gt;user.current_sign_in_ip&lt;/p&gt;
&lt;h1&gt;
  
  
  =&amp;gt; nil
&lt;/h1&gt;

&lt;p&gt;user.last_sign_in_at&lt;/p&gt;
&lt;h1&gt;
  
  
  =&amp;gt; nil
&lt;/h1&gt;

&lt;p&gt;user.last_sign_in_ip&lt;/p&gt;
&lt;h1&gt;
  
  
  =&amp;gt; nil
&lt;/h1&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2. Open your browser and sign in. Back to the rails console to check the data. You'll find the current_sign_in_x and last_sign_in_x are identical. The IP address is `::1` because you connect to the server locally.
```ruby


user.sign_in_count
#=&amp;gt; 1
user.current_sign_in_at
#=&amp;gt; Thu, 22 Jun 2023 22:02:03.818027000 UTC +00:00
user.current_sign_in_ip
#=&amp;gt; "::1"
user.last_sign_in_at
#=&amp;gt; Thu, 22 Jun 2023 22:02:03.818027000 UTC +00:00
user.last_sign_in_ip
#=&amp;gt; "::1"


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

&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;If you want to see different IP addresses being recorded, you can use your mobile or other laptops to connect to your development server. You can do that by starting the server with &lt;code&gt;-b&lt;/code&gt; argument
```shell
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;rails s -b 0.0.0.0&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;4. Sign in on your other devices and then go back to the console to check. `user.sign_in_count` increased by 1 and `current_sign_in_x` information is also updated.
```ruby


user.sign_in_count
#=&amp;gt; 2
user.current_sign_in_at
#=&amp;gt; Thu, 22 Jun 2023 22:06:57.822208000 UTC +00:00
user.current_sign_in_ip
#=&amp;gt; "192.168.1.107"
user.last_sign_in_at
#=&amp;gt; Thu, 22 Jun 2023 22:02:03.818027000 UTC +00:00
user.last_sign_in_ip
#=&amp;gt; "::1"


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

&lt;/div&gt;
&lt;h3&gt;
  
  
  Checkpoint - IP
&lt;/h3&gt;

&lt;p&gt;How do we know the origin IP address of a request? It's actually a Rails feature provided by &lt;code&gt;Actionpack&lt;/code&gt;. It uses &lt;code&gt;ActionDispatch::Request#remote_ip&lt;/code&gt;. &lt;code&gt;#remote_ip&lt;/code&gt; is smart enough that it will ignore the proxy server's IP addresses and find the original IP address of the request.&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;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remote_ip&lt;/span&gt;


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

&lt;/div&gt;
&lt;h2&gt;
  
  
  Omniauthable &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;:omniauthable&lt;/code&gt; is a special module in &lt;code&gt;devise&lt;/code&gt; but it's also in charge of a very common feature: letting users log in by using a user's session from another website, e.g. Facebook, Google, Twitter, Github, etc. It's kind of delegating authentication work to those big tech companies. Nowadays, most companies follow &lt;strong&gt;OAuth&lt;/strong&gt;'s standards to build the authentication workflow (OAuth always means &lt;strong&gt;OAuth 2.0&lt;/strong&gt; in this article). However, each company may have different &lt;strong&gt;dialects&lt;/strong&gt; when you communicate via OAuth. This module is called &lt;code&gt;:omniauthable&lt;/code&gt; because &lt;code&gt;devise&lt;/code&gt; has integrated with the gem &lt;a href="https://github.com/omniauth/omniauth" rel="noopener noreferrer"&gt;omniauth&lt;/a&gt;, which provides a unified interface to realize the login process via OAuth.&lt;/p&gt;

&lt;p&gt;The root logic of &lt;code&gt;:omniauthable&lt;/code&gt; is totally different from traditional authentication, which makes it feel like a social outcast in &lt;code&gt;devise&lt;/code&gt;. Therefore, the steps to enable &lt;code&gt;:omniauthable&lt;/code&gt; are pretty different. If you aren't familiar with OAuth, don't worry, it's fine to read this chapter. I'll make changes step by step.&lt;/p&gt;

&lt;p&gt;I'll use &lt;strong&gt;Facebook&lt;/strong&gt; as the OAuth provider. The main steps we're going to go through are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;[[#Setup the Omniauth environment]]&lt;/li&gt;
&lt;li&gt;[[#Connect to Facebook to grant the access]]&lt;/li&gt;
&lt;li&gt;[[#Callback from Facebook]]&lt;/li&gt;
&lt;li&gt;[[#Use returned user's information to create/find the user and sign him/her in]]&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  Setup the Omniauth environment
&lt;/h3&gt;
&lt;h4&gt;
  
  
  Install assistance gems
&lt;/h4&gt;

&lt;p&gt;Although we know &lt;code&gt;devise&lt;/code&gt; has integrated with the gem &lt;code&gt;omniauth&lt;/code&gt;, it just defines a unified interface without any functionalities. It will be easier if we just use other gems that implement the customized OAuth workflow for specific providers. For example, I'm going to use Facebook's OAuth login so I add the gems below in the Gemfile and do &lt;code&gt;bundle install&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;

&lt;span class="c1"&gt;# Gemfile&lt;/span&gt;
&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'omniauth-facebook'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'~&amp;gt; 9.0'&lt;/span&gt;
&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'omniauth-rails_csrf_protection'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'~&amp;gt; 1.0.1'&lt;/span&gt;


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

&lt;/div&gt;
&lt;p&gt;You can find gems for other platforms by googling gems name &lt;code&gt;omniauth-[Provider's name]&lt;/code&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  Database migration
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;:omniauthable&lt;/code&gt; needs 2 additional string columns:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;provider&lt;/code&gt;: it stores provider's name, &lt;code&gt;facebook&lt;/code&gt;, &lt;code&gt;google&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;uid&lt;/code&gt;: it stores the user's &lt;strong&gt;unique identifier&lt;/strong&gt; on the provider's website. For instance, you can see it as the id of your Facebook account&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Execute&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

rails g migration AddOmniauthToUsers provider uid:index


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

&lt;/div&gt;
&lt;p&gt;and it will produce the migration file like below:&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;AddOmniauthToUsers&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;7.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;add_column&lt;/span&gt; &lt;span class="ss"&gt;:users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:provider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:string&lt;/span&gt;
    &lt;span class="n"&gt;add_column&lt;/span&gt; &lt;span class="ss"&gt;:users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:string&lt;/span&gt;
    &lt;span class="n"&gt;add_index&lt;/span&gt; &lt;span class="ss"&gt;:users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:uid&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;


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

&lt;/div&gt;
&lt;p&gt;Run &lt;code&gt;rails db:migrate&lt;/code&gt; to make the changes.&lt;/p&gt;
&lt;h4&gt;
  
  
  Enable modules
&lt;/h4&gt;

&lt;p&gt;I assume you already have an App on Facebook for performing OAuth. If you don't know how to create an App on Facebook, you can check out their documentation: &lt;a href="https://developers.facebook.com/docs/development/create-an-appso" rel="noopener noreferrer"&gt;https://developers.facebook.com/docs/development/create-an-appso&lt;/a&gt; &lt;br&gt;
You can put the corresponding App's ID and App's secret in the &lt;code&gt;devise&lt;/code&gt; initializer. &lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;

&lt;span class="c1"&gt;# config/initializers/devise.rb&lt;/span&gt;
&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;omniauth&lt;/span&gt; &lt;span class="ss"&gt;:facebook&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"APP_ID"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"APP_SECRET"&lt;/span&gt;


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

&lt;/div&gt;
&lt;p&gt;Anyway, I think we'd better not commit confidential information in git. I suggest using the Rails credentials to do that. First, edit the credentials YAML:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

&lt;span class="nv"&gt;EDITOR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;vim rails credentials:edit


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

&lt;/div&gt;
&lt;p&gt;Add credentials:&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;facebook&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;app_id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;APP_ID"&lt;/span&gt;
  &lt;span class="na"&gt;app_secret&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;APP_SECRET"&lt;/span&gt;


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

&lt;/div&gt;
&lt;p&gt;We can access the values via &lt;code&gt;Rails.application.credentials&lt;/code&gt; in the initializer.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;

&lt;span class="c1"&gt;# config/initializers/devise.rb&lt;/span&gt;
&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;omniauth&lt;/span&gt; &lt;span class="ss"&gt;:facebook&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;facebook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;app_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;facebook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;app_secret&lt;/span&gt;


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

&lt;/div&gt;
&lt;p&gt;We can enable the module now. There's a special attribute &lt;code&gt;omniauth_providers&lt;/code&gt; to configure, too.&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;User&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;devise&lt;/span&gt; &lt;span class="ss"&gt;:database_authenticatable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:registerable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:confirmable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:validatable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
         &lt;span class="ss"&gt;:recoverable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:rememberable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:timeoutable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:lockable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:trackable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
         &lt;span class="ss"&gt;:omniauthable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;omniauth_providers: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:facebook&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;h3&gt;
  
  
  Connect to Facebook to grant the access
&lt;/h3&gt;

&lt;p&gt;The first step of OAuth is to tell Facebook that you, a Facebook user, agree this web application is going to access your information on Facebook. Let's add the following code:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;

&lt;span class="c1"&gt;# config/routes.rb&lt;/span&gt;
&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;draw&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="s2"&gt;"pages#index"&lt;/span&gt;
  &lt;span class="n"&gt;devise_for&lt;/span&gt; &lt;span class="ss"&gt;:users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;omniauth_providers: &lt;/span&gt;&lt;span class="sx"&gt;%i[facebook]&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;


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

&lt;/div&gt;
&lt;p&gt;This will add 2 more routing endpoints:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

user_facebook_omniauth_authorize GET|POST /users/auth/facebook(.:format)          devise/omniauth_callbacks#passthru
 user_facebook_omniauth_callback GET|POST /users/auth/facebook/callback(.:format) devise/omniauth_callbacks#facebook


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

&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/users/auth/facebook&lt;/code&gt; is used to redirect the user to Facebook to grant the access&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/users/auth/facebook/callback&lt;/code&gt; is used to receive the callback request from Facebook with the asked information. In our case, that should be data including the user's email on Facebook.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Add a link to the &lt;code&gt;/users/auth/facebook&lt;/code&gt; which is the &lt;code&gt;user_facebook_omniauth_authorize_path&lt;/code&gt;. We use &lt;code&gt;button_to&lt;/code&gt; because we want to trigger a form submission to send a POST request. We also add &lt;code&gt;turbo: false&lt;/code&gt; to disable Turbo because we'd like to have a full-page reload&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;

&lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Pages#index&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%&lt;/span&gt; &lt;span class="na"&gt;if&lt;/span&gt; &lt;span class="na"&gt;user_signed_in&lt;/span&gt;&lt;span class="err"&gt;?&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="na"&gt;link_to&lt;/span&gt; &lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="na"&gt;Sign&lt;/span&gt; &lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="err"&gt;',&lt;/span&gt; &lt;span class="na"&gt;destroy_user_session_path&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="na"&gt;data:&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt; &lt;span class="na"&gt;turbo_method:&lt;/span&gt; &lt;span class="na"&gt;:delete&lt;/span&gt; &lt;span class="err"&gt;}&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%&lt;/span&gt; &lt;span class="na"&gt;else&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="na"&gt;link_to&lt;/span&gt; &lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="na"&gt;Sign&lt;/span&gt; &lt;span class="na"&gt;in&lt;/span&gt;&lt;span class="err"&gt;',&lt;/span&gt; &lt;span class="na"&gt;new_user_session_path&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="na"&gt;button_to&lt;/span&gt; &lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="na"&gt;Sign&lt;/span&gt; &lt;span class="na"&gt;in&lt;/span&gt; &lt;span class="na"&gt;with&lt;/span&gt; &lt;span class="na"&gt;Facebook&lt;/span&gt;&lt;span class="err"&gt;",&lt;/span&gt; &lt;span class="na"&gt;user_facebook_omniauth_authorize_path&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="na"&gt;data:&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt; &lt;span class="na"&gt;turbo:&lt;/span&gt; &lt;span class="na"&gt;:false&lt;/span&gt; &lt;span class="err"&gt;}&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%&lt;/span&gt; &lt;span class="na"&gt;end&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;


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

&lt;/div&gt;
&lt;p&gt;Maybe it's worth mentioning that &lt;code&gt;button_to&lt;/code&gt; is a convenient way to create a form with only a submit button. It's almost identical to this:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;

&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="na"&gt;form_with&lt;/span&gt; &lt;span class="na"&gt;url:&lt;/span&gt; &lt;span class="na"&gt;user_facebook_omniauth_authorize_path&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="na"&gt;data:&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt; &lt;span class="na"&gt;turbo:&lt;/span&gt; &lt;span class="na"&gt;false&lt;/span&gt; &lt;span class="err"&gt;}&lt;/span&gt; &lt;span class="na"&gt;do&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="na"&gt;f&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="na"&gt;f.submit&lt;/span&gt; &lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="na"&gt;Sign&lt;/span&gt; &lt;span class="na"&gt;in&lt;/span&gt; &lt;span class="na"&gt;with&lt;/span&gt; &lt;span class="na"&gt;Facebook&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%&lt;/span&gt; &lt;span class="na"&gt;end&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;


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

&lt;/div&gt;
&lt;h3&gt;
  
  
  Callback from Facebook
&lt;/h3&gt;

&lt;p&gt;If you grant access, Facebook will send back the requested information. I didn't mention that but the URL of the path, &lt;code&gt;/users/auth/facebook/callback&lt;/code&gt;,  was embedded in the previous request. Facebook will redirect the users to that callback URL with the requested information.&lt;/p&gt;

&lt;p&gt;If you take a look at &lt;code&gt;rails routes&lt;/code&gt; again, you'll find that it's &lt;code&gt;devise/omniauth_callbacks#facebook&lt;/code&gt; handling &lt;code&gt;/users/auth/facebook/callback&lt;/code&gt;.  For other modules, exporting controllers is part of the customization. However, we have to export the &lt;code&gt;OmniauthController&lt;/code&gt; for enabling &lt;code&gt;:omniauthable&lt;/code&gt;. Execute&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

rails generate devise:controllers &lt;span class="nb"&gt;users&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; omniauth_callbacks


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

&lt;/div&gt;
&lt;p&gt;and make &lt;code&gt;devise&lt;/code&gt; use of this exported controller in routes:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;

&lt;span class="c1"&gt;# config/routes.rb&lt;/span&gt;
&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;draw&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="s2"&gt;"pages#index"&lt;/span&gt;
  &lt;span class="n"&gt;devise_for&lt;/span&gt; &lt;span class="ss"&gt;:users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;omniauth_providers: &lt;/span&gt;&lt;span class="sx"&gt;%i[facebook]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;controllers: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;omniauth_callbacks: &lt;/span&gt;&lt;span class="s1"&gt;'users/omniauth_callbacks'&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;You should add the action &lt;code&gt;#facebook&lt;/code&gt; in the controller because it doesn't exist yet.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;

&lt;span class="c1"&gt;# app/controllers/users/omniauth_callbacks_controller.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Users::OmniauthCallbacksController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Devise&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;OmniauthCallbacksController&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;facebook&lt;/span&gt;
    &lt;span class="c1"&gt;# you will get the requested information from Facebook here&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;h3&gt;
  
  
  Use returned user's information to create/find the user and sign him/her in
&lt;/h3&gt;

&lt;p&gt;We now know Facebook will return the requested information, a.k.a the user's data, to &lt;code&gt;Users::OmniauthCallbacksController#facebook&lt;/code&gt; so we can use that information to create or find the user in the rails application. How do we do that?&lt;/p&gt;

&lt;p&gt;It's where the &lt;code&gt;omniauth&lt;/code&gt; gem shines. It defines a &lt;strong&gt;shared schema&lt;/strong&gt; for all providers. You can check it at &lt;a href="https://github.com/omniauth/omniauth/wiki/Auth-Hash-Schema" rel="noopener noreferrer"&gt;https://github.com/omniauth/omniauth/wiki/Auth-Hash-Schema&lt;/a&gt;. &lt;code&gt;omniauth&lt;/code&gt; will put all the information in a specific header and you can access that via &lt;code&gt;request.env["omniauth.auth"]&lt;/code&gt;. By looking up the schema page, we know this is the data schema&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&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="err"&gt;provider:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"facebook"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;uid:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"uid of user's Facebook account"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;info:&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="err"&gt;email:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user's email"&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="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Other&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;fields&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;are&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;ignored&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;p&gt;The next step is to create or find the user based on that information in &lt;code&gt;#facebook&lt;/code&gt; action. We can utilize &lt;code&gt;User.find_or_create_by&lt;/code&gt; to do that and then sign in the user by &lt;code&gt;sign_in_and_redirect&lt;/code&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;Users::OmniauthCallbacksController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Devise&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;OmniauthCallbacksController&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;facebook&lt;/span&gt;
    &lt;span class="n"&gt;auth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"omniauth.auth"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="vi"&gt;@user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_or_create_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;provider: &lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;uid: &lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;email&lt;/span&gt;
      &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Devise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;friendly_token&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;# give it a random password&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="vi"&gt;@user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;confirm&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="vi"&gt;@user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;confirmed?&lt;/span&gt; &lt;span class="c1"&gt;# add this line if :confirmable is enabled&lt;/span&gt;
    &lt;span class="n"&gt;sign_in_and_redirect&lt;/span&gt; &lt;span class="n"&gt;root_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;event: :authentication&lt;/span&gt; &lt;span class="c1"&gt;# :event is for warden to execute callbacks for :authentication&lt;/span&gt;
    &lt;span class="n"&gt;set_flash_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:notice&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:success&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;kind: &lt;/span&gt;&lt;span class="s2"&gt;"Facebook"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;


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

&lt;/div&gt;
&lt;p&gt;I usually see people encapsulate the logic of user creation and finding in a class method called &lt;code&gt;.from_omniauth&lt;/code&gt; so it can be used  by multiple providers in the future.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;

&lt;span class="c1"&gt;# app/models/user.rb&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_omniauth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;find_or_create_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;provider: &lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;uid: &lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;email&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Devise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;friendly_token&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;# give it a random password&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;


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

&lt;/div&gt;
&lt;p&gt;so the &lt;code&gt;#facebook&lt;/code&gt; can be refactored into&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;

&lt;span class="c1"&gt;# app/controllers/users/omniauth_callbacks_controller.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Users::OmniauthCallbacksController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Devise&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;OmniauthCallbacksController&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;facebook&lt;/span&gt;
    &lt;span class="vi"&gt;@user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_omniauth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"omniauth.auth"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="vi"&gt;@user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;confirm&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="vi"&gt;@user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;confirmed?&lt;/span&gt; &lt;span class="c1"&gt;# add this line if :confirmable is enabled&lt;/span&gt;
    &lt;span class="n"&gt;sign_in_and_redirect&lt;/span&gt; &lt;span class="vi"&gt;@user&lt;/span&gt;
    &lt;span class="n"&gt;set_flash_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:notice&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:success&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;kind: &lt;/span&gt;&lt;span class="s2"&gt;"Facebook"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;


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

&lt;/div&gt;
&lt;h3&gt;
  
  
  Test
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Go to the root page, you'll see the new button, &lt;strong&gt;Sign in with Facebook&lt;/strong&gt;
&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa278xstbjq825168c3re.png" alt="Home page"&gt;
&lt;/li&gt;
&lt;li&gt;Click the button, and you'll be redirected to Facebook granting access page. My Facebook app name is "develop only" so it says "develop only is requesting access to:"
&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fooc0rjqenc2wn8hzmi51.png" alt="grant facebook access"&gt;
&lt;/li&gt;
&lt;li&gt;After granting access, you will find you have already logged in
&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbp06ioai5zna0ie9chr4.png" alt="root page"&gt;
&lt;/li&gt;
&lt;li&gt;Open the rails console, you'll find a new user is created and &lt;code&gt;provider&lt;/code&gt; and &lt;code&gt;uid&lt;/code&gt; are filled
```ruby
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;user = User.last&lt;br&gt;
user.provider&lt;/p&gt;
&lt;h1&gt;
  
  
  =&amp;gt; 'facebook'
&lt;/h1&gt;

&lt;p&gt;user.uid.present?&lt;/p&gt;
&lt;h1&gt;
  
  
  =&amp;gt; true
&lt;/h1&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
I only showed the general idea here. You can find more information on `devise`'s wiki page for OmniAuth: https://github.com/heartcombo/devise/wiki/OmniAuth:-Overview. You can read [OAuth RFC](https://datatracker.ietf.org/doc/html/rfc6749) if you are eager to know the details.

## Conclusion
What a journey! We made it 😭! To be honest, I didn't expect I would spend so much time on this article because I've used every module more or less in the past. I thought I was pretty familiar with `devise`. It turned out there are still many things that are different from what I imagined when starting digging deeper. 

I tried to write this article as a tutorial for beginners to enable each module. Therefore, I demonstrate them step by step. In practice, you should execute the macro
```bash


rails generate devise User


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

&lt;/div&gt;
&lt;p&gt;and all migrations and related configurations will be there for you (except &lt;code&gt;:omniauthable&lt;/code&gt; so I say it's an outcast😆). You can then opt-in to what you want. Whatever, I think it's still valuable if you just took over an existing project that uses &lt;code&gt;devise&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Thank you for reading this article. I hope this article makes you understand more about &lt;code&gt;devise&lt;/code&gt; and the authentication of web applications🙏. Maybe after reading this article, you may find you don't need &lt;code&gt;devise&lt;/code&gt; at all due to its complexity. If that's true, you can read my other article, building your own authentication system in Rails. &lt;/p&gt;
&lt;div class="ltag__link"&gt;
  &lt;a href="/kevinluo201" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F283252%2F07012131-5bb5-4aff-b73c-0dbd85e2439f.png" alt="kevinluo201"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="/kevinluo201/building-a-simple-authentication-in-rails-7-from-scratch-2dhb" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Building a simple authentication in Rails 7 from scratch&lt;/h2&gt;
      &lt;h3&gt;Kevin Luo ・ Jun 4 '23&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;



&lt;p&gt;If you think this article is helpful, you can buy me a coffee to encourage me 😉&lt;br&gt;
&lt;a href="https://www.buymeacoffee.com/kevinluo" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftwve1hh3j8ewl5aowo7r.png" alt="Buy Me A Coffee"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Code Repo
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/kevinluo201/devise_modules" rel="noopener noreferrer"&gt;https://github.com/kevinluo201/devise_modules&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/heartcombo/devise" rel="noopener noreferrer"&gt;https://github.com/heartcombo/devise&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#session_cookie" rel="noopener noreferrer"&gt;https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#session_cookie&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>tutorial</category>
      <category>authentication</category>
    </item>
    <item>
      <title>Set the value of datetime-local input field from an Date object</title>
      <dc:creator>Kevin Luo</dc:creator>
      <pubDate>Sun, 11 Jun 2023 17:31:34 +0000</pubDate>
      <link>https://forem.com/kevinluo201/set-value-of-datetime-local-input-field-3435</link>
      <guid>https://forem.com/kevinluo201/set-value-of-datetime-local-input-field-3435</guid>
      <description>&lt;p&gt;We can use &lt;code&gt;datetime-local&lt;/code&gt; to let user enter a date-time string. Most browsers have already implemented a native UI for it. It should be the most ideal way to have date-time input.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; 
  &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"start-time"&lt;/span&gt; 
  &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"datetime-local"&lt;/span&gt; 
  &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"start_time"&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The input is always blank at the beginning. I wanted to give it the current time as its default value but it didn't work.&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;currentTime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;start-time&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;currentTime&lt;/span&gt; &lt;span class="c1"&gt;// does not work&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because &lt;code&gt;datetime-local&lt;/code&gt; only accepts a specific string value, &lt;code&gt;YYYY-mm-ddTHH:MM&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;YYYY-mm-dd&lt;/code&gt; is the year-month-date&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;T&lt;/code&gt; is just the seperator of date and time&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;HH:MM&lt;/code&gt; is the hours and minutes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Therefore, instead of assigning a &lt;code&gt;Date&lt;/code&gt; object directly, you have to convert it first into that form. You can use &lt;code&gt;toISOString()&lt;/code&gt; because the it will convert the Date object to&lt;br&gt;
&lt;code&gt;YYYY-MM-DDTHH:mm:ss.sssZ&lt;/code&gt; and the first part is exactly the same format the &lt;code&gt;datetime-local&lt;/code&gt; needs.&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;currentTime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;start-time&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;currentTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, it has a problem that it will use the UTC time which is timezone &lt;code&gt;+00:00&lt;/code&gt;. I guess not so many people live within that time zone compared to all populations on Earth. I think most developers want to convert that to the local time.&lt;/p&gt;

&lt;p&gt;Unfortunately, I found it ridiculously difficult to do that because there's no suitable method for handling that. I think it's due to the fact that most languages have a method similar to &lt;code&gt;strftime&lt;/code&gt; so we are used to using an existing method to do what we want. There are some methods like &lt;code&gt;toLocaleString()&lt;/code&gt; which seems like we can convert Date into some specific format but none of the format fits our needs. It feels like a basic functionality so I don't want to use any other libraries, either. &lt;/p&gt;

&lt;p&gt;Finally, with some help from chatGPT and looking up of MDN page of the Date library. I finally realize the best to do that is to compose the string by myself.&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;// getFullYear, getMonth, getDate, getHours, getMinutes all return values of local time.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;convertToDateTimeLocalString&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;date&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;year&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getFullYear&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;month&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getMonth&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;padStart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0&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;day&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getDate&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;padStart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0&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;hours&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getHours&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;padStart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0&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;minutes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getMinutes&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;padStart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;year&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="nx"&gt;month&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="nx"&gt;day&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;T&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;hours&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="nx"&gt;minutes&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;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;currentTime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;start-time&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;convertToDateTimeLocalString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentTime&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Reference
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date" rel="noopener noreferrer"&gt;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>javascript</category>
      <category>html</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
