<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: rbglod</title>
    <description>The latest articles on DEV Community by rbglod (@rbglod).</description>
    <link>https://dev.to/rbglod</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F34225%2F045d1c9c-a6b7-4d0b-a6d8-64c461588626.png</url>
      <title>DEV Community: rbglod</title>
      <link>https://dev.to/rbglod</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rbglod"/>
    <language>en</language>
    <item>
      <title>How to monitor your app's health</title>
      <dc:creator>rbglod</dc:creator>
      <pubDate>Thu, 18 Dec 2025 14:36:06 +0000</pubDate>
      <link>https://dev.to/rbglod/how-to-monitor-your-apps-health-3cbn</link>
      <guid>https://dev.to/rbglod/how-to-monitor-your-apps-health-3cbn</guid>
      <description>&lt;p&gt;Monitoring in web apps is crucial element in terms of maintaining stability and ability to quickly react to critical incidents.&lt;br&gt;
When an incident happens, you or even worse - your customer - finds a bug, you drop your feature work and hop on a debugging train. Having metrics and monitoring in place is incredibly helpful to trace where the issue lies, what's the impact and how it can be fixed quickly.&lt;/p&gt;

&lt;p&gt;In this blog post I'd like to share some ideas around health metrics that can be added to crucial parts of the app to increase visibilty of how our code works, what's working as expected and what code paths fail most often. I'm not gonna dive into tools like Airbrake or Sentry, which are used to catch errors and report them. I'll explore what we can implement in our code to have as much useful information in our logs (or tools like Datadog) instead.&lt;/p&gt;
&lt;h2&gt;
  
  
  Health metrics and where to put them
&lt;/h2&gt;

&lt;p&gt;Start with finding crucial parts of the app without which the project is broken. For me, the first thing that comes to my mind is payment processing. If we fail to process a payment, business is not making money, which means that we're losing it. We pay for hosting the app, but fail to satisfy users needs, which may lead to users abandoning our app - reducing our income, increasing our costs.&lt;/p&gt;
&lt;h2&gt;
  
  
  attempt, success, failure
&lt;/h2&gt;

&lt;p&gt;So we have our most vulnerable area - payment processing. Implementing health metrics is extremely easy, but also extremely useful. Let's have a look on this example class.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PaymentProcessor&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;perform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;payment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;create_payment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;payment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;process_payment&lt;/span&gt;
    &lt;span class="n"&gt;payment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mark_as_finished&lt;/span&gt;
  &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;Payments&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Errors&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;PaymentFailed&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
    &lt;span class="n"&gt;payment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mark_as_failed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;ensure&lt;/span&gt; 
    &lt;span class="n"&gt;set_result&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can define three phases of what's going on here. &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A payment is being created, we attempt to process a payment.&lt;/li&gt;
&lt;li&gt;If payment processing succeeds, we mark it as &lt;code&gt;finished&lt;/code&gt; and return payment's status.&lt;/li&gt;
&lt;li&gt;If payment processing fails, we mark it as &lt;code&gt;failed&lt;/code&gt;, save error message and return payment's status.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This gives us an idea of what health metrics we can implement here. And this rule is rather universal. We &lt;code&gt;attempt&lt;/code&gt; to perform &lt;em&gt;an action&lt;/em&gt;, then we either receive &lt;code&gt;success&lt;/code&gt; or &lt;code&gt;failure&lt;/code&gt; from the operation.&lt;/p&gt;

&lt;p&gt;Here's how the metrics may look like in the most simple way.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PaymentProcessor&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;perform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;metric_recorder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;record_attempt&lt;/span&gt;
    &lt;span class="n"&gt;payment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;create_payment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;payment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;process_payment&lt;/span&gt;
    &lt;span class="n"&gt;payment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mark_as_finished&lt;/span&gt;
    &lt;span class="n"&gt;metric_recorder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;record_success&lt;/span&gt;
  &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;Payments&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Errors&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;PaymentFailed&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
    &lt;span class="n"&gt;payment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mark_as_failed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;metric_recorder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;record_failure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error_code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;ensure&lt;/span&gt; 
    &lt;span class="n"&gt;set_result&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="kp"&gt;private&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;metric_recorder&lt;/span&gt;
    &lt;span class="vi"&gt;@metric_recorder&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="no"&gt;MetricRecorder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;domain: &lt;/span&gt;&lt;span class="s1"&gt;'payment_processing'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This would already give us some insight on how much payment processing attempts we had, how many of them succeeded and how many failed.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;attempt&lt;/code&gt;s = &lt;code&gt;success&lt;/code&gt; + &lt;code&gt;failed&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;If the numbers don't add up, it means we have some issue in-between the metrics being recorded. Maybe we get an error in &lt;code&gt;payment.mark_as_finished&lt;/code&gt; that is not handled, hence we don't get &lt;em&gt;ending&lt;/em&gt; metric properly? That's also valuable information for debuggin purposes. We can see &lt;em&gt;how far&lt;/em&gt; the code execution went.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapper
&lt;/h2&gt;

&lt;p&gt;This was the simpliest version, but it pollutes the code, and we need to remember about adding it each time we implement new service. Ruby let's us make it much more cleaner way and makes it possible to reuse the code. We can add a method to our &lt;code&gt;MetricRecorder&lt;/code&gt; module that will accept a block and handle the rest.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;MetricRecorder&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;record_health&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;record_attempt&lt;/span&gt;
    &lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;
    &lt;span class="n"&gt;record_success&lt;/span&gt;
  &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;StandardError&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
    &lt;span class="n"&gt;record_failure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="c1"&gt;# we want to re-raise error to handle it outside of the recorder&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And here's how &lt;code&gt;record_health&lt;/code&gt; wrapper method can be used in the &lt;code&gt;PaymentProcessor&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PaymentProcessor&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;MetricRecorder&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;perform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;record_health&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;payment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;create_payment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;payment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;process_payment&lt;/span&gt;
      &lt;span class="n"&gt;payment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mark_as_finished&lt;/span&gt;
    &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;Payments&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Errors&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;PaymentFailed&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
      &lt;span class="n"&gt;payment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mark_as_failed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="c1"&gt;# we re-raise the error to propagate it to recorder and further&lt;/span&gt;
    &lt;span class="k"&gt;ensure&lt;/span&gt;
      &lt;span class="n"&gt;set_result&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes our class clean again, reduces need to think where we should put metric loggers, and moves metric recorders outside of this class, making it reusable in the whole project. Once we want to add some extra data to metrics, we can update just &lt;code&gt;MetricRecorder&lt;/code&gt; instead of all classes that use &lt;code&gt;record_...&lt;/code&gt; methods.&lt;/p&gt;

