<?xml version="1.0" encoding="UTF-8" ?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <title type="text">Martin Tan's Blog</title>
    <subtitle type="html"><![CDATA[Martin Tan's Blog]]></subtitle>
    <link href="https://mrtan.me/feed.xml"></link>
    <id>https://mrtan.me/feed.xml</id>
    <link rel="alternate" type="text/html" href="https://mrtan.me/feed.xml"></link>
    <link rel="self" type="application/atom+xml" href="https://mrtan.me/feed.xml"></link>
    <logo>https://mrtan.me/image/logo.jpg</logo>
    <updated>2025-11-03T04:46:27+08:00</updated>
            <entry>
            <author>
                <name>Martin Tan</name>
            </author>
            <title type="text"><![CDATA[How to Setup a Gitlab CE Docker instance 2026]]></title>
            <link rel="alternate" type="text/html" href="https://mrtan.me/post/how-to-setup-a-gitlab-ce-docker-instance-2026.html"></link>
            <id>https://mrtan.me/post/how-to-setup-a-gitlab-ce-docker-instance-2026.html</id>
            <summary type="html"><![CDATA[<p><img src="/upload/images/2025/11/setup-gitlab-easy.jpg" alt="setup-gitlab-easy" /></p>
<h2>Why Self-Host GitLab CE?</h2>
<p>There's something compelling about running your own Git server instead of relying on a cloud provider. GitLab CE (Community Edition) is completely free and open-source, which means you're not locked into anyone's ecosystem or pricing model. When you self-host, you get total control over your data - your repositories stay exactly where you want them, your CI/CD pipelines run on infrastructure you own, and you're not subject to any vendor's terms of service changes. Plus, from a cost perspective, if you already have a server running somewhere, the marginal cost of adding GitLab is basically zero. You get the full Git hosting experience with merge requests, code review, project management, and a powerful CI/CD system - essentially everything you'd get from GitHub or GitLab.com, but under your own roof.</p>
<h2>Prerequisites</h2>
<p>Before you dive in, make sure you have a few things in place. You'll obviously need Docker installed and running - I'm assuming you're comfortable with Docker at this point since you're self-hosting. GitLab isn't exactly lightweight, so you'll want at least 4 CPU cores and 12GB of RAM available for the container to breathe comfortably. For accessing it, you'll need either a local domain name (I use <code>gitlab.test</code> for local development, which you can add to your <code>/etc/hosts</code>) or an IP address. And while it's not strictly required, I'd strongly recommend setting up persistent storage directories for config, logs, and data - losing everything when a container restarts is a painful lesson I don't recommend learning firsthand.</p>
<h2>Setup the GitLab Instance</h2>
<p>Here's the nice part - you don't need Docker Compose or any complex orchestration for this. A single Docker container is absolutely sufficient for a development or small team setup, which keeps things refreshingly simple.</p>
<p>Start by browsing to <a href="https://hub.docker.com/r/gitlab/gitlab-ce/tags">Docker Hub and checking out the available GitLab CE versions</a>. Pick whichever version you prefer - I've found newer versions are generally more stable, but if you need a specific feature from an older release, you can obviously go with that.</p>
<p>Once you've decided on a version, run this command:</p>
<pre><code class="language-shell">docker run --detach \
  --env GITLAB_OMNIBUS_CONFIG="external_url 'http://gitlab.test'" \
  --name gitlab-local \
  --cpus="4" \
  --memory=12g \
  --volume $PWD/gitlab/config:/etc/gitlab \
  --volume $PWD/gitlab/logs:/var/log/gitlab \
  --volume $PWD/gitlab/data:/var/opt/gitlab \
  --shm-size 256m \
  gitlab/gitlab-ce:18.3.0-ce.0</code></pre>
<p>Let me break down what each flag does here because it matters:</p>
<ul>
<li><code>--detach</code>: Runs the container in the background so you get your terminal back</li>
<li><code>--env GITLAB_OMNIBUS_CONFIG</code>: This sets the external URL that GitLab will use - this is important because GitLab needs to know how to construct links and authentication URLs</li>
<li><code>--cpus="4"</code>: Gives the container access to 4 CPU cores - GitLab really benefits from having enough CPU, and 4 is a reasonable minimum</li>
<li><code>--memory=12g</code>: Allocates 12GB of RAM - GitLab's Puma server and other processes are memory-hungry, so this is pretty essential</li>
<li><code>--volume</code>: These three volume mounts ensure your data survives container restarts:
<ul>
<li><code>config</code>: Where all your GitLab settings live</li>
<li><code>logs</code>: Application logs for debugging</li>
<li><code>data</code>: The actual repositories, databases, and everything else important</li>
</ul></li>
<li><code>--shm-size 256m</code>: Shared memory allocation - this helps with performance and prevents some weird errors you might encounter otherwise</li>
</ul>
<p>If you're just kicking the tires and don't care about losing data, you can completely skip the <code>--volume</code> flags. But honestly, it's worth the 30 seconds to create those directories and get persistence working.</p>
<p>After running the command, grab a coffee because GitLab takes a few minutes to initialize - probably 2-3 minutes depending on your hardware. You can watch the startup process with:</p>
<pre><code class="language-shell">docker logs -f gitlab-local</code></pre>
<p>Once you see messages indicating it's ready (look for something about Unicorn or Puma starting up), you can visit <code>http://gitlab.test</code> in your browser. GitLab will ask you to set a new password for the root user - the initial temporary password is stored in a file inside the container at <code>/etc/gitlab/initial_root_password</code>. You can grab it with:</p>
<pre><code class="language-shell">docker exec gitlab-local cat /etc/gitlab/initial_root_password</code></pre>
<p>That first login feels good - you're now running your own Git server.</p>
<h2>Setting Up the Runner</h2>
<p>Now here's where things get interesting. A GitLab Runner is basically a service that actually executes your CI/CD jobs - it's the thing that runs your tests, builds your Docker images, deploys your code, or whatever else you've defined in your pipeline. Without a runner, your GitLab instance is just a fancy repository browser; nothing actually happens when you push code.</p>
<p>There are two approaches here. The first is the quick and dirty method:</p>
<pre><code class="language-shell">docker run -d --rm --name gitlab-docker-runner \
  -v /var/run/docker.sock:/var/run/docker.sock \
  gitlab/gitlab-runner:latest</code></pre>
<p>This works fine, and the runner will happily execute jobs. The problem is that when the container stops or restarts, all the runner configuration goes away with it, and you'll need to re-register it next time. If you're just experimenting, that's fine. But if you're actually using this in any kind of real way, you'll want persistence.</p>
<p>Here's the version I recommend - it takes literally one extra line to mount a config directory:</p>
<pre><code class="language-shell">docker run -d --rm --name gitlab-docker-runner \
  -v /YOUR_PATH_TO_SAVE/docker-runner/config:/etc/gitlab-runner \
  -v /var/run/docker.sock:/var/run/docker.sock \
  gitlab/gitlab-runner:latest</code></pre>
<p>Just so we're clear on what's happening here:</p>
<ul>
<li><code>-d</code>: Runs in the background (detached mode)</li>
<li><code>--rm</code>: Automatically cleans up the container when it stops - this keeps things tidy</li>
<li><code>-v /var/run/docker.sock</code>: This mounts the Docker socket into the container, which lets the runner spawn Docker containers to actually run your jobs</li>
<li><code>-v /YOUR_PATH_TO_SAVE/docker-runner/config:/etc/gitlab-runner</code>: This persists the runner configuration so you don't lose it on restarts</li>
</ul>
<p>Replace <code>/YOUR_PATH_TO_SAVE</code> with wherever you want to keep the config - maybe something like <code>$HOME/gitlab-runner/config</code> or <code>/opt/gitlab-runner/config</code>.</p>
<p>I'd really suggest going with the persistent version from the start. Those extra 30 seconds of setup will save you from re-registering the runner every time your machine reboots.</p>
<h2>Register the Runner with GitLab</h2>
<p>Now that both GitLab and the runner are running, you need to tell them about each other - this is the registration process. It's actually pretty straightforward, and the GitLab UI guides you through it pretty nicely.</p>
<p><img src="/upload/images/2025/11/runner-list.png" alt="runner-list" /></p>
<p><img src="/upload/images/2025/11/runner-create-form.png" alt="runner-create-form" /></p>
<p>Here's the step-by-step process, which honestly just takes a few minutes:</p>
<ol>
<li>
<p>Navigate to <code>http://gitlab.test/admin/runners</code> - this is the runners administration page where you can see all your runners and create new ones</p>
</li>
<li>
<p>Click the &quot;Create instance runner&quot; button - don't let the terminology throw you; this just means you're creating a runner that belongs to the GitLab instance and isn't tied to a specific project</p>
</li>
<li>
<p>Check the &quot;Run untagged jobs&quot; checkbox - this lets the runner execute jobs that don't have specific tags, which is what you want for general use</p>
</li>
<li>
<p>Click &quot;Create runner&quot; and GitLab will generate a registration token for you - this token is basically a password that lets your runner prove it belongs to your GitLab instance</p>
</li>
<li>
<p>Copy that token somewhere you can access it in your terminal</p>
</li>
<li>
<p>Now in your terminal, register the runner by executing this inside the runner container:</p>
</li>
</ol>
<pre><code class="language-shell">docker exec -it gitlab-docker-runner gitlab-runner register \
  --url http://gitlab.test \
  --token &lt;PASTE_TOKEN_HERE&gt; \
  --executor docker \
  --docker-image alpine:latest</code></pre>
<p>Obviously replace <code>&lt;PASTE_TOKEN_HERE&gt;</code> with the actual token you just copied. The <code>--executor docker</code> part tells the runner to use Docker for executing jobs, and <code>alpine:latest</code> is a lightweight base image for running commands. This is a sensible default but you can change it later if you need.</p>
<ol start="7">
<li>Once the registration completes, refresh the runners page and you should see your runner listed there with a green checkmark showing it's active and healthy</li>
</ol>
<p>At this point, your runner is alive and ready to execute jobs. Any CI/CD pipeline you define in a <code>.gitlab-ci.yml</code> file will now actually run when you push code.</p>
<h2>Troubleshooting: Runner Running But Not Picking Up Jobs</h2>
<p>This is probably the most frustrating scenario - you can see the runner is there, it's showing as active in the admin panel with a green checkmark, but when you push code and create a pipeline, the jobs just sit in a &quot;pending&quot; state forever. The runner completely ignores them. This usually happens because of one specific thing: <strong>the runner and the pipeline jobs have a tag mismatch</strong>.</p>
<p>Here's how the tag system works. When you define a job in your <code>.gitlab-ci.yml</code> file, you can optionally specify tags like this:</p>
<pre><code class="language-yaml">test:
  tags:
    - docker
  script:
    - npm test</code></pre>
<p>This tells GitLab: &quot;I want this job to run on a runner that has the <code>docker</code> tag.&quot; Now, when you register your runner, you can assign it tags. If you didn't assign any tags during registration (which is the common case), your runner gets no tags by default - it's literally a runner with an empty tag list.</p>
<p>The problem is that if your job explicitly specifies tags but your runner has no tags, there's no match, and the job will never run. It's like putting up a job posting that says &quot;must speak French&quot; and then hiring someone who only speaks English - they're never going to apply.</p>
<p><strong>The fix is usually one of these:</strong></p>
<p><strong>Option 1: Make your runner accept untagged jobs</strong> (the easiest way)
Go back to <code>http://gitlab.test/admin/runners</code>, click on your runner, and make sure the &quot;Run untagged jobs&quot; toggle is enabled. This is exactly what you did in step 3 of the registration process. If you enabled it but jobs still aren't running, try toggling it off and back on again - sometimes it needs a refresh.</p>
<p><strong>Option 2: Remove tags from your pipeline jobs</strong>
In your <code>.gitlab-ci.yml</code>, just don't specify any tags at all. By default, untagged jobs will match any runner that has &quot;Run untagged jobs&quot; enabled. This is probably what you want for a small setup anyway.</p>
<pre><code class="language-yaml">test:
  script:
    - npm test</code></pre>
<p><strong>Option 3: Add tags to your runner and match them in your jobs</strong>
If you really want to use tags (which can be useful for more complex setups with multiple runners), you need to match them exactly. Go to your runner settings, add a tag like <code>my-runner</code>, then in your <code>.gitlab-ci.yml</code> specify <code>tags: [my-runner]</code>. But honestly, for a first setup, this is overcomplicating things.</p>
<p><strong>Pro tip:</strong> Check the pipeline job status page to see why it's not running. Click on a pending job and you should see a message like &quot;This job could not be run because no runners are available&quot; or &quot;Runner can't pick job because it doesn't match tags.&quot; These messages are actually super helpful and will tell you exactly what the problem is.</p>
<p>The vast majority of the time, the issue is just that &quot;Run untagged jobs&quot; isn't enabled or the runner needs a moment to reconnect. But understanding the tag system will save you hours of confusion, so it's worth knowing how it actually works.</p>
<h2>Next Steps</h2>
<p>Congratulations - you've just built your own Git hosting platform. This is actually a pretty solid foundation. With GitLab and a runner both up and running, you now have the infrastructure to do some genuinely useful things.</p>
<p>You can start creating projects and pushing repositories to it like you would with GitHub or GitLab.com. The UI is almost identical, so if you're familiar with GitLab, you'll feel right at home. More importantly though, you can now define CI/CD pipelines by adding a <code>.gitlab-ci.yml</code> file to your repositories - that's where the real power comes in. You can run automated tests on every push, build Docker images, deploy applications, run linting checks, or basically anything else you can script.</p>
<p>The merge request workflow is also fully functional - you can have team members (or just yourself) open merge requests, request code reviews, and use all the collaboration features. The beauty of self-hosting is that all of this runs on your own hardware with no SaaS bills or vendor lock-in.</p>
<p>If you run into issues during the setup, the logs are your friend - <code>docker logs gitlab-local</code> and <code>docker logs gitlab-docker-runner</code> will show you what's happening. And honestly, once this is running, it's pretty stable. I've had instances running like this for months without any real maintenance required beyond the occasional restart.</p>
<p>Your self-hosted GitLab instance is now ready for development and CI/CD automation.</p>]]></summary>
            <content type="html"><![CDATA[<p><img src="/upload/images/2025/11/setup-gitlab-easy.jpg" alt="setup-gitlab-easy" /></p>
<h2>Why Self-Host GitLab CE?</h2>
<p>There's something compelling about running your own Git server instead of relying on a cloud provider. GitLab CE (Community Edition) is completely free and open-source, which means you're not locked into anyone's ecosystem or pricing model. When you self-host, you get total control over your data - your repositories stay exactly where you want them, your CI/CD pipelines run on infrastructure you own, and you're not subject to any vendor's terms of service changes. Plus, from a cost perspective, if you already have a server running somewhere, the marginal cost of adding GitLab is basically zero. You get the full Git hosting experience with merge requests, code review, project management, and a powerful CI/CD system - essentially everything you'd get from GitHub or GitLab.com, but under your own roof.</p>
<h2>Prerequisites</h2>
<p>Before you dive in, make sure you have a few things in place. You'll obviously need Docker installed and running - I'm assuming you're comfortable with Docker at this point since you're self-hosting. GitLab isn't exactly lightweight, so you'll want at least 4 CPU cores and 12GB of RAM available for the container to breathe comfortably. For accessing it, you'll need either a local domain name (I use <code>gitlab.test</code> for local development, which you can add to your <code>/etc/hosts</code>) or an IP address. And while it's not strictly required, I'd strongly recommend setting up persistent storage directories for config, logs, and data - losing everything when a container restarts is a painful lesson I don't recommend learning firsthand.</p>
<h2>Setup the GitLab Instance</h2>
<p>Here's the nice part - you don't need Docker Compose or any complex orchestration for this. A single Docker container is absolutely sufficient for a development or small team setup, which keeps things refreshingly simple.</p>
<p>Start by browsing to <a href="https://hub.docker.com/r/gitlab/gitlab-ce/tags">Docker Hub and checking out the available GitLab CE versions</a>. Pick whichever version you prefer - I've found newer versions are generally more stable, but if you need a specific feature from an older release, you can obviously go with that.</p>
<p>Once you've decided on a version, run this command:</p>
<pre><code class="language-shell">docker run --detach \
  --env GITLAB_OMNIBUS_CONFIG="external_url 'http://gitlab.test'" \
  --name gitlab-local \
  --cpus="4" \
  --memory=12g \
  --volume $PWD/gitlab/config:/etc/gitlab \
  --volume $PWD/gitlab/logs:/var/log/gitlab \
  --volume $PWD/gitlab/data:/var/opt/gitlab \
  --shm-size 256m \
  gitlab/gitlab-ce:18.3.0-ce.0</code></pre>
<p>Let me break down what each flag does here because it matters:</p>
<ul>
<li><code>--detach</code>: Runs the container in the background so you get your terminal back</li>
<li><code>--env GITLAB_OMNIBUS_CONFIG</code>: This sets the external URL that GitLab will use - this is important because GitLab needs to know how to construct links and authentication URLs</li>
<li><code>--cpus="4"</code>: Gives the container access to 4 CPU cores - GitLab really benefits from having enough CPU, and 4 is a reasonable minimum</li>
<li><code>--memory=12g</code>: Allocates 12GB of RAM - GitLab's Puma server and other processes are memory-hungry, so this is pretty essential</li>
<li><code>--volume</code>: These three volume mounts ensure your data survives container restarts:
<ul>
<li><code>config</code>: Where all your GitLab settings live</li>
<li><code>logs</code>: Application logs for debugging</li>
<li><code>data</code>: The actual repositories, databases, and everything else important</li>
</ul></li>
<li><code>--shm-size 256m</code>: Shared memory allocation - this helps with performance and prevents some weird errors you might encounter otherwise</li>
</ul>
<p>If you're just kicking the tires and don't care about losing data, you can completely skip the <code>--volume</code> flags. But honestly, it's worth the 30 seconds to create those directories and get persistence working.</p>
<p>After running the command, grab a coffee because GitLab takes a few minutes to initialize - probably 2-3 minutes depending on your hardware. You can watch the startup process with:</p>
<pre><code class="language-shell">docker logs -f gitlab-local</code></pre>
<p>Once you see messages indicating it's ready (look for something about Unicorn or Puma starting up), you can visit <code>http://gitlab.test</code> in your browser. GitLab will ask you to set a new password for the root user - the initial temporary password is stored in a file inside the container at <code>/etc/gitlab/initial_root_password</code>. You can grab it with:</p>
<pre><code class="language-shell">docker exec gitlab-local cat /etc/gitlab/initial_root_password</code></pre>
<p>That first login feels good - you're now running your own Git server.</p>
<h2>Setting Up the Runner</h2>
<p>Now here's where things get interesting. A GitLab Runner is basically a service that actually executes your CI/CD jobs - it's the thing that runs your tests, builds your Docker images, deploys your code, or whatever else you've defined in your pipeline. Without a runner, your GitLab instance is just a fancy repository browser; nothing actually happens when you push code.</p>
<p>There are two approaches here. The first is the quick and dirty method:</p>
<pre><code class="language-shell">docker run -d --rm --name gitlab-docker-runner \
  -v /var/run/docker.sock:/var/run/docker.sock \
  gitlab/gitlab-runner:latest</code></pre>
<p>This works fine, and the runner will happily execute jobs. The problem is that when the container stops or restarts, all the runner configuration goes away with it, and you'll need to re-register it next time. If you're just experimenting, that's fine. But if you're actually using this in any kind of real way, you'll want persistence.</p>
<p>Here's the version I recommend - it takes literally one extra line to mount a config directory:</p>
<pre><code class="language-shell">docker run -d --rm --name gitlab-docker-runner \
  -v /YOUR_PATH_TO_SAVE/docker-runner/config:/etc/gitlab-runner \
  -v /var/run/docker.sock:/var/run/docker.sock \
  gitlab/gitlab-runner:latest</code></pre>
<p>Just so we're clear on what's happening here:</p>
<ul>
<li><code>-d</code>: Runs in the background (detached mode)</li>
<li><code>--rm</code>: Automatically cleans up the container when it stops - this keeps things tidy</li>
<li><code>-v /var/run/docker.sock</code>: This mounts the Docker socket into the container, which lets the runner spawn Docker containers to actually run your jobs</li>
<li><code>-v /YOUR_PATH_TO_SAVE/docker-runner/config:/etc/gitlab-runner</code>: This persists the runner configuration so you don't lose it on restarts</li>
</ul>
<p>Replace <code>/YOUR_PATH_TO_SAVE</code> with wherever you want to keep the config - maybe something like <code>$HOME/gitlab-runner/config</code> or <code>/opt/gitlab-runner/config</code>.</p>
<p>I'd really suggest going with the persistent version from the start. Those extra 30 seconds of setup will save you from re-registering the runner every time your machine reboots.</p>
<h2>Register the Runner with GitLab</h2>
<p>Now that both GitLab and the runner are running, you need to tell them about each other - this is the registration process. It's actually pretty straightforward, and the GitLab UI guides you through it pretty nicely.</p>
<p><img src="/upload/images/2025/11/runner-list.png" alt="runner-list" /></p>
<p><img src="/upload/images/2025/11/runner-create-form.png" alt="runner-create-form" /></p>
<p>Here's the step-by-step process, which honestly just takes a few minutes:</p>
<ol>
<li>
<p>Navigate to <code>http://gitlab.test/admin/runners</code> - this is the runners administration page where you can see all your runners and create new ones</p>
</li>
<li>
<p>Click the &quot;Create instance runner&quot; button - don't let the terminology throw you; this just means you're creating a runner that belongs to the GitLab instance and isn't tied to a specific project</p>
</li>
<li>
<p>Check the &quot;Run untagged jobs&quot; checkbox - this lets the runner execute jobs that don't have specific tags, which is what you want for general use</p>
</li>
<li>
<p>Click &quot;Create runner&quot; and GitLab will generate a registration token for you - this token is basically a password that lets your runner prove it belongs to your GitLab instance</p>
</li>
<li>
<p>Copy that token somewhere you can access it in your terminal</p>
</li>
<li>
<p>Now in your terminal, register the runner by executing this inside the runner container:</p>
</li>
</ol>
<pre><code class="language-shell">docker exec -it gitlab-docker-runner gitlab-runner register \
  --url http://gitlab.test \
  --token &lt;PASTE_TOKEN_HERE&gt; \
  --executor docker \
  --docker-image alpine:latest</code></pre>
<p>Obviously replace <code>&lt;PASTE_TOKEN_HERE&gt;</code> with the actual token you just copied. The <code>--executor docker</code> part tells the runner to use Docker for executing jobs, and <code>alpine:latest</code> is a lightweight base image for running commands. This is a sensible default but you can change it later if you need.</p>
<ol start="7">
<li>Once the registration completes, refresh the runners page and you should see your runner listed there with a green checkmark showing it's active and healthy</li>
</ol>
<p>At this point, your runner is alive and ready to execute jobs. Any CI/CD pipeline you define in a <code>.gitlab-ci.yml</code> file will now actually run when you push code.</p>
<h2>Troubleshooting: Runner Running But Not Picking Up Jobs</h2>
<p>This is probably the most frustrating scenario - you can see the runner is there, it's showing as active in the admin panel with a green checkmark, but when you push code and create a pipeline, the jobs just sit in a &quot;pending&quot; state forever. The runner completely ignores them. This usually happens because of one specific thing: <strong>the runner and the pipeline jobs have a tag mismatch</strong>.</p>
<p>Here's how the tag system works. When you define a job in your <code>.gitlab-ci.yml</code> file, you can optionally specify tags like this:</p>
<pre><code class="language-yaml">test:
  tags:
    - docker
  script:
    - npm test</code></pre>
<p>This tells GitLab: &quot;I want this job to run on a runner that has the <code>docker</code> tag.&quot; Now, when you register your runner, you can assign it tags. If you didn't assign any tags during registration (which is the common case), your runner gets no tags by default - it's literally a runner with an empty tag list.</p>
<p>The problem is that if your job explicitly specifies tags but your runner has no tags, there's no match, and the job will never run. It's like putting up a job posting that says &quot;must speak French&quot; and then hiring someone who only speaks English - they're never going to apply.</p>
<p><strong>The fix is usually one of these:</strong></p>
<p><strong>Option 1: Make your runner accept untagged jobs</strong> (the easiest way)
Go back to <code>http://gitlab.test/admin/runners</code>, click on your runner, and make sure the &quot;Run untagged jobs&quot; toggle is enabled. This is exactly what you did in step 3 of the registration process. If you enabled it but jobs still aren't running, try toggling it off and back on again - sometimes it needs a refresh.</p>
<p><strong>Option 2: Remove tags from your pipeline jobs</strong>
In your <code>.gitlab-ci.yml</code>, just don't specify any tags at all. By default, untagged jobs will match any runner that has &quot;Run untagged jobs&quot; enabled. This is probably what you want for a small setup anyway.</p>
<pre><code class="language-yaml">test:
  script:
    - npm test</code></pre>
<p><strong>Option 3: Add tags to your runner and match them in your jobs</strong>
If you really want to use tags (which can be useful for more complex setups with multiple runners), you need to match them exactly. Go to your runner settings, add a tag like <code>my-runner</code>, then in your <code>.gitlab-ci.yml</code> specify <code>tags: [my-runner]</code>. But honestly, for a first setup, this is overcomplicating things.</p>
<p><strong>Pro tip:</strong> Check the pipeline job status page to see why it's not running. Click on a pending job and you should see a message like &quot;This job could not be run because no runners are available&quot; or &quot;Runner can't pick job because it doesn't match tags.&quot; These messages are actually super helpful and will tell you exactly what the problem is.</p>
<p>The vast majority of the time, the issue is just that &quot;Run untagged jobs&quot; isn't enabled or the runner needs a moment to reconnect. But understanding the tag system will save you hours of confusion, so it's worth knowing how it actually works.</p>
<h2>Next Steps</h2>
<p>Congratulations - you've just built your own Git hosting platform. This is actually a pretty solid foundation. With GitLab and a runner both up and running, you now have the infrastructure to do some genuinely useful things.</p>
<p>You can start creating projects and pushing repositories to it like you would with GitHub or GitLab.com. The UI is almost identical, so if you're familiar with GitLab, you'll feel right at home. More importantly though, you can now define CI/CD pipelines by adding a <code>.gitlab-ci.yml</code> file to your repositories - that's where the real power comes in. You can run automated tests on every push, build Docker images, deploy applications, run linting checks, or basically anything else you can script.</p>
<p>The merge request workflow is also fully functional - you can have team members (or just yourself) open merge requests, request code reviews, and use all the collaboration features. The beauty of self-hosting is that all of this runs on your own hardware with no SaaS bills or vendor lock-in.</p>
<p>If you run into issues during the setup, the logs are your friend - <code>docker logs gitlab-local</code> and <code>docker logs gitlab-docker-runner</code> will show you what's happening. And honestly, once this is running, it's pretty stable. I've had instances running like this for months without any real maintenance required beyond the occasional restart.</p>
<p>Your self-hosted GitLab instance is now ready for development and CI/CD automation.</p>]]></content>
            <updated>2025-11-03T04:46:27-06:00</updated>
        </entry>
            <entry>
            <author>
                <name>Martin Tan</name>
            </author>
            <title type="text"><![CDATA[Rails is Still the GOAT for Building Web Apps - My Experience with Gisia]]></title>
            <link rel="alternate" type="text/html" href="https://mrtan.me/post/rails-is-still-the-goat-for-building-web-apps-my-experience-with-gisia.html"></link>
            <id>https://mrtan.me/post/rails-is-still-the-goat-for-building-web-apps-my-experience-with-gisia.html</id>
            <summary type="html"><![CDATA[<p><img src="/upload/images/2025/10/gisa-rails-development-experience.jpg" alt="gisa-rails-development-experience" /></p>
<p>When I decided to build a personal Git hosting server, I had a choice: self-host GitLab or build something lighter. I chose the latter, and here's why Rails made it ridiculously fast to build <strong>Gisia</strong> – a lightweight alternative that proves Rails is still the king for modern web applications.</p>
<h2>Why Not GitLab? Why Build Gisia Instead?</h2>
<p>GitLab is incredible, but it's also <strong>heavy</strong>. Here's the reality:</p>
<ul>
<li><strong>Resource hungry</strong>: Requires Vue.js, Elasticsearch, and more. Just getting it running locally takes significant setup.</li>
<li><strong>Bloated for small teams</strong>: If you just need Git hosting + basic CI/CD, you're carrying around features you'll never use.</li>
<li><strong>Overkill for personal/small-team use</strong>: The complexity overhead isn't worth it when you just want a focused, maintainable codebase.</li>
</ul>
<p>Gisia takes inspiration from GitLab's feature set but strips away the unnecessary complexity. It's built for:</p>
<ul>
<li>Personal Git hosting servers</li>
<li>Small teams that need CI/CD without the overhead</li>
<li>Developers who want to understand and customize their Git platform</li>
<li>Anyone tired of managing ten different services just for version control</li>
</ul>
<p>The result? A single Rails app with PostgreSQL, minimal external dependencies, and a codebase you can actually understand and modify.</p>
<h2>The Frontend Problem: Why I Almost Chose a JavaScript Framework</h2>
<p>When I started Gisia, I faced a common dilemma: build a SPA with React/Vue, or keep it simple?</p>
<p>I chose Rails + Hotwired, and it saved me <strong>weeks</strong> of development time.</p>
<h3>The Problem with Heavy Frontend Frameworks</h3>
<p>Most modern Git hosting UIs involve:</p>
<ul>
<li>Merge request creation and editing</li>
<li>Real-time status updates for CI/CD pipelines</li>
<li>Interactive forms and modals</li>
<li>Live notifications</li>
</ul>
<p>Traditionally, this means building a JavaScript SPA with:</p>
<ul>
<li>React/Vue/Svelte + separate frontend codebase</li>
<li>Complex build tools (Webpack, Vite, etc.)</li>
<li>NPM dependency management and lock files</li>
<li>Redux/Zustand/Pinia state management</li>
<li>API contracts to maintain between frontend and backend</li>
</ul>
<p>With a 20,000+ line codebase, you're managing <strong>two</strong> codebases instead of one.</p>
<h3>Hotwired to the Rescue</h3>
<p>Gisia uses <strong>Turbo</strong> and <strong>Stimulus</strong> – Rails' secret weapons for modern interactivity without the JavaScript bloat.</p>
<p><strong>Turbo</strong> handles navigation and form submissions elegantly:</p>
<pre><code class="language-html">&lt;%= form_with model: @merge_request, local: true do |f| %&gt;
  &lt;%= f.text_field :title %&gt;
  &lt;%= f.submit "Create", data: { turbo: true } %&gt;
&lt;% end %&gt;</code></pre>
<p>No API endpoints needed. No JSON parsing. Just server-rendered HTML.</p>
<p><strong>Stimulus</strong> handles the few cases where you need client-side interactivity:</p>
<pre><code class="language-javascript">// app/javascript/controllers/color_picker_controller.js
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static targets = ["input", "preview"]

  change(event) {
    this.previewTarget.style.backgroundColor = event.target.value
    this.inputTarget.value = event.target.value
  }
}</code></pre>
<p>That's it. A few lines of JavaScript for interactive color picking on label creation. In React? You'd have 50+ lines of hooks and state management.</p>
<p><strong>The result</strong>: Gisia's entire JavaScript footprint is tiny. No webpack, no npm dependency hell, just simple, declarative controllers that enhance server-rendered HTML.</p>
<h2>Solid Queue: Simplifying Background Jobs for CI/CD</h2>
<p>Gisia includes CI/CD pipeline support – running builds, executing jobs, storing artifacts. This traditionally means:</p>
<ul>
<li>Sidekiq + Redis setup</li>
<li>Complex job retry logic</li>
<li>Deployment headaches</li>
</ul>
<p><strong>Solid Queue</strong> changed everything. It's Rails' new job processing framework, and it's <em>fast</em>.</p>
<pre><code class="language-ruby"># app/jobs/ci/build_job.rb
class Ci::BuildJob &lt; ApplicationJob
  queue_as :default

  def perform(build_id)
    build = Ci::Build.find(build_id)
    build.execute!
  end
end</code></pre>
<p>No Redis. No worker processes to manage. Solid Queue uses your existing PostgreSQL database and processes jobs in the background. For Gisia, this means:</p>
<ul>
<li>CI/CD pipelines run smoothly without extra infrastructure</li>
<li>Job execution is straightforward and debuggable</li>
<li>Scaling is as simple as adding more Rails processes</li>
</ul>
<p>Compare this to GitLab's architecture where you need separate runner infrastructure, Redis for job queues, and complex coordination. Gisia handles it all with Rails + PostgreSQL.</p>
<h2>Rails Architecture: Built for Speed</h2>
<p>Here's how Gisia is structured, and why Rails conventions saved me massive amounts of time:</p>
<h3>Multi-Database Setup</h3>
<pre><code class="language-ruby"># config/database.yml
primary:
  database: gisia_development
cache:
  database: gisia_cache_development
queue:
  database: gisia_queue_development</code></pre>
<p>Rails handles this seamlessly. No custom connection logic needed.</p>
<h3>Authentication &amp; Authorization</h3>
<p>Devise handles user auth. Custom Pundit-style policies handle permissions:</p>
<pre><code class="language-ruby"># app/policies/project_policy.rb
class ProjectPolicy
  def update?
    user.namespace.projects.include?(project)
  end
end</code></pre>
<p>Simple, declarative, and testable.</p>
<h3>Business Logic in Models and Concerns</h3>
<p>Complex operations live in models and concerns, keeping controllers thin and logic organized. In Gisia, the MergeRequest model handles associations, validations, and query scopes directly. Concerns (like <code>MergeRequests::Status</code> and <code>MergeRequests::MergeStatus</code>) handle cross-cutting logic for state transitions and merge behavior. Instance methods encapsulate specific behavior like calculating commit counts.</p>
<p>This approach keeps business logic close to the data where it belongs. Controllers stay thin (under 15 lines per action), and everything is testable on the model without complex setup or service layer indirection.</p>
<h2>Real Examples from Gisia</h2>
<h3>Interactive Label Management</h3>
<p>Creating labels with custom colors uses Stimulus controllers for a clean, interactive experience:</p>
<pre><code class="language-javascript">// app/javascript/controllers/color_picker_controller.js
import { Controller } from '@hotwired/stimulus'

export default class extends Controller {
  static targets = ['preview']

  updatePreview() {
    const color = this.element.querySelector('input[type="text"]').value
    if (/^#[0-9A-F]{6}$/i.test(color)) {
      this.previewTarget.style.backgroundColor = color
    }
  }

  selectColor(event) {
    event.preventDefault()
    const color = event.target.dataset.colorValue
    const input = this.element.querySelector('input[type="text"]')
    input.value = color
    this.previewTarget.style.backgroundColor = color
  }
}</code></pre>
<p>And the Label model is refreshingly simple:</p>
<pre><code class="language-ruby">class Label &lt; ApplicationRecord
  belongs_to :namespace
  has_many :label_work_items, dependent: :destroy
  has_many :work_items, through: :label_work_items

  validates :title, presence: true
  validates :color, presence: true
  validates :namespace_id, presence: true
end</code></pre>
<p>That's all you need. A few lines of Stimulus for color preview, a simple model with validations. No React, no Vue, no build pipeline complexity.</p>
<h3>Real-Time Pipeline Updates (Planned)</h3>
<p>Gisia's future real-time pipeline updates will leverage Turbo Streams for seamless status updates:</p>
<pre><code class="language-erb">&lt;%= turbo_stream_from @pipeline, :builds %&gt;
&lt;div id="&lt;%= dom_id(@pipeline) %&gt;"&gt;
  &lt;% @pipeline.builds.each do |build| %&gt;
    &lt;%= render build %&gt;
  &lt;% end %&gt;
&lt;/div&gt;</code></pre>
<p>When a build completes (via Solid Queue jobs), a server-side event will broadcast updates. The browser automatically re-renders. Zero custom JavaScript orchestration needed.</p>
<h2>Development Speed: The Real Win</h2>
<p>Here's what matters: <strong>I built Gisia faster than I could have with any other tech stack.</strong></p>
<p>A simple tech stack removes friction. With Rails, you get everything built-in: database migrations, ORM, testing framework, asset pipeline, background jobs, routing, authentication. You don't need to piece together 10 different libraries or spend days configuring build tools.</p>
<p>Compare this to a JavaScript SPA approach: you'd need to choose a frontend framework, pick a state management solution, configure a build tool (Webpack/Vite), decide on an API structure, set up API authentication, handle CORS, and manage environment variables across frontend and backend. Each decision adds complexity and time.</p>
<p>With Rails, these decisions are already made for you. You can focus on building features instead of configuring tooling. The conventional structure means new developers onboard faster. The opinionated defaults eliminate bikeshedding.</p>
<p>For Gisia specifically, this meant:</p>
<ul>
<li>Building merge request logic in days instead of weeks</li>
<li>Implementing CI/CD pipelines without wrestling with job queues</li>
<li>Adding interactive features with Stimulus without learning a complex JavaScript framework</li>
<li>Writing tests that actually catch bugs, not fighting test setup</li>
</ul>
<p>That's the Rails advantage: simplicity and velocity.</p>
<h2>The Result</h2>
<p>Gisia is a fully-featured Git hosting platform built in <strong>Rails 8</strong> with:</p>
<ul>
<li>User authentication and authorization</li>
<li>Project and namespace management</li>
<li>Merge requests with code review</li>
<li>CI/CD pipelines and builds</li>
<li>Real-time status updates</li>
<li>Interactive UI without heavy frontend frameworks</li>
</ul>
<p>The entire codebase is maintainable, understandable, and ready for customization. All possible because Rails + Hotwired eliminated the JavaScript complexity that would have bloated the project.</p>
<h2>Why Rails is Still the GOAT</h2>
<p>Nowadays, people tend to reach for JavaScript frameworks, microservices, or trendy new languages. But Rails has strengths that remain unmatched:</p>
<ul>
<li><strong>For web applications</strong>, nothing is faster than Rails. Period.</li>
<li><strong>For teams</strong>, Rails conventions eliminate endless bikeshedding.</li>
<li><strong>For complexity</strong>, Rails handles databases, authentication, background jobs, and real-time features seamlessly.</li>
<li><strong>For learning</strong>, a Rails codebase teaches you more about web development than 10 JavaScript frameworks.</li>
</ul>
<p>Gisia proves it. I built a GitLab alternative with:</p>
<ul>
<li>Minimal JavaScript footprint (no build pipeline overhead)</li>
<li>Minimal external dependencies</li>
<li>Clean, understandable code</li>
<li>Incredible development velocity</li>
</ul>
<p>If you're building a web app in 2025, don't sleep on Rails. Especially if you're tired of JavaScript complexity.</p>
<h2>Try Gisia</h2>
<p>If you're interested in personal Git hosting or want to see Rails architecture in action, check out <a href="https://github.com/gisiahq/gisia">Gisia on GitHub</a>.</p>
<p>Rails isn't dead. It's just better than ever.</p>]]></summary>
            <content type="html"><![CDATA[<p><img src="/upload/images/2025/10/gisa-rails-development-experience.jpg" alt="gisa-rails-development-experience" /></p>
<p>When I decided to build a personal Git hosting server, I had a choice: self-host GitLab or build something lighter. I chose the latter, and here's why Rails made it ridiculously fast to build <strong>Gisia</strong> – a lightweight alternative that proves Rails is still the king for modern web applications.</p>
<h2>Why Not GitLab? Why Build Gisia Instead?</h2>
<p>GitLab is incredible, but it's also <strong>heavy</strong>. Here's the reality:</p>
<ul>
<li><strong>Resource hungry</strong>: Requires Vue.js, Elasticsearch, and more. Just getting it running locally takes significant setup.</li>
<li><strong>Bloated for small teams</strong>: If you just need Git hosting + basic CI/CD, you're carrying around features you'll never use.</li>
<li><strong>Overkill for personal/small-team use</strong>: The complexity overhead isn't worth it when you just want a focused, maintainable codebase.</li>
</ul>
<p>Gisia takes inspiration from GitLab's feature set but strips away the unnecessary complexity. It's built for:</p>
<ul>
<li>Personal Git hosting servers</li>
<li>Small teams that need CI/CD without the overhead</li>
<li>Developers who want to understand and customize their Git platform</li>
<li>Anyone tired of managing ten different services just for version control</li>
</ul>
<p>The result? A single Rails app with PostgreSQL, minimal external dependencies, and a codebase you can actually understand and modify.</p>
<h2>The Frontend Problem: Why I Almost Chose a JavaScript Framework</h2>
<p>When I started Gisia, I faced a common dilemma: build a SPA with React/Vue, or keep it simple?</p>
<p>I chose Rails + Hotwired, and it saved me <strong>weeks</strong> of development time.</p>
<h3>The Problem with Heavy Frontend Frameworks</h3>
<p>Most modern Git hosting UIs involve:</p>
<ul>
<li>Merge request creation and editing</li>
<li>Real-time status updates for CI/CD pipelines</li>
<li>Interactive forms and modals</li>
<li>Live notifications</li>
</ul>
<p>Traditionally, this means building a JavaScript SPA with:</p>
<ul>
<li>React/Vue/Svelte + separate frontend codebase</li>
<li>Complex build tools (Webpack, Vite, etc.)</li>
<li>NPM dependency management and lock files</li>
<li>Redux/Zustand/Pinia state management</li>
<li>API contracts to maintain between frontend and backend</li>
</ul>
<p>With a 20,000+ line codebase, you're managing <strong>two</strong> codebases instead of one.</p>
<h3>Hotwired to the Rescue</h3>
<p>Gisia uses <strong>Turbo</strong> and <strong>Stimulus</strong> – Rails' secret weapons for modern interactivity without the JavaScript bloat.</p>
<p><strong>Turbo</strong> handles navigation and form submissions elegantly:</p>
<pre><code class="language-html">&lt;%= form_with model: @merge_request, local: true do |f| %&gt;
  &lt;%= f.text_field :title %&gt;
  &lt;%= f.submit "Create", data: { turbo: true } %&gt;
&lt;% end %&gt;</code></pre>
<p>No API endpoints needed. No JSON parsing. Just server-rendered HTML.</p>
<p><strong>Stimulus</strong> handles the few cases where you need client-side interactivity:</p>
<pre><code class="language-javascript">// app/javascript/controllers/color_picker_controller.js
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static targets = ["input", "preview"]

  change(event) {
    this.previewTarget.style.backgroundColor = event.target.value
    this.inputTarget.value = event.target.value
  }
}</code></pre>
<p>That's it. A few lines of JavaScript for interactive color picking on label creation. In React? You'd have 50+ lines of hooks and state management.</p>
<p><strong>The result</strong>: Gisia's entire JavaScript footprint is tiny. No webpack, no npm dependency hell, just simple, declarative controllers that enhance server-rendered HTML.</p>
<h2>Solid Queue: Simplifying Background Jobs for CI/CD</h2>
<p>Gisia includes CI/CD pipeline support – running builds, executing jobs, storing artifacts. This traditionally means:</p>
<ul>
<li>Sidekiq + Redis setup</li>
<li>Complex job retry logic</li>
<li>Deployment headaches</li>
</ul>
<p><strong>Solid Queue</strong> changed everything. It's Rails' new job processing framework, and it's <em>fast</em>.</p>
<pre><code class="language-ruby"># app/jobs/ci/build_job.rb
class Ci::BuildJob &lt; ApplicationJob
  queue_as :default

  def perform(build_id)
    build = Ci::Build.find(build_id)
    build.execute!
  end
end</code></pre>
<p>No Redis. No worker processes to manage. Solid Queue uses your existing PostgreSQL database and processes jobs in the background. For Gisia, this means:</p>
<ul>
<li>CI/CD pipelines run smoothly without extra infrastructure</li>
<li>Job execution is straightforward and debuggable</li>
<li>Scaling is as simple as adding more Rails processes</li>
</ul>
<p>Compare this to GitLab's architecture where you need separate runner infrastructure, Redis for job queues, and complex coordination. Gisia handles it all with Rails + PostgreSQL.</p>
<h2>Rails Architecture: Built for Speed</h2>
<p>Here's how Gisia is structured, and why Rails conventions saved me massive amounts of time:</p>
<h3>Multi-Database Setup</h3>
<pre><code class="language-ruby"># config/database.yml
primary:
  database: gisia_development
cache:
  database: gisia_cache_development
queue:
  database: gisia_queue_development</code></pre>
<p>Rails handles this seamlessly. No custom connection logic needed.</p>
<h3>Authentication &amp; Authorization</h3>
<p>Devise handles user auth. Custom Pundit-style policies handle permissions:</p>
<pre><code class="language-ruby"># app/policies/project_policy.rb
class ProjectPolicy
  def update?
    user.namespace.projects.include?(project)
  end
end</code></pre>
<p>Simple, declarative, and testable.</p>
<h3>Business Logic in Models and Concerns</h3>
<p>Complex operations live in models and concerns, keeping controllers thin and logic organized. In Gisia, the MergeRequest model handles associations, validations, and query scopes directly. Concerns (like <code>MergeRequests::Status</code> and <code>MergeRequests::MergeStatus</code>) handle cross-cutting logic for state transitions and merge behavior. Instance methods encapsulate specific behavior like calculating commit counts.</p>
<p>This approach keeps business logic close to the data where it belongs. Controllers stay thin (under 15 lines per action), and everything is testable on the model without complex setup or service layer indirection.</p>
<h2>Real Examples from Gisia</h2>
<h3>Interactive Label Management</h3>
<p>Creating labels with custom colors uses Stimulus controllers for a clean, interactive experience:</p>
<pre><code class="language-javascript">// app/javascript/controllers/color_picker_controller.js
import { Controller } from '@hotwired/stimulus'

export default class extends Controller {
  static targets = ['preview']

  updatePreview() {
    const color = this.element.querySelector('input[type="text"]').value
    if (/^#[0-9A-F]{6}$/i.test(color)) {
      this.previewTarget.style.backgroundColor = color
    }
  }

  selectColor(event) {
    event.preventDefault()
    const color = event.target.dataset.colorValue
    const input = this.element.querySelector('input[type="text"]')
    input.value = color
    this.previewTarget.style.backgroundColor = color
  }
}</code></pre>
<p>And the Label model is refreshingly simple:</p>
<pre><code class="language-ruby">class Label &lt; ApplicationRecord
  belongs_to :namespace
  has_many :label_work_items, dependent: :destroy
  has_many :work_items, through: :label_work_items

  validates :title, presence: true
  validates :color, presence: true
  validates :namespace_id, presence: true
end</code></pre>
<p>That's all you need. A few lines of Stimulus for color preview, a simple model with validations. No React, no Vue, no build pipeline complexity.</p>
<h3>Real-Time Pipeline Updates (Planned)</h3>
<p>Gisia's future real-time pipeline updates will leverage Turbo Streams for seamless status updates:</p>
<pre><code class="language-erb">&lt;%= turbo_stream_from @pipeline, :builds %&gt;
&lt;div id="&lt;%= dom_id(@pipeline) %&gt;"&gt;
  &lt;% @pipeline.builds.each do |build| %&gt;
    &lt;%= render build %&gt;
  &lt;% end %&gt;
&lt;/div&gt;</code></pre>
<p>When a build completes (via Solid Queue jobs), a server-side event will broadcast updates. The browser automatically re-renders. Zero custom JavaScript orchestration needed.</p>
<h2>Development Speed: The Real Win</h2>
<p>Here's what matters: <strong>I built Gisia faster than I could have with any other tech stack.</strong></p>
<p>A simple tech stack removes friction. With Rails, you get everything built-in: database migrations, ORM, testing framework, asset pipeline, background jobs, routing, authentication. You don't need to piece together 10 different libraries or spend days configuring build tools.</p>
<p>Compare this to a JavaScript SPA approach: you'd need to choose a frontend framework, pick a state management solution, configure a build tool (Webpack/Vite), decide on an API structure, set up API authentication, handle CORS, and manage environment variables across frontend and backend. Each decision adds complexity and time.</p>
<p>With Rails, these decisions are already made for you. You can focus on building features instead of configuring tooling. The conventional structure means new developers onboard faster. The opinionated defaults eliminate bikeshedding.</p>
<p>For Gisia specifically, this meant:</p>
<ul>
<li>Building merge request logic in days instead of weeks</li>
<li>Implementing CI/CD pipelines without wrestling with job queues</li>
<li>Adding interactive features with Stimulus without learning a complex JavaScript framework</li>
<li>Writing tests that actually catch bugs, not fighting test setup</li>
</ul>
<p>That's the Rails advantage: simplicity and velocity.</p>
<h2>The Result</h2>
<p>Gisia is a fully-featured Git hosting platform built in <strong>Rails 8</strong> with:</p>
<ul>
<li>User authentication and authorization</li>
<li>Project and namespace management</li>
<li>Merge requests with code review</li>
<li>CI/CD pipelines and builds</li>
<li>Real-time status updates</li>
<li>Interactive UI without heavy frontend frameworks</li>
</ul>
<p>The entire codebase is maintainable, understandable, and ready for customization. All possible because Rails + Hotwired eliminated the JavaScript complexity that would have bloated the project.</p>
<h2>Why Rails is Still the GOAT</h2>
<p>Nowadays, people tend to reach for JavaScript frameworks, microservices, or trendy new languages. But Rails has strengths that remain unmatched:</p>
<ul>
<li><strong>For web applications</strong>, nothing is faster than Rails. Period.</li>
<li><strong>For teams</strong>, Rails conventions eliminate endless bikeshedding.</li>
<li><strong>For complexity</strong>, Rails handles databases, authentication, background jobs, and real-time features seamlessly.</li>
<li><strong>For learning</strong>, a Rails codebase teaches you more about web development than 10 JavaScript frameworks.</li>
</ul>
<p>Gisia proves it. I built a GitLab alternative with:</p>
<ul>
<li>Minimal JavaScript footprint (no build pipeline overhead)</li>
<li>Minimal external dependencies</li>
<li>Clean, understandable code</li>
<li>Incredible development velocity</li>
</ul>
<p>If you're building a web app in 2025, don't sleep on Rails. Especially if you're tired of JavaScript complexity.</p>
<h2>Try Gisia</h2>
<p>If you're interested in personal Git hosting or want to see Rails architecture in action, check out <a href="https://github.com/gisiahq/gisia">Gisia on GitHub</a>.</p>
<p>Rails isn't dead. It's just better than ever.</p>]]></content>
            <updated>2025-10-28T11:24:59-06:00</updated>
        </entry>
            <entry>
            <author>
                <name>Martin Tan</name>
            </author>
            <title type="text"><![CDATA[Kubernetes Rails Sidekiq Liveness Probe]]></title>
            <link rel="alternate" type="text/html" href="https://mrtan.me/post/kubernetes-rails-sidekiq-liveness-probe.html"></link>
            <id>https://mrtan.me/post/kubernetes-rails-sidekiq-liveness-probe.html</id>
            <summary type="html"><![CDATA[<p><img src="/upload/images/2020/12/sidekiq-errors-away.png" alt="sidekiq-errors-away" /></p>
<p>Before we dive into the details, I'd like to say that you may don't need a Sidekiq liveness probe if you only get a few Sidekiq instances  on production and it's integrated with an error tracking system. Better  to keep things simple.</p>
<h2>The Issue</h2>
<p>The only issue I've encountered on Production about Sidekiq workers not  working is the outage of Redis. Sidekiq noticed this but didn't restart, just pending there. It's a good design since lots of companies these  days still not using docker.</p>
<p>From the <a href="https://github.com/mperham/sidekiq/blob/66a815524801b5f58c62930c9e22bf8582dc5b43/lib/sidekiq/launcher.rb#L181">Source Code</a> of Sidekiq, we can see that it catches all the errors, so the Sidekiq process will keep running in the background.</p>
<pre><code class="language-ruby">rescue =&gt; e
  # ignore all redis/network issues
  logger.error("heartbeat: #{e}")
  # don't lose the counts if there was a network issue
  Processor::PROCESSED.incr(procd)
  Processor::FAILURE.incr(fails)
end</code></pre>
<p>Here are the Sidekiq error messages when Redis server is down.</p>
<pre><code class="language-shell">2020-12-20T10:47:09.966Z pid=12434 tid=6ka ERROR: heartbeat: Error connecting to Redis on 127.0.0.1:6379 (Errno::ECONNREFUSED)
2020-12-20T10:47:44.693Z pid=12434 tid=6jy WARN: Redis::CannotConnectError: Error connecting to Redis on 127.0.0.1:6379 (Errno::ECONNREFUSED)</code></pre>
<h2>Solution</h2>
<p>The solution is simple, you can just integrate an  error tracking service into your project, for example, Rollbar, to log  all the errors on production and create an alert message to your Slack  channel.</p>
<p>I've tested it on my local, it runs out that Rollbar works well on this kind of thing.</p>
<p><img src="/upload/images/2020/12/sidekiq-rollbar-log.png" alt="sidekiq-rollbar-log" /></p>
<p>But we can take a further step forward, I'd like to make sure no job will be lost after restart Sidekiq.</p>
<h2>How to restart Sidekiq without loosing Jobs</h2>
<p>When the job is processing in the background, you should not just kill the Sidekiq process directly, instead we want to restart it gracefully.</p>
<p>System provides Job Signals to interact with background jobs, we call it Job  Control, and we can tell the job to stop ingesting new jobs by sending  signals.</p>
<p>Sidekiq responds to <a href="https://github.com/mperham/sidekiq/wiki/Signals">Several Signals</a>, the signal that can be used here is <a href="https://www.gnu.org/software/libc/manual/html_node/Job-Control-Signals.html">SIGTSTP</a>.</p>
<blockquote>
<p>The SIGTSTP signal is an interactive stop signal. Unlike SIGSTOP, this signal can be handled and ignored.</p>
<p>Your program should handle this signal if you have a special need to leave  files or system tables in a secure state when a process is stopped. For  example, programs that turn off echoing should handle SIGTSTP so they  can turn echoing back on before stopping.</p>
</blockquote>
<p>Finally, we get the right signal for stopping the background job, but  when we send this signal to Sidekiq. Since I'm using Kubernetes as our  pods' management, the following tutorial is about the implementation in  K8s.</p>
<h2>How to implement Sidekiq Probes In Kubernetes</h2>
<p>The first thing about when to send the signal is that we must figure out  the Lifecycle of Kubernetes pods termination. It looks like this:</p>
<ol>
<li>The pod is set to the <code>Terminating</code> State and stop getting new traffic</li>
<li><code>preStop Hook</code> is executed</li>
<li><code>SIGTERM</code> signal is sent to the pod</li>
<li>Kubernetes waits for a grace period</li>
<li><code>SIGKILL</code> signal is sent to the pod, and the pod is removed</li>
</ol>
<p>To set a gracefully restart, we can send the signal <code>SIGTSTP</code> at #2 stage <code>proStop Hook</code> with a <code>terminationGracePeriodSeconds</code> value to let the  processing jobs finally to be done.</p>
<p>There is already a Gem to support<a href="https://github.com/arturictus/sidekiq_alive"> Sidekiq Liveness Probe</a> and <code>readinessProbe</code> .</p>
<pre><code>spec:
  containers:
    - name: my_app
      image: my_app:latest
      env:
        - name: RAILS_ENV
          value: production
      command:
        - bundle
        - exec
        - sidekiq
      ports:
        - containerPort: 7433
      livenessProbe:
        httpGet:
          path: /
          port: 7433
        initialDelaySeconds: 80 # app specific. Time your sidekiq takes to start processing.
        timeoutSeconds: 5 # can be much less
      readinessProbe:
        httpGet:
          path: /
          port: 7433
        initialDelaySeconds: 80 # app specific
        timeoutSeconds: 5 # can be much less
      lifecycle:
        preStop:
          exec:
            # SIGTERM triggers a quick exit; gracefully terminate instead
            command: ["kube/sidekiq_quiet"]
  terminationGracePeriodSeconds: 60 # put your longest Job time here plus security time.</code></pre>
<p>Please aware that you'd better not run time-consuming jobs, or you may see your Sidekiq pods in <code>Terminating</code> status in a long time.</p>]]></summary>
            <content type="html"><![CDATA[<p><img src="/upload/images/2020/12/sidekiq-errors-away.png" alt="sidekiq-errors-away" /></p>
<p>Before we dive into the details, I'd like to say that you may don't need a Sidekiq liveness probe if you only get a few Sidekiq instances  on production and it's integrated with an error tracking system. Better  to keep things simple.</p>
<h2>The Issue</h2>
<p>The only issue I've encountered on Production about Sidekiq workers not  working is the outage of Redis. Sidekiq noticed this but didn't restart, just pending there. It's a good design since lots of companies these  days still not using docker.</p>
<p>From the <a href="https://github.com/mperham/sidekiq/blob/66a815524801b5f58c62930c9e22bf8582dc5b43/lib/sidekiq/launcher.rb#L181">Source Code</a> of Sidekiq, we can see that it catches all the errors, so the Sidekiq process will keep running in the background.</p>
<pre><code class="language-ruby">rescue =&gt; e
  # ignore all redis/network issues
  logger.error("heartbeat: #{e}")
  # don't lose the counts if there was a network issue
  Processor::PROCESSED.incr(procd)
  Processor::FAILURE.incr(fails)
end</code></pre>
<p>Here are the Sidekiq error messages when Redis server is down.</p>
<pre><code class="language-shell">2020-12-20T10:47:09.966Z pid=12434 tid=6ka ERROR: heartbeat: Error connecting to Redis on 127.0.0.1:6379 (Errno::ECONNREFUSED)
2020-12-20T10:47:44.693Z pid=12434 tid=6jy WARN: Redis::CannotConnectError: Error connecting to Redis on 127.0.0.1:6379 (Errno::ECONNREFUSED)</code></pre>
<h2>Solution</h2>
<p>The solution is simple, you can just integrate an  error tracking service into your project, for example, Rollbar, to log  all the errors on production and create an alert message to your Slack  channel.</p>
<p>I've tested it on my local, it runs out that Rollbar works well on this kind of thing.</p>
<p><img src="/upload/images/2020/12/sidekiq-rollbar-log.png" alt="sidekiq-rollbar-log" /></p>
<p>But we can take a further step forward, I'd like to make sure no job will be lost after restart Sidekiq.</p>
<h2>How to restart Sidekiq without loosing Jobs</h2>
<p>When the job is processing in the background, you should not just kill the Sidekiq process directly, instead we want to restart it gracefully.</p>
<p>System provides Job Signals to interact with background jobs, we call it Job  Control, and we can tell the job to stop ingesting new jobs by sending  signals.</p>
<p>Sidekiq responds to <a href="https://github.com/mperham/sidekiq/wiki/Signals">Several Signals</a>, the signal that can be used here is <a href="https://www.gnu.org/software/libc/manual/html_node/Job-Control-Signals.html">SIGTSTP</a>.</p>
<blockquote>
<p>The SIGTSTP signal is an interactive stop signal. Unlike SIGSTOP, this signal can be handled and ignored.</p>
<p>Your program should handle this signal if you have a special need to leave  files or system tables in a secure state when a process is stopped. For  example, programs that turn off echoing should handle SIGTSTP so they  can turn echoing back on before stopping.</p>
</blockquote>
<p>Finally, we get the right signal for stopping the background job, but  when we send this signal to Sidekiq. Since I'm using Kubernetes as our  pods' management, the following tutorial is about the implementation in  K8s.</p>
<h2>How to implement Sidekiq Probes In Kubernetes</h2>
<p>The first thing about when to send the signal is that we must figure out  the Lifecycle of Kubernetes pods termination. It looks like this:</p>
<ol>
<li>The pod is set to the <code>Terminating</code> State and stop getting new traffic</li>
<li><code>preStop Hook</code> is executed</li>
<li><code>SIGTERM</code> signal is sent to the pod</li>
<li>Kubernetes waits for a grace period</li>
<li><code>SIGKILL</code> signal is sent to the pod, and the pod is removed</li>
</ol>
<p>To set a gracefully restart, we can send the signal <code>SIGTSTP</code> at #2 stage <code>proStop Hook</code> with a <code>terminationGracePeriodSeconds</code> value to let the  processing jobs finally to be done.</p>
<p>There is already a Gem to support<a href="https://github.com/arturictus/sidekiq_alive"> Sidekiq Liveness Probe</a> and <code>readinessProbe</code> .</p>
<pre><code>spec:
  containers:
    - name: my_app
      image: my_app:latest
      env:
        - name: RAILS_ENV
          value: production
      command:
        - bundle
        - exec
        - sidekiq
      ports:
        - containerPort: 7433
      livenessProbe:
        httpGet:
          path: /
          port: 7433
        initialDelaySeconds: 80 # app specific. Time your sidekiq takes to start processing.
        timeoutSeconds: 5 # can be much less
      readinessProbe:
        httpGet:
          path: /
          port: 7433
        initialDelaySeconds: 80 # app specific
        timeoutSeconds: 5 # can be much less
      lifecycle:
        preStop:
          exec:
            # SIGTERM triggers a quick exit; gracefully terminate instead
            command: ["kube/sidekiq_quiet"]
  terminationGracePeriodSeconds: 60 # put your longest Job time here plus security time.</code></pre>
<p>Please aware that you'd better not run time-consuming jobs, or you may see your Sidekiq pods in <code>Terminating</code> status in a long time.</p>]]></content>
            <updated>2020-12-21T22:40:28-06:00</updated>
        </entry>
            <entry>
            <author>
                <name>Martin Tan</name>
            </author>
            <title type="text"><![CDATA[Days Off-set at Chengdu WeWork]]></title>
            <link rel="alternate" type="text/html" href="https://mrtan.me/post/days-off-set-at-chengdu-wework.html"></link>
            <id>https://mrtan.me/post/days-off-set-at-chengdu-wework.html</id>
            <summary type="html"><![CDATA[<p>Days before the Chinese Spring Festival of 2020, I traveled to Chengdu city to avoid the annual travel peak. I'd like to thanks WeWork's policy to allow me to work remotely. Daily syncing goes really well on Slack and Wechat as what we always did in HQ. While keeping work on the track, it's also a good opportunity for me to visit some locations of WeWork in Chengdu.</p>
<p><img src="/upload/images/2020/01/lobby-of-pinnacle-one.jpg" alt="IMG_0239" /></p>
<p>Lobby of Pinnacle One</p>
<p>That morning, the sun just raise to the top-right corn of the glass wall, which gives me a very strong implicit feel that I'm in a scenario of Blade Runner.</p>
<p><img src="/upload/images/2020/01/pantry.jpg" alt="IMG_0248" /></p>
<p>Pantry of 40th floor</p>
<p><img src="/upload/images/2020/01/cityscape.jpg" alt="IMG_0297" /></p>
<p>It's a sunny day for local people, but the air was not clear, not only due to air pollution, but fog. From a famous proverb, dogs at Sichuan bark the sun (蜀犬吠日 in Chinese), you can get it. This proverb is a sarcasm that dogs here do not have much time to see the sun. Chengdu is located at the Sichuan plain and rounded by high mountains, it‘s foggy especially in winter.</p>
<p><img src="/upload/images/2020/01/tree-and-shadow.jpg" alt="tree-and-shadow" /></p>
<p>Even after years of high speed developing, still lots of spots remaining there as indicators of traditional local culture.
I found a peaceful zen view sitting at a corner of the main round when I was out for lunch on the first day. A slim and long tree and a grey wall, the shadow of the tree was waving on the wall when the wind breezed through. I was wondering why there is such a stylized building in this commercial district, it should be a patthana, I found Daci Temple (大慈寺 in Chinese) the day after.</p>
<p><img src="/upload/images/2020/01/daci-temple-location.png" alt="daci-temple-location" /></p>
<p>Daci Temple, which was destroyed and rebuilt several times in history, was founded by an Indian monk about 1600 years ago. But event today, it still has a strong vitality rooted in the local community. I saw many people pray for blessings at the temple and found some modernized gallery photos on a wall, all these are signs.</p>
<p><img src="/upload/images/2020/01/daci-temple-chengdu-praying.jpg" alt="daci-temple-chengdu-praying" /></p>
<p><img src="/upload/images/2020/01/daci-temple-galley-photo.jpg" alt="daci-temple-galley-photo" /></p>
<p><img src="/upload/images/2020/01/ancient-style-daci-temple.jpg" alt="ancient-style-daci-temple" /></p>
<p>The zen of East Asia temples are built internally, you don't have to look for it, it's always there.</p>
<p><img src="/upload/images/2020/01/daci-temple-arounded-by-buidlings.jpg" alt="daci-temple-arounded-by-buidlings" /></p>
<p>But, skyscrapers are rising, I can see the conflicts between a modern world and a traditional society. Hopes are still there.</p>]]></summary>
            <content type="html"><![CDATA[<p>Days before the Chinese Spring Festival of 2020, I traveled to Chengdu city to avoid the annual travel peak. I'd like to thanks WeWork's policy to allow me to work remotely. Daily syncing goes really well on Slack and Wechat as what we always did in HQ. While keeping work on the track, it's also a good opportunity for me to visit some locations of WeWork in Chengdu.</p>
<p><img src="/upload/images/2020/01/lobby-of-pinnacle-one.jpg" alt="IMG_0239" /></p>
<p>Lobby of Pinnacle One</p>
<p>That morning, the sun just raise to the top-right corn of the glass wall, which gives me a very strong implicit feel that I'm in a scenario of Blade Runner.</p>
<p><img src="/upload/images/2020/01/pantry.jpg" alt="IMG_0248" /></p>
<p>Pantry of 40th floor</p>
<p><img src="/upload/images/2020/01/cityscape.jpg" alt="IMG_0297" /></p>
<p>It's a sunny day for local people, but the air was not clear, not only due to air pollution, but fog. From a famous proverb, dogs at Sichuan bark the sun (蜀犬吠日 in Chinese), you can get it. This proverb is a sarcasm that dogs here do not have much time to see the sun. Chengdu is located at the Sichuan plain and rounded by high mountains, it‘s foggy especially in winter.</p>
<p><img src="/upload/images/2020/01/tree-and-shadow.jpg" alt="tree-and-shadow" /></p>
<p>Even after years of high speed developing, still lots of spots remaining there as indicators of traditional local culture.
I found a peaceful zen view sitting at a corner of the main round when I was out for lunch on the first day. A slim and long tree and a grey wall, the shadow of the tree was waving on the wall when the wind breezed through. I was wondering why there is such a stylized building in this commercial district, it should be a patthana, I found Daci Temple (大慈寺 in Chinese) the day after.</p>
<p><img src="/upload/images/2020/01/daci-temple-location.png" alt="daci-temple-location" /></p>
<p>Daci Temple, which was destroyed and rebuilt several times in history, was founded by an Indian monk about 1600 years ago. But event today, it still has a strong vitality rooted in the local community. I saw many people pray for blessings at the temple and found some modernized gallery photos on a wall, all these are signs.</p>
<p><img src="/upload/images/2020/01/daci-temple-chengdu-praying.jpg" alt="daci-temple-chengdu-praying" /></p>
<p><img src="/upload/images/2020/01/daci-temple-galley-photo.jpg" alt="daci-temple-galley-photo" /></p>
<p><img src="/upload/images/2020/01/ancient-style-daci-temple.jpg" alt="ancient-style-daci-temple" /></p>
<p>The zen of East Asia temples are built internally, you don't have to look for it, it's always there.</p>
<p><img src="/upload/images/2020/01/daci-temple-arounded-by-buidlings.jpg" alt="daci-temple-arounded-by-buidlings" /></p>
<p>But, skyscrapers are rising, I can see the conflicts between a modern world and a traditional society. Hopes are still there.</p>]]></content>
            <updated>2020-01-22T14:27:00-06:00</updated>
        </entry>
            <entry>
            <author>
                <name>Martin Tan</name>
            </author>
            <title type="text"><![CDATA[RabbittMQ Dealing with Failure]]></title>
            <link rel="alternate" type="text/html" href="https://mrtan.me/post/rabbittmq-dealing-with-failure.html"></link>
            <id>https://mrtan.me/post/rabbittmq-dealing-with-failure.html</id>
            <summary type="html"><![CDATA[<p><img src="/upload/images/2020/01/rabbitmq-ha.jpg" alt="rabbitmq-ha" /></p>
<h2>Goal</h2>
<p>To figure out a sample, but an implementable way of handling RabbitMQ failure. We are going to set up a RabbitMQ cluster on your local device with HAProxy as a load balancer, and synchronizing queues between different RabbitMQ nodes will also be included.</p>
<h2>Setup Environment</h2>
<p>In this post, I'm going to use Ubuntu 18.04.3 LTS and going to build RabbitMQ and HAProxy from the source  package with the latest version.</p>
<p>Ruby is the programming language for the following examples.</p>
<p>I'm not going to use the docker images which are ready on Docker hub. This time we can do it step by step to see how it works.</p>
<h3>Install Build Environment</h3>
<pre><code class="language-shell">sudo apt update
sudo apt -y install autoconf libncurses-dev build-essential
sudo apt -y install m4 unixodbc-dev libssl-dev libwxgtk3.0-dev libglu-dev fop xsltproc g++ default-jdk libxml2-utils</code></pre>
<h3>Install Ruby Bunny</h3>
<p>As described on the document, <code>bunny</code> is easy to use, feature-complete Ruby client for RabbitMQ.</p>
<pre><code class="language-shell">sudo apt -y install ruby
sudo gem install bunny</code></pre>
<h3>Install Erlang</h3>
<blockquote>
<p>Erlang, the programming language that Ericsson had originally developed for their telephone switching gear. What grabbed Matthias’s attention was that Erlang excelled at distributed programming and robust failure recovery.</p>
<p>RabbitMQ in Action: Distributed Messaging for Everyone
Book by Alvaro Videla and Jason J. W. Williams</p>
</blockquote>
<p>RabbitMQ is written by Erlang, also has the advantages of Erlang.</p>
<pre><code class="language-shell">wget http://erlang.org/download/otp_src_22.2.tar.gz
tar -zxf otp_src_22.2.tar.gz
cd otp_src_22.2
export ERL_TOP=`pwd`
./configure
make
sudo make install</code></pre>
<h3>Install RabbitMQ</h3>
<p>Thanks to Erlang VM, we don't need to rebuild the package, we can use RabbitMQ after unpacking it.</p>
<pre><code class="language-shell">cd ~
wget https://github.com/rabbitmq/rabbitmq-server/releases/download/v3.8.2/rabbitmq-server-generic-unix-3.8.2.tar.xz
tar -xf rabbitmq-server-generic-unix-3.8.2.tar.xz
cd rabbitmq_server-3.8.2</code></pre>
<h3>Install HAProxy</h3>
<pre><code class="language-shell"># Install HAProxy
wget http://www.haproxy.org/download/2.1/src/haproxy-2.1.2.tar.gz
tar xfz haproxy-2.1.2.tar.gz
cd haproxy-2.1.
make clean
make -j $(nproc) TARGET=linux-glibc
sudo make install</code></pre>
<h2>Setup RabbitMQ Cluster</h2>
<h3>Pull Up Servers</h3>
<p>First, we should create pull up three RabbitMQ servers in the background, and each one has its port.</p>
<pre><code class="language-shell"># Pull up servers
RABBITMQ_NODE_PORT=5672 RABBITMQ_NODENAME=rabbit ./sbin/rabbitmq-server --detached
RABBITMQ_NODE_PORT=5673 RABBITMQ_NODENAME=rabbit_1 ./sbin/rabbitmq-server -detached
RABBITMQ_NODE_PORT=5674 RABBITMQ_NODENAME=rabbit_2  ./sbin/rabbitmq-server -detached</code></pre>
<h3>Join Cluster</h3>
<p>Then, Join the <code>rabbit_1</code> and <code>rabbit2</code> to <code>rabbit@host01</code> to form a RabbitMQ cluster. To keep the servers pure, we have to stop them first and run reset.</p>
<pre><code class="language-shell"># Join cluster 
./sbin/rabbitmqctl -n rabbit_1@host01 stop_app
./sbin/rabbitmqctl -n rabbit_1@host01 reset
./sbin/rabbitmqctl -n rabbit_1@host01 join_cluster rabbit@host01

./sbin/rabbitmqctl -n rabbit_2@host01 stop_app
./sbin/rabbitmqctl -n rabbit_2@host01 reset
./sbin/rabbitmqctl -n rabbit_2@host01 join_cluster rabbit@host01</code></pre>
<h3>Run RabbitMQ Apps</h3>
<p>After the management enabled on the first node, we can access the dashboard by open <code>http://localhost:15672/#/</code>, or by running a command line <code>./sbin/rabbitmqctl cluster_status</code>.</p>
<pre><code class="language-shell"># Start RabbitMQ Apps
./sbin/rabbitmqctl  -n rabbit_1@host01 start_app
./sbin/rabbitmqctl  -n rabbit_2@host01 start_app
./sbin/rabbitmq-plugins enable rabbitmq_management
./sbin/rabbitmqctl -n rabbit_2@host01  cluster_status</code></pre>
<p><img src="/upload/images/2020/01/rabbitmq-cluster-status.png" alt="rabbitmq-cluster-status" /></p>
<h3>Remote Access</h3>
<p>If you do not visit the dashboard from <code>localhost</code>, you should setup a new user for remote access.</p>
<pre><code class="language-shell"># set a new user
./sbin/rabbitmqctl add_user test test
./sbin/rabbitmqctl set_user_tags test administrator
./sbin/rabbitmqctl set_permissions -p / test ".*" ".*" ".*"</code></pre>
<p><img src="/upload/images/2020/01/rabbitmq-cluster-overview.png" alt="rabbitmq-cluster-overview" /></p>
<p>All RabbitMQ nodes are up and running well in a cluster.</p>
<h2>RabbitMQ HAProxy Load Balance</h2>
<h3>HAProxy Configuration</h3>
<p>Here is an example of my local HAProxy configuration for three RabbitMQ nodes.</p>
<pre><code class="language-shell">global
    log 127.0.0.1   local0
    log 127.0.0.1   local1 notice
    maxconn 4096

defaults
    log global
    option tcplog
    option  dontlognull
    timeout connect 6s
    timeout client 60s
    timeout server 60s

listen  stats
    bind *:9000
    mode http
    option httplog
    stats enable
    stats hide-version
    stats realm Haproxy\ Statistics
    stats uri /

listen rabbitmq
    bind *:1999
    mode tcp
    option tcplog
    log stdout format raw daemon debug
    balance leastconn
    server rabbitmq localhost:5672 check
    server rabbitmq-01 localhost:5673 check
    server rabbitmq-02 localhost:5674 check
</code></pre>
<p>Now, you can run the <code>haproxy</code> with the configuration file you created.</p>
<pre><code class="language-shell"># start HAPorxy
haproxy -f haproxy.cfg</code></pre>
<h3>Send and Receive Messages</h3>
<h4>Code Example</h4>
<p><strong>send.rb</strong></p>
<pre><code class="language-ruby">#!/usr/bin/env ruby
require 'bunny'

connection = Bunny.new

connection.start
channel = connection.create_channel
queue = channel.queue('hello', durable: true)

channel.default_exchange.publish(ARGV[0], routing_key: queue.name)
puts " [x] Sent #{ARGV[0]}"

connection.close</code></pre>
<p><strong>receive.rb</strong></p>
<pre><code class="language-shell">#!/usr/bin/env ruby
require 'bunny'

connection = Bunny.new
connection.start

channel = connection.create_channel
queue = channel.queue('hello', :durable =&gt; true)

begin
  puts ' [*] Waiting for messages. To exit press CTRL+C'
  # block: true is only used to keep the main thread
  # alive. Please avoid using it in real world applications.
  queue.subscribe(block: true) do |_delivery_info, _properties, body|
    puts " [x] Received #{body}"
  end
rescue Interrupt =&gt; _
  connection.close

  exit(0)
end
</code></pre>
<h3>Load Balance Example</h3>
<h4>Step 1, send messages</h4>
<p><img src="/upload/images/2020/01/load-balance-message-send.png" alt="load-balance-message-send" /></p>
<h4>Step 2, view HAProxy logs</h4>
<p><img src="/upload/images/2020/01/load-balance-enqueue-log.png" alt="load-balance-enqueue-log" /></p>
<p>We can see that the load balancer is working very well, the connections are distributed to all three nodes, <code>rabbitmq</code>, <code>rabbitmq-01</code> and <code>rabbitmq-02</code>.</p>
<h2>Mirrored Queues</h2>
<p>In the case above, the messages of that queue are stored on only one node whose name is  <code>rabbitmq</code>. The queue will be unavailable if that node is down. But if we mirror the queues which we want to, the messages will be always available, even only one node left.</p>
<p><strong>command</strong></p>
<pre><code class="language-shell"># List queues
./sbin/rabbitmqctl -n rabbit_2@host01 list_queues name policy pid slave_pids synchronised_slave_pids</code></pre>
<p><strong>output</strong></p>
<pre><code class="language-shell">vagrant@host01:~/rabbitmq_server-3.8.2$ ./sbin/rabbitmqctl -n rabbit_2@host01 list_queues name policy pid slave_pids synchronised_slave_pids

Timeout: 60.0 seconds ...
Listing queues for vhost / ...
name    policy  pid slave_pids  synchronised_slave_pids
hello       &lt;rabbit_1@host01.1.3543.0&gt;</code></pre>
<h3>Enable Queue Mirror</h3>
<p>The following command will set all the queues with names started with <code>hello</code> to be mirrored. I'm very pleased to see the pattern that supports regex.</p>
<p><em>From what i know, this feature of declaring queue with policy is not supported in the latest version of rabbitmq. The follow code will not be working.</em></p>
<pre><code class="language-ruby">queue = ch.queue('hello', durable: true, arguments: { 'x-ha-policy' =&gt; 'all' })</code></pre>
<p><strong>command</strong></p>
<pre><code class="language-shell">./sbin/rabbitmqctl set_policy ha-all "^hello" '{"ha-mode":"all"}'</code></pre>
<p><strong>output</strong></p>
<pre><code class="language-shell">vagrant@host01:~/rabbitmq_server-3.8.2$ ./sbin/rabbitmqctl set_policy ha-all "^hello" '{"ha-mode":"all"}'
Setting policy "ha-all" for pattern "^hello" to "{"ha-mode":"all"}" with priority "0" for vhost "/" ...</code></pre>
<p>now we can see the queue is mirrored with the other two nodes, but the messages are not synced between nodes, you can run commandline <code>./sbin/rabbitmqctl sync_queue hello</code> or just click the button <code>Synchronise</code> on RabbitMQ dashboard.</p>
<p><img src="/upload/images/2020/01/list-mirrored-queue.png" alt="list-mirrored-queue" /></p>
<p><img src="/upload/images/2020/01/rabbitmq-dashboard-queue-mirrors.png" alt="rabbitmq-dashboard-queue-mirrors" /></p>
<p>Under this condition, the consumer client won't encounter any errors with nodes failure. The messages will be published and consumed smoothly.</p>
<h2>Recovery from Error on Code Level</h2>
<p>If you are using docker, you can set up your own policy for failure restart, also you can do it only code level as you like.
Here is the pseudo-code of how to handle it on language level by using <code>rescue</code> or <code>catch</code>.</p>
<pre><code class="language-ruby">def consume
  begin
    do_something_with_bunny
  rescue =&gt; e
    logger.error e
    do_something_with_bunny
  end
end</code></pre>
<p>Coming to conclusion, we have addressed four strategies on how to handle RabbitMQ failure.</p>
<ul>
<li>Using RabbitMQ Cluster to enable capacity.</li>
<li>Using load balance to improve availability.</li>
<li>Using mirrored queue to higher the durability.</li>
<li>Using code error handler to improve the elasticity.</li>
</ul>
<p><strong>References</strong></p>
<ul>
<li>Cover image from https://pixabay.com/vectors/rabbit-character-alice-in-wonderland-30751/</li>
</ul>]]></summary>
            <content type="html"><![CDATA[<p><img src="/upload/images/2020/01/rabbitmq-ha.jpg" alt="rabbitmq-ha" /></p>
<h2>Goal</h2>
<p>To figure out a sample, but an implementable way of handling RabbitMQ failure. We are going to set up a RabbitMQ cluster on your local device with HAProxy as a load balancer, and synchronizing queues between different RabbitMQ nodes will also be included.</p>
<h2>Setup Environment</h2>
<p>In this post, I'm going to use Ubuntu 18.04.3 LTS and going to build RabbitMQ and HAProxy from the source  package with the latest version.</p>
<p>Ruby is the programming language for the following examples.</p>
<p>I'm not going to use the docker images which are ready on Docker hub. This time we can do it step by step to see how it works.</p>
<h3>Install Build Environment</h3>
<pre><code class="language-shell">sudo apt update
sudo apt -y install autoconf libncurses-dev build-essential
sudo apt -y install m4 unixodbc-dev libssl-dev libwxgtk3.0-dev libglu-dev fop xsltproc g++ default-jdk libxml2-utils</code></pre>
<h3>Install Ruby Bunny</h3>
<p>As described on the document, <code>bunny</code> is easy to use, feature-complete Ruby client for RabbitMQ.</p>
<pre><code class="language-shell">sudo apt -y install ruby
sudo gem install bunny</code></pre>
<h3>Install Erlang</h3>
<blockquote>
<p>Erlang, the programming language that Ericsson had originally developed for their telephone switching gear. What grabbed Matthias’s attention was that Erlang excelled at distributed programming and robust failure recovery.</p>
<p>RabbitMQ in Action: Distributed Messaging for Everyone
Book by Alvaro Videla and Jason J. W. Williams</p>
</blockquote>
<p>RabbitMQ is written by Erlang, also has the advantages of Erlang.</p>
<pre><code class="language-shell">wget http://erlang.org/download/otp_src_22.2.tar.gz
tar -zxf otp_src_22.2.tar.gz
cd otp_src_22.2
export ERL_TOP=`pwd`
./configure
make
sudo make install</code></pre>
<h3>Install RabbitMQ</h3>
<p>Thanks to Erlang VM, we don't need to rebuild the package, we can use RabbitMQ after unpacking it.</p>
<pre><code class="language-shell">cd ~
wget https://github.com/rabbitmq/rabbitmq-server/releases/download/v3.8.2/rabbitmq-server-generic-unix-3.8.2.tar.xz
tar -xf rabbitmq-server-generic-unix-3.8.2.tar.xz
cd rabbitmq_server-3.8.2</code></pre>
<h3>Install HAProxy</h3>
<pre><code class="language-shell"># Install HAProxy
wget http://www.haproxy.org/download/2.1/src/haproxy-2.1.2.tar.gz
tar xfz haproxy-2.1.2.tar.gz
cd haproxy-2.1.
make clean
make -j $(nproc) TARGET=linux-glibc
sudo make install</code></pre>
<h2>Setup RabbitMQ Cluster</h2>
<h3>Pull Up Servers</h3>
<p>First, we should create pull up three RabbitMQ servers in the background, and each one has its port.</p>
<pre><code class="language-shell"># Pull up servers
RABBITMQ_NODE_PORT=5672 RABBITMQ_NODENAME=rabbit ./sbin/rabbitmq-server --detached
RABBITMQ_NODE_PORT=5673 RABBITMQ_NODENAME=rabbit_1 ./sbin/rabbitmq-server -detached
RABBITMQ_NODE_PORT=5674 RABBITMQ_NODENAME=rabbit_2  ./sbin/rabbitmq-server -detached</code></pre>
<h3>Join Cluster</h3>
<p>Then, Join the <code>rabbit_1</code> and <code>rabbit2</code> to <code>rabbit@host01</code> to form a RabbitMQ cluster. To keep the servers pure, we have to stop them first and run reset.</p>
<pre><code class="language-shell"># Join cluster 
./sbin/rabbitmqctl -n rabbit_1@host01 stop_app
./sbin/rabbitmqctl -n rabbit_1@host01 reset
./sbin/rabbitmqctl -n rabbit_1@host01 join_cluster rabbit@host01

./sbin/rabbitmqctl -n rabbit_2@host01 stop_app
./sbin/rabbitmqctl -n rabbit_2@host01 reset
./sbin/rabbitmqctl -n rabbit_2@host01 join_cluster rabbit@host01</code></pre>
<h3>Run RabbitMQ Apps</h3>
<p>After the management enabled on the first node, we can access the dashboard by open <code>http://localhost:15672/#/</code>, or by running a command line <code>./sbin/rabbitmqctl cluster_status</code>.</p>
<pre><code class="language-shell"># Start RabbitMQ Apps
./sbin/rabbitmqctl  -n rabbit_1@host01 start_app
./sbin/rabbitmqctl  -n rabbit_2@host01 start_app
./sbin/rabbitmq-plugins enable rabbitmq_management
./sbin/rabbitmqctl -n rabbit_2@host01  cluster_status</code></pre>
<p><img src="/upload/images/2020/01/rabbitmq-cluster-status.png" alt="rabbitmq-cluster-status" /></p>
<h3>Remote Access</h3>
<p>If you do not visit the dashboard from <code>localhost</code>, you should setup a new user for remote access.</p>
<pre><code class="language-shell"># set a new user
./sbin/rabbitmqctl add_user test test
./sbin/rabbitmqctl set_user_tags test administrator
./sbin/rabbitmqctl set_permissions -p / test ".*" ".*" ".*"</code></pre>
<p><img src="/upload/images/2020/01/rabbitmq-cluster-overview.png" alt="rabbitmq-cluster-overview" /></p>
<p>All RabbitMQ nodes are up and running well in a cluster.</p>
<h2>RabbitMQ HAProxy Load Balance</h2>
<h3>HAProxy Configuration</h3>
<p>Here is an example of my local HAProxy configuration for three RabbitMQ nodes.</p>
<pre><code class="language-shell">global
    log 127.0.0.1   local0
    log 127.0.0.1   local1 notice
    maxconn 4096

defaults
    log global
    option tcplog
    option  dontlognull
    timeout connect 6s
    timeout client 60s
    timeout server 60s

listen  stats
    bind *:9000
    mode http
    option httplog
    stats enable
    stats hide-version
    stats realm Haproxy\ Statistics
    stats uri /

listen rabbitmq
    bind *:1999
    mode tcp
    option tcplog
    log stdout format raw daemon debug
    balance leastconn
    server rabbitmq localhost:5672 check
    server rabbitmq-01 localhost:5673 check
    server rabbitmq-02 localhost:5674 check
</code></pre>
<p>Now, you can run the <code>haproxy</code> with the configuration file you created.</p>
<pre><code class="language-shell"># start HAPorxy
haproxy -f haproxy.cfg</code></pre>
<h3>Send and Receive Messages</h3>
<h4>Code Example</h4>
<p><strong>send.rb</strong></p>
<pre><code class="language-ruby">#!/usr/bin/env ruby
require 'bunny'

connection = Bunny.new

connection.start
channel = connection.create_channel
queue = channel.queue('hello', durable: true)

channel.default_exchange.publish(ARGV[0], routing_key: queue.name)
puts " [x] Sent #{ARGV[0]}"

connection.close</code></pre>
<p><strong>receive.rb</strong></p>
<pre><code class="language-shell">#!/usr/bin/env ruby
require 'bunny'

connection = Bunny.new
connection.start

channel = connection.create_channel
queue = channel.queue('hello', :durable =&gt; true)

begin
  puts ' [*] Waiting for messages. To exit press CTRL+C'
  # block: true is only used to keep the main thread
  # alive. Please avoid using it in real world applications.
  queue.subscribe(block: true) do |_delivery_info, _properties, body|
    puts " [x] Received #{body}"
  end
rescue Interrupt =&gt; _
  connection.close

  exit(0)
end
</code></pre>
<h3>Load Balance Example</h3>
<h4>Step 1, send messages</h4>
<p><img src="/upload/images/2020/01/load-balance-message-send.png" alt="load-balance-message-send" /></p>
<h4>Step 2, view HAProxy logs</h4>
<p><img src="/upload/images/2020/01/load-balance-enqueue-log.png" alt="load-balance-enqueue-log" /></p>
<p>We can see that the load balancer is working very well, the connections are distributed to all three nodes, <code>rabbitmq</code>, <code>rabbitmq-01</code> and <code>rabbitmq-02</code>.</p>
<h2>Mirrored Queues</h2>
<p>In the case above, the messages of that queue are stored on only one node whose name is  <code>rabbitmq</code>. The queue will be unavailable if that node is down. But if we mirror the queues which we want to, the messages will be always available, even only one node left.</p>
<p><strong>command</strong></p>
<pre><code class="language-shell"># List queues
./sbin/rabbitmqctl -n rabbit_2@host01 list_queues name policy pid slave_pids synchronised_slave_pids</code></pre>
<p><strong>output</strong></p>
<pre><code class="language-shell">vagrant@host01:~/rabbitmq_server-3.8.2$ ./sbin/rabbitmqctl -n rabbit_2@host01 list_queues name policy pid slave_pids synchronised_slave_pids

Timeout: 60.0 seconds ...
Listing queues for vhost / ...
name    policy  pid slave_pids  synchronised_slave_pids
hello       &lt;rabbit_1@host01.1.3543.0&gt;</code></pre>
<h3>Enable Queue Mirror</h3>
<p>The following command will set all the queues with names started with <code>hello</code> to be mirrored. I'm very pleased to see the pattern that supports regex.</p>
<p><em>From what i know, this feature of declaring queue with policy is not supported in the latest version of rabbitmq. The follow code will not be working.</em></p>
<pre><code class="language-ruby">queue = ch.queue('hello', durable: true, arguments: { 'x-ha-policy' =&gt; 'all' })</code></pre>
<p><strong>command</strong></p>
<pre><code class="language-shell">./sbin/rabbitmqctl set_policy ha-all "^hello" '{"ha-mode":"all"}'</code></pre>
<p><strong>output</strong></p>
<pre><code class="language-shell">vagrant@host01:~/rabbitmq_server-3.8.2$ ./sbin/rabbitmqctl set_policy ha-all "^hello" '{"ha-mode":"all"}'
Setting policy "ha-all" for pattern "^hello" to "{"ha-mode":"all"}" with priority "0" for vhost "/" ...</code></pre>
<p>now we can see the queue is mirrored with the other two nodes, but the messages are not synced between nodes, you can run commandline <code>./sbin/rabbitmqctl sync_queue hello</code> or just click the button <code>Synchronise</code> on RabbitMQ dashboard.</p>
<p><img src="/upload/images/2020/01/list-mirrored-queue.png" alt="list-mirrored-queue" /></p>
<p><img src="/upload/images/2020/01/rabbitmq-dashboard-queue-mirrors.png" alt="rabbitmq-dashboard-queue-mirrors" /></p>
<p>Under this condition, the consumer client won't encounter any errors with nodes failure. The messages will be published and consumed smoothly.</p>
<h2>Recovery from Error on Code Level</h2>
<p>If you are using docker, you can set up your own policy for failure restart, also you can do it only code level as you like.
Here is the pseudo-code of how to handle it on language level by using <code>rescue</code> or <code>catch</code>.</p>
<pre><code class="language-ruby">def consume
  begin
    do_something_with_bunny
  rescue =&gt; e
    logger.error e
    do_something_with_bunny
  end
end</code></pre>
<p>Coming to conclusion, we have addressed four strategies on how to handle RabbitMQ failure.</p>
<ul>
<li>Using RabbitMQ Cluster to enable capacity.</li>
<li>Using load balance to improve availability.</li>
<li>Using mirrored queue to higher the durability.</li>
<li>Using code error handler to improve the elasticity.</li>
</ul>
<p><strong>References</strong></p>
<ul>
<li>Cover image from https://pixabay.com/vectors/rabbit-character-alice-in-wonderland-30751/</li>
</ul>]]></content>
            <updated>2020-01-20T11:31:31-06:00</updated>
        </entry>
            <entry>
            <author>
                <name>Martin Tan</name>
            </author>
            <title type="text"><![CDATA[Java RGB to CMYK Converter]]></title>
            <link rel="alternate" type="text/html" href="https://mrtan.me/post/java-rgb-to-cmyk-converter.html"></link>
            <id>https://mrtan.me/post/java-rgb-to-cmyk-converter.html</id>
            <summary type="html"><![CDATA[<p><img src="/upload/images/2019/10/rgb-to-cmyk.jpg" alt="RGB to CMYK converter" /></p>
<p>As we already talked about the implementation of how to convert a color pixel value from RGB to CMYK, now I'd like to show you how to convert it from CMYK to RGB.</p>
<h2>Question</h2>
<blockquote>
<p><strong>RGB to CMYK  color matching.</strong> Write a java program <code>RGBtoCMYK</code> that reads in four command line inputs R, G, B between 0 and 255, and prints the corresponding CMYK values. Devise the appropriate formula by &quot;inverting&quot; the CMYK to RGB conversion formula.</p>
</blockquote>
<h2>Implementing Steps</h2>
<p>The CMYK color model has 4 color values from 0 to 100, and the RGB color model has 3 color values from 0 to 255. The formula is very simple and listed in the code.</p>
<p><strong>Here is the formula</strong></p>
<pre><code class="language-shell">R' = R/255

G' = G/255

B' = B/255

K = 1-max(R', G', B')

C = (1-R'-K) / (1-K)

M = (1-G'-K) / (1-K)

Y = (1-B'-K) / (1-K)
</code></pre>
<p><strong>Here the goes the key steps:</strong></p>
<ol>
<li>
<p>Pass 3 values matching R, G, B</p>
</li>
<li>
<p>Calculate the CMYK values according to the RGB values</p>
</li>
<li>
<p>Print the CMYK values as <code>String</code></p>
</li>
</ol>
<h2>Solution</h2>
<pre><code class="language-java">
/******************************************************************************
 *  Compilation:  javac RgbCmykConverter.java
 *  Execution:    java RgbCmykConverter R G B
 *  
 *  Convert color values from RGB to CMYK format.
 *  
 *  %  java RgbCmykConverter 0 0 0
 *  [0, 0, 0, 100]
 *  
 *  %  java RgbCmykConverter 255 0 0
 *  [0, 100, 100, 0]
 *  
 *  %  java RgbCmykConverter 0 0 255
 *  [100, 100, 0, 0]
 *  
 ******************************************************************************/

import java.util.Arrays;

public class RgbCmykConverter {
    public static void main(String[] args) {
        int[] cmyk = rgbToCmyk(
            Integer.parseInt(args[0]),
            Integer.parseInt(args[1]),
            Integer.parseInt(args[2])
        );

        System.out.println(Arrays.toString(cmyk));
    }

    private static int[] rgbToCmyk(int r, int g, int b) {
        double percentageR = r / 255.0 * 100;
        double percentageG = g / 255.0 * 100;
        double percentageB = b / 255.0 * 100;

        double k = 100 - Math.max(Math.max(percentageR, percentageG), percentageB);

        if (k == 100) {
            return new int[]{ 0, 0, 0, 100 };
        }

        int c = (int)((100 - percentageR - k) / (100 - k) * 100);
        int m = (int)((100 - percentageG - k) / (100 - k) * 100);
        int y = (int)((100 - percentageB - k) / (100 - k) * 100);

        return new int[]{ c, m, y, (int)k };
    }
}
</code></pre>
<p><strong>Output</strong></p>
<pre><code class="language-shell">-&gt; javac RgbCmykConverter.java

--&gt; java RgbCmykConverter 255 255 255
[0, 0, 0, 0]

-&gt; java RgbCmykConverter 0 255 255
[100, 0, 0, 0]

--&gt; java RgbCmykConverter 0 0 255
[100, 100, 0, 0]</code></pre>
<p>The code above is a very simple java code of how to convert a pixel color value from RGB to CMYK.</p>]]></summary>
            <content type="html"><![CDATA[<p><img src="/upload/images/2019/10/rgb-to-cmyk.jpg" alt="RGB to CMYK converter" /></p>
<p>As we already talked about the implementation of how to convert a color pixel value from RGB to CMYK, now I'd like to show you how to convert it from CMYK to RGB.</p>
<h2>Question</h2>
<blockquote>
<p><strong>RGB to CMYK  color matching.</strong> Write a java program <code>RGBtoCMYK</code> that reads in four command line inputs R, G, B between 0 and 255, and prints the corresponding CMYK values. Devise the appropriate formula by &quot;inverting&quot; the CMYK to RGB conversion formula.</p>
</blockquote>
<h2>Implementing Steps</h2>
<p>The CMYK color model has 4 color values from 0 to 100, and the RGB color model has 3 color values from 0 to 255. The formula is very simple and listed in the code.</p>
<p><strong>Here is the formula</strong></p>
<pre><code class="language-shell">R' = R/255

G' = G/255

B' = B/255

K = 1-max(R', G', B')

C = (1-R'-K) / (1-K)

M = (1-G'-K) / (1-K)

Y = (1-B'-K) / (1-K)
</code></pre>
<p><strong>Here the goes the key steps:</strong></p>
<ol>
<li>
<p>Pass 3 values matching R, G, B</p>
</li>
<li>
<p>Calculate the CMYK values according to the RGB values</p>
</li>
<li>
<p>Print the CMYK values as <code>String</code></p>
</li>
</ol>
<h2>Solution</h2>
<pre><code class="language-java">
/******************************************************************************
 *  Compilation:  javac RgbCmykConverter.java
 *  Execution:    java RgbCmykConverter R G B
 *  
 *  Convert color values from RGB to CMYK format.
 *  
 *  %  java RgbCmykConverter 0 0 0
 *  [0, 0, 0, 100]
 *  
 *  %  java RgbCmykConverter 255 0 0
 *  [0, 100, 100, 0]
 *  
 *  %  java RgbCmykConverter 0 0 255
 *  [100, 100, 0, 0]
 *  
 ******************************************************************************/

import java.util.Arrays;

public class RgbCmykConverter {
    public static void main(String[] args) {
        int[] cmyk = rgbToCmyk(
            Integer.parseInt(args[0]),
            Integer.parseInt(args[1]),
            Integer.parseInt(args[2])
        );

        System.out.println(Arrays.toString(cmyk));
    }

    private static int[] rgbToCmyk(int r, int g, int b) {
        double percentageR = r / 255.0 * 100;
        double percentageG = g / 255.0 * 100;
        double percentageB = b / 255.0 * 100;

        double k = 100 - Math.max(Math.max(percentageR, percentageG), percentageB);

        if (k == 100) {
            return new int[]{ 0, 0, 0, 100 };
        }

        int c = (int)((100 - percentageR - k) / (100 - k) * 100);
        int m = (int)((100 - percentageG - k) / (100 - k) * 100);
        int y = (int)((100 - percentageB - k) / (100 - k) * 100);

        return new int[]{ c, m, y, (int)k };
    }
}
</code></pre>
<p><strong>Output</strong></p>
<pre><code class="language-shell">-&gt; javac RgbCmykConverter.java

--&gt; java RgbCmykConverter 255 255 255
[0, 0, 0, 0]

-&gt; java RgbCmykConverter 0 255 255
[100, 0, 0, 0]

--&gt; java RgbCmykConverter 0 0 255
[100, 100, 0, 0]</code></pre>
<p>The code above is a very simple java code of how to convert a pixel color value from RGB to CMYK.</p>]]></content>
            <updated>2019-10-30T00:06:20-06:00</updated>
        </entry>
            <entry>
            <author>
                <name>Martin Tan</name>
            </author>
            <title type="text"><![CDATA[Java CMYK to RGB Converter]]></title>
            <link rel="alternate" type="text/html" href="https://mrtan.me/post/java-cmyk-to-rgb-converter.html"></link>
            <id>https://mrtan.me/post/java-cmyk-to-rgb-converter.html</id>
            <summary type="html"><![CDATA[<p><img src="/upload/images/2019/10/cmyk-to-rgb.jpg" alt="CMYK to RGB converter" /></p>
<p>Almost everyone knows the RGB color model, red, green and blue, especially as a software engineer. It's widely applied in many industries. The CMYK, stands for &quot;Cyan Magenta Yellow Black&quot;, is mostly used in paper print. Today we are going to create a java program to convert a color from CMYK to RGB.</p>
<h2>Question</h2>
<blockquote>
<p><strong>CMYK to RGB color matching.</strong> Write a java program <code>CMYKtoRGB</code> that reads in four command line inputs C, M, Y, and K between 0 and 100, and prints the corresponding RGB parameters. Devise the appropriate formula by &quot;inverting&quot; the RGB to CMYK conversion formula.</p>
</blockquote>
<h2>Implementing Steps</h2>
<p>The CMYK color model has 4 color values from 0 to 100, and the RGB color model has 3 color values from 0 to 255. The formula is very simple and listed in the code.</p>
<p><strong>Here is the formula</strong></p>
<pre><code class="language-shell">R = 255 × (1-C) × (1-K)
G = 255 × (1-M) × (1-K)
B = 255 × (1-Y) × (1-K)</code></pre>
<p><strong>Here the goes the key steps:</strong></p>
<ol>
<li>Pass 4 CMYK values as command line parameters</li>
<li>Calculate the RGB values according to the CMYK values</li>
<li>Print the RGB values as <code>String</code></li>
</ol>
<h2>Solution</h2>
<pre><code class="language-java">
/******************************************************************************
 *  Compilation:  javac CmykRgbConverter.java
 *  Execution:    java CmykRgbConverter c m y k
 *  
 *  Convert color values from CMYK to RGB format.
 *  
 *  %  java CmykRgbConverter 0 0 0 0
 *  [255, 255, 255]
 *  
 *  %  java CmykRgbConverter 0 0 0 100
 *  [0, 0, 0]
 *  
 *  %  java CmykRgbConverter 100 0 100 0
 *  [0, 255, 0]
 *  
 ******************************************************************************/

import java.util.Arrays;

public class CmykRgbConverter {
    public static void main(String[] args) {
        int[] rgb = cmykToRgb(
            Integer.parseInt(args[0]),
            Integer.parseInt(args[1]),
            Integer.parseInt(args[2]),
            Integer.parseInt(args[3])
        );

        System.out.println(Arrays.toString(rgb));
    }

    private static int[] cmykToRgb(int c, int m, int y, int k) {
        int r = 255 * (1 - c/100) * (1 - k/100);
        int g = 255 * (1 - m/100) * (1 - k/100);
        int b = 255 * (1 - y/100) * (1 - k/100);
        int[] rgb = new int[]{ r, g, b };

        return rgb;
    }
}
</code></pre>
<p><strong>Output</strong></p>
<pre><code class="language-shell">-&gt; javac CmykRgbConverter.java

-&gt; java CmykRgbConverter 0 0 0 0
[255, 255, 255]

-&gt; java CmykRgbConverter 0 100 100 0
[255, 0, 0]

-&gt; java CmykRgbConverter 0 100 100 100
[0, 0, 0]</code></pre>
<p>The code above is a very simple java code of how to convert a pixel color value from CMYK to RGB.</p>]]></summary>
            <content type="html"><![CDATA[<p><img src="/upload/images/2019/10/cmyk-to-rgb.jpg" alt="CMYK to RGB converter" /></p>
<p>Almost everyone knows the RGB color model, red, green and blue, especially as a software engineer. It's widely applied in many industries. The CMYK, stands for &quot;Cyan Magenta Yellow Black&quot;, is mostly used in paper print. Today we are going to create a java program to convert a color from CMYK to RGB.</p>
<h2>Question</h2>
<blockquote>
<p><strong>CMYK to RGB color matching.</strong> Write a java program <code>CMYKtoRGB</code> that reads in four command line inputs C, M, Y, and K between 0 and 100, and prints the corresponding RGB parameters. Devise the appropriate formula by &quot;inverting&quot; the RGB to CMYK conversion formula.</p>
</blockquote>
<h2>Implementing Steps</h2>
<p>The CMYK color model has 4 color values from 0 to 100, and the RGB color model has 3 color values from 0 to 255. The formula is very simple and listed in the code.</p>
<p><strong>Here is the formula</strong></p>
<pre><code class="language-shell">R = 255 × (1-C) × (1-K)
G = 255 × (1-M) × (1-K)
B = 255 × (1-Y) × (1-K)</code></pre>
<p><strong>Here the goes the key steps:</strong></p>
<ol>
<li>Pass 4 CMYK values as command line parameters</li>
<li>Calculate the RGB values according to the CMYK values</li>
<li>Print the RGB values as <code>String</code></li>
</ol>
<h2>Solution</h2>
<pre><code class="language-java">
/******************************************************************************
 *  Compilation:  javac CmykRgbConverter.java
 *  Execution:    java CmykRgbConverter c m y k
 *  
 *  Convert color values from CMYK to RGB format.
 *  
 *  %  java CmykRgbConverter 0 0 0 0
 *  [255, 255, 255]
 *  
 *  %  java CmykRgbConverter 0 0 0 100
 *  [0, 0, 0]
 *  
 *  %  java CmykRgbConverter 100 0 100 0
 *  [0, 255, 0]
 *  
 ******************************************************************************/

import java.util.Arrays;

public class CmykRgbConverter {
    public static void main(String[] args) {
        int[] rgb = cmykToRgb(
            Integer.parseInt(args[0]),
            Integer.parseInt(args[1]),
            Integer.parseInt(args[2]),
            Integer.parseInt(args[3])
        );

        System.out.println(Arrays.toString(rgb));
    }

    private static int[] cmykToRgb(int c, int m, int y, int k) {
        int r = 255 * (1 - c/100) * (1 - k/100);
        int g = 255 * (1 - m/100) * (1 - k/100);
        int b = 255 * (1 - y/100) * (1 - k/100);
        int[] rgb = new int[]{ r, g, b };

        return rgb;
    }
}
</code></pre>
<p><strong>Output</strong></p>
<pre><code class="language-shell">-&gt; javac CmykRgbConverter.java

-&gt; java CmykRgbConverter 0 0 0 0
[255, 255, 255]

-&gt; java CmykRgbConverter 0 100 100 0
[255, 0, 0]

-&gt; java CmykRgbConverter 0 100 100 100
[0, 0, 0]</code></pre>
<p>The code above is a very simple java code of how to convert a pixel color value from CMYK to RGB.</p>]]></content>
            <updated>2019-10-29T23:28:11-06:00</updated>
        </entry>
            <entry>
            <author>
                <name>Martin Tan</name>
            </author>
            <title type="text"><![CDATA[Java Fibonacci Word]]></title>
            <link rel="alternate" type="text/html" href="https://mrtan.me/post/java-fibonacci-word.html"></link>
            <id>https://mrtan.me/post/java-fibonacci-word.html</id>
            <summary type="html"><![CDATA[<p><img src="/upload/images/2019/10/fibonacci-word.jpg" alt="Fibonacci-word" /></p>
<p>Fibonacci Word means the next word  is concatenated up by the two words before it.</p>
<p>Sn = S(n-1) · S(n-2)</p>
<pre><code class="language-shell">0
01
010
01001</code></pre>
<h2>Question</h2>
<blockquote>
<p>Write a program <code>FibonacciWord.java</code> that prints the Fibonacci word of order 0 through 10. f(0) = &quot;a&quot;, f(1) = &quot;b&quot;, f(2) = &quot;ba&quot;, f(3) = &quot;bab&quot;, f(4) = &quot;babba&quot;, and in general f(n) = f(n-1) followed by f(n-2). Use string concatenation.</p>
</blockquote>
<h2>Implementing Steps</h2>
<p>First of all, we are going to define two base words <code>0</code> and <code>01</code>, and pass a length n as the parameter. Then, we can use a <code>for</code> loop to go through each word and use a temp variable as a swap place to help to forward the two variables of base words.</p>
<p><strong>Here the goes the key steps:</strong></p>
<ol>
<li>There two variables <code>wordN</code>, <code>wordN_1</code> for S(n) and S(n-1)</li>
<li>There a variable temp for exchanging values</li>
<li>Loop N times</li>
<li>Return the last value</li>
</ol>
<h2>Solution</h2>
<pre><code class="language-java">
/******************************************************************************
 *  Compilation:  javac FibonacciWord.java
 *  Execution:    java FibonacciWord n
 *
 *  Prints the Fibonacci word of f(n) = f(n-1) followed by f(n-2).
 *
 *  % java FibonacciWord 3
 *  01001
 *  % java FibonacciWord 4
 *  01001010
 *
 ******************************************************************************/

public class FibonacciWord {
    private static String fibWord(int n) {
        String wordN = "01";
        String wordN_1 = "0";
        String temp;

        for (int i = 2; i &lt;= n; i++) {
            temp = wordN;
            wordN += wordN_1;
            wordN_1 = temp;
        }

        return wordN;
    }

    public static void main(String[] args) {
        int n = Integer.parseInt(args[0]);
        System.out.println(fibWord(n));
    }
}
</code></pre>
<p><strong>Output</strong></p>
<pre><code class="language-shell">-&gt; javac FibonacciWord.java
-&gt; java FibonacciWord 2
010
-&gt; java FibonacciWord 3
01001
-&gt; java FibonacciWord 4
01001010</code></pre>]]></summary>
            <content type="html"><![CDATA[<p><img src="/upload/images/2019/10/fibonacci-word.jpg" alt="Fibonacci-word" /></p>
<p>Fibonacci Word means the next word  is concatenated up by the two words before it.</p>
<p>Sn = S(n-1) · S(n-2)</p>
<pre><code class="language-shell">0
01
010
01001</code></pre>
<h2>Question</h2>
<blockquote>
<p>Write a program <code>FibonacciWord.java</code> that prints the Fibonacci word of order 0 through 10. f(0) = &quot;a&quot;, f(1) = &quot;b&quot;, f(2) = &quot;ba&quot;, f(3) = &quot;bab&quot;, f(4) = &quot;babba&quot;, and in general f(n) = f(n-1) followed by f(n-2). Use string concatenation.</p>
</blockquote>
<h2>Implementing Steps</h2>
<p>First of all, we are going to define two base words <code>0</code> and <code>01</code>, and pass a length n as the parameter. Then, we can use a <code>for</code> loop to go through each word and use a temp variable as a swap place to help to forward the two variables of base words.</p>
<p><strong>Here the goes the key steps:</strong></p>
<ol>
<li>There two variables <code>wordN</code>, <code>wordN_1</code> for S(n) and S(n-1)</li>
<li>There a variable temp for exchanging values</li>
<li>Loop N times</li>
<li>Return the last value</li>
</ol>
<h2>Solution</h2>
<pre><code class="language-java">
/******************************************************************************
 *  Compilation:  javac FibonacciWord.java
 *  Execution:    java FibonacciWord n
 *
 *  Prints the Fibonacci word of f(n) = f(n-1) followed by f(n-2).
 *
 *  % java FibonacciWord 3
 *  01001
 *  % java FibonacciWord 4
 *  01001010
 *
 ******************************************************************************/

public class FibonacciWord {
    private static String fibWord(int n) {
        String wordN = "01";
        String wordN_1 = "0";
        String temp;

        for (int i = 2; i &lt;= n; i++) {
            temp = wordN;
            wordN += wordN_1;
            wordN_1 = temp;
        }

        return wordN;
    }

    public static void main(String[] args) {
        int n = Integer.parseInt(args[0]);
        System.out.println(fibWord(n));
    }
}
</code></pre>
<p><strong>Output</strong></p>
<pre><code class="language-shell">-&gt; javac FibonacciWord.java
-&gt; java FibonacciWord 2
010
-&gt; java FibonacciWord 3
01001
-&gt; java FibonacciWord 4
01001010</code></pre>]]></content>
            <updated>2019-10-29T22:12:09-06:00</updated>
        </entry>
            <entry>
            <author>
                <name>Martin Tan</name>
            </author>
            <title type="text"><![CDATA[Rails on Kubernetes Deployment Tutorial]]></title>
            <link rel="alternate" type="text/html" href="https://mrtan.me/post/37.html"></link>
            <id>https://mrtan.me/post/37.html</id>
            <summary type="html"><![CDATA[<p>Kubernetes is different from Docker Swarm, it has Pods and running Containers inside Pods. Here is a simplified Kubernetes architecture diagram. In this tutorial, we are going to go through all the steps from setup Kubernetes on your local device to run Rails on a local Kubernetes cluster. </p>
<p><img src="/upload/images/2019/10/kubernetes-architecture.jpg" alt="image-20191015143558663" /></p>
<h2>Activate Kubernetes Support on Docker for Mac</h2>
<p>Using Docker App is a very common way for engineers to run and debug Kubernetes on local devices. You can enable Kubernetes by clicking on the checkbox.</p>
<p><img src="/upload/images/2019/10/docker-app-kubernetes-config.jpg" alt="docker-app-kubernetes-config" /></p>
<p>Let's make sure we already connected to the Docker-Desktop, please set up the config first if it's not. Each context has its namespaces and users.</p>
<pre><code class="language-shell">➜  ~ kubectl config get-contexts
CURRENT   NAME                 CLUSTER                      AUTHINFO             NAMESPACE
*         docker-for-desktop   docker-for-desktop-cluster   docker-for-desktop
          minikube             minikube                     minikube

➜  ~ kubectl config use-context docker-for-desktop
Switched to context "docker-for-desktop".</code></pre>
<h3>Create a Rails Project</h3>
<h4>Rails New</h4>
<p>First of all, if you don't get any Rails project on your hand, create a new one. Since we don't need run Rails on the host machine, just skip installing Gem packages by adding <code>--skip-bundle</code>.</p>
<pre><code class="language-shell">rails new rails-on-kube --skip-bundle</code></pre>
<h4>Create Docker Entypoint</h4>
<p>Once you get a Rails project created, touch a docker entry point file <code>docker-entrypoint.sh</code>. </p>
<pre><code class="language-shell">#!/bin/ash

bundle exec rails db:migrate &amp;&amp; bundle exec puma -C config/puma.rb</code></pre>
<h4>Create Dockerfile</h4>
<p>To build a docker image, we need to create a  <code>Dockerfile</code>  at the project root directory.</p>
<pre><code class="language-shell">FROM ruby:2.6.0-alpine

RUN apk --update add build-base tzdata git \
    libxslt-dev libxml2-dev openssl \
    sqlite-dev yarn\
    &amp;&amp; rm -rf /var/cache/apk/*
RUN gem install bundler

ENV RAILS_ROOT /var/www
WORKDIR $RAILS_ROOT

COPY Gemfile* ./

ENV RAILS_ENV=production

RUN bundle install --jobs 10 --retry 5 --without development test
RUN rails webpacker:install

COPY . .

RUN chmod u+x docker-entrypoint.sh
CMD ["sh", "docker-entrypoint.sh"]</code></pre>
<h4>Build Project Image</h4>
<p>Now, we come to the last steps of creating a Rails project image. Paste and run the following command, a new Docker image will be available on your local.</p>
<pre><code class="language-shell">docker build -f Dockerfile -t rails-on-kube .</code></pre>
<p><img src="/upload/images/2019/10/docker-images-list.jpg" alt="docker-images-list" /></p>
<h3>Setup Kubernetes for Rails</h3>
<p>Finally, we reach the last part of setting up Kubernetes. We should keep in mind, every object in a Kubernetes context can be considered as a resource, each resource can be defined by a YAML file.</p>
<p>To keep this tutorial clear and sample, we only need one resource YAML file, here we go.</p>
<h4>Create Kubernetes Resource File</h4>
<p>Filename <code>rails-on-kube.yaml</code></p>
<p>In this file, we are going to deploy a Rails service based on the docker image we built before. <code>imagePullPolicy: Never</code> means using local images, or you may get an image Not-Found-Error.</p>
<p><code>replicas: 2</code> means there is going to have two replicas of the Rails service.</p>
<p>The two main functions of <code>Deployment</code> Yaml is to declare pods and setup replicas.</p>
<pre><code class="language-shell">apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app.kubernetes.io/name: rails-on-kube
  name: 
spec:
  replicas: 2
  selector:
    matchLabels:
      app.kubernetes.io/name: rails-on-kube
  template:
    metadata:
      labels:
        app.kubernetes.io/name: rails-on-kube
    spec:
      containers:
      - image: blog:latest
        imagePullPolicy: Never
        name: 
        ports:
        - containerPort: 3000
</code></pre>
<h4>Pull up Rails App</h4>
<p>The last step is using <code>kubectl apply</code> to update the Kubernetes cluster to match the descriptions from a YAML file. Then, exposes an external IP address, so we can access the service.</p>
<pre><code class="language-shell">kubectl apply -f rails-on-kube.yaml
kubectl expose deployment rails-app --type=LoadBalancer --name=rails-service
</code></pre>
<p><code>curl localhost:3000</code> to check whether the server is up running.</p>
<h4>Destroy Rails App</h4>
<p>Here are commands to release the resource we have declared before.</p>
<pre><code class="language-shell">kubectl delete deployment rails-app
kubectl delete services rails-service

# kubectl scale deployment my-app --replicas=1</code></pre>
<h2>Extra Commands</h2>
<p>I also got some extra command lines of Kubernetes for you to debug.</p>
<h4>Debugging and Logging</h4>
<pre><code class="language-shell"># Get the detail of pods
kubectl describe pods

# Get the info of a service 
kubectl get services rails-service</code></pre>
<p><img src="/upload/images/2019/10/kubernetes-show-services.jpg" alt="kubernetes-show-services" /></p>
<h4>Exec Command in Container</h4>
<pre><code class="language-shell"># Show all service pods you created
kubectl get pods

# Exec sh in a pod
kubectl exec -it my-app-64d75bd9d9-8nlfk -- sh

# Show logs of a pod
kubectl logs rails-app-5897fdc7b8-dhvj6</code></pre>
<p><img src="/upload/images/2019/10/kubernetes-show-logs.jpg" alt="kubernetes-show-logs" /></p>
<p>In addition, it's better to use Helm to manage your Kuberneters applications in the production environment. Check it out if you are interested, but for a demo purpose, using Kubernetes YAML file directly is more efficient.</p>]]></summary>
            <content type="html"><![CDATA[<p>Kubernetes is different from Docker Swarm, it has Pods and running Containers inside Pods. Here is a simplified Kubernetes architecture diagram. In this tutorial, we are going to go through all the steps from setup Kubernetes on your local device to run Rails on a local Kubernetes cluster. </p>
<p><img src="/upload/images/2019/10/kubernetes-architecture.jpg" alt="image-20191015143558663" /></p>
<h2>Activate Kubernetes Support on Docker for Mac</h2>
<p>Using Docker App is a very common way for engineers to run and debug Kubernetes on local devices. You can enable Kubernetes by clicking on the checkbox.</p>
<p><img src="/upload/images/2019/10/docker-app-kubernetes-config.jpg" alt="docker-app-kubernetes-config" /></p>
<p>Let's make sure we already connected to the Docker-Desktop, please set up the config first if it's not. Each context has its namespaces and users.</p>
<pre><code class="language-shell">➜  ~ kubectl config get-contexts
CURRENT   NAME                 CLUSTER                      AUTHINFO             NAMESPACE
*         docker-for-desktop   docker-for-desktop-cluster   docker-for-desktop
          minikube             minikube                     minikube

➜  ~ kubectl config use-context docker-for-desktop
Switched to context "docker-for-desktop".</code></pre>
<h3>Create a Rails Project</h3>
<h4>Rails New</h4>
<p>First of all, if you don't get any Rails project on your hand, create a new one. Since we don't need run Rails on the host machine, just skip installing Gem packages by adding <code>--skip-bundle</code>.</p>
<pre><code class="language-shell">rails new rails-on-kube --skip-bundle</code></pre>
<h4>Create Docker Entypoint</h4>
<p>Once you get a Rails project created, touch a docker entry point file <code>docker-entrypoint.sh</code>. </p>
<pre><code class="language-shell">#!/bin/ash

bundle exec rails db:migrate &amp;&amp; bundle exec puma -C config/puma.rb</code></pre>
<h4>Create Dockerfile</h4>
<p>To build a docker image, we need to create a  <code>Dockerfile</code>  at the project root directory.</p>
<pre><code class="language-shell">FROM ruby:2.6.0-alpine

RUN apk --update add build-base tzdata git \
    libxslt-dev libxml2-dev openssl \
    sqlite-dev yarn\
    &amp;&amp; rm -rf /var/cache/apk/*
RUN gem install bundler

ENV RAILS_ROOT /var/www
WORKDIR $RAILS_ROOT

COPY Gemfile* ./

ENV RAILS_ENV=production

RUN bundle install --jobs 10 --retry 5 --without development test
RUN rails webpacker:install

COPY . .

RUN chmod u+x docker-entrypoint.sh
CMD ["sh", "docker-entrypoint.sh"]</code></pre>
<h4>Build Project Image</h4>
<p>Now, we come to the last steps of creating a Rails project image. Paste and run the following command, a new Docker image will be available on your local.</p>
<pre><code class="language-shell">docker build -f Dockerfile -t rails-on-kube .</code></pre>
<p><img src="/upload/images/2019/10/docker-images-list.jpg" alt="docker-images-list" /></p>
<h3>Setup Kubernetes for Rails</h3>
<p>Finally, we reach the last part of setting up Kubernetes. We should keep in mind, every object in a Kubernetes context can be considered as a resource, each resource can be defined by a YAML file.</p>
<p>To keep this tutorial clear and sample, we only need one resource YAML file, here we go.</p>
<h4>Create Kubernetes Resource File</h4>
<p>Filename <code>rails-on-kube.yaml</code></p>
<p>In this file, we are going to deploy a Rails service based on the docker image we built before. <code>imagePullPolicy: Never</code> means using local images, or you may get an image Not-Found-Error.</p>
<p><code>replicas: 2</code> means there is going to have two replicas of the Rails service.</p>
<p>The two main functions of <code>Deployment</code> Yaml is to declare pods and setup replicas.</p>
<pre><code class="language-shell">apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app.kubernetes.io/name: rails-on-kube
  name: 
spec:
  replicas: 2
  selector:
    matchLabels:
      app.kubernetes.io/name: rails-on-kube
  template:
    metadata:
      labels:
        app.kubernetes.io/name: rails-on-kube
    spec:
      containers:
      - image: blog:latest
        imagePullPolicy: Never
        name: 
        ports:
        - containerPort: 3000
</code></pre>
<h4>Pull up Rails App</h4>
<p>The last step is using <code>kubectl apply</code> to update the Kubernetes cluster to match the descriptions from a YAML file. Then, exposes an external IP address, so we can access the service.</p>
<pre><code class="language-shell">kubectl apply -f rails-on-kube.yaml
kubectl expose deployment rails-app --type=LoadBalancer --name=rails-service
</code></pre>
<p><code>curl localhost:3000</code> to check whether the server is up running.</p>
<h4>Destroy Rails App</h4>
<p>Here are commands to release the resource we have declared before.</p>
<pre><code class="language-shell">kubectl delete deployment rails-app
kubectl delete services rails-service

# kubectl scale deployment my-app --replicas=1</code></pre>
<h2>Extra Commands</h2>
<p>I also got some extra command lines of Kubernetes for you to debug.</p>
<h4>Debugging and Logging</h4>
<pre><code class="language-shell"># Get the detail of pods
kubectl describe pods

# Get the info of a service 
kubectl get services rails-service</code></pre>
<p><img src="/upload/images/2019/10/kubernetes-show-services.jpg" alt="kubernetes-show-services" /></p>
<h4>Exec Command in Container</h4>
<pre><code class="language-shell"># Show all service pods you created
kubectl get pods

# Exec sh in a pod
kubectl exec -it my-app-64d75bd9d9-8nlfk -- sh

# Show logs of a pod
kubectl logs rails-app-5897fdc7b8-dhvj6</code></pre>
<p><img src="/upload/images/2019/10/kubernetes-show-logs.jpg" alt="kubernetes-show-logs" /></p>
<p>In addition, it's better to use Helm to manage your Kuberneters applications in the production environment. Check it out if you are interested, but for a demo purpose, using Kubernetes YAML file directly is more efficient.</p>]]></content>
            <updated>2019-10-22T00:40:54-06:00</updated>
        </entry>
            <entry>
            <author>
                <name>Martin Tan</name>
            </author>
            <title type="text"><![CDATA[Using N2N VPN to Enable NAS Remote Accessing]]></title>
            <link rel="alternate" type="text/html" href="https://mrtan.me/post/36.html"></link>
            <id>https://mrtan.me/post/36.html</id>
            <summary type="html"><![CDATA[<p><img src="/upload/images/2019/08/n2n-virtual-network.jpg" alt="n2n-virtual-network" /></p>
<h2>Background</h2>
<p>I have multiple devices including a NAS, a Raspberry Pi, and two MacBooks in different networks, my home and office. All I need is that I want to let all these devices can be accessed in any of those networks. After searching on the internet, I found out n2n should be the one I looked for. The architecture and setup are very simple 👍.</p>
<h2>Steps to Setup N2N Virtual Network</h2>
<h3>1. Setup a super node</h3>
<p>The supernode should be run on a device that can be accessed from the public network. I'm using a VPS with public IP to run this, so all edge nodes can connect to it directly.
The VPS I have is based on Debian, and the following commands only testyed on Debian in this post. Let's say the public IP of the VPS is 200.200.200.200.</p>
<pre><code class="language-shell">supernode -l 9053</code></pre>
<pre><code class="language-shell"># output

-&gt; supernode -l 9053
07/Aug/2019 06:45:47 [supernode.c: 476] Supernode ready: listening on port 9053 [TCP/UDP]
</code></pre>
<h3>2. Run edge nodes</h3>
<p>We can have multiple edge nodes that connect to one super node. Don't forget to change the virtual IP of the following command.</p>
<pre><code class="language-shell">edge -d n2n0 -a 192.168.100.1 -c YOU_NETWORK_NAME -k YOUR_PASS -l 200.200.200.200:9053</code></pre>
<p>now, we have a device with private network IP <code>192.168.100.1</code>. If you have run this command on other devices, you can ping it from each other.</p>
<h4>Run n2n in Docker Container</h4>
<p>To run N2N VPN on macOS is not a good choice, I run into problems. It's better to use docker or vagrant, it depends on you.</p>
<pre><code class="language-shell"># Dockerfile

FROM debian:stable-slim

RUN apt-get update &amp;&amp; apt-get -y install iputils-ping n2n socat</code></pre>
<pre><code class="language-shell"># Build the Dockerfile
docker build -f Dockerfile -t n2n .

# Run a edge node
docker run -d -p 9091:9091/tcp --name=n2n --privileged --rm -it n2n edge -d n2n0 -a 192.168.100.1 -c YOU_NETWORK_NAME -k YOU_PASS -l 200.200.200.200:9053</code></pre>
<h3>Enable Port Forwarding</h3>
<p>The port of 9091 is used by Transmission. At first, I tried to use iptables to forward port, so I can visit a remote Transmission by typing <a href="http://localhost:9091">http://localhost:9091</a> in my browser on docker host device. But, it turns out not working, the socat is a good one for port forwarding. I also encountered a known bug of Linux, here the link if you are interested in it. </p>
<p>Run the command below in your edge node container.</p>
<pre><code class="language-shell"># Forward a port

ifconfig n2n0 mtu 500
nohup socat TCP4-LISTEN:9091,fork TCP4:192.168.100.1:9091 &amp;</code></pre>
<p>Then, we can visit the Transimission web UI on my Chrome browser.</p>
<table>
<thead>
<tr>
<th>REPOSITORY</th>
<th>TAG</th>
<th>IMAGE ID</th>
<th>CREATED</th>
<th>SIZE</th>
</tr>
</thead>
<tbody>
<tr>
<td>n2n</td>
<td>latest</td>
<td>f0628fbbb0e6</td>
<td>3 hours ago</td>
<td>93.4MB</td>
</tr>
</tbody>
</table>
<table>
<thead>
<tr>
<th>CONTAINER ID</th>
<th>NAME</th>
<th>CPU %</th>
<th>MEM USAGE / LIMIT</th>
<th>MEM %</th>
<th>NET I/O</th>
</tr>
</thead>
<tbody>
<tr>
<td>b6a1d36c60b2</td>
<td>n2n</td>
<td>0.00%</td>
<td>1.77MiB / 1.952GiB</td>
<td>0.09%</td>
<td>40.2kB / 39.7kB</td>
</tr>
</tbody>
</table>
<p>From the two tables above, the memory and CPU usage is very small, don't have to worry about resource usage.</p>]]></summary>
            <content type="html"><![CDATA[<p><img src="/upload/images/2019/08/n2n-virtual-network.jpg" alt="n2n-virtual-network" /></p>
<h2>Background</h2>
<p>I have multiple devices including a NAS, a Raspberry Pi, and two MacBooks in different networks, my home and office. All I need is that I want to let all these devices can be accessed in any of those networks. After searching on the internet, I found out n2n should be the one I looked for. The architecture and setup are very simple 👍.</p>
<h2>Steps to Setup N2N Virtual Network</h2>
<h3>1. Setup a super node</h3>
<p>The supernode should be run on a device that can be accessed from the public network. I'm using a VPS with public IP to run this, so all edge nodes can connect to it directly.
The VPS I have is based on Debian, and the following commands only testyed on Debian in this post. Let's say the public IP of the VPS is 200.200.200.200.</p>
<pre><code class="language-shell">supernode -l 9053</code></pre>
<pre><code class="language-shell"># output

-&gt; supernode -l 9053
07/Aug/2019 06:45:47 [supernode.c: 476] Supernode ready: listening on port 9053 [TCP/UDP]
</code></pre>
<h3>2. Run edge nodes</h3>
<p>We can have multiple edge nodes that connect to one super node. Don't forget to change the virtual IP of the following command.</p>
<pre><code class="language-shell">edge -d n2n0 -a 192.168.100.1 -c YOU_NETWORK_NAME -k YOUR_PASS -l 200.200.200.200:9053</code></pre>
<p>now, we have a device with private network IP <code>192.168.100.1</code>. If you have run this command on other devices, you can ping it from each other.</p>
<h4>Run n2n in Docker Container</h4>
<p>To run N2N VPN on macOS is not a good choice, I run into problems. It's better to use docker or vagrant, it depends on you.</p>
<pre><code class="language-shell"># Dockerfile

FROM debian:stable-slim

RUN apt-get update &amp;&amp; apt-get -y install iputils-ping n2n socat</code></pre>
<pre><code class="language-shell"># Build the Dockerfile
docker build -f Dockerfile -t n2n .

# Run a edge node
docker run -d -p 9091:9091/tcp --name=n2n --privileged --rm -it n2n edge -d n2n0 -a 192.168.100.1 -c YOU_NETWORK_NAME -k YOU_PASS -l 200.200.200.200:9053</code></pre>
<h3>Enable Port Forwarding</h3>
<p>The port of 9091 is used by Transmission. At first, I tried to use iptables to forward port, so I can visit a remote Transmission by typing <a href="http://localhost:9091">http://localhost:9091</a> in my browser on docker host device. But, it turns out not working, the socat is a good one for port forwarding. I also encountered a known bug of Linux, here the link if you are interested in it. </p>
<p>Run the command below in your edge node container.</p>
<pre><code class="language-shell"># Forward a port

ifconfig n2n0 mtu 500
nohup socat TCP4-LISTEN:9091,fork TCP4:192.168.100.1:9091 &amp;</code></pre>
<p>Then, we can visit the Transimission web UI on my Chrome browser.</p>
<table>
<thead>
<tr>
<th>REPOSITORY</th>
<th>TAG</th>
<th>IMAGE ID</th>
<th>CREATED</th>
<th>SIZE</th>
</tr>
</thead>
<tbody>
<tr>
<td>n2n</td>
<td>latest</td>
<td>f0628fbbb0e6</td>
<td>3 hours ago</td>
<td>93.4MB</td>
</tr>
</tbody>
</table>
<table>
<thead>
<tr>
<th>CONTAINER ID</th>
<th>NAME</th>
<th>CPU %</th>
<th>MEM USAGE / LIMIT</th>
<th>MEM %</th>
<th>NET I/O</th>
</tr>
</thead>
<tbody>
<tr>
<td>b6a1d36c60b2</td>
<td>n2n</td>
<td>0.00%</td>
<td>1.77MiB / 1.952GiB</td>
<td>0.09%</td>
<td>40.2kB / 39.7kB</td>
</tr>
</tbody>
</table>
<p>From the two tables above, the memory and CPU usage is very small, don't have to worry about resource usage.</p>]]></content>
            <updated>2019-08-07T21:46:03-06:00</updated>
        </entry>
            <entry>
            <author>
                <name>Martin Tan</name>
            </author>
            <title type="text"><![CDATA[Error: Docker Error response from daemon: Container id is not running]]></title>
            <link rel="alternate" type="text/html" href="https://mrtan.me/post/35.html"></link>
            <id>https://mrtan.me/post/35.html</id>
            <summary type="html"><![CDATA[<p><img src="/upload/images/2019/07/docker-container-id-not-running.jpg" alt="docker-container-id-not-running" /></p>
<h2>Issue</h2>
<p>The following  lines is an example of how to reproduce this error.</p>
<pre><code class="language-shell"># Pull the latest version of alpine image from docker hub
➜ docker pull alpine

# Create a container from the alpine image and get on to the terminal of that container
➜ docker run -it --name alpine_bash alpine ash
➜ exit # inside the container

# Execute the command sh in that container
➜ docker exec -it alpine_bash sh</code></pre>
<p>Here is the output.</p>
<pre><code class="language-shell">Error response from daemon: Container 00f5f2aeae9f2d74b8f92d769da5086b4a72eaa01bdae8351fbd77b753e74b51 is not running</code></pre>
<pre><code class="language-shell">➜ docker run -it --name alpine_bash alpine sh

docker: Error response from daemon: Conflict. The container name "/alpine_bash" is already in use by container "00f5f2aeae9f2d74b8f92d769da5086b4a72eaa01bdae8351fbd77b753e74b51". You have to remove (or rename) that container to be able to reuse that name.
See 'docker run --help'.</code></pre>
<p>We got this error because the <strong>container is stopped</strong> immediately after we exit the shell, no process is running anymore, and the <strong>docker exec</strong> is only working on running containers.</p>
<h2>Solution</h2>
<p>Remove the container automatically.</p>
<pre><code class="language-shell">docker run -it --rm --name alpine_bash alpine sh</code></pre>
<p>This is the description of <strong>--rm</strong> from official docker reference.</p>
<blockquote>
<p>If instead you’d like Docker to <strong>automatically clean up the container and remove the file system when the container exits</strong>, you can add the <code>--rm</code> flag</p>
</blockquote>
<p>Or we delete the container mannually.</p>
<pre><code class="language-shell">➜ docker container ls -a

CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                     PORTS               NAMES
e877fcd9fe0e        alpine              "ash"               7 seconds ago       Exited (0) 5 seconds ago                       alpine_bash</code></pre>
<pre><code class="language-shell"># Delete container by name
docker container rm alpine_bash</code></pre>]]></summary>
            <content type="html"><![CDATA[<p><img src="/upload/images/2019/07/docker-container-id-not-running.jpg" alt="docker-container-id-not-running" /></p>
<h2>Issue</h2>
<p>The following  lines is an example of how to reproduce this error.</p>
<pre><code class="language-shell"># Pull the latest version of alpine image from docker hub
➜ docker pull alpine

# Create a container from the alpine image and get on to the terminal of that container
➜ docker run -it --name alpine_bash alpine ash
➜ exit # inside the container

# Execute the command sh in that container
➜ docker exec -it alpine_bash sh</code></pre>
<p>Here is the output.</p>
<pre><code class="language-shell">Error response from daemon: Container 00f5f2aeae9f2d74b8f92d769da5086b4a72eaa01bdae8351fbd77b753e74b51 is not running</code></pre>
<pre><code class="language-shell">➜ docker run -it --name alpine_bash alpine sh

docker: Error response from daemon: Conflict. The container name "/alpine_bash" is already in use by container "00f5f2aeae9f2d74b8f92d769da5086b4a72eaa01bdae8351fbd77b753e74b51". You have to remove (or rename) that container to be able to reuse that name.
See 'docker run --help'.</code></pre>
<p>We got this error because the <strong>container is stopped</strong> immediately after we exit the shell, no process is running anymore, and the <strong>docker exec</strong> is only working on running containers.</p>
<h2>Solution</h2>
<p>Remove the container automatically.</p>
<pre><code class="language-shell">docker run -it --rm --name alpine_bash alpine sh</code></pre>
<p>This is the description of <strong>--rm</strong> from official docker reference.</p>
<blockquote>
<p>If instead you’d like Docker to <strong>automatically clean up the container and remove the file system when the container exits</strong>, you can add the <code>--rm</code> flag</p>
</blockquote>
<p>Or we delete the container mannually.</p>
<pre><code class="language-shell">➜ docker container ls -a

CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                     PORTS               NAMES
e877fcd9fe0e        alpine              "ash"               7 seconds ago       Exited (0) 5 seconds ago                       alpine_bash</code></pre>
<pre><code class="language-shell"># Delete container by name
docker container rm alpine_bash</code></pre>]]></content>
            <updated>2019-07-07T00:27:46-06:00</updated>
        </entry>
            <entry>
            <author>
                <name>Martin Tan</name>
            </author>
            <title type="text"><![CDATA[Error: An error occurred while installing nokogiri, and Bundler cannot continue on Ubuntu]]></title>
            <link rel="alternate" type="text/html" href="https://mrtan.me/post/34.html"></link>
            <id>https://mrtan.me/post/34.html</id>
            <summary type="html"><![CDATA[<p><img src="/upload/images/2019/06/install-nokogiri-error-ubuntu.jpg" alt="install-nokogiri-error-ubuntu" /></p>
<h2>Issue</h2>
<p>I got this error  on when try to install nokogiri on a Ubuntu 18.10 server. It seems like I've not install the required packages correct on the system. </p>
<pre><code class="language-shell">Gem::Ext::BuildError: ERROR: Failed to build gem native extension.

    current directory: /root/alert/vendor/bundle/ruby/2.5.0/gems/nokogiri-1.10.3/ext/nokogiri
/usr/bin/ruby2.5 -I /usr/local/lib/site_ruby/2.5.0 -r ./siteconf20190630-30475-1wuk0lw.rb extconf.rb
mkmf.rb can't find header files for ruby at /usr/lib/ruby/include/ruby.h

extconf failed, exit code 1

Gem files will remain installed in /root/alert/vendor/bundle/ruby/2.5.0/gems/nokogiri-1.10.3 for inspection.
Results logged to /root/alert/vendor/bundle/ruby/2.5.0/extensions/x86_64-linux/2.5.0/nokogiri-1.10.3/gem_make.out

An error occurred while installing nokogiri (1.10.3), and Bundler cannot continue.
Make sure that `gem install nokogiri -v '1.10.3' --source 'https://rubygems.org/'` succeeds before bundling.

In Gemfile:
  nokogiri</code></pre>
<pre><code class="language-shell">
root@server:~/alert# gem install nokogiri -v '1.10.3' --source 'https://rubygems.org/'
Fetching nokogiri-1.10.3.gem
Fetching mini_portile2-2.4.0.gem
Successfully installed mini_portile2-2.4.0
Building native extensions. This could take a while...
ERROR:  Error installing nokogiri:
    ERROR: Failed to build gem native extension.

    current directory: /var/lib/gems/2.5.0/gems/nokogiri-1.10.3/ext/nokogiri
/usr/bin/ruby2.5 -I /usr/local/lib/site_ruby/2.5.0 -r ./siteconf20190630-30630-1ukvdw7.rb extconf.rb
mkmf.rb can't find header files for ruby at /usr/lib/ruby/include/ruby.h

extconf failed, exit code 1

Gem files will remain installed in /var/lib/gems/2.5.0/gems/nokogiri-1.10.3 for inspection.
Results logged to /var/lib/gems/2.5.0/extensions/x86_64-linux/2.5.0/nokogiri-1.10.3/gem_make.out</code></pre>
<h2>Solution</h2>
<p>If you also got this issue on Ubuntu, use the following steps. The installation document is list on the official site of nokogiri, you can also check it out on that website.</p>
<pre><code class="language-shell">sudo apt-get install build-essential patch ruby-dev zlib1g-dev liblzma-dev
gem install nokogiri
bundle</code></pre>]]></summary>
            <content type="html"><![CDATA[<p><img src="/upload/images/2019/06/install-nokogiri-error-ubuntu.jpg" alt="install-nokogiri-error-ubuntu" /></p>
<h2>Issue</h2>
<p>I got this error  on when try to install nokogiri on a Ubuntu 18.10 server. It seems like I've not install the required packages correct on the system. </p>
<pre><code class="language-shell">Gem::Ext::BuildError: ERROR: Failed to build gem native extension.

    current directory: /root/alert/vendor/bundle/ruby/2.5.0/gems/nokogiri-1.10.3/ext/nokogiri
/usr/bin/ruby2.5 -I /usr/local/lib/site_ruby/2.5.0 -r ./siteconf20190630-30475-1wuk0lw.rb extconf.rb
mkmf.rb can't find header files for ruby at /usr/lib/ruby/include/ruby.h

extconf failed, exit code 1

Gem files will remain installed in /root/alert/vendor/bundle/ruby/2.5.0/gems/nokogiri-1.10.3 for inspection.
Results logged to /root/alert/vendor/bundle/ruby/2.5.0/extensions/x86_64-linux/2.5.0/nokogiri-1.10.3/gem_make.out

An error occurred while installing nokogiri (1.10.3), and Bundler cannot continue.
Make sure that `gem install nokogiri -v '1.10.3' --source 'https://rubygems.org/'` succeeds before bundling.

In Gemfile:
  nokogiri</code></pre>
<pre><code class="language-shell">
root@server:~/alert# gem install nokogiri -v '1.10.3' --source 'https://rubygems.org/'
Fetching nokogiri-1.10.3.gem
Fetching mini_portile2-2.4.0.gem
Successfully installed mini_portile2-2.4.0
Building native extensions. This could take a while...
ERROR:  Error installing nokogiri:
    ERROR: Failed to build gem native extension.

    current directory: /var/lib/gems/2.5.0/gems/nokogiri-1.10.3/ext/nokogiri
/usr/bin/ruby2.5 -I /usr/local/lib/site_ruby/2.5.0 -r ./siteconf20190630-30630-1ukvdw7.rb extconf.rb
mkmf.rb can't find header files for ruby at /usr/lib/ruby/include/ruby.h

extconf failed, exit code 1

Gem files will remain installed in /var/lib/gems/2.5.0/gems/nokogiri-1.10.3 for inspection.
Results logged to /var/lib/gems/2.5.0/extensions/x86_64-linux/2.5.0/nokogiri-1.10.3/gem_make.out</code></pre>
<h2>Solution</h2>
<p>If you also got this issue on Ubuntu, use the following steps. The installation document is list on the official site of nokogiri, you can also check it out on that website.</p>
<pre><code class="language-shell">sudo apt-get install build-essential patch ruby-dev zlib1g-dev liblzma-dev
gem install nokogiri
bundle</code></pre>]]></content>
            <updated>2019-06-30T13:11:41-06:00</updated>
        </entry>
            <entry>
            <author>
                <name>Martin Tan</name>
            </author>
            <title type="text"><![CDATA[ERROR: Error Installing Nokogiri: Invalid Gem: Package is Corrupt on Mac]]></title>
            <link rel="alternate" type="text/html" href="https://mrtan.me/post/33.html"></link>
            <id>https://mrtan.me/post/33.html</id>
            <summary type="html"><![CDATA[<h2>Issue</h2>
<p>This error came out when I'm trying to bundle install Rails with rbenv on my MacBook, after retry several times, finally got this solution to install Nokogiri successfully.</p>
<pre><code class="language-shell">-&gt; bundle

Bundler::GemspecError: Could not read gem at /Users/username/.rbenv/versions/2.6.0/lib/ruby/gems/2.6.0/cache/nokogiri-1.10.3.gem. It may be corrupted.
An error occurred while installing nokogiri (1.10.3), and Bundler cannot continue.
Make sure that `gem install nokogiri -v '1.10.3' --source 'https://rubygems.org/'` succeeds before bundling.</code></pre>
<pre><code class="language-shell">-&gt;  gem install nokogiri -v '1.10.3' --source 'https://rubygems.org/'

ERROR:  Error installing nokogiri:
    invalid gem: package is corrupt, exception while verifying: undefined method `size' for nil:NilClass (NoMethodError) in /Users/username/.rbenv/versions/2.6.0/lib/ruby/gems/2.6.0/cache/nokogiri-1.10.3.gem</code></pre>
<p><img src="/upload/images/2019/06/error-installing-nokogiri-on-mac.jpg" alt="error-installing-nokogiri-on-mac" /></p>
<h2>Solution</h2>
<pre><code class="language-shell">brew install libiconv

rm /Users/username/.rbenv/versions/2.6.0/lib/ruby/gems/2.6.0/cache/nokogiri-1.10.3.gem

gem install nokogiri -v '1.10.3' --source 'https://rubygems.org/'

bundle</code></pre>]]></summary>
            <content type="html"><![CDATA[<h2>Issue</h2>
<p>This error came out when I'm trying to bundle install Rails with rbenv on my MacBook, after retry several times, finally got this solution to install Nokogiri successfully.</p>
<pre><code class="language-shell">-&gt; bundle

Bundler::GemspecError: Could not read gem at /Users/username/.rbenv/versions/2.6.0/lib/ruby/gems/2.6.0/cache/nokogiri-1.10.3.gem. It may be corrupted.
An error occurred while installing nokogiri (1.10.3), and Bundler cannot continue.
Make sure that `gem install nokogiri -v '1.10.3' --source 'https://rubygems.org/'` succeeds before bundling.</code></pre>
<pre><code class="language-shell">-&gt;  gem install nokogiri -v '1.10.3' --source 'https://rubygems.org/'

ERROR:  Error installing nokogiri:
    invalid gem: package is corrupt, exception while verifying: undefined method `size' for nil:NilClass (NoMethodError) in /Users/username/.rbenv/versions/2.6.0/lib/ruby/gems/2.6.0/cache/nokogiri-1.10.3.gem</code></pre>
<p><img src="/upload/images/2019/06/error-installing-nokogiri-on-mac.jpg" alt="error-installing-nokogiri-on-mac" /></p>
<h2>Solution</h2>
<pre><code class="language-shell">brew install libiconv

rm /Users/username/.rbenv/versions/2.6.0/lib/ruby/gems/2.6.0/cache/nokogiri-1.10.3.gem

gem install nokogiri -v '1.10.3' --source 'https://rubygems.org/'

bundle</code></pre>]]></content>
            <updated>2019-06-24T21:43:49-06:00</updated>
        </entry>
            <entry>
            <author>
                <name>Martin Tan</name>
            </author>
            <title type="text"><![CDATA[The Behaviors of Dependent Destroy on Rails ActiveModel]]></title>
            <link rel="alternate" type="text/html" href="https://mrtan.me/post/32.html"></link>
            <id>https://mrtan.me/post/32.html</id>
            <summary type="html"><![CDATA[<p><img src="/upload/images/2019/06/the-behaviors-of-dependent-destroy.jpg" alt="the-behaviors-of-dependent-destroy" /></p>
<h2>what is dependent :destroy</h2>
<p><em>Dependent</em> is an option of Rails collection association declaration to cascade the delete action. The <em>:destroy</em> is to cause the associated object to also be destroyed when its owner is destroyed.</p>
<h2>Code Preparation</h2>
<p>First, the DB shemas and Rails Model should be ready for the following experiment.</p>
<pre><code class="language-shell">rails g model books
rails g model authors
rails g model comments
rails g model authors_books

rails g migration CreateJoinTableBooksAuthors books authors
</code></pre>
<h3>Migrations</h3>
<p>The migration files should be looked like as listed.</p>
<pre><code class="language-ruby"># db/migrate/2019_create_join_table_books_authors.rb

class CreateJoinTableBooksAuthors &lt; ActiveRecord::Migration[6.0]
  def change
    create_table :authors_books do |t|
      t.integer "book_id", null: false
      t.integer "author_id", null: false

      t.index [:book_id, :author_id], unique: true
      t.index [:author_id, :book_id]

      t.timestamps
    end
  end
end
</code></pre>
<pre><code class="language-ruby"># db/migrate/2019_create_books.rb

class CreateBooks &lt; ActiveRecord::Migration[6.0]
  def change
    create_table :books do |t|
      t.string :name, null: false
      t.timestamps
    end
  end
end
</code></pre>
<pre><code class="language-ruby"># db/migrate/2019_create_authors.rb

class CreateAuthors &lt; ActiveRecord::Migration[6.0]
  def change
    create_table :authors do |t|
      t.string :name, index: { unique: true }, null: false
      t.timestamps
    end
  end
end
</code></pre>
<pre><code class="language-ruby"># db/migrate/2019_create_comments.rb

class CreateComments &lt; ActiveRecord::Migration[6.0]
  def change
    create_table :comments do |t|
      t.integer :book_id, null: false
      t.string :content, null: false
      t.timestamps
    end
  end
end
</code></pre>
<pre><code class="language-ruby"># db/migrate/2019_create_details.rb

class CreateDetails &lt; ActiveRecord::Migration[6.0]
  def change
    create_table :details do |t|
      t.integer :book_id, null: false
      t.integer :paperback, null: false
      t.string :publisher, null: false

      t.timestamps
    end
  end
end
</code></pre>
<p><strong>Run the db migration commands</strong></p>
<pre><code class="language-shell">rails db:create
rails db:migrate</code></pre>
<h3></h3>
<p><img src="/upload/images/2019/06/rails-db-migration.png" alt="rails-db-migration" /></p>
<h3>Models</h3>
<p>Then update the model files for querying. </p>
<pre><code class="language-ruby"># app/models/author.rb

class Author &lt; ApplicationRecord
  has_many :authors_books
  has_many :books, through: :authors_books, dependent: :destroy
end
</code></pre>
<pre><code class="language-ruby"># app/models/authors_book.rb

class AuthorsBook &lt; ApplicationRecord
  belongs_to :author
  belongs_to :book

  validates :author, :book, presence: true
end
</code></pre>
<pre><code class="language-ruby"># app/models/book.rb

class Book &lt; ApplicationRecord
  has_many :comments, dependent: :destroy
  has_many :authors_books
  has_many :authors, through: :authors_books, dependent: :destroy
  has_one :detail, dependent: :destroy 
end
</code></pre>
<pre><code class="language-ruby"># app/models/comment.rb

class Comment &lt; ApplicationRecord
  belongs_to :book
end
</code></pre>
<pre><code class="language-ruby"># app/models/detail.rb

class Detail &lt; ApplicationRecord
  belongs_to :book, dependent: :destroy
end
</code></pre>
<h2>How Dependent :destroy Working</h2>
<h3>One-to-One Association</h3>
<p>Has_one &amp; belongs_to</p>
<pre><code class="language-ruby">book = Book.create name: Faker::Book.title
Detail.create book_id: book.id, paperback: rand(330..2_000), publisher: Faker::Company.name
book.destroy
book.detail.destroy
</code></pre>
<p>If you want to keep the strict one to one relations betweens books and details, you can add <strong>dependent: :destroy</strong> on both sides to remove the combined records. On contract, the book record won't be deleted when the detail record is destroyed.</p>
<p><img src="/upload/images/2019/06/one-to-one-active-model.jpg" alt="one-to-one-active-model" /></p>
<h3>One-to-Many Association</h3>
<p><strong>Has_many &amp; belongs to</strong></p>
<pre><code class="language-ruby">book = Book.create name: Faker::Book.title
3.times { Comment.create content: Faker::Quotes::Shakespeare.hamlet_quote, book_id: book.id }
book.comments
# book.destroy
# book.comments.first.destroy</code></pre>
<p><strong>Books</strong></p>
<table>
<thead>
<tr>
<th>ID</th>
<th>NAME</th>
<th>CREATED_AT</th>
<th>UPDATED_AT</th>
</tr>
</thead>
<tbody>
<tr>
<td>3</td>
<td>Arms and the Man</td>
<td>2019-06-24 14:05:05</td>
<td>2019-06-24 14:05:05</td>
</tr>
</tbody>
</table>
<p><strong>Comments</strong></p>
<table>
<thead>
<tr>
<th>ID</th>
<th>BOOK_ID</th>
<th>CONTENT</th>
<th>CREATED_AT</th>
<th>UPDATED_AT</th>
</tr>
</thead>
<tbody>
<tr>
<td>4</td>
<td>3</td>
<td>A little more than kin, and...</td>
<td>2019-06-24 14:05:05</td>
<td>2019-06-24 14:05:05</td>
</tr>
<tr>
<td>5</td>
<td>3</td>
<td>This above all: to thine ow...</td>
<td>2019-06-24 14:05:05</td>
<td>2019-06-24 14:05:05</td>
</tr>
<tr>
<td>6</td>
<td>3</td>
<td>The lady doth protest too m...</td>
<td>2019-06-24 14:05:05</td>
<td>2019-06-24 14:05:05</td>
</tr>
</tbody>
</table>
<p><strong>What happens when the owner record is deleted</strong></p>
<p>If the owner record is delete, all the assocated records will be deleted immediately. Such as, if the a book row is delete in the <strong>books</strong> table, all the related coments will be delete as well. But, if a comment is deleted, the related book row won't be delete.</p>
<p><img src="/upload/images/2019/06/one-to-many-active-model.jpg" alt="one-to-many-active-model" /></p>
<pre><code class="language-ruby"># has_many :comments
book = Book.create name: Faker::Book.title
3.times { Comment.create content: Faker::Quotes::Shakespeare.hamlet_quote, book_id: book.id }
# Book.first.destroy!</code></pre>
<pre><code class="language-shell">irb(main):012:0&gt; Book.first.destroy!
  Book Load (0.2ms)  SELECT "books".* FROM "books" ORDER BY "books"."id" ASC LIMIT ?  [["LIMIT", 1]]
   (0.1ms)  begin transaction
  Author Load (0.2ms)  SELECT "authors".* FROM "authors" INNER JOIN "authors_books" ON "authors"."id" = "authors_books"."author_id" WHERE "authors_books"."book_id" = ?  [["book_id", 1]]
  Book Destroy (1.4ms)  DELETE FROM "books" WHERE "books"."id" = ?  [["id", 1]]
   (0.8ms)  commit transaction
=&gt; #&lt;Book id: 1, name: "A Farewell to Arms", created_at: "2019-06-14 09:28:35", updated_at: "2019-06-14 09:28:35"&gt;</code></pre>
<pre><code class="language-ruby">has_many :comments, dependent: :destroy </code></pre>
<pre><code class="language-ruby">Book.first.comments</code></pre>
<table>
<thead>
<tr>
<th>ID</th>
<th>BOOK_ID</th>
<th>CONTENT</th>
<th>CREATED_AT</th>
<th>UPDATED_AT</th>
</tr>
</thead>
<tbody>
<tr>
<td>13</td>
<td>6</td>
<td>There is nothing either goo...</td>
<td>2019-06-24 14:10:24</td>
<td>2019-06-24 14:10:24</td>
</tr>
<tr>
<td>14</td>
<td>6</td>
<td>Though this be madness, yet...</td>
<td>2019-06-24 14:10:24</td>
<td>2019-06-24 14:10:24</td>
</tr>
<tr>
<td>15</td>
<td>6</td>
<td>This above all: to thine ow...</td>
<td>2019-06-24 14:10:24</td>
<td>2019-06-24 14:10:24</td>
</tr>
</tbody>
</table>
<pre><code class="language-shell">Book.first.destroy!
   (0.1ms)  SELECT sqlite_version(*)
  Book Load (0.1ms)  SELECT "books".* FROM "books" ORDER BY "books"."id" ASC LIMIT ? OFFSET ?  [["LIMIT", 1], ["OFFSET", 1]]
   (0.1ms)  begin transaction
  Comment Load (0.1ms)  SELECT "comments".* FROM "comments" WHERE "comments"."book_id" = ?  [["book_id", 4]]
  Author Load (0.1ms)  SELECT "authors".* FROM "authors" INNER JOIN "authors_books" ON "authors"."id" = "authors_books"."author_id" WHERE "authors_books"."book_id" = ?  [["book_id", 4]]
  Book Destroy (0.2ms)  DELETE FROM "books" WHERE "books"."id" = ?  [["id", 4]]
   (0.9ms)  commit transaction
=&gt; #&lt;Book id: 4, name: "Blood's a Rover", created_at: "2019-06-14 09:28:38", updated_at: "2019-06-14 09:28:38"&gt;</code></pre>
<h3>Many-to-Many Association</h3>
<p>Has_many through a Pivot Table</p>
<p>Mock Data</p>
<pre><code class="language-ruby">Author.delete_all
Book.delete_all

Author.create name: Faker::Book.author
Author.create name: Faker::Book.author
4.times { Book.create name: Faker::Book.title }

Author.first.books &lt;&lt; Book.first
Author.first.books &lt;&lt; Book.second
Author.second.books &lt;&lt; Book.third
Author.second.books &lt;&lt; Book.fourth</code></pre>
<p>Author</p>
<table>
<thead>
<tr>
<th>ID</th>
<th>NAME</th>
<th>CREATED_AT</th>
<th>UPDATED_AT</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>Mrs. Willard Balistreri</td>
<td>2019-06-24 14:11:17</td>
<td>2019-06-24 14:11:17</td>
</tr>
</tbody>
</table>
<p>Books</p>
<table>
<thead>
<tr>
<th>ID</th>
<th>NAME</th>
<th>CREATED_AT</th>
<th>UPDATED_AT</th>
</tr>
</thead>
<tbody>
<tr>
<td>7</td>
<td>Fear and Trembling</td>
<td>2019-06-24 14:11:17</td>
<td>2019-06-24 14:11:17</td>
</tr>
<tr>
<td>8</td>
<td>The Proper Study</td>
<td>2019-06-24 14:11:17</td>
<td>2019-06-24 14:11:17</td>
</tr>
<tr>
<td>9</td>
<td>After Many a Summer Dies th...</td>
<td>2019-06-24 14:11:17</td>
<td>2019-06-24 14:11:17</td>
</tr>
<tr>
<td>10</td>
<td>Tirra Lirra by the River</td>
<td>2019-06-24 14:11:17</td>
<td>2019-06-24 14:11:17</td>
</tr>
</tbody>
</table>
<p>AuthorsBooks</p>
<table>
<thead>
<tr>
<th>ID</th>
<th>BOOK_ID</th>
<th>AUTHOR_ID</th>
<th>CREATED_AT</th>
<th>UPDATED_AT</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>7</td>
<td>1</td>
<td>2019-06-24 14:11:17</td>
<td>2019-06-24 14:11:17</td>
</tr>
<tr>
<td>2</td>
<td>8</td>
<td>1</td>
<td>2019-06-24 14:11:17</td>
<td>2019-06-24 14:11:17</td>
</tr>
<tr>
<td>3</td>
<td>9</td>
<td>2</td>
<td>2019-06-24 14:11:17</td>
<td>2019-06-24 14:11:17</td>
</tr>
<tr>
<td>4</td>
<td>10</td>
<td>2</td>
<td>2019-06-24 14:11:17</td>
<td>2019-06-24 14:11:17</td>
</tr>
</tbody>
</table>
<p>Delete </p>
<pre><code class="language-ruby">Author.second.books
Author.first.authors_books
Author.first.books.first.destroy
# AuthorsBook.find(14).destroy</code></pre>
<p>Removing records from the pivot table , only applied on that pivot table, no assocated records will be delete. In the case, <em>authors</em> and <em>books</em> won't be deleted when deleting <em>authors_books</em> rows.</p>
<p>If a assocated record is delete, the relation rows in that pivot table will also be deleted and all these operations are wrapped in a transaction.</p>
<p><img src="/upload/images/2019/06/many-to-many-active-model.jpg" alt="many-to-many-active-model" /></p>
<h2>Conclusion</h2>
<p>In one -to-one, and one-to-many scenarios, has_one or has_many, the associated records will be deleted if the owner record got deleted. But, With <em>has_and_belongs_to_many</em> and <em>has_many :through</em>, the many to many scenario, the join records will be deleted, but the associated records won't.</p>]]></summary>
            <content type="html"><![CDATA[<p><img src="/upload/images/2019/06/the-behaviors-of-dependent-destroy.jpg" alt="the-behaviors-of-dependent-destroy" /></p>
<h2>what is dependent :destroy</h2>
<p><em>Dependent</em> is an option of Rails collection association declaration to cascade the delete action. The <em>:destroy</em> is to cause the associated object to also be destroyed when its owner is destroyed.</p>
<h2>Code Preparation</h2>
<p>First, the DB shemas and Rails Model should be ready for the following experiment.</p>
<pre><code class="language-shell">rails g model books
rails g model authors
rails g model comments
rails g model authors_books

rails g migration CreateJoinTableBooksAuthors books authors
</code></pre>
<h3>Migrations</h3>
<p>The migration files should be looked like as listed.</p>
<pre><code class="language-ruby"># db/migrate/2019_create_join_table_books_authors.rb

class CreateJoinTableBooksAuthors &lt; ActiveRecord::Migration[6.0]
  def change
    create_table :authors_books do |t|
      t.integer "book_id", null: false
      t.integer "author_id", null: false

      t.index [:book_id, :author_id], unique: true
      t.index [:author_id, :book_id]

      t.timestamps
    end
  end
end
</code></pre>
<pre><code class="language-ruby"># db/migrate/2019_create_books.rb

class CreateBooks &lt; ActiveRecord::Migration[6.0]
  def change
    create_table :books do |t|
      t.string :name, null: false
      t.timestamps
    end
  end
end
</code></pre>
<pre><code class="language-ruby"># db/migrate/2019_create_authors.rb

class CreateAuthors &lt; ActiveRecord::Migration[6.0]
  def change
    create_table :authors do |t|
      t.string :name, index: { unique: true }, null: false
      t.timestamps
    end
  end
end
</code></pre>
<pre><code class="language-ruby"># db/migrate/2019_create_comments.rb

class CreateComments &lt; ActiveRecord::Migration[6.0]
  def change
    create_table :comments do |t|
      t.integer :book_id, null: false
      t.string :content, null: false
      t.timestamps
    end
  end
end
</code></pre>
<pre><code class="language-ruby"># db/migrate/2019_create_details.rb

class CreateDetails &lt; ActiveRecord::Migration[6.0]
  def change
    create_table :details do |t|
      t.integer :book_id, null: false
      t.integer :paperback, null: false
      t.string :publisher, null: false

      t.timestamps
    end
  end
end
</code></pre>
<p><strong>Run the db migration commands</strong></p>
<pre><code class="language-shell">rails db:create
rails db:migrate</code></pre>
<h3></h3>
<p><img src="/upload/images/2019/06/rails-db-migration.png" alt="rails-db-migration" /></p>
<h3>Models</h3>
<p>Then update the model files for querying. </p>
<pre><code class="language-ruby"># app/models/author.rb

class Author &lt; ApplicationRecord
  has_many :authors_books
  has_many :books, through: :authors_books, dependent: :destroy
end
</code></pre>
<pre><code class="language-ruby"># app/models/authors_book.rb

class AuthorsBook &lt; ApplicationRecord
  belongs_to :author
  belongs_to :book

  validates :author, :book, presence: true
end
</code></pre>
<pre><code class="language-ruby"># app/models/book.rb

class Book &lt; ApplicationRecord
  has_many :comments, dependent: :destroy
  has_many :authors_books
  has_many :authors, through: :authors_books, dependent: :destroy
  has_one :detail, dependent: :destroy 
end
</code></pre>
<pre><code class="language-ruby"># app/models/comment.rb

class Comment &lt; ApplicationRecord
  belongs_to :book
end
</code></pre>
<pre><code class="language-ruby"># app/models/detail.rb

class Detail &lt; ApplicationRecord
  belongs_to :book, dependent: :destroy
end
</code></pre>
<h2>How Dependent :destroy Working</h2>
<h3>One-to-One Association</h3>
<p>Has_one &amp; belongs_to</p>
<pre><code class="language-ruby">book = Book.create name: Faker::Book.title
Detail.create book_id: book.id, paperback: rand(330..2_000), publisher: Faker::Company.name
book.destroy
book.detail.destroy
</code></pre>
<p>If you want to keep the strict one to one relations betweens books and details, you can add <strong>dependent: :destroy</strong> on both sides to remove the combined records. On contract, the book record won't be deleted when the detail record is destroyed.</p>
<p><img src="/upload/images/2019/06/one-to-one-active-model.jpg" alt="one-to-one-active-model" /></p>
<h3>One-to-Many Association</h3>
<p><strong>Has_many &amp; belongs to</strong></p>
<pre><code class="language-ruby">book = Book.create name: Faker::Book.title
3.times { Comment.create content: Faker::Quotes::Shakespeare.hamlet_quote, book_id: book.id }
book.comments
# book.destroy
# book.comments.first.destroy</code></pre>
<p><strong>Books</strong></p>
<table>
<thead>
<tr>
<th>ID</th>
<th>NAME</th>
<th>CREATED_AT</th>
<th>UPDATED_AT</th>
</tr>
</thead>
<tbody>
<tr>
<td>3</td>
<td>Arms and the Man</td>
<td>2019-06-24 14:05:05</td>
<td>2019-06-24 14:05:05</td>
</tr>
</tbody>
</table>
<p><strong>Comments</strong></p>
<table>
<thead>
<tr>
<th>ID</th>
<th>BOOK_ID</th>
<th>CONTENT</th>
<th>CREATED_AT</th>
<th>UPDATED_AT</th>
</tr>
</thead>
<tbody>
<tr>
<td>4</td>
<td>3</td>
<td>A little more than kin, and...</td>
<td>2019-06-24 14:05:05</td>
<td>2019-06-24 14:05:05</td>
</tr>
<tr>
<td>5</td>
<td>3</td>
<td>This above all: to thine ow...</td>
<td>2019-06-24 14:05:05</td>
<td>2019-06-24 14:05:05</td>
</tr>
<tr>
<td>6</td>
<td>3</td>
<td>The lady doth protest too m...</td>
<td>2019-06-24 14:05:05</td>
<td>2019-06-24 14:05:05</td>
</tr>
</tbody>
</table>
<p><strong>What happens when the owner record is deleted</strong></p>
<p>If the owner record is delete, all the assocated records will be deleted immediately. Such as, if the a book row is delete in the <strong>books</strong> table, all the related coments will be delete as well. But, if a comment is deleted, the related book row won't be delete.</p>
<p><img src="/upload/images/2019/06/one-to-many-active-model.jpg" alt="one-to-many-active-model" /></p>
<pre><code class="language-ruby"># has_many :comments
book = Book.create name: Faker::Book.title
3.times { Comment.create content: Faker::Quotes::Shakespeare.hamlet_quote, book_id: book.id }
# Book.first.destroy!</code></pre>
<pre><code class="language-shell">irb(main):012:0&gt; Book.first.destroy!
  Book Load (0.2ms)  SELECT "books".* FROM "books" ORDER BY "books"."id" ASC LIMIT ?  [["LIMIT", 1]]
   (0.1ms)  begin transaction
  Author Load (0.2ms)  SELECT "authors".* FROM "authors" INNER JOIN "authors_books" ON "authors"."id" = "authors_books"."author_id" WHERE "authors_books"."book_id" = ?  [["book_id", 1]]
  Book Destroy (1.4ms)  DELETE FROM "books" WHERE "books"."id" = ?  [["id", 1]]
   (0.8ms)  commit transaction
=&gt; #&lt;Book id: 1, name: "A Farewell to Arms", created_at: "2019-06-14 09:28:35", updated_at: "2019-06-14 09:28:35"&gt;</code></pre>
<pre><code class="language-ruby">has_many :comments, dependent: :destroy </code></pre>
<pre><code class="language-ruby">Book.first.comments</code></pre>
<table>
<thead>
<tr>
<th>ID</th>
<th>BOOK_ID</th>
<th>CONTENT</th>
<th>CREATED_AT</th>
<th>UPDATED_AT</th>
</tr>
</thead>
<tbody>
<tr>
<td>13</td>
<td>6</td>
<td>There is nothing either goo...</td>
<td>2019-06-24 14:10:24</td>
<td>2019-06-24 14:10:24</td>
</tr>
<tr>
<td>14</td>
<td>6</td>
<td>Though this be madness, yet...</td>
<td>2019-06-24 14:10:24</td>
<td>2019-06-24 14:10:24</td>
</tr>
<tr>
<td>15</td>
<td>6</td>
<td>This above all: to thine ow...</td>
<td>2019-06-24 14:10:24</td>
<td>2019-06-24 14:10:24</td>
</tr>
</tbody>
</table>
<pre><code class="language-shell">Book.first.destroy!
   (0.1ms)  SELECT sqlite_version(*)
  Book Load (0.1ms)  SELECT "books".* FROM "books" ORDER BY "books"."id" ASC LIMIT ? OFFSET ?  [["LIMIT", 1], ["OFFSET", 1]]
   (0.1ms)  begin transaction
  Comment Load (0.1ms)  SELECT "comments".* FROM "comments" WHERE "comments"."book_id" = ?  [["book_id", 4]]
  Author Load (0.1ms)  SELECT "authors".* FROM "authors" INNER JOIN "authors_books" ON "authors"."id" = "authors_books"."author_id" WHERE "authors_books"."book_id" = ?  [["book_id", 4]]
  Book Destroy (0.2ms)  DELETE FROM "books" WHERE "books"."id" = ?  [["id", 4]]
   (0.9ms)  commit transaction
=&gt; #&lt;Book id: 4, name: "Blood's a Rover", created_at: "2019-06-14 09:28:38", updated_at: "2019-06-14 09:28:38"&gt;</code></pre>
<h3>Many-to-Many Association</h3>
<p>Has_many through a Pivot Table</p>
<p>Mock Data</p>
<pre><code class="language-ruby">Author.delete_all
Book.delete_all

Author.create name: Faker::Book.author
Author.create name: Faker::Book.author
4.times { Book.create name: Faker::Book.title }

Author.first.books &lt;&lt; Book.first
Author.first.books &lt;&lt; Book.second
Author.second.books &lt;&lt; Book.third
Author.second.books &lt;&lt; Book.fourth</code></pre>
<p>Author</p>
<table>
<thead>
<tr>
<th>ID</th>
<th>NAME</th>
<th>CREATED_AT</th>
<th>UPDATED_AT</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>Mrs. Willard Balistreri</td>
<td>2019-06-24 14:11:17</td>
<td>2019-06-24 14:11:17</td>
</tr>
</tbody>
</table>
<p>Books</p>
<table>
<thead>
<tr>
<th>ID</th>
<th>NAME</th>
<th>CREATED_AT</th>
<th>UPDATED_AT</th>
</tr>
</thead>
<tbody>
<tr>
<td>7</td>
<td>Fear and Trembling</td>
<td>2019-06-24 14:11:17</td>
<td>2019-06-24 14:11:17</td>
</tr>
<tr>
<td>8</td>
<td>The Proper Study</td>
<td>2019-06-24 14:11:17</td>
<td>2019-06-24 14:11:17</td>
</tr>
<tr>
<td>9</td>
<td>After Many a Summer Dies th...</td>
<td>2019-06-24 14:11:17</td>
<td>2019-06-24 14:11:17</td>
</tr>
<tr>
<td>10</td>
<td>Tirra Lirra by the River</td>
<td>2019-06-24 14:11:17</td>
<td>2019-06-24 14:11:17</td>
</tr>
</tbody>
</table>
<p>AuthorsBooks</p>
<table>
<thead>
<tr>
<th>ID</th>
<th>BOOK_ID</th>
<th>AUTHOR_ID</th>
<th>CREATED_AT</th>
<th>UPDATED_AT</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>7</td>
<td>1</td>
<td>2019-06-24 14:11:17</td>
<td>2019-06-24 14:11:17</td>
</tr>
<tr>
<td>2</td>
<td>8</td>
<td>1</td>
<td>2019-06-24 14:11:17</td>
<td>2019-06-24 14:11:17</td>
</tr>
<tr>
<td>3</td>
<td>9</td>
<td>2</td>
<td>2019-06-24 14:11:17</td>
<td>2019-06-24 14:11:17</td>
</tr>
<tr>
<td>4</td>
<td>10</td>
<td>2</td>
<td>2019-06-24 14:11:17</td>
<td>2019-06-24 14:11:17</td>
</tr>
</tbody>
</table>
<p>Delete </p>
<pre><code class="language-ruby">Author.second.books
Author.first.authors_books
Author.first.books.first.destroy
# AuthorsBook.find(14).destroy</code></pre>
<p>Removing records from the pivot table , only applied on that pivot table, no assocated records will be delete. In the case, <em>authors</em> and <em>books</em> won't be deleted when deleting <em>authors_books</em> rows.</p>
<p>If a assocated record is delete, the relation rows in that pivot table will also be deleted and all these operations are wrapped in a transaction.</p>
<p><img src="/upload/images/2019/06/many-to-many-active-model.jpg" alt="many-to-many-active-model" /></p>
<h2>Conclusion</h2>
<p>In one -to-one, and one-to-many scenarios, has_one or has_many, the associated records will be deleted if the owner record got deleted. But, With <em>has_and_belongs_to_many</em> and <em>has_many :through</em>, the many to many scenario, the join records will be deleted, but the associated records won't.</p>]]></content>
            <updated>2019-06-24T21:21:28-06:00</updated>
        </entry>
            <entry>
            <author>
                <name>Martin Tan</name>
            </author>
            <title type="text"><![CDATA[Rails GraphQL Beginner Tutorial - Part 1]]></title>
            <link rel="alternate" type="text/html" href="https://mrtan.me/post/31.html"></link>
            <id>https://mrtan.me/post/31.html</id>
            <summary type="html"><![CDATA[<p><img src="/upload/images/2019/06/rails-graphql-tutorial.jpg" alt="rails-graphql-tutorial" /></p>
<h2>Goal</h2>
<p>This article is the first post of rails GraphQL beginner serial, I'm going to create a demo to show how to use GraphQL in Rails by querying book records in DB.</p>
<p>First of all, let's list all the thing we need to know about the following steps.</p>
<ul>
<li>
<p>The <a href="https://rubygems.org/gems/graphql" target="_blank">graphql</a> gem.</p>
</li>
<li>
<p>A table named books.</p>
<table>
<thead>
<tr>
<th>ID</th>
<th>NAME</th>
<th>PRICE</th>
<th>CREATED_AT</th>
<th>UPDATED_AT</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>Where the Wild Things Are</td>
<td>18.95</td>
<td>2019-06-10 19:41:28</td>
<td>2019-06-10 19:41:28</td>
</tr>
</tbody>
</table>
</li>
<li>
<p>A GraphQL query for fetching book by id.</p>
</li>
</ul>
<pre><code class="language-sql">{
  book(id: 1) {
    id
    name
    price
  }
}</code></pre>
<p>Then, let's explore the steps of how GraphQL is working as.</p>
<h2>Initialization</h2>
<h4>Create a New Project</h4>
<pre><code class="language-shell">rails new graphql-tutorial</code></pre>
<h4>Update Gemfile</h4>
<p>Add the graphql gem in the file named Gemfile at project root directory.</p>
<pre><code class="language-ruby">gem 'graphql'</code></pre>
<p>Remove the following lines since we are only going to test API without CSS or JS files, or the Node.js is required by webpacker.</p>
<pre><code class="language-ruby">gem 'sass-rails', '~&gt; 5'
gem 'webpacker', '~&gt; 4.0'</code></pre>
<h4>Install graphql gem in Rails</h4>
<p>Run the following commands to install graphql gem in Rails.</p>
<pre><code class="language-shell">bundle install
rails generate graphql:install</code></pre>
<p>Here is the output, you can see the directory of graphql is created by this command, also routes got updated.</p>
<p><img src="/upload/images/2019/06/rails-graphql-installation.png" alt="rails graphql installation" /></p>
<p>A graphql interface is added to routes and another one is GraphiQL which only available in develop environment for debug purpose.</p>
<p>File content in routes.rb</p>
<pre><code class="language-ruby">Rails.application.routes.draw do
  if Rails.env.development?
    mount GraphiQL::Rails::Engine, at: "/graphiql", graphql_path: "/graphql"
  end
  post "/graphql", to: "graphql#execute"
  # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
end</code></pre>
<h2>Mock data</h2>
<h4>Generate a model</h4>
<pre><code class="language-shell">rails g model book</code></pre>
<p><img src="/upload/images/2019/06/book-model-creation.png" alt="book model creation" /></p>
<p>Paste the following code to db/migrate/20190610102537_create_books.rb.</p>
<pre><code class="language-ruby">class CreateBooks &lt; ActiveRecord::Migration[6.0]
  def change
    create_table :books do |t|
      t.string :name, unique: true, null: false
      t.decimal :price, precision: 10, scale: 2, default: 0.0, null: false
      t.timestamps
    end
  end
end
</code></pre>
<h4>Migrate table schema</h4>
<pre><code class="language-shell">rails db:migrate</code></pre>
<p><img src="/upload/images/2019/06/rails-db-migration.png" alt="rails db migration" /></p>
<h4>Mock a book record in Rails Console</h4>
<pre><code class="language-ruby">Book.create name: 'Where the Wild Things Are', price: '18.95'</code></pre>
<p><img src="/upload/images/2019/06/mock-book-record.png" alt="mock-book-data" /></p>
<h4>Pull up the server</h4>
<pre><code class="language-shell">rails s
#http://localhost:3000/graphiql</code></pre>
<p><img src="/upload/images/2019/06/request-graphql-server.png" alt="request-graphql-server" /></p>
<p>It works well if you open the same page as what I have.</p>
<h2>Link Book Model to GraphQL Interface</h2>
<h4>Add schema and queries for GraphQL</h4>
<p>To link data in DB with GraphQL, we need to create a book_type.rb file and a book_query.rb file.</p>
<p><em>book_type.rb</em></p>
<pre><code class="language-ruby">module Types
  class BookType &lt; Types::BaseObject
    description 'The book type of this schema'

    field :id, ID, null: false
    field :name, String, null: false
    field :price, String, null: false
  end
end
</code></pre>
<p>Before we create book query, we create a base query first.</p>
<p><em>base_query.rb</em></p>
<pre><code class="language-ruby">module Queries
  class BaseQuery &lt; GraphQL::Schema::Resolver
  end
end
</code></pre>
<p><em>book_query.rb</em></p>
<pre><code class="language-ruby">module Queries
  class BookQuery &lt; BaseQuery
    type Types::BookType, null: true
    description 'Find a book by id'

    argument :id, Integer, required: true

    def resolve(id:)
      Book.find_by(id: id)
    end
  end
end
</code></pre>
<p><strong>Add the new book query to query_type.rb</strong></p>
<p><em>query_type.rb</em></p>
<pre><code class="language-ruby">module Types
  class QueryType &lt; Types::BaseObject
    field :book, resolver: Queries::BookQuery
  end
end
</code></pre>
<p>The files you changed and created should be the same as below.</p>
<p><img src="/upload/images/2019/06/graphql-query-files.png" alt="graphql-query-files" /></p>
<h2>Query Graphql Server</h2>
<h4>Query Graphql in Browser</h4>
<p>Paste the query string to your GraphiQL web client and query.</p>
<p><img src="/upload/images/2019/06/query-on-graphql-api.png" alt="query-on-graphql-api" /></p>
<p>Finally, we come to a successful end. This is the beginning article to show you how to integrate GraphQL in Rails, so I keep it as simple as I can and we have not seen the difference and advantage of GraphQL. I'll talk more in following posts.</p>]]></summary>
            <content type="html"><![CDATA[<p><img src="/upload/images/2019/06/rails-graphql-tutorial.jpg" alt="rails-graphql-tutorial" /></p>
<h2>Goal</h2>
<p>This article is the first post of rails GraphQL beginner serial, I'm going to create a demo to show how to use GraphQL in Rails by querying book records in DB.</p>
<p>First of all, let's list all the thing we need to know about the following steps.</p>
<ul>
<li>
<p>The <a href="https://rubygems.org/gems/graphql" target="_blank">graphql</a> gem.</p>
</li>
<li>
<p>A table named books.</p>
<table>
<thead>
<tr>
<th>ID</th>
<th>NAME</th>
<th>PRICE</th>
<th>CREATED_AT</th>
<th>UPDATED_AT</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>Where the Wild Things Are</td>
<td>18.95</td>
<td>2019-06-10 19:41:28</td>
<td>2019-06-10 19:41:28</td>
</tr>
</tbody>
</table>
</li>
<li>
<p>A GraphQL query for fetching book by id.</p>
</li>
</ul>
<pre><code class="language-sql">{
  book(id: 1) {
    id
    name
    price
  }
}</code></pre>
<p>Then, let's explore the steps of how GraphQL is working as.</p>
<h2>Initialization</h2>
<h4>Create a New Project</h4>
<pre><code class="language-shell">rails new graphql-tutorial</code></pre>
<h4>Update Gemfile</h4>
<p>Add the graphql gem in the file named Gemfile at project root directory.</p>
<pre><code class="language-ruby">gem 'graphql'</code></pre>
<p>Remove the following lines since we are only going to test API without CSS or JS files, or the Node.js is required by webpacker.</p>
<pre><code class="language-ruby">gem 'sass-rails', '~&gt; 5'
gem 'webpacker', '~&gt; 4.0'</code></pre>
<h4>Install graphql gem in Rails</h4>
<p>Run the following commands to install graphql gem in Rails.</p>
<pre><code class="language-shell">bundle install
rails generate graphql:install</code></pre>
<p>Here is the output, you can see the directory of graphql is created by this command, also routes got updated.</p>
<p><img src="/upload/images/2019/06/rails-graphql-installation.png" alt="rails graphql installation" /></p>
<p>A graphql interface is added to routes and another one is GraphiQL which only available in develop environment for debug purpose.</p>
<p>File content in routes.rb</p>
<pre><code class="language-ruby">Rails.application.routes.draw do
  if Rails.env.development?
    mount GraphiQL::Rails::Engine, at: "/graphiql", graphql_path: "/graphql"
  end
  post "/graphql", to: "graphql#execute"
  # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
end</code></pre>
<h2>Mock data</h2>
<h4>Generate a model</h4>
<pre><code class="language-shell">rails g model book</code></pre>
<p><img src="/upload/images/2019/06/book-model-creation.png" alt="book model creation" /></p>
<p>Paste the following code to db/migrate/20190610102537_create_books.rb.</p>
<pre><code class="language-ruby">class CreateBooks &lt; ActiveRecord::Migration[6.0]
  def change
    create_table :books do |t|
      t.string :name, unique: true, null: false
      t.decimal :price, precision: 10, scale: 2, default: 0.0, null: false
      t.timestamps
    end
  end
end
</code></pre>
<h4>Migrate table schema</h4>
<pre><code class="language-shell">rails db:migrate</code></pre>
<p><img src="/upload/images/2019/06/rails-db-migration.png" alt="rails db migration" /></p>
<h4>Mock a book record in Rails Console</h4>
<pre><code class="language-ruby">Book.create name: 'Where the Wild Things Are', price: '18.95'</code></pre>
<p><img src="/upload/images/2019/06/mock-book-record.png" alt="mock-book-data" /></p>
<h4>Pull up the server</h4>
<pre><code class="language-shell">rails s
#http://localhost:3000/graphiql</code></pre>
<p><img src="/upload/images/2019/06/request-graphql-server.png" alt="request-graphql-server" /></p>
<p>It works well if you open the same page as what I have.</p>
<h2>Link Book Model to GraphQL Interface</h2>
<h4>Add schema and queries for GraphQL</h4>
<p>To link data in DB with GraphQL, we need to create a book_type.rb file and a book_query.rb file.</p>
<p><em>book_type.rb</em></p>
<pre><code class="language-ruby">module Types
  class BookType &lt; Types::BaseObject
    description 'The book type of this schema'

    field :id, ID, null: false
    field :name, String, null: false
    field :price, String, null: false
  end
end
</code></pre>
<p>Before we create book query, we create a base query first.</p>
<p><em>base_query.rb</em></p>
<pre><code class="language-ruby">module Queries
  class BaseQuery &lt; GraphQL::Schema::Resolver
  end
end
</code></pre>
<p><em>book_query.rb</em></p>
<pre><code class="language-ruby">module Queries
  class BookQuery &lt; BaseQuery
    type Types::BookType, null: true
    description 'Find a book by id'

    argument :id, Integer, required: true

    def resolve(id:)
      Book.find_by(id: id)
    end
  end
end
</code></pre>
<p><strong>Add the new book query to query_type.rb</strong></p>
<p><em>query_type.rb</em></p>
<pre><code class="language-ruby">module Types
  class QueryType &lt; Types::BaseObject
    field :book, resolver: Queries::BookQuery
  end
end
</code></pre>
<p>The files you changed and created should be the same as below.</p>
<p><img src="/upload/images/2019/06/graphql-query-files.png" alt="graphql-query-files" /></p>
<h2>Query Graphql Server</h2>
<h4>Query Graphql in Browser</h4>
<p>Paste the query string to your GraphiQL web client and query.</p>
<p><img src="/upload/images/2019/06/query-on-graphql-api.png" alt="query-on-graphql-api" /></p>
<p>Finally, we come to a successful end. This is the beginning article to show you how to integrate GraphQL in Rails, so I keep it as simple as I can and we have not seen the difference and advantage of GraphQL. I'll talk more in following posts.</p>]]></content>
            <updated>2019-06-18T23:56:33-06:00</updated>
        </entry>
            <entry>
            <author>
                <name>Martin Tan</name>
            </author>
            <title type="text"><![CDATA[My Mid-Year Coding Review 2019: Magic Tips for Coding]]></title>
            <link rel="alternate" type="text/html" href="https://mrtan.me/post/30.html"></link>
            <id>https://mrtan.me/post/30.html</id>
            <summary type="html"><![CDATA[<p><img src="/upload/images/2019/05/twitters_on_line_for_coding.jpg" alt="Twitters online for coding" /></p>
<p>This article is a brief review of the last half year in my professional life, and all these topics are flowing in my mind for months. It's better to release the reins, let the paw-prints appear on the paper.</p>
<p>I took a long work gap, which went through the whole hot summer last year, to have a vacation, meanwhile,  it's a certain time when introspection happens. After this, I joined a new company, same position as a web developer, but got to learn a new language Ruby in a different industry both of these I never had experienced before.</p>
<p>Time goes faster than you think, and you also have sailed far far away along your new direction. I looked back and asked my self what's the balance of diving into a new language, and what are the skillful tricks of being a more professional software engineer, while inertia was still forcing the ship forward to the blue water. It's not about the definition of enough or success in a career field. However it's like to introduce the way of how to take less effort, but make a better outcome. Fortunately, I got the following three practices which are short and should be asked to yourself before writing down your first line of code in every project.</p>
<h2>Less is More</h2>
<p>This should be the most famous slogan I've ever read in recent years, especially in product design books. This sentence is firstly created in the minimalism movement, architect Ludwig Mies van der Rohe (1886–1969) adopted the motto &quot;Less is more&quot; to describe his aesthetic.[^1] We don't have to work as artists,  but there is a popular book describes coding as an art, same as painting, [^2]at least that is a theoretical status to describe what's the well performed coding work should be like.</p>
<h3>Language Advantage</h3>
<p>When I first got a chance to write the If-Condition in ruby, I was fascinated with the well-designed syntax sugar. See the code first.</p>
<pre><code class="language-ruby">def greet(name)
  return if name.empty?
  puts "Hi #{name}!"
end</code></pre>
<p>From the code above, you can see the If-Condition is written like typing a sentence in your normal article, do nothing and return if the name is empty while trying to greet someone. Anyway, it's the first piece of code coming to my brain as usual. But how about this?</p>
<pre><code class="language-ruby">def greet(name)
  puts "Hi #{name}!" if !name.empty?
end</code></pre>
<p>This If-Condition is written explicitly, just as What You See Is What You Get, and you don't even have to think about the meaning of this line before you understand it. You do the same thing with less code if you try a slight effort while writing down your thought, so cool.</p>
<h3>Less Code</h3>
<p>Another example is shared in a session from my colleague, the topic is about how to get better performance of testing. One of the rules,  which I think is very helpful,  is that writing down fewer test cases by using PRIVATE keyword more widely.</p>
<pre><code class="language-ruby">class Greeting
  def say(name)
    puts "Hi #{name}!" if validate(name)
  end

  def validate(name)
    return true if !name.empty?
  end
end

# Hi Martin!
Greeting.new.say 'Martin'</code></pre>
<p>To get 100% of test coverage, we have to create at least two test cases for #say and #validate. As a contrast, we can use the PRIVATE keyword instead.</p>
<pre><code class="language-ruby">class Greeting
  def say(name)
    puts "Hi #{name}!" if validate(name)
  end

  private

  def validate(name)
    return true if !name.empty?
  end
end

# Hi Martin!
Greeting.new.say 'Martin'</code></pre>
<p>Under this situation, we could achieve the 100% of test coverage by only adding one test case. Less code, but more productive.</p>
<h2>Premature Optimization is the Root of All Evil</h2>
<p>First of all, let me explain what is Premature Optimization when we are talking about coding. Premature Optimization can be defined as optimizing before we know that we need to. Optimizing up front is often regarded as breaking YouArentGonnaNeedIt (YAGNI).[^3]</p>
<p>While I have to clarify that this is not an excuse of being complacent with your code to avoid optimization. Most of our time is focusing on writing code, reviewing code, refactoring code, and all this kind of normal working flows are going to be excluded in this topic. The original quota of this is focusing on software efficiency optimization, which we talk less about during modern web developing, but It can still be considered as a golden sentence on code organization. Here I have two rules for you to determine whether it's prematurely optimized.</p>
<h3>Don't Optimize Without a Clear Benefit</h3>
<p><em>Snippet A</em></p>
<pre><code class="language-ruby">class TestParam
  def initialize
    @arguments = {
      author: 'Mark Twain',
      book: 'The Adventures of Tom Sawyer',
    }
  end

  def author
    @arguments[:author]
  end

  def book
    @arguments[:book]
  end
end

# output: Mark Twain
puts TestParam.new.author
# output: The Adventures of Tom Sawyer
puts TestParam.new.book
</code></pre>
<p><em>Snippet B</em></p>
<pre><code class="language-ruby">class TestParam
  def initialize
    @arguments = {
      author: 'Mark Twain',
      book: 'The Adventures of Tom Sawyer',
    }
  end

  def param(key)
    @arguments[key]
  end
end

# output: Mark Twain
puts TestParam.new.param :author
# output: The Adventures of Tom Sawyer
puts TestParam.new.param :book
</code></pre>
<p>Comparing A with B, B has fewer code lines, less method, but the code structure of A is more semantic. When you read the code, you'll know that you can only get two types of data from the instance, author name and book title. However, you can't get what are the exactly available items from B immediately after reading the code.  It's an important rule that not optimize without a clear benefit by sacrificing code readability and maintainability.</p>
<h3>Don't optimize without profiling</h3>
<p>Today we've got a lot of profiling tools, static code analysis tools, tracing profiling tools, APM (Application Performance Monitoring ) tools, hence it's much easier for us to do profiling.</p>
<p>During the past months of running online services, I do have chances to refactor code and improve the performance. For example,  after inspecting a high latency API, I just found out it's because of fetching lots of cache items one by one on our Redis server. The optimization solution is quite easy by combining multiple Redis requests to only one to reduce the network time-consuming.</p>
<p>On the other hand, I created a background job to synchronize user data from another service last week. In this job, it retrieves all the data for about 5k items by sending an API, and at the same, the unavailable records should also be deleted from DB. My solution is very straightforward by computing the two collections in memory, for the data from API and the data from DB. We really don't need to worry about the memory usage for now, since each item in the collection is about 200 bytes, it's only 2MB for 5K items.</p>
<p>In a Scrum training, our coach told us that new requirements and details are emerging during a Sprint. This theory is also suitable for coding. In a nutshell, this rule can be more sample that not try to optimize until the predictable bottleneck shows up.</p>
<h2>To Be or Not to Be</h2>
<p>Premature Optimization is one of the famous anti-patterns that we should be aware of. On the contrary, should we always follow design patterns? Lots of patterns are widely implemented in my past projects, like Singleton, Factory Method, IoC. I've read many articles about why we should follow the design patterns to avoid system design failed. Even during the earlier years in my career, colleagues are always talking about design pattern.</p>
<p>I was shocked that the first thing came in my mind is to choose which design pattern I'm going to use when I wrote down the code, but not to write down a simple working code. The intent of pattern is to provide a general solution for a commonly occurring problem in software design.[^4] In other words, design pattern cannot satisfy the unique of projects.</p>
<h3>Straightforward above design patterns</h3>
<p>Here is a code piece from https://github.com/storeon/router/ shows how to create routes based on the module, the code is straightforward.</p>
<pre><code class="language-javascript">router.createRouter([
    ['/', () =&gt; ({ page: 'home' })],
    ['/blog', () =&gt; ({ page: 'blog' })],
    ['/blog/post/*', (id) =&gt; ({ page: 'post', id })],
    [
      /^blog\/post\/(\d+)\/(\d+)$/,
      (year, month) =&gt; ({ page: 'post', year, month })
    ]
])</code></pre>
<p>Let's speak, what would you do if you are going to write a blog system and forget the design patterns. The pseudocode code of my solution is like this:</p>
<pre><code class="language-pseudocode">function response(uri)
  render 'post' and return if uri.matchs '/post'
  render 'category' and return if uri.matchs '/category'
  render 'home' and return if uri.matchs '/'
end</code></pre>
<p>Since the routes of blog system are very simple, the straightforward way works better and the code is more readable. To handle a new route is also very simple by adding a new line of route code.</p>
<p>If you get a chance to see the API between different languages of Youtube, you can see they are the following the same structure and workflow, even it's not a good practice in those languages. I've seen a project that defines vast of fundamental interfaces as an influence of Google API style. In the code, the thought often lost in business logic and implementing different interfaces, although it's just to interact with a third party service which should be an easy thing to do.</p>
<p>You may wonder what is the best practice of coding, the answer there is not a general best practice for every project. Hence, no pattern fits every project, just ask yourself the sentences above and make the best choice depending on your own experience.</p>
<p>[^1]: https://en.wikipedia.org/wiki/Minimalism#cite_note-25 Less but better.
[^2]: https://www.goodreads.com/book/show/41793 Hackers_Painters
[^3]: http://wiki.c2.com/?PrematureOptimization Premature Optimization
[^4]: <a href="https://sourcemaking.com/design_patterns">https://sourcemaking.com/design_patterns</a> Design Patterns</p>]]></summary>
            <content type="html"><![CDATA[<p><img src="/upload/images/2019/05/twitters_on_line_for_coding.jpg" alt="Twitters online for coding" /></p>
<p>This article is a brief review of the last half year in my professional life, and all these topics are flowing in my mind for months. It's better to release the reins, let the paw-prints appear on the paper.</p>
<p>I took a long work gap, which went through the whole hot summer last year, to have a vacation, meanwhile,  it's a certain time when introspection happens. After this, I joined a new company, same position as a web developer, but got to learn a new language Ruby in a different industry both of these I never had experienced before.</p>
<p>Time goes faster than you think, and you also have sailed far far away along your new direction. I looked back and asked my self what's the balance of diving into a new language, and what are the skillful tricks of being a more professional software engineer, while inertia was still forcing the ship forward to the blue water. It's not about the definition of enough or success in a career field. However it's like to introduce the way of how to take less effort, but make a better outcome. Fortunately, I got the following three practices which are short and should be asked to yourself before writing down your first line of code in every project.</p>
<h2>Less is More</h2>
<p>This should be the most famous slogan I've ever read in recent years, especially in product design books. This sentence is firstly created in the minimalism movement, architect Ludwig Mies van der Rohe (1886–1969) adopted the motto &quot;Less is more&quot; to describe his aesthetic.[^1] We don't have to work as artists,  but there is a popular book describes coding as an art, same as painting, [^2]at least that is a theoretical status to describe what's the well performed coding work should be like.</p>
<h3>Language Advantage</h3>
<p>When I first got a chance to write the If-Condition in ruby, I was fascinated with the well-designed syntax sugar. See the code first.</p>
<pre><code class="language-ruby">def greet(name)
  return if name.empty?
  puts "Hi #{name}!"
end</code></pre>
<p>From the code above, you can see the If-Condition is written like typing a sentence in your normal article, do nothing and return if the name is empty while trying to greet someone. Anyway, it's the first piece of code coming to my brain as usual. But how about this?</p>
<pre><code class="language-ruby">def greet(name)
  puts "Hi #{name}!" if !name.empty?
end</code></pre>
<p>This If-Condition is written explicitly, just as What You See Is What You Get, and you don't even have to think about the meaning of this line before you understand it. You do the same thing with less code if you try a slight effort while writing down your thought, so cool.</p>
<h3>Less Code</h3>
<p>Another example is shared in a session from my colleague, the topic is about how to get better performance of testing. One of the rules,  which I think is very helpful,  is that writing down fewer test cases by using PRIVATE keyword more widely.</p>
<pre><code class="language-ruby">class Greeting
  def say(name)
    puts "Hi #{name}!" if validate(name)
  end

  def validate(name)
    return true if !name.empty?
  end
end

# Hi Martin!
Greeting.new.say 'Martin'</code></pre>
<p>To get 100% of test coverage, we have to create at least two test cases for #say and #validate. As a contrast, we can use the PRIVATE keyword instead.</p>
<pre><code class="language-ruby">class Greeting
  def say(name)
    puts "Hi #{name}!" if validate(name)
  end

  private

  def validate(name)
    return true if !name.empty?
  end
end

# Hi Martin!
Greeting.new.say 'Martin'</code></pre>
<p>Under this situation, we could achieve the 100% of test coverage by only adding one test case. Less code, but more productive.</p>
<h2>Premature Optimization is the Root of All Evil</h2>
<p>First of all, let me explain what is Premature Optimization when we are talking about coding. Premature Optimization can be defined as optimizing before we know that we need to. Optimizing up front is often regarded as breaking YouArentGonnaNeedIt (YAGNI).[^3]</p>
<p>While I have to clarify that this is not an excuse of being complacent with your code to avoid optimization. Most of our time is focusing on writing code, reviewing code, refactoring code, and all this kind of normal working flows are going to be excluded in this topic. The original quota of this is focusing on software efficiency optimization, which we talk less about during modern web developing, but It can still be considered as a golden sentence on code organization. Here I have two rules for you to determine whether it's prematurely optimized.</p>
<h3>Don't Optimize Without a Clear Benefit</h3>
<p><em>Snippet A</em></p>
<pre><code class="language-ruby">class TestParam
  def initialize
    @arguments = {
      author: 'Mark Twain',
      book: 'The Adventures of Tom Sawyer',
    }
  end

  def author
    @arguments[:author]
  end

  def book
    @arguments[:book]
  end
end

# output: Mark Twain
puts TestParam.new.author
# output: The Adventures of Tom Sawyer
puts TestParam.new.book
</code></pre>
<p><em>Snippet B</em></p>
<pre><code class="language-ruby">class TestParam
  def initialize
    @arguments = {
      author: 'Mark Twain',
      book: 'The Adventures of Tom Sawyer',
    }
  end

  def param(key)
    @arguments[key]
  end
end

# output: Mark Twain
puts TestParam.new.param :author
# output: The Adventures of Tom Sawyer
puts TestParam.new.param :book
</code></pre>
<p>Comparing A with B, B has fewer code lines, less method, but the code structure of A is more semantic. When you read the code, you'll know that you can only get two types of data from the instance, author name and book title. However, you can't get what are the exactly available items from B immediately after reading the code.  It's an important rule that not optimize without a clear benefit by sacrificing code readability and maintainability.</p>
<h3>Don't optimize without profiling</h3>
<p>Today we've got a lot of profiling tools, static code analysis tools, tracing profiling tools, APM (Application Performance Monitoring ) tools, hence it's much easier for us to do profiling.</p>
<p>During the past months of running online services, I do have chances to refactor code and improve the performance. For example,  after inspecting a high latency API, I just found out it's because of fetching lots of cache items one by one on our Redis server. The optimization solution is quite easy by combining multiple Redis requests to only one to reduce the network time-consuming.</p>
<p>On the other hand, I created a background job to synchronize user data from another service last week. In this job, it retrieves all the data for about 5k items by sending an API, and at the same, the unavailable records should also be deleted from DB. My solution is very straightforward by computing the two collections in memory, for the data from API and the data from DB. We really don't need to worry about the memory usage for now, since each item in the collection is about 200 bytes, it's only 2MB for 5K items.</p>
<p>In a Scrum training, our coach told us that new requirements and details are emerging during a Sprint. This theory is also suitable for coding. In a nutshell, this rule can be more sample that not try to optimize until the predictable bottleneck shows up.</p>
<h2>To Be or Not to Be</h2>
<p>Premature Optimization is one of the famous anti-patterns that we should be aware of. On the contrary, should we always follow design patterns? Lots of patterns are widely implemented in my past projects, like Singleton, Factory Method, IoC. I've read many articles about why we should follow the design patterns to avoid system design failed. Even during the earlier years in my career, colleagues are always talking about design pattern.</p>
<p>I was shocked that the first thing came in my mind is to choose which design pattern I'm going to use when I wrote down the code, but not to write down a simple working code. The intent of pattern is to provide a general solution for a commonly occurring problem in software design.[^4] In other words, design pattern cannot satisfy the unique of projects.</p>
<h3>Straightforward above design patterns</h3>
<p>Here is a code piece from https://github.com/storeon/router/ shows how to create routes based on the module, the code is straightforward.</p>
<pre><code class="language-javascript">router.createRouter([
    ['/', () =&gt; ({ page: 'home' })],
    ['/blog', () =&gt; ({ page: 'blog' })],
    ['/blog/post/*', (id) =&gt; ({ page: 'post', id })],
    [
      /^blog\/post\/(\d+)\/(\d+)$/,
      (year, month) =&gt; ({ page: 'post', year, month })
    ]
])</code></pre>
<p>Let's speak, what would you do if you are going to write a blog system and forget the design patterns. The pseudocode code of my solution is like this:</p>
<pre><code class="language-pseudocode">function response(uri)
  render 'post' and return if uri.matchs '/post'
  render 'category' and return if uri.matchs '/category'
  render 'home' and return if uri.matchs '/'
end</code></pre>
<p>Since the routes of blog system are very simple, the straightforward way works better and the code is more readable. To handle a new route is also very simple by adding a new line of route code.</p>
<p>If you get a chance to see the API between different languages of Youtube, you can see they are the following the same structure and workflow, even it's not a good practice in those languages. I've seen a project that defines vast of fundamental interfaces as an influence of Google API style. In the code, the thought often lost in business logic and implementing different interfaces, although it's just to interact with a third party service which should be an easy thing to do.</p>
<p>You may wonder what is the best practice of coding, the answer there is not a general best practice for every project. Hence, no pattern fits every project, just ask yourself the sentences above and make the best choice depending on your own experience.</p>
<p>[^1]: https://en.wikipedia.org/wiki/Minimalism#cite_note-25 Less but better.
[^2]: https://www.goodreads.com/book/show/41793 Hackers_Painters
[^3]: http://wiki.c2.com/?PrematureOptimization Premature Optimization
[^4]: <a href="https://sourcemaking.com/design_patterns">https://sourcemaking.com/design_patterns</a> Design Patterns</p>]]></content>
            <updated>2019-06-03T23:57:21-06:00</updated>
        </entry>
            <entry>
            <author>
                <name>Martin Tan</name>
            </author>
            <title type="text"><![CDATA[Java Load Balancer Simulator Based on Random Array Queue]]></title>
            <link rel="alternate" type="text/html" href="https://mrtan.me/post/29.html"></link>
            <id>https://mrtan.me/post/29.html</id>
            <summary type="html"><![CDATA[<p><img src="/upload/images/2019/04/load_balancer.jpg" alt="load balancer diagram" /></p>
<p>Thinking of there are hundreds of thousands of users request a website at the same time, we must have multiple servers to make sure our service is stable and won't out of usage. You may ask how could we distribute requests to these different servers, the answer is Load Balancer.  Load Balancer is a service to distribute network traffic across multiple servers.</p>
<h2>Question</h2>
<blockquote>
<p>The load balancer simulation maintains a random queue of queues and builds the computation around an inner loop where each new request for service goes on the smallest of a sample of queues, using the sample() method from RandomQueue to randomly sample queues.</p>
</blockquote>
<h2>Implementing Steps</h2>
<p>Different load balancing algorithms have different application scenarios, such as Round Robin, IP Hash, Least Connections. This time we are going to use a random queue, an easy implementation of Least Connections, to simulate load balancer. Let's create a Load Balancer Simulator based on a random queue.</p>
<ol>
<li>Use the code snippets on <a href="https://mrtan.me/post/22.html" target="_blank">RandomArrayQueue.java</a></li>
<li>Create a random queue with a specified size to maintain the servers</li>
<li>Dequeue a server randomly</li>
<li>Dequeue other servers with a specified sample-times until pick out the server with the minimum requests passed to.</li>
<li>passing a request to this server</li>
</ol>
<h2>Solution</h2>
<pre><code class="language-java">
/******************************************************************************
 *  Compilation:  javac LoadBalance.java
 *  Execution:    java LoadBalance 5 5000 0
 *  Dependencies:  RandomArrayQueue.java @link https://mrtan.me/post/22.html
 *
 *  Passing m requests to n servers with a limited random picking times.
 *
 *  % java LoadBalance 5 5000 0
 *   total Servers: 5
 *   total Reqeusts: 5000
 *   Sample Time: 0
 *   [956.0, 995.0, 959.0, 1080.0, 1010.0]
 *
 ******************************************************************************/

import java.util.Arrays;

public class LoadBalance {
    public static void main(String[] args) {
        int totalServers= Integer.parseInt(args[0]);
        int totalRequests = Integer.parseInt(args[1]);
        int sampleTime = Integer.parseInt(args[2]);

        System.out.println("total Servers: " + totalServers);
        System.out.println("total Reqeusts: " + totalRequests);
        System.out.println("Sample Time: " + sampleTime);

        // Create server queues.
        RandomArrayQueue&lt;Queue&lt;Integer&gt;&gt; servers;
        servers = new RandomArrayQueue&lt;Queue&lt;Integer&gt;&gt;();
        for (int i = 0; i &lt; totalServers; i++) {
            servers.enqueue(new Queue&lt;Integer&gt;());
        }

        // Passing reqeusts to servers  
        for (int j = 0; j &lt; totalRequests; j++) {
            Queue&lt;Integer&gt; min = servers.sample();
            for (int k = 1; k &lt; sampleTime; k++) {
                // Pick a sample
                Queue&lt;Integer&gt; queue = servers.sample();
                if (queue.size() &lt; min.size()) min = queue;
            }

            // min surpposed to be the shortest server queue.
            min.enqueue(j);
        }

        int i = 0;
        double[] lengths = new double[totalServers];
        for (Queue&lt;Integer&gt; queue : servers)
            lengths[i++] = queue.size();

        System.out.println(Arrays.toString(lengths));
    }
}</code></pre>
<p><strong>Output</strong></p>
<pre><code class="language-shell">-&gt; java LoadBalance 5 5000 0
total Servers: 5
total Reqeusts: 5000
Sample Time: 0
[956.0, 995.0, 959.0, 1080.0, 1010.0]

-&gt; java LoadBalance 5 5000 2
total Servers: 5
total Reqeusts: 5000
Sample Time: 2
[999.0, 1000.0, 1000.0, 1001.0, 1000.0]</code></pre>
<p><img src="/upload/images/2019/04/laod_balancing_1.png" alt="load balancing 1" /></p>
<p>image - result 1</p>
<p><img src="/upload/images/2019/04/laod_balancing_2.png" alt="load balancing 2" /></p>
<p>image - result 2</p>
<p><em>The second output gets a well-balanced result, seems like the 2 is a good sample value for 5000 / 5.</em></p>
<p>The time complexity of this implementation is Θ(n) because it's an array-based queue which the BIG O of access item isΘ(1), so the total complexity depends on sample-times in the implementation steps.</p>]]></summary>
            <content type="html"><![CDATA[<p><img src="/upload/images/2019/04/load_balancer.jpg" alt="load balancer diagram" /></p>
<p>Thinking of there are hundreds of thousands of users request a website at the same time, we must have multiple servers to make sure our service is stable and won't out of usage. You may ask how could we distribute requests to these different servers, the answer is Load Balancer.  Load Balancer is a service to distribute network traffic across multiple servers.</p>
<h2>Question</h2>
<blockquote>
<p>The load balancer simulation maintains a random queue of queues and builds the computation around an inner loop where each new request for service goes on the smallest of a sample of queues, using the sample() method from RandomQueue to randomly sample queues.</p>
</blockquote>
<h2>Implementing Steps</h2>
<p>Different load balancing algorithms have different application scenarios, such as Round Robin, IP Hash, Least Connections. This time we are going to use a random queue, an easy implementation of Least Connections, to simulate load balancer. Let's create a Load Balancer Simulator based on a random queue.</p>
<ol>
<li>Use the code snippets on <a href="https://mrtan.me/post/22.html" target="_blank">RandomArrayQueue.java</a></li>
<li>Create a random queue with a specified size to maintain the servers</li>
<li>Dequeue a server randomly</li>
<li>Dequeue other servers with a specified sample-times until pick out the server with the minimum requests passed to.</li>
<li>passing a request to this server</li>
</ol>
<h2>Solution</h2>
<pre><code class="language-java">
/******************************************************************************
 *  Compilation:  javac LoadBalance.java
 *  Execution:    java LoadBalance 5 5000 0
 *  Dependencies:  RandomArrayQueue.java @link https://mrtan.me/post/22.html
 *
 *  Passing m requests to n servers with a limited random picking times.
 *
 *  % java LoadBalance 5 5000 0
 *   total Servers: 5
 *   total Reqeusts: 5000
 *   Sample Time: 0
 *   [956.0, 995.0, 959.0, 1080.0, 1010.0]
 *
 ******************************************************************************/

import java.util.Arrays;

public class LoadBalance {
    public static void main(String[] args) {
        int totalServers= Integer.parseInt(args[0]);
        int totalRequests = Integer.parseInt(args[1]);
        int sampleTime = Integer.parseInt(args[2]);

        System.out.println("total Servers: " + totalServers);
        System.out.println("total Reqeusts: " + totalRequests);
        System.out.println("Sample Time: " + sampleTime);

        // Create server queues.
        RandomArrayQueue&lt;Queue&lt;Integer&gt;&gt; servers;
        servers = new RandomArrayQueue&lt;Queue&lt;Integer&gt;&gt;();
        for (int i = 0; i &lt; totalServers; i++) {
            servers.enqueue(new Queue&lt;Integer&gt;());
        }

        // Passing reqeusts to servers  
        for (int j = 0; j &lt; totalRequests; j++) {
            Queue&lt;Integer&gt; min = servers.sample();
            for (int k = 1; k &lt; sampleTime; k++) {
                // Pick a sample
                Queue&lt;Integer&gt; queue = servers.sample();
                if (queue.size() &lt; min.size()) min = queue;
            }

            // min surpposed to be the shortest server queue.
            min.enqueue(j);
        }

        int i = 0;
        double[] lengths = new double[totalServers];
        for (Queue&lt;Integer&gt; queue : servers)
            lengths[i++] = queue.size();

        System.out.println(Arrays.toString(lengths));
    }
}</code></pre>
<p><strong>Output</strong></p>
<pre><code class="language-shell">-&gt; java LoadBalance 5 5000 0
total Servers: 5
total Reqeusts: 5000
Sample Time: 0
[956.0, 995.0, 959.0, 1080.0, 1010.0]

-&gt; java LoadBalance 5 5000 2
total Servers: 5
total Reqeusts: 5000
Sample Time: 2
[999.0, 1000.0, 1000.0, 1001.0, 1000.0]</code></pre>
<p><img src="/upload/images/2019/04/laod_balancing_1.png" alt="load balancing 1" /></p>
<p>image - result 1</p>
<p><img src="/upload/images/2019/04/laod_balancing_2.png" alt="load balancing 2" /></p>
<p>image - result 2</p>
<p><em>The second output gets a well-balanced result, seems like the 2 is a good sample value for 5000 / 5.</em></p>
<p>The time complexity of this implementation is Θ(n) because it's an array-based queue which the BIG O of access item isΘ(1), so the total complexity depends on sample-times in the implementation steps.</p>]]></content>
            <updated>2019-04-15T23:17:20-06:00</updated>
        </entry>
            <entry>
            <author>
                <name>Martin Tan</name>
            </author>
            <title type="text"><![CDATA[Java Merging Two Sorted Queues in Ascending Order]]></title>
            <link rel="alternate" type="text/html" href="https://mrtan.me/post/28.html"></link>
            <id>https://mrtan.me/post/28.html</id>
            <summary type="html"><![CDATA[<p><img src="/upload/images/2019/04/a_real_queue.jpg" alt="a real queue" /></p>
<p>A sorted queue means the items in this queue are in descending order or ascending order. If we insert the items into a new queue one by one and keep the previous order, the new queue should also be sorted.</p>
<h2>Question</h2>
<blockquote>
<p>4.3.42 Merging two sorted queues. Given two queues with strings in ascending order, move all of the strings to a third queue so that the third queue ends up with the strings in ascending order.</p>
<p>Computer Science An Interdisciplinary Approach (2016, page 260).</p>
</blockquote>
<h2>Implementing Steps</h2>
<p><img src="/upload/images/2019/04/merging_two_sorted_queues.jpg" alt="Merging two sorted queues" /></p>
<p>This time, we are going to implement queues based on <strong>java.util.Queue</strong> instead of the Queue created by ourself. The queue interface from java util uses <strong>add</strong> method to enqueue a new item, uses <strong>poll</strong> to dequeue an existing item and uses <strong>peek</strong> to retrieve the value at the head of the queue, but without removing that item.</p>
<p><strong>Here goes the key steps:</strong></p>
<ol>
<li>There are two sorted queues <strong>A</strong>, <strong>B</strong>, and an empty queue <strong>C</strong>.</li>
<li>Peek two items from <strong>A</strong>, <strong>B</strong> if not empty.</li>
<li>Poll and Insert the greater value to the empty queue.</li>
<li>Return to step 2.</li>
<li>Insert the remaining items into <strong>C</strong> if <strong>A</strong> or <strong>B</strong> is not empty.</li>
</ol>
<h2>Solution</h2>
<pre><code class="language-java">/******************************************************************************
 *  Compilation:  javac MergingTwoSortedQueues.java
 *  Execution:    java MergingTwoSortedQueues
 *
 *  Merging two ascending sorted queues to one queue with ascending order.
 *
 *  % java MergingTwoSortedQueues
 *  First queue:
 *  [a, b, g, h]
 *
 *  Second queue:
 *  [A, c, d, e, f]
 *
 *  Merged queue:
 *  [A, a, b, c, d, e, f, g, h]
 *
 ******************************************************************************/
import java.util.Queue;
import java.util.PriorityQueue;

class MergingTwoSortedQueues {
    public static void main(String[] args) {
        Queue first = new PriorityQueue&lt;String&gt;();
        Queue second = new PriorityQueue&lt;String&gt;();

        first.add("a");
        first.add("b");
        first.add("g");
        first.add("h");

        second.add("A");
        second.add("c");
        second.add("d");
        second.add("e");
        second.add("f");

        System.out.println("First queue: ");
        System.out.println(first.toString());
        System.out.println("\nSecond queue: ");
        System.out.println(second.toString());

        Queue result = MergingTwoSortedQueues.merge(first, second);

        System.out.println("\nMerged queue: ");
        System.out.println(result.toString());
    }

    public static Queue&lt;String&gt; merge(Queue&lt;String&gt; first, Queue&lt;String&gt; second) {
        Queue&lt;String&gt; mergedQueue = new PriorityQueue&lt;String&gt;();

        // If both queues are not empty.
        while (!first.isEmpty() &amp;&amp; !second.isEmpty()) {
            String left = first.peek();
            String right = second.peek();

            if (left.compareTo(right) &lt; 0) {
                mergedQueue.add(first.poll());
            } else {
                mergedQueue.add(second.poll());
            }
        }

        // If there are remaining items in one of the queue.
        while (!first.isEmpty()) {
            mergedQueue.add(first.poll());
        }

        while (!second.isEmpty()) {
            mergedQueue.add(second.poll());
        }

        return mergedQueue;
    }
}</code></pre>
<p><strong>Output</strong></p>
<pre><code class="language-shell">-&gt; javac MergingTwoSortedQueues.java
Note: MergingTwoSortedQueues.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.

-&gt; java MergingTwoSortedQueues
First queue:
[a, b, g, h]

Second queue:
[A, c, d, e, f]

Merged queue:
[A, a, b, c, d, e, f, g, h]</code></pre>
<p>The time complexity of merging two sorted queues in this implementation is Θ(n).</p>]]></summary>
            <content type="html"><![CDATA[<p><img src="/upload/images/2019/04/a_real_queue.jpg" alt="a real queue" /></p>
<p>A sorted queue means the items in this queue are in descending order or ascending order. If we insert the items into a new queue one by one and keep the previous order, the new queue should also be sorted.</p>
<h2>Question</h2>
<blockquote>
<p>4.3.42 Merging two sorted queues. Given two queues with strings in ascending order, move all of the strings to a third queue so that the third queue ends up with the strings in ascending order.</p>
<p>Computer Science An Interdisciplinary Approach (2016, page 260).</p>
</blockquote>
<h2>Implementing Steps</h2>
<p><img src="/upload/images/2019/04/merging_two_sorted_queues.jpg" alt="Merging two sorted queues" /></p>
<p>This time, we are going to implement queues based on <strong>java.util.Queue</strong> instead of the Queue created by ourself. The queue interface from java util uses <strong>add</strong> method to enqueue a new item, uses <strong>poll</strong> to dequeue an existing item and uses <strong>peek</strong> to retrieve the value at the head of the queue, but without removing that item.</p>
<p><strong>Here goes the key steps:</strong></p>
<ol>
<li>There are two sorted queues <strong>A</strong>, <strong>B</strong>, and an empty queue <strong>C</strong>.</li>
<li>Peek two items from <strong>A</strong>, <strong>B</strong> if not empty.</li>
<li>Poll and Insert the greater value to the empty queue.</li>
<li>Return to step 2.</li>
<li>Insert the remaining items into <strong>C</strong> if <strong>A</strong> or <strong>B</strong> is not empty.</li>
</ol>
<h2>Solution</h2>
<pre><code class="language-java">/******************************************************************************
 *  Compilation:  javac MergingTwoSortedQueues.java
 *  Execution:    java MergingTwoSortedQueues
 *
 *  Merging two ascending sorted queues to one queue with ascending order.
 *
 *  % java MergingTwoSortedQueues
 *  First queue:
 *  [a, b, g, h]
 *
 *  Second queue:
 *  [A, c, d, e, f]
 *
 *  Merged queue:
 *  [A, a, b, c, d, e, f, g, h]
 *
 ******************************************************************************/
import java.util.Queue;
import java.util.PriorityQueue;

class MergingTwoSortedQueues {
    public static void main(String[] args) {
        Queue first = new PriorityQueue&lt;String&gt;();
        Queue second = new PriorityQueue&lt;String&gt;();

        first.add("a");
        first.add("b");
        first.add("g");
        first.add("h");

        second.add("A");
        second.add("c");
        second.add("d");
        second.add("e");
        second.add("f");

        System.out.println("First queue: ");
        System.out.println(first.toString());
        System.out.println("\nSecond queue: ");
        System.out.println(second.toString());

        Queue result = MergingTwoSortedQueues.merge(first, second);

        System.out.println("\nMerged queue: ");
        System.out.println(result.toString());
    }

    public static Queue&lt;String&gt; merge(Queue&lt;String&gt; first, Queue&lt;String&gt; second) {
        Queue&lt;String&gt; mergedQueue = new PriorityQueue&lt;String&gt;();

        // If both queues are not empty.
        while (!first.isEmpty() &amp;&amp; !second.isEmpty()) {
            String left = first.peek();
            String right = second.peek();

            if (left.compareTo(right) &lt; 0) {
                mergedQueue.add(first.poll());
            } else {
                mergedQueue.add(second.poll());
            }
        }

        // If there are remaining items in one of the queue.
        while (!first.isEmpty()) {
            mergedQueue.add(first.poll());
        }

        while (!second.isEmpty()) {
            mergedQueue.add(second.poll());
        }

        return mergedQueue;
    }
}</code></pre>
<p><strong>Output</strong></p>
<pre><code class="language-shell">-&gt; javac MergingTwoSortedQueues.java
Note: MergingTwoSortedQueues.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.

-&gt; java MergingTwoSortedQueues
First queue:
[a, b, g, h]

Second queue:
[A, c, d, e, f]

Merged queue:
[A, a, b, c, d, e, f, g, h]</code></pre>
<p>The time complexity of merging two sorted queues in this implementation is Θ(n).</p>]]></content>
            <updated>2019-04-03T21:30:00+08:00</updated>
        </entry>
            <entry>
            <author>
                <name>Martin Tan</name>
            </author>
            <title type="text"><![CDATA[Ring Buffer - Java Implementation of Circular Queue Using Fixed-length Array]]></title>
            <link rel="alternate" type="text/html" href="https://mrtan.me/post/27.html"></link>
            <id>https://mrtan.me/post/27.html</id>
            <summary type="html"><![CDATA[<p><img src="/upload/images/2019/03/circular_queue.png" alt="circular queue" /></p>
<p>Ring Buffer also is known as Circular Queue is a type of linear data structure. It following the FIFO rule of Queue, but it doesn't have an ending position. The last inserted item can overwrite the existing items, the front and rear are connected together like a ring and often used as Buffer in the software industry. That's why we call it Ring Buffer.</p>
<h2>Question</h2>
<blockquote>
<p>4.3.41 Ring buffer. A ring buffer (or circular queue) is a FIFO collection that stores a sequence of items, up to a prespecified limit. If you insert an item into a ring buffer that is full, the new item replaces the least recently inserted item. Ring buffers are useful for transferring data between asynchronous processes and for storing log files. When the buffer is empty, the consumer waits until data is deposited; when the buffer is full, the producer waits to deposit data. Develop an API for a ring buffer and an implementation that uses a fixed-length array.</p>
<p>Computer Science An Interdisciplinary Approach (2016, page 260).</p>
</blockquote>
<h2>Implementing Steps</h2>
<p><img src="/upload/images/2019/03/circular_queue_with_read_and_write_positions.jpg" alt="a circular queue with read and write positions." /></p>
<p>In this solution, we are going to use a fixed-length array as the element's container. Using the Enqueue and Dequeue methods to manage the elements. The differences between this implementation and a normal queue are the size of the elements are not incrementable and elements can be overwritten.</p>
<p><strong>Here goes the key points:</strong></p>
<ol>
<li>Fixed-length array.</li>
<li>Using two variable to track the read-position and write-position of the queue. Initializing the value of two positions to -1.</li>
<li>if a position beyond the end of the array's length, reset the position to zero.</li>
<li>if the write-positon cached up with the front position, increase the read-position by 1.</li>
<li>if the read-position cached up with the write-position, reset positions to -1.</li>
<li>Be aware of the starting value of the two positions.</li>
</ol>
<h2>Solution</h2>
<pre><code class="language-java">/******************************************************************************
 *  Compilation:  javac CircularQueue.java
 *  Execution:    java CircularQueue
 *
 *  Using a fixed-length array to implement a FIFO queue.
 *
 *  % java CircularQueue
 *  Print out the queue.
 *  [a, b, c, d, e, f]
 *
 *  Add a new element to the full circle queue.
 *  [g, b, c, d, e, f]
 *
 *  Read elements from the cilcle queue.
 *  b
 *  c
 *  d
 *  e
 *  f
 *
 *  Add a new element X to the circle queue.
 *  [g, X, null, null, null, null]
 *  Continue reading elements from the circle queue.
 *  g
 *  [null, X, null, null, null, null]
 *  X
 *  [null, null, null, null, null, null]
 *
 *  Add new elements to the circle queue.
 *  [Y, Z, null, null, null, null]
 *  Read one more element from the circle queue.
 *  Y
 *  [null, Z, null, null, null, null]
 *
 ******************************************************************************/
import java.util.Arrays;

class CircularQueue&lt;Item&gt; {
    private int capacity;
    public int readPos  = -1;
    public int writePos = -1;
    private Item[] elements;

    public static void main(String[] args) {
        CircularQueue&lt;String&gt; circle = new CircularQueue&lt;String&gt;(6);
        circle.enqueue("a");
        circle.enqueue("b");
        circle.enqueue("c");
        circle.enqueue("d");
        circle.enqueue("e");
        circle.enqueue("f");
        System.out.println("Print out the queue.");
        System.out.println(circle.toString());

        System.out.println("\nAdd a new element to the full circle queue.");
        circle.enqueue("g");
        System.out.println(circle.toString());

        System.out.println("\nRead elements from the cilcle queue.");
        System.out.println(circle.dequeue());

        System.out.println(circle.dequeue());
        System.out.println(circle.dequeue());
        System.out.println(circle.dequeue());
        System.out.println(circle.dequeue());

        System.out.println("\nAdd a new element X to the circle queue.");
        circle.enqueue("X");
        System.out.println(circle.toString());

        System.out.println("Continue reading elements from the circle queue.");
        System.out.println(circle.dequeue());
        System.out.println(circle.toString());
        System.out.println(circle.dequeue());
        System.out.println(circle.toString());

        System.out.println("\nAdd new elements to the circle queue.");
        circle.enqueue("Y");
        circle.enqueue("Z");

        System.out.println(circle.toString());

        System.out.println("Read one more element from the circle queue.");
        System.out.println(circle.dequeue());
        System.out.println(circle.toString());
    }

    public CircularQueue(int capacity) {
        this.capacity = capacity;
        elements = (Item[])new Object[capacity];
    }

    public void enqueue(Item item) {
        incrementWritePos();
        elements[writePos] = item;
    }

    public Item dequeue() {
        incrementReadPos();
        Item element = elements[readPos];
        elements[readPos] = null;

        // Rest the reading and writing postions if the circle is empty.
        if (readPos == writePos) {
            readPos = -1;
            writePos = -1;
        }

        return element;
    }

    public void incrementWritePos() {
        writePos++;

        // Write-postion reached the bottom of the array.
        if (writePos == capacity) {
            writePos = 0;
            if (readPos == -1) {
                readPos = 0;
            }
            return;
        }

        if (writePos == readPos) {
            incrementReadPos();
        }
    }

    public void incrementReadPos() {
        readPos++;

        // Read-postion reached the bottom of the array.
        if (readPos == capacity) {
            readPos = 0;
        }
    }

    public String toString() {
        return Arrays.toString(elements);
    }
}</code></pre>
<p><strong>Output</strong></p>
<pre><code class="language-shell">-&gt; javac CircularQueue.java
Note: CircularQueue.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.

-&gt; java CircularQueue
Print out the queue.
[a, b, c, d, e, f]

Add a new element to the full circle queue.
[g, b, c, d, e, f]

Read elements from the cilcle queue.
b
c
d
e
f

Add a new element X to the circle queue.
[g, X, null, null, null, null]
Continue reading elements from the circle queue.
g
[null, X, null, null, null, null]
X
[null, null, null, null, null, null]

Add new elements to the circle queue.
[Y, Z, null, null, null, null]
Read one more element from the circle queue.
Y
[null, Z, null, null, null, null]</code></pre>
<p>The time complexity of enqueue and dequeue in this implementation are the same Θ(1).</p>]]></summary>
            <content type="html"><![CDATA[<p><img src="/upload/images/2019/03/circular_queue.png" alt="circular queue" /></p>
<p>Ring Buffer also is known as Circular Queue is a type of linear data structure. It following the FIFO rule of Queue, but it doesn't have an ending position. The last inserted item can overwrite the existing items, the front and rear are connected together like a ring and often used as Buffer in the software industry. That's why we call it Ring Buffer.</p>
<h2>Question</h2>
<blockquote>
<p>4.3.41 Ring buffer. A ring buffer (or circular queue) is a FIFO collection that stores a sequence of items, up to a prespecified limit. If you insert an item into a ring buffer that is full, the new item replaces the least recently inserted item. Ring buffers are useful for transferring data between asynchronous processes and for storing log files. When the buffer is empty, the consumer waits until data is deposited; when the buffer is full, the producer waits to deposit data. Develop an API for a ring buffer and an implementation that uses a fixed-length array.</p>
<p>Computer Science An Interdisciplinary Approach (2016, page 260).</p>
</blockquote>
<h2>Implementing Steps</h2>
<p><img src="/upload/images/2019/03/circular_queue_with_read_and_write_positions.jpg" alt="a circular queue with read and write positions." /></p>
<p>In this solution, we are going to use a fixed-length array as the element's container. Using the Enqueue and Dequeue methods to manage the elements. The differences between this implementation and a normal queue are the size of the elements are not incrementable and elements can be overwritten.</p>
<p><strong>Here goes the key points:</strong></p>
<ol>
<li>Fixed-length array.</li>
<li>Using two variable to track the read-position and write-position of the queue. Initializing the value of two positions to -1.</li>
<li>if a position beyond the end of the array's length, reset the position to zero.</li>
<li>if the write-positon cached up with the front position, increase the read-position by 1.</li>
<li>if the read-position cached up with the write-position, reset positions to -1.</li>
<li>Be aware of the starting value of the two positions.</li>
</ol>
<h2>Solution</h2>
<pre><code class="language-java">/******************************************************************************
 *  Compilation:  javac CircularQueue.java
 *  Execution:    java CircularQueue
 *
 *  Using a fixed-length array to implement a FIFO queue.
 *
 *  % java CircularQueue
 *  Print out the queue.
 *  [a, b, c, d, e, f]
 *
 *  Add a new element to the full circle queue.
 *  [g, b, c, d, e, f]
 *
 *  Read elements from the cilcle queue.
 *  b
 *  c
 *  d
 *  e
 *  f
 *
 *  Add a new element X to the circle queue.
 *  [g, X, null, null, null, null]
 *  Continue reading elements from the circle queue.
 *  g
 *  [null, X, null, null, null, null]
 *  X
 *  [null, null, null, null, null, null]
 *
 *  Add new elements to the circle queue.
 *  [Y, Z, null, null, null, null]
 *  Read one more element from the circle queue.
 *  Y
 *  [null, Z, null, null, null, null]
 *
 ******************************************************************************/
import java.util.Arrays;

class CircularQueue&lt;Item&gt; {
    private int capacity;
    public int readPos  = -1;
    public int writePos = -1;
    private Item[] elements;

    public static void main(String[] args) {
        CircularQueue&lt;String&gt; circle = new CircularQueue&lt;String&gt;(6);
        circle.enqueue("a");
        circle.enqueue("b");
        circle.enqueue("c");
        circle.enqueue("d");
        circle.enqueue("e");
        circle.enqueue("f");
        System.out.println("Print out the queue.");
        System.out.println(circle.toString());

        System.out.println("\nAdd a new element to the full circle queue.");
        circle.enqueue("g");
        System.out.println(circle.toString());

        System.out.println("\nRead elements from the cilcle queue.");
        System.out.println(circle.dequeue());

        System.out.println(circle.dequeue());
        System.out.println(circle.dequeue());
        System.out.println(circle.dequeue());
        System.out.println(circle.dequeue());

        System.out.println("\nAdd a new element X to the circle queue.");
        circle.enqueue("X");
        System.out.println(circle.toString());

        System.out.println("Continue reading elements from the circle queue.");
        System.out.println(circle.dequeue());
        System.out.println(circle.toString());
        System.out.println(circle.dequeue());
        System.out.println(circle.toString());

        System.out.println("\nAdd new elements to the circle queue.");
        circle.enqueue("Y");
        circle.enqueue("Z");

        System.out.println(circle.toString());

        System.out.println("Read one more element from the circle queue.");
        System.out.println(circle.dequeue());
        System.out.println(circle.toString());
    }

    public CircularQueue(int capacity) {
        this.capacity = capacity;
        elements = (Item[])new Object[capacity];
    }

    public void enqueue(Item item) {
        incrementWritePos();
        elements[writePos] = item;
    }

    public Item dequeue() {
        incrementReadPos();
        Item element = elements[readPos];
        elements[readPos] = null;

        // Rest the reading and writing postions if the circle is empty.
        if (readPos == writePos) {
            readPos = -1;
            writePos = -1;
        }

        return element;
    }

    public void incrementWritePos() {
        writePos++;

        // Write-postion reached the bottom of the array.
        if (writePos == capacity) {
            writePos = 0;
            if (readPos == -1) {
                readPos = 0;
            }
            return;
        }

        if (writePos == readPos) {
            incrementReadPos();
        }
    }

    public void incrementReadPos() {
        readPos++;

        // Read-postion reached the bottom of the array.
        if (readPos == capacity) {
            readPos = 0;
        }
    }

    public String toString() {
        return Arrays.toString(elements);
    }
}</code></pre>
<p><strong>Output</strong></p>
<pre><code class="language-shell">-&gt; javac CircularQueue.java
Note: CircularQueue.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.

-&gt; java CircularQueue
Print out the queue.
[a, b, c, d, e, f]

Add a new element to the full circle queue.
[g, b, c, d, e, f]

Read elements from the cilcle queue.
b
c
d
e
f

Add a new element X to the circle queue.
[g, X, null, null, null, null]
Continue reading elements from the circle queue.
g
[null, X, null, null, null, null]
X
[null, null, null, null, null, null]

Add new elements to the circle queue.
[Y, Z, null, null, null, null]
Read one more element from the circle queue.
Y
[null, Z, null, null, null, null]</code></pre>
<p>The time complexity of enqueue and dequeue in this implementation are the same Θ(1).</p>]]></content>
            <updated>2019-03-16T16:37:19+08:00</updated>
        </entry>
            <entry>
            <author>
                <name>Martin Tan</name>
            </author>
            <title type="text"><![CDATA[Using Java Queue to Solve the Josephus Problem]]></title>
            <link rel="alternate" type="text/html" href="https://mrtan.me/post/23.html"></link>
            <id>https://mrtan.me/post/23.html</id>
            <summary type="html"><![CDATA[<p><img src="/upload/images/2018/10/Josephus-Problem-Circle.jpg" alt="Josephus-Problem-Circle" /></p>
<h2>Question</h2>
<p>4.3.39 Josephus problem. In the Josephus problem from antiquity, n people are in dire straits and agree to the following strategy to reduce the population. They arrange themselves in a circle (at positions numbered from 0 to n 1) and proceed around the circle, eliminating every mth person until only one person is left. Leg- end has it that Josephus figured out where to sit to avoid being eliminated. Write a Queue client Josephus that takes two integer command-line arguments m and n and prints the order in which people are eliminated (and thus would show Jose- phus where to sit in the circle). </p>
<pre><code class="language-shell">% java Josephus 2 7 
1 3 5 0 4 2 6 </code></pre>
<h2>Implementing Steps</h2>
<p>In this solution, we are going to use the Queue as the position container. Using the Dequeue and Enequeue methods to iterate all the positions. Keep doing this until all of the positions are popped out.</p>
<p><strong>Here goes the steps:</strong></p>
<ol>
<li>Create a Integer Queue.</li>
<li>Iterate all the items. </li>
<li>Find and remove the item at the eliminated position.</li>
<li>If the Queue is empty, done.</li>
</ol>
<h3>Implementing Steps</h3>
<ol>
<li>Copy the Stack code on <a href="https://mrtan.me/post/17.html" target="_blank">Java Queue Implementation</a> page.</li>
<li>Save the Queue code as <strong>Queue.java</strong></li>
<li>Copy the following code and save as <strong>JosephusQueue.java</strong></li>
</ol>
<h2>Solution</h2>
<pre><code class="language-java">/******************************************************************************
 *  Compilation:  javac JosephusQueue.java
 *  Execution:    java JosephusQueue args
 *  Dependencies: Queue.java @link https://mrtan.me/post/17.html
 *
 *  Using Queue to print out the ordered eliminated positions
 *  in Josephus Problem.
 *
 *  % java JosephusQueue 2 7
 *  1 3 5 0 4 2 6
 *
 ******************************************************************************/

class JosephusQueue {
    public static void main(String[] args) {
        int position = Integer.parseInt(args[0]);
        int count = Integer.parseInt(args[1]);

        printJosephusPositions(count, position);
    }

    public static void printJosephusPositions(int count, int position) {
        Queue&lt;Integer&gt; queue = new Queue&lt;Integer&gt;();
        for (int i = 0; i &lt; count; i++) {
            queue.enqueue(i);
        }

        while(!queue.isEmpty()) {
            // The eliminated position counted from 1.
            for (int i = 1; i &lt;= position; i++) {
                int eliminatedPosition = queue.dequeue();

                if (i == position) {
                    System.out.print(eliminatedPosition + " ");
                    break;
                }

                queue.enqueue(eliminatedPosition);
            }
        }
    }
}</code></pre>
<p><strong>Output</strong></p>
<pre><code class="language-shell">-&gt; java JosephusQueue 2 7
1 3 5 0 4 2 6</code></pre>
<p>The time complexity of this method is O(n).</p>]]></summary>
            <content type="html"><![CDATA[<p><img src="/upload/images/2018/10/Josephus-Problem-Circle.jpg" alt="Josephus-Problem-Circle" /></p>
<h2>Question</h2>
<p>4.3.39 Josephus problem. In the Josephus problem from antiquity, n people are in dire straits and agree to the following strategy to reduce the population. They arrange themselves in a circle (at positions numbered from 0 to n 1) and proceed around the circle, eliminating every mth person until only one person is left. Leg- end has it that Josephus figured out where to sit to avoid being eliminated. Write a Queue client Josephus that takes two integer command-line arguments m and n and prints the order in which people are eliminated (and thus would show Jose- phus where to sit in the circle). </p>
<pre><code class="language-shell">% java Josephus 2 7 
1 3 5 0 4 2 6 </code></pre>
<h2>Implementing Steps</h2>
<p>In this solution, we are going to use the Queue as the position container. Using the Dequeue and Enequeue methods to iterate all the positions. Keep doing this until all of the positions are popped out.</p>
<p><strong>Here goes the steps:</strong></p>
<ol>
<li>Create a Integer Queue.</li>
<li>Iterate all the items. </li>
<li>Find and remove the item at the eliminated position.</li>
<li>If the Queue is empty, done.</li>
</ol>
<h3>Implementing Steps</h3>
<ol>
<li>Copy the Stack code on <a href="https://mrtan.me/post/17.html" target="_blank">Java Queue Implementation</a> page.</li>
<li>Save the Queue code as <strong>Queue.java</strong></li>
<li>Copy the following code and save as <strong>JosephusQueue.java</strong></li>
</ol>
<h2>Solution</h2>
<pre><code class="language-java">/******************************************************************************
 *  Compilation:  javac JosephusQueue.java
 *  Execution:    java JosephusQueue args
 *  Dependencies: Queue.java @link https://mrtan.me/post/17.html
 *
 *  Using Queue to print out the ordered eliminated positions
 *  in Josephus Problem.
 *
 *  % java JosephusQueue 2 7
 *  1 3 5 0 4 2 6
 *
 ******************************************************************************/

class JosephusQueue {
    public static void main(String[] args) {
        int position = Integer.parseInt(args[0]);
        int count = Integer.parseInt(args[1]);

        printJosephusPositions(count, position);
    }

    public static void printJosephusPositions(int count, int position) {
        Queue&lt;Integer&gt; queue = new Queue&lt;Integer&gt;();
        for (int i = 0; i &lt; count; i++) {
            queue.enqueue(i);
        }

        while(!queue.isEmpty()) {
            // The eliminated position counted from 1.
            for (int i = 1; i &lt;= position; i++) {
                int eliminatedPosition = queue.dequeue();

                if (i == position) {
                    System.out.print(eliminatedPosition + " ");
                    break;
                }

                queue.enqueue(eliminatedPosition);
            }
        }
    }
}</code></pre>
<p><strong>Output</strong></p>
<pre><code class="language-shell">-&gt; java JosephusQueue 2 7
1 3 5 0 4 2 6</code></pre>
<p>The time complexity of this method is O(n).</p>]]></content>
            <updated>2018-10-20T17:18:00+08:00</updated>
        </entry>
    </feed>