&lt;h3&gt;
  
  
  The recorder module
&lt;/h3&gt;

&lt;p&gt;Okay, we know how this should look from the code execution perspective, but what the &lt;code&gt;record_attempt&lt;/code&gt;, &lt;code&gt;record_success&lt;/code&gt; and &lt;code&gt;record_failure&lt;/code&gt; should actually do?&lt;/p&gt;

&lt;p&gt;Metrics are designed to just give an idea of the traffic in the app. If we want to have more detailed data, we should incorporate loggers probably.&lt;/p&gt;

&lt;p&gt;If we're using Datadog, then we'd probably just increment &lt;code&gt;attempt&lt;/code&gt;, &lt;code&gt;success&lt;/code&gt;, &lt;code&gt;failure&lt;/code&gt; metrics, with adding &lt;code&gt;error_code&lt;/code&gt; to &lt;code&gt;failure&lt;/code&gt; probably. It's due to costs and metrics design in DD - we can only send defined set of metrics. We can't propagate IDs, amount, or some specific user data in those metrics, as those are infinite.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;MetricRecorder&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;record_health&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;metric_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;record_attempt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;metric_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;
    &lt;span class="n"&gt;record_success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;metric_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;StandardError&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
    &lt;span class="n"&gt;record_failure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;metric_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="kp"&gt;private&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;record_attempt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;metric_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="no"&gt;DatadogService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;metric_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'attempt'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;record_success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;metric_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="no"&gt;DatadogService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;metric_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'success'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;record_failure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;metric_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="no"&gt;DatadogService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;metric_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'failure'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;error_code: &lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error_code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To get more data and really &lt;em&gt;monitor&lt;/em&gt; our app, we could also write a logger module.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;LogsRecorder&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;record_log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;method_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;method_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;action_type: &lt;/span&gt;&lt;span class="s1"&gt;'attempt'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;method_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;action_type: &lt;/span&gt;&lt;span class="s1"&gt;'success'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;result: &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt;
  &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;StandardError&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;method_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;action_type: &lt;/span&gt;&lt;span class="s1"&gt;'failure'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;result: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;error_class: &lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;demodulize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;error_message: &lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;message&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="kp"&gt;private&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;method_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;additional_tags&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="ss"&gt;class_name: &lt;/span&gt;&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;demodulize&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;underscore&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;method_name: &lt;/span&gt;&lt;span class="n"&gt;method_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;additional_tags: &lt;/span&gt;&lt;span class="n"&gt;additional_tags&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And here's how we could incorporate both modules - having metrics and logs in place. This would let us quickly monitor metrics from Datadog web dashboard for example, and if something feels off - we can jump into detailed logs, &lt;code&gt;grep&lt;/code&gt; for &lt;code&gt;action_type: 'failure'&lt;/code&gt; and see what went wrong.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PaymentProcessor&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;MetricRecorder&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;LogsRecorder&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;perform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;record_health&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'payment_processing'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;record_log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'perform'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="n"&gt;payment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;create_payment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;payment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;process_payment&lt;/span&gt;
        &lt;span class="n"&gt;payment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mark_as_finished&lt;/span&gt;
      &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;Payments&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Errors&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;PaymentFailed&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
        &lt;span class="n"&gt;payment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mark_as_failed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
      &lt;span class="k"&gt;ensure&lt;/span&gt;
        &lt;span class="n"&gt;set_result&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>rails</category>
      <category>monitoring</category>
      <category>metrics</category>
      <category>logs</category>
    </item>
    <item>
      <title>Untangling the Rails Monolith - quick look at the code</title>
      <dc:creator>rbglod</dc:creator>
      <pubDate>Thu, 11 Dec 2025 22:05:47 +0000</pubDate>
      <link>https://dev.to/rbglod/untangling-the-rails-monolith-quick-look-at-the-code-29e9</link>
      <guid>https://dev.to/rbglod/untangling-the-rails-monolith-quick-look-at-the-code-29e9</guid>
      <description>&lt;p&gt;Last time I wrote about the data separation at the database level. As we already know &lt;strong&gt;each component should work as an (almost) independent service&lt;/strong&gt;. How can that be achevied at the codebase level? What to do when for example in Accounting we need address data for an Employee? How can we get the Organization subscription plan if we're in the Checkout component?&lt;/p&gt;

&lt;p&gt;Each of those components should work as a separate app ideally. If we imagine them as a standalone apps, it might be easier to design future code and not couple services with each other.&lt;/p&gt;

&lt;p&gt;As in the previous blog post, let's focus on some kind-of real life example.&lt;/p&gt;

&lt;h3&gt;
  
  
  Marketplace
&lt;/h3&gt;

&lt;p&gt;Let's say we have a simple app in which a User can register an account, select given subscription plan, put ads with their Goods and also buy Goods from other users. Depending on the subscription type, we offer them better shipping options, discounts, etc.&lt;/p&gt;

&lt;p&gt;We can describe few components in this scenario.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;app/
|- components/
  |- users/
  |- marketplace/
  |- payments/
  |- subscriptions/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each of the components has their own &lt;code&gt;controllers/&lt;/code&gt;, &lt;code&gt;models/&lt;/code&gt;, &lt;code&gt;services/&lt;/code&gt;, etc. In more complex apps we might see &lt;code&gt;infrastructure/&lt;/code&gt;, &lt;code&gt;domain/&lt;/code&gt;, &lt;code&gt;repositories/&lt;/code&gt; and others, but let's stick to simple example.&lt;/p&gt;

&lt;p&gt;In ideal world, each of the component lives on it's own - Users know only about the &lt;code&gt;Users&lt;/code&gt; model (and other related to Users), they check if User is logged in, update their passwords, account data, etc. &lt;/p&gt;

&lt;p&gt;But at some point user hits a &lt;code&gt;Marketplace&lt;/code&gt; and want to order something. In &lt;code&gt;Marketplace&lt;/code&gt; component we want to know where the order should go, who is the recipient and what possible discounts we should offer.&lt;/p&gt;

&lt;p&gt;Instead of doing something like this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/components/marketplace/controllers/orders_controller.rb&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;
  &lt;span class="vi"&gt;@order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Marketplace&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Models&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="ss"&gt;user_id: &lt;/span&gt;&lt;span class="n"&gt;current_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;shipping_address: &lt;/span&gt;&lt;span class="n"&gt;current_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;shipping_address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;offering: &lt;/span&gt;&lt;span class="n"&gt;current_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscription_plan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;discounts: &lt;/span&gt;&lt;span class="n"&gt;current_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;discounts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;items: &lt;/span&gt;&lt;span class="n"&gt;order_params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:items_ids&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We don't want to couple &lt;code&gt;Marketplace&lt;/code&gt; with &lt;code&gt;Users&lt;/code&gt;. We definitely shouldn't use &lt;code&gt;user&lt;/code&gt; relations here, as they might change in future and this code would break.&lt;br&gt;
The default scope of &lt;code&gt;user.discounts&lt;/code&gt; might change and it'd be hard to debug, why suddenly the behavior of our endpoint changed.&lt;/p&gt;

&lt;p&gt;Instead we should use Facades or APIs or any other approach that will let us put the query to another component in one place and make sure we always get the same result.&lt;/p&gt;

&lt;p&gt;What I mean through that is the &lt;code&gt;users/&lt;/code&gt; domain could expose their public API for other domains to fetch users' data.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/components/users/public/api.rb&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;
  &lt;span class="no"&gt;UserResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:shipping_address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:subscription_plan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:discounts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fetch_user_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;:)&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Users&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Models&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;:)&lt;/span&gt;
    &lt;span class="no"&gt;UserResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="ss"&gt;shipping_address: &lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;shipping_address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;subscription_plan: &lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscription_plan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;discounts: &lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;discounts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;active&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:discount_amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/components/marketplace/controllers/orders_controller.rb&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;
  &lt;span class="n"&gt;user_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Users&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Public&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch_user_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;user_id: &lt;/span&gt;&lt;span class="n"&gt;current_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Marketplace&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Models&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="ss"&gt;user_id: &lt;/span&gt;&lt;span class="n"&gt;current_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;shipping_address: &lt;/span&gt;&lt;span class="n"&gt;user_data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;shipping_address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;offering: &lt;/span&gt;&lt;span class="n"&gt;user_data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscription_plan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;discounts: &lt;/span&gt;&lt;span class="n"&gt;user_data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;discounts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;items: &lt;/span&gt;&lt;span class="n"&gt;order_params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:items_ids&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What this changes is that this API is the only source of truth. If every component uses &lt;code&gt;Users::Public::Api&lt;/code&gt; to fetch User data, we don't need to worry about checking all references to &lt;code&gt;users.shipping_address&lt;/code&gt; when changing something around users' address. The only thing we need to ensure is working as expected is the API we wrote - if it's still returning same values as before our refactor of &lt;code&gt;shipping_address&lt;/code&gt;, then we're golden.&lt;/p&gt;

&lt;p&gt;All &lt;code&gt;user.shipping_address&lt;/code&gt; occurrences should happen within &lt;code&gt;users/&lt;/code&gt; component, all other components should access it through &lt;code&gt;Users::Public::Api&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;users/&lt;/code&gt; component is the owner of the code, and they know that they can't change it, break it, or do whatever that affects other domains that consume that data.&lt;/p&gt;

&lt;p&gt;If they want to implement a change in a way how discounts are returned, then a new version of API might be implemented while the old one remains the same. Or, if everyone  (all components that use that data) agree and synchronize, then the change is possible in existing API.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why is it that important
&lt;/h3&gt;

&lt;p&gt;This prevents from calling directly ActiveRecord models from different domains. Direct calls create strong coupling when it's not needed. We just need data from three columns, we don't need whole &lt;code&gt;User&lt;/code&gt; record. What if those columns got migrated to different model? We'll try to read &lt;code&gt;user.shipping_address&lt;/code&gt;, but it's no longer there and we need to debug. If we're using given component's API, if they migrate data to another table, they also make sure that the API returns the same values but from another data source.&lt;/p&gt;

&lt;p&gt;For us (consumers) it doesn't matter from where the data comes from. We just want the address and the &lt;code&gt;users/&lt;/code&gt; should give it to us if we're calling their API. If we call their models directly, we need to know where exactly the data is - and that's not needed if we're in &lt;code&gt;marketplace/&lt;/code&gt;.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>ddd</category>
      <category>database</category>
    </item>
    <item>
      <title>Untangling the Rails Monolith - quick look at the database</title>
      <dc:creator>rbglod</dc:creator>
      <pubDate>Thu, 11 Dec 2025 11:51:00 +0000</pubDate>
      <link>https://dev.to/rbglod/untangling-the-rails-monolith-quick-look-at-the-database-dpd</link>
      <guid>https://dev.to/rbglod/untangling-the-rails-monolith-quick-look-at-the-database-dpd</guid>
      <description>&lt;p&gt;Having worked in a few quite big Rails projects already, I started to appreciate domains separation. &lt;/p&gt;

&lt;p&gt;Each component should work as an (almost) independent service. This reduces complexity and dependency on other areas. This reduces development time and allows the team to operate faster.&lt;/p&gt;

&lt;p&gt;Simplier code = quicker code reviews = faster releases&lt;/p&gt;

&lt;h2&gt;
  
  
  I have a monolith, what do I do
&lt;/h2&gt;

&lt;p&gt;Every project has some &lt;em&gt;areas&lt;/em&gt; that it can be split into. In most apps there're probably areas of Users management, Payments processing, Reports generators, &lt;em&gt;X&lt;/em&gt; management, etc... &lt;/p&gt;

&lt;p&gt;Of course we need to know if User X has already paid for theirs subscription, or what bank account was used to process last payment, but not necessarily all of this data has to be kept in one or two models.&lt;/p&gt;

&lt;p&gt;We can manage Users - their personal info, contact data, preferences - without knowing on which subscription they currently are. When changing profile picture we don't have to know if they already added a credit card, or how many resources of X they already added to the system.&lt;/p&gt;

&lt;p&gt;Where I'm going with this is - &lt;strong&gt;each area can operate independently from the others&lt;/strong&gt; - and we should always try to decouple as much as possible and be diligent about keeping the context boundaries clear. &lt;/p&gt;

&lt;p&gt;It's rather rare scenario that we end up in greenfield project where we can design everything from scratch (which btw isn't that easy as it might seem), so let's have a look on some legacy app and how we could implement small changes that would benefit in future.&lt;/p&gt;

&lt;h2&gt;
  
  
  Untangling legacy monolith - database level
&lt;/h2&gt;

&lt;p&gt;I guess it's common scenario - let's prepare some MVP and check if it works... &lt;/p&gt;

&lt;p&gt;Oh, it actually makes sense and there's demand for that! Let's build something more on top of it!&lt;/p&gt;

&lt;p&gt;Oooh, there's even more demand, let's add some more features...&lt;/p&gt;

&lt;p&gt;And the development lifecycle goes on and on, but the foundation stay the same - the small MVP project that wasn't prepared to scale that much. So we might end up with a tables similar to this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# schema.rb&lt;/span&gt;

&lt;span class="n"&gt;create_table&lt;/span&gt; &lt;span class="s2"&gt;"employees"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt; &lt;span class="s2"&gt;"uuid"&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt; &lt;span class="s2"&gt;"name"&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt; &lt;span class="s2"&gt;"email"&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;integer&lt;/span&gt; &lt;span class="s2"&gt;"organization_id"&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;boolean&lt;/span&gt; &lt;span class="s2"&gt;"active"&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt; &lt;span class="s2"&gt;"onboarding_status"&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt; &lt;span class="s2"&gt;"preferred_working_days"&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt; &lt;span class="s2"&gt;"contact_preference"&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;boolean&lt;/span&gt; &lt;span class="s2"&gt;"contract_up_to_date"&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt; &lt;span class="s2"&gt;"payment_method"&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt; &lt;span class="s2"&gt;"payment_details"&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt; &lt;span class="s2"&gt;"address_street"&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt; &lt;span class="s2"&gt;"city"&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt; &lt;span class="s2"&gt;"zip"&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt; &lt;span class="s2"&gt;"country"&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt; &lt;span class="s2"&gt;"phone_number"&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt; &lt;span class="s2"&gt;"notes"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's a mess, but since business demanded, the devs had to deliver. But it could be done in a slightly more future-proof way.&lt;/p&gt;

&lt;p&gt;We could split this table into at least three smaller ones, making them faster to operate with (in terms of performing queries and cognitive load for developers).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# schema.rb&lt;/span&gt;

&lt;span class="n"&gt;create_table&lt;/span&gt; &lt;span class="s2"&gt;"employees"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt; &lt;span class="s2"&gt;"uuid"&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt; &lt;span class="s2"&gt;"name"&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt; &lt;span class="s2"&gt;"email"&lt;/span&gt; &lt;span class="c1"&gt;# might be a contact_detail but it's often used to find given employee by it's email&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;integer&lt;/span&gt; &lt;span class="s2"&gt;"organization_id"&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;boolean&lt;/span&gt; &lt;span class="s2"&gt;"active"&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt; &lt;span class="s2"&gt;"notes"&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;index&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;create_table&lt;/span&gt; &lt;span class="s2"&gt;"employee_settings"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt; &lt;span class="s2"&gt;"preferred_working_days"&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;boolean&lt;/span&gt; &lt;span class="s2"&gt;"contract_up_to_date"&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;integer&lt;/span&gt; &lt;span class="s2"&gt;"employee_id"&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt; &lt;span class="s2"&gt;"onboarding_status"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;create_table&lt;/span&gt; &lt;span class="s2"&gt;"employee_payments"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt; &lt;span class="s2"&gt;"payment_method"&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt; &lt;span class="s2"&gt;"payment_details"&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;integer&lt;/span&gt; &lt;span class="s2"&gt;"employee_id"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;create_table&lt;/span&gt; &lt;span class="s2"&gt;"employee_contact_details"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt; &lt;span class="s2"&gt;"address_street"&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt; &lt;span class="s2"&gt;"city"&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt; &lt;span class="s2"&gt;"zip"&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt; &lt;span class="s2"&gt;"country"&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt; &lt;span class="s2"&gt;"phone_number"&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt; &lt;span class="s2"&gt;"contact_preference"&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;integer&lt;/span&gt; &lt;span class="s2"&gt;"employee_id"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each table represents separate area of employee "details". Those are related to payments, contact details or some generic ones (which with time might grow up to another big table that could be split into smaller ones).&lt;/p&gt;

&lt;h3&gt;
  
  
  How can we strive to achieve this kind of structure in a legacy app?
&lt;/h3&gt;

&lt;p&gt;Every time we need to add some new setting, detail, boolean, etc. we should ask a question - can it be moved into a separate table? Is it crucial to keep in the same table as other data? &lt;/p&gt;

&lt;p&gt;Every time we add new column to existing table, it might be an opportunity to initiate decoupling existing monolith. Start with small steps, but with time you'll see the benefit.&lt;/p&gt;

&lt;p&gt;Need to store employee's t-shirt size that should be ordered? Let's launch a new table with &lt;code&gt;employee_equipment&lt;/code&gt; - maybe in future you'll have to store their shoe size, or glove size if they work in a warehouse.&lt;/p&gt;

&lt;p&gt;Look for opportunities to start with clean tables. Adding new column to existing ones is the easiest, but migrating data to new tables is a lot of work to do it safely and with no disruptions for the whole system.&lt;/p&gt;

&lt;p&gt;Apart from reading task requirement "store employee's tshirt size", think about the big picture - how in future this might impact &lt;code&gt;employees&lt;/code&gt; table? How can it be done in a more efficient way?&lt;/p&gt;

&lt;h2&gt;
  
  
  Untangling the code
&lt;/h2&gt;

&lt;p&gt;In the next post I'll explore an approach which I find very usefull in setting clear boundaries of domains, while keeping the data flow between them. This, again, reduces the code coupling, complexity and probability of unexpected code behaviour. I'll talk about APIs inside the monolith.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>ddd</category>
      <category>database</category>
    </item>
    <item>
      <title>Sensitive data encryption in Rails</title>
      <dc:creator>rbglod</dc:creator>
      <pubDate>Wed, 29 Jul 2020 18:40:28 +0000</pubDate>
      <link>https://dev.to/rbglod/sensitive-data-encryption-in-rails-1f1</link>
      <guid>https://dev.to/rbglod/sensitive-data-encryption-in-rails-1f1</guid>
      <description>&lt;p&gt;Recently I received a request to encrypt some part of data that was already in my client’s app. I don’t want to share too many details, so let’s say it was few columns of an existing table that already contained some data.&lt;/p&gt;

&lt;p&gt;So my task was to enrypt all existing records, delete non-encrypted data and save all future records as encrypted. Sounds cool!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lockbox&lt;/strong&gt;&lt;br&gt;
What I needed was a simple ruby gem that would help me with all above tasks. I found &lt;a href="https://github.com/ankane/lockbox"&gt;Lockbox&lt;/a&gt; or - the Lockbox found me! I’ve read about it some time ago in RubyWeekly newsletter. It offers migrating existing data, encrypting columns, decrypting fields on the fly if you need to display them somewhere in the views, etc.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Plan&lt;/strong&gt;&lt;br&gt;
Since our app is used 24/7 we didn’t want to put it in the maintenance mode, we had to split update into few steps without causing any downtime.&lt;br&gt;
I came up with this solution:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Add Lockbox to gemfile, add new columns to persist encrypted data, update model to migrate existing data&lt;/li&gt;
&lt;li&gt;Add &lt;code&gt;ignored_columns&lt;/code&gt; attribute to model (to remove columns in next step without any downtime)&lt;/li&gt;
&lt;li&gt;Remove non-encrypted columns from table&lt;/li&gt;
&lt;li&gt;Remove &lt;code&gt;ignored_columns&lt;/code&gt; attribute&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This required 4 pull requests creation, 1 task execution and zero downtime :)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Execution&lt;/strong&gt;&lt;br&gt;
I started by adding &lt;code&gt;gem 'lockbox', '~&amp;gt; 0.4.6'&lt;/code&gt; to the Gemfile and generating Lockbox master key.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Lockbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate_key&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This key had to be added to all secrets (in dev, staging, production and test environments). I also created &lt;code&gt;lockbox.rb&lt;/code&gt; file inside &lt;code&gt;initializers/&lt;/code&gt; folder.&lt;/p&gt;

&lt;p&gt;After that Lockbox was ready to go, so I could start work on encryption of my desired model. Let’s say that I wanted encrypt our clients’ correspondence data. Inside &lt;code&gt;Client::Address&lt;/code&gt; model, I had to encrypt following fields: &lt;code&gt;street&lt;/code&gt;, &lt;code&gt;city&lt;/code&gt;, &lt;code&gt;postal_code&lt;/code&gt;, &lt;code&gt;country&lt;/code&gt;, &lt;code&gt;phone_number&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;So inside &lt;code&gt;models/client/address.rb&lt;/code&gt; I had to add:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Client::Address&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;
  &lt;span class="n"&gt;encrypts&lt;/span&gt; &lt;span class="ss"&gt;:street&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:city&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:postal_code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:country&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:phone_number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;migrating: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Notice last part of it - &lt;code&gt;migrating: true&lt;/code&gt;. This is crucial for next step - migrate existing data. But before that I had to create new columns for encrypted data. So I came up with following migration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;up&lt;/span&gt;
  &lt;span class="n"&gt;add_column&lt;/span&gt; &lt;span class="ss"&gt;:client_address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:street_ciphertext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:text&lt;/span&gt;
  &lt;span class="n"&gt;add_column&lt;/span&gt; &lt;span class="ss"&gt;:client_address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:city_ciphertext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:text&lt;/span&gt;
  &lt;span class="n"&gt;add_column&lt;/span&gt; &lt;span class="ss"&gt;:client_address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:postal_code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:text&lt;/span&gt;
  &lt;span class="n"&gt;add_column&lt;/span&gt; &lt;span class="ss"&gt;:client_address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:country&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:text&lt;/span&gt;
  &lt;span class="n"&gt;add_column&lt;/span&gt; &lt;span class="ss"&gt;:client_address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:phone_number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:text&lt;/span&gt;

  &lt;span class="n"&gt;change_column&lt;/span&gt; &lt;span class="ss"&gt;:client_address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:street&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;null: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
  &lt;span class="n"&gt;change_column&lt;/span&gt; &lt;span class="ss"&gt;:client_address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:city&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;null: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
  &lt;span class="n"&gt;change_column&lt;/span&gt; &lt;span class="ss"&gt;:client_address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:postal_code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;null: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
  &lt;span class="n"&gt;change_column&lt;/span&gt; &lt;span class="ss"&gt;:client_address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:country&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;null: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
  &lt;span class="n"&gt;change_column&lt;/span&gt; &lt;span class="ss"&gt;:client_address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:phone_number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;null: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;At this point no &lt;code&gt;null: false&lt;/code&gt; constraint could take place - adding &lt;code&gt;not_null&lt;/code&gt; columns to existing records would paralyze app. Also &lt;code&gt;null: true&lt;/code&gt; had to be added to existing columns - from now on we would save new records only to &lt;code&gt;_ciphertext&lt;/code&gt; columns. &lt;/p&gt;

&lt;p&gt;After successful migration, there was only one thing missing. Data migration itself!&lt;br&gt;
Thanks to Lockbox, this is done by running simple command in rails console. After merging above changes, I had to ssh to production environment and run&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Lockbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;migrate&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;At this point all existing data was migrated to &lt;code&gt;_ciphertext&lt;/code&gt; columns. But I needed more - I needed to remove non-encrypted columns with sensitive data saved in plain text!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step II&lt;/strong&gt;&lt;br&gt;
First of all I removed &lt;code&gt;migrating: true&lt;/code&gt; from &lt;code&gt;models/client/address.rb&lt;/code&gt; model - this was no longer needed.&lt;br&gt;
Next thing was migration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;up&lt;/span&gt;
  &lt;span class="n"&gt;change_column&lt;/span&gt; &lt;span class="ss"&gt;:client_address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:street&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;null: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;For all &lt;code&gt;_ciphertext&lt;/code&gt; columns I added &lt;code&gt;not_null&lt;/code&gt; constraint, since we’re already saving records to that columns and none of them was empty anymore.&lt;br&gt;
And the most important part of this pull request was adding &lt;code&gt;ignored_columns&lt;/code&gt; to Address model. Since in next PR I wanted to remove all unnecessary columns this was crucial at to add this at this point.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Client::Address&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;
  &lt;span class="ss"&gt;ignored_columns: &lt;/span&gt;&lt;span class="sx"&gt;%w[street city postal_code country phone_number]&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step III&lt;/strong&gt;&lt;br&gt;
This part is only about removing non-encrypted columns. So it included only this migration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;up&lt;/span&gt;
  &lt;span class="n"&gt;remove_column&lt;/span&gt; &lt;span class="ss"&gt;:client_address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:street&lt;/span&gt;
  &lt;span class="n"&gt;remove_column&lt;/span&gt; &lt;span class="ss"&gt;:client_address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:city&lt;/span&gt;
  &lt;span class="n"&gt;remove_column&lt;/span&gt; &lt;span class="ss"&gt;:client_address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:postal_code&lt;/span&gt;
  &lt;span class="n"&gt;remove_column&lt;/span&gt; &lt;span class="ss"&gt;:client_address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:country&lt;/span&gt;
  &lt;span class="n"&gt;remove_column&lt;/span&gt; &lt;span class="ss"&gt;:client_address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:phone_number&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Thanks to &lt;code&gt;ignored_columns&lt;/code&gt; this could be deployed without causing any downtime to app. From this point there were no decrypted clients’ address data in our database.&lt;/p&gt;

&lt;p&gt;Last step was to remove &lt;code&gt;ignored_columns&lt;/code&gt; attribute from the &lt;code&gt;Address&lt;/code&gt; model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;br&gt;
Encryption was definitely worth adding and considering fact that this update was deployed at Friday after 2a.m. with no issues, this shows that process is very easy.&lt;br&gt;
At least on that set of data that I had to encrypt.&lt;/p&gt;

</description>
      <category>rails</category>
      <category>lockbox</category>
      <category>ruby</category>
      <category>security</category>
    </item>
    <item>
      <title>How I learned to make use of rake when blogging</title>
      <dc:creator>rbglod</dc:creator>
      <pubDate>Fri, 08 Nov 2019 17:36:47 +0000</pubDate>
      <link>https://dev.to/rbglod/how-i-learned-rake-to-help-me-blogging-2l7f</link>
      <guid>https://dev.to/rbglod/how-i-learned-rake-to-help-me-blogging-2l7f</guid>
      <description>&lt;p&gt;When I was using Hugo as my blogging engine I get used to automated post generator. I just opened console, typed one command and had ready-to-extend post template. &lt;br&gt;
When I switched to Jekyll, I spent maybe like 10 seconds to search for that command and - as you may expect - didn't found any. I forgot about it and kept using Jekyll, but when I wanted to write new post today something switched in my head!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Man, you're programmer,
why don't you write your own ruby command to create that dummy post file for you?
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Since I'm mainly Rails developer &lt;code&gt;Rake&lt;/code&gt; came to my mind at first place.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rakefile
&lt;/h2&gt;

&lt;p&gt;I opened my &lt;code&gt;blog/&lt;/code&gt; directory and created &lt;code&gt;Rakefile&lt;/code&gt;. It is needed for &lt;code&gt;rake&lt;/code&gt; to load commands (or tasks, as they are called in rake). I started with classic &lt;code&gt;Hello World&lt;/code&gt; example, just to check how things work!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="ss"&gt;:default&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'hello'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;desc&lt;/span&gt; &lt;span class="s1"&gt;'Greeting from rake task'&lt;/span&gt;
&lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="ss"&gt;:hello&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s1"&gt;'Hello World from rake task!'&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;and that's it. Run &lt;code&gt;rake hello&lt;/code&gt; from command line and (if you have &lt;code&gt;rake&lt;/code&gt; gem installed) you'll be greeted by your ruby code!&lt;/p&gt;

&lt;p&gt;One thing needs explanation, though. That &lt;code&gt;task :default&lt;/code&gt; line. It is said, that's that good practice to always set a default task, so when &lt;code&gt;rake&lt;/code&gt; is invoked without argument it will fallback to the default one. So, if we put nothing after &lt;code&gt;rake&lt;/code&gt;, it will greet us, isn't it nice?&lt;/p&gt;

&lt;h2&gt;
  
  
  How to use arguments in rake tasks
&lt;/h2&gt;

&lt;p&gt;So yeah, that was easy one. But when I want to create new post, I would like to name new file with text provided by me, not always the same, default one. So now, I had to learn how to pass arguments to task.&lt;br&gt;
As you may expect, it is easy as previous example. Let's assume that we're still expanding our &lt;code&gt;Rakefile&lt;/code&gt; so I won't need to repeat myself with that &lt;code&gt;task :default&lt;/code&gt; code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;desc&lt;/span&gt; &lt;span class="s1"&gt;'Greet someone with his name!'&lt;/span&gt;
&lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="ss"&gt;:hello_dear&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;with_defaults&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s1"&gt;'Dude'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Hello, &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;capitalize&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;!"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now, if you run &lt;code&gt;rake hello_dear&lt;/code&gt; you'll see wonderful greeting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Hello, Dude!
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;But to make use of that cool method, you can now run &lt;code&gt;rake hello_dear[rob]&lt;/code&gt; and see:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Hello, Rob!
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Yeah, we did it! We know how to use arguments in rake tasks. Let's create some files!&lt;/p&gt;

&lt;h2&gt;
  
  
  Using bash commands from ruby
&lt;/h2&gt;

&lt;p&gt;It may not be suprising, but... invoking bash commands from ruby is very easy. And pleasant.&lt;br&gt;
To make use of all that &lt;code&gt;cd&lt;/code&gt;, &lt;code&gt;touch&lt;/code&gt;, &lt;code&gt;cat&lt;/code&gt;, etc. you just need to surround it with &lt;code&gt;backtick&lt;/code&gt; sign. Check it out!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;desc&lt;/span&gt; &lt;span class="s1"&gt;'Touch command ruby wrapper'&lt;/span&gt;
&lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="ss"&gt;:touch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:file_name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;with_defaults&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;file_name: &lt;/span&gt;&lt;span class="s1"&gt;'new_file'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="sb"&gt;`touch &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new_file&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Created new file with name: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new_file&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;And here you go, here's your file maker. Create few files with theese commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rake touch
rake touch[thats_my_file]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;With &lt;code&gt;backticks&lt;/code&gt; you can use string interpolation same as with '\"\"'. Isn't it cool?&lt;/p&gt;

&lt;h2&gt;
  
  
  The final purpose of this post
&lt;/h2&gt;

&lt;p&gt;So finally we got all needed skills to generate new post template with &lt;code&gt;rake&lt;/code&gt;. Let's see my code!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;desc&lt;/span&gt; &lt;span class="s1"&gt;'Create new post with given name'&lt;/span&gt;
&lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="ss"&gt;:new_post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:post_name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;with_defaults&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;post_name: &lt;/span&gt;&lt;span class="s1"&gt;'new_post'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;time_now&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strftime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'%Y-%m-%d'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;safe_post_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post_name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gsub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;' '&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'_'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;post_name_with_timestamp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;time_now&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;safe_post_name&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'-'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s1"&gt;'.md'&lt;/span&gt;
  &lt;span class="n"&gt;post_header&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s2"&gt;"---"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"layout: post"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"title: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"date: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'keywords: '&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"---"&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Timestamp: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;time_now&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Creating new post with file name: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;post_name_with_timestamp&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="sb"&gt;`touch ./_posts/&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;post_name_with_timestamp&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;

  &lt;span class="n"&gt;post_header&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="sb"&gt;`echo &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt; &amp;gt;&amp;gt; ./_posts/&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;post_name_with_timestamp&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"You can find it in ./_posts/&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;post_name_with_timestamp&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Few words about that script. At first I needed timestamp, to use it in filename. Jekyll makes use of that to order posts and display them correctly. So i used built-in &lt;code&gt;Time&lt;/code&gt; module and formatted it nicely, to produce Jekyll-friendly date format.&lt;/p&gt;

&lt;p&gt;Next line, with &lt;code&gt;safe_post_name&lt;/code&gt; is 'just to make sure' that the file name won't have spaces. So with this 'feature', you can pass arguments like that &lt;code&gt;rake new_post['thats new post with spaces in title']&lt;/code&gt; and it won't raise any errors nor exceptions. And, what is even more cool, in &lt;code&gt;"title"&lt;/code&gt; tag it will provide name with spaces, ready to display on webpage!&lt;/p&gt;

&lt;p&gt;Next, I combine safe post name with timestamp and &lt;code&gt;.md&lt;/code&gt; file extension - Jekyll uses &lt;code&gt;markdown&lt;/code&gt; files, so I need to create file with that extension. Then I create post_header, which is used by Jekyll to provide proper page layout (it may be post or page or whatever I implement in future), display title, date and make use of keywords.&lt;/p&gt;

&lt;p&gt;Actually I don't use keywords anywhere in my blog I suppose, but maybe in future it will be useful to have it. You never know!&lt;/p&gt;

&lt;p&gt;Then, few &lt;code&gt;puts&lt;/code&gt; to inform user (me) what is going on, when I run that task, and then bash command - &lt;code&gt;touch&lt;/code&gt; - to create file with given filename.&lt;/p&gt;

&lt;p&gt;And at the end, I fill that new file with &lt;code&gt;post_header&lt;/code&gt; data, to finally get that ready-to-use post template file.&lt;/p&gt;

&lt;p&gt;At the last line I just tell myself where to find new file, in case I would forget the structure of Jekyll project (which is impossible I think, it is really very straight forward).&lt;/p&gt;

&lt;p&gt;That's it! Now you can grab that code, improve, tune to your needs and make use of that file generator made in ruby lang! Cheers!&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rake</category>
      <category>automation</category>
      <category>jekyll</category>
    </item>
    <item>
      <title>redis-namespace: when one sidekiq process is not enough</title>
      <dc:creator>rbglod</dc:creator>
      <pubDate>Wed, 24 Jul 2019 18:13:06 +0000</pubDate>
      <link>https://dev.to/rbglod/redis-namespace-when-one-sidekiq-process-is-not-enough-39am</link>
      <guid>https://dev.to/rbglod/redis-namespace-when-one-sidekiq-process-is-not-enough-39am</guid>
      <description>&lt;p&gt;Few months ago I was working in a project in which we had two applications communicating with each other. &lt;br&gt;
Each of them had to has its own sidekiq process, but they were deployed to one server. After some time, I realized that after deploying one app, the second one has problems with invoking its workers. The problem was that sidekiq was restarting and loading workers only from one (last deployed) app. So after each deploy I had many errors saying that&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NameError: uninitialized constant AppOneWorker
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;and another time&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NameError: uninitialized constant AppTwoWorker
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;With rescue came gem called &lt;code&gt;redis-namespace&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://github.com/resque/redis-namespace"&gt;redis-namespace&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;As pointed in it's repo description, &lt;code&gt;redis-namespace&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...adds a Redis::Namespace class which can be used to namespace Redis keys. 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;What does it mean? It means that we can have plenty of apps using one Redis server, with many sidekiq processes not conflicting with each other.&lt;/p&gt;

&lt;p&gt;All you need to do is to add &lt;code&gt;redis-namespace&lt;/code&gt; to your Gemfile and configure it in &lt;code&gt;app/config/initializers/sidekiq.rb&lt;/code&gt; like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# frozen_string_literal: true

require 'sidekiq'

Sidekiq.configure_server do |config|
  config.redis = {
    url: 'redis://127.0.0.1:6379',
    namespace: 'my_first_app_name'
  }
end

Sidekiq.configure_client do |config|
  config.redis = {
    url: 'redis://127.0.0.1:6379',
    namespace: 'my_first_app_name'
  }
end
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;And do the same for the second app (with its proper name, of course).  &lt;/p&gt;

&lt;p&gt;After that, when you visit your app's &lt;code&gt;/sidekiq&lt;/code&gt; route (if you mounted it in &lt;code&gt;routes.rb&lt;/code&gt;), you can see that sidekiq process has namespace added at the beginning of it's name.&lt;br&gt;
Other way to check if everything is OK is to launch &lt;code&gt;redis-cli&lt;/code&gt; in command line, and look up for keys containing your namespace.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;redis 127.0.0.1:6379&amp;gt; keys *app_name*
 1) "my_first_app_name:queues"
 2) "my_second_app_name:queues"
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  Credits
&lt;/h3&gt;

&lt;p&gt;When I had that problem, I wouldn't resolve it without &lt;a href="https://mensfeld.pl/2014/07/multiple-sidekiq-processes-for-multiple-railssinatra-applications-namespacing/"&gt;Maciej Mensfeld's post&lt;/a&gt; about redis-namespace. Thanks!&lt;br&gt;&lt;br&gt;
You can even find my enthusiastic comment there, I left back then, haha.&lt;/p&gt;

</description>
      <category>sidekiq</category>
      <category>redis</category>
      <category>ruby</category>
      <category>rails</category>
    </item>
    <item>
      <title>SQL joins recall - a little bit too late</title>
      <dc:creator>rbglod</dc:creator>
      <pubDate>Tue, 23 Jul 2019 06:39:08 +0000</pubDate>
      <link>https://dev.to/rbglod/sql-joins-recall-a-little-bit-too-late-6e1</link>
      <guid>https://dev.to/rbglod/sql-joins-recall-a-little-bit-too-late-6e1</guid>
      <description>&lt;p&gt;Some time ago I had an interview with really cool company. I totally messed it up, despite the fact that... questions were really basic.&lt;br&gt;&lt;br&gt;
There were two tasks: one in Ruby and one with SQL. Ruby took me less than 10 minutes, but SQL... It's embarrassing to talk about it.  &lt;/p&gt;

&lt;p&gt;The question was about joining three tables in one query... With ActiveRecord I did it on the fly, but then I realized that I'm Rails developer without SQL skills. Shame. So let's start with simple joins.&lt;/p&gt;
&lt;h2&gt;
  
  
  JOINS
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FHs9auHg.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FHs9auHg.jpg" alt="img"&gt;&lt;/a&gt;  &lt;/p&gt;

&lt;p&gt;Let's say, we have 2 tables:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Books,
&lt;/li&gt;
&lt;li&gt;Authors,
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;with following structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;books |   
------|   
id    |   
title |
author_id|

authors|
-------|
id     |
name   |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And following data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;test=# select * from books;
 id |       title       | author_id 
----+-------------------+-----------
  1 | Sapiens           |         3
  2 | Homo Deus         |         3
  3 | Harry Potter      |         2
(3 rows)

test=# SELECT * FROM authors;
 id |     name     
----+--------------
  1 | Mr. Hopkins
  2 | J.K. Rowling
  3 | Yuval Noah Harari
(3 rows)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You see that associations between them, right? Author has many books, book belongs to author. &lt;/p&gt;

&lt;h3&gt;
  
  
  INNER JOIN
&lt;/h3&gt;

&lt;p&gt;So now, what we want is to select only authors who have any books assigned to them.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT authors.* FROM authors
  INNER JOIN books ON books.author_id = authors.id;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What we did here is &lt;code&gt;INNER JOIN&lt;/code&gt; - we joined two tables and printed out records, which have matching values (&lt;code&gt;authors.id&lt;/code&gt; equals &lt;code&gt;books.author_id&lt;/code&gt;).&lt;br&gt;
If we'd like not to repeat authors printed out, we should append &lt;code&gt;DISTINCT&lt;/code&gt; keyword after &lt;code&gt;SELECT&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  FULL OUTER JOIN / FULL JOIN
&lt;/h3&gt;

&lt;p&gt;To select all data from both tables we should use &lt;code&gt;FULL OUTER JOIN&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT * FROM authors
  FULL OUTER JOIN books ON books.author_id = authors.id;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It prints out all data - all records from books table and all records from authors table. It is the same as &lt;code&gt;FULL JOIN&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  LEFT JOIN / LEFT OUTER JOIN
&lt;/h3&gt;

&lt;p&gt;Left join returns all records from &lt;code&gt;authors&lt;/code&gt; (the table on the left) - if we start with &lt;code&gt;books&lt;/code&gt;, it would be &lt;code&gt;books&lt;/code&gt; of course - and matched records from joined table - &lt;code&gt;books&lt;/code&gt;.&lt;br&gt;
So, let's print all authors, and their book's count!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT authors.name, COUNT(authors.name) AS books_count FROM authors
  LEFT OUTER JOIN books
  ON authors.id = books.author_id
  GROUP BY authors.name;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It prints out nice, grouped data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;     name          | books_count 
-------------------+-------------
 Yuval Noah Harari |     2
 J.K. Rowling      |     1
 Hopkins           |     1
(3 rows)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What about more than two tables?
&lt;/h3&gt;

&lt;p&gt;At some point (at recruitment interview for example) you will need to join more than two tables and fetch data from them.&lt;br&gt;&lt;br&gt;
Let's add another table to the previous ones.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;authors_books
-------------
author_id
book_id
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And remove column &lt;code&gt;books.author_id&lt;/code&gt;. It gives us many to many association.&lt;br&gt;&lt;br&gt;
Now, records in this table should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;test=# SELECT * FROM authors_books;
 id | author_id | book_id 
----+-----------+---------
  1 |         3 |       1
  2 |         3 |       2
  3 |         2 |       3
(3 rows)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To print authors with corresponding books, we need to join &lt;code&gt;authors&lt;/code&gt; with &lt;code&gt;authors_books&lt;/code&gt; and then &lt;code&gt;authors_books&lt;/code&gt; with &lt;code&gt;books&lt;/code&gt; to match given ids.&lt;br&gt;
Let's try with this SQL query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT authors.name, books.title FROM authors 
  JOIN authors_books ON authors.id = authors_books.author_id
    JOIN books ON authors_books.book_id = books.id;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;        name        |       title       
--------------------+-------------------
 Yuval Noah Harari  | Sapiens
 Yuval Noah Harari  | Homo Deus
 J.K. Rowling       | Harry Potter
(3 rows)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, we can count their books as well:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;test=# SELECT authors.name, COUNT(authors.name) FROM authors
         JOIN authors_books ON authors.id = authors_books.author_id
           JOIN books ON authors_books.book_id = books.id GROUP BY authors.name;

        name        | count 
--------------------+-------
 Yuval Noah Harari  |     2
 J.K. Rowling       |     1
(2 rows)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Enough joins for now
&lt;/h3&gt;

&lt;p&gt;As you can see, SQL join queries aren't that hard and scary as they seem to be. ActiveRecord made Rails developers lazy and sql-disabled (as I am), but sometimes it is worth-knowing how to write some complex joins in raw SQL.&lt;/p&gt;

</description>
      <category>job</category>
      <category>interview</category>
      <category>ruby</category>
      <category>sql</category>
    </item>
  </channel>
</rss>
