<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Amplifr.com</title>
    <description>The latest articles on Forem by Amplifr.com (@amplifr).</description>
    <link>https://forem.com/amplifr</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%2Forganization%2Fprofile_image%2F252%2Faf830be3-07c9-4f30-aaac-987ba7a4f493.png</url>
      <title>Forem: Amplifr.com</title>
      <link>https://forem.com/amplifr</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/amplifr"/>
    <language>en</language>
    <item>
      <title>Setup web-page screenshots within a minute</title>
      <dc:creator>Dmitry Salahutdinov</dc:creator>
      <pubDate>Mon, 06 Apr 2020 09:40:43 +0000</pubDate>
      <link>https://forem.com/amplifr/setup-web-page-screenshots-within-a-minute-53ba</link>
      <guid>https://forem.com/amplifr/setup-web-page-screenshots-within-a-minute-53ba</guid>
      <description>&lt;p&gt;For many years &lt;a href="https://phantomjs.org/" rel="noopener noreferrer"&gt;PhantomJS&lt;/a&gt; is used to generate a web-page screenshot.  As we have a Ruby web-application on Kubebernetes, it brought some inconveniences: we had to install NodeJs and PhantomJS rights into the main Ruby Docker image:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Phantomjs is installed via npm to avoid HUGE dependencies such as QT and others.
RUN npm install phantomjs-prebuilt \
  &amp;amp;&amp;amp; ln -s /node_modules/phantomjs-prebuilt/lib/phantom/bin/phantomjs /usr/local/bin/phantomjs \
  &amp;amp;&amp;amp; rm -rf /tmp/phantomjs

COPY ./fonts/Helvetica.ttf /usr/share/fonts/truetype/Helvetica.ttf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It causes all the application instances, like web, sidekiq, and others, had these low-level dependencies installed even if they did not need to generate screenshots.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://semaphoreci.com/blog/2018/03/27/phantomjs-is-dead-use-chrome-headless-in-continuous-integration.html" rel="noopener noreferrer"&gt;For some time&lt;/a&gt; PhantomJS are not supported anymore. The new alternative is to use Google Chrome within the "headless" mode, a great example of doing it from the command line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;google-chrome --headless --window-size=1180,768 --screenshot=/tmp/screenshot.png https://amplifr.com/en
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We need to get google-chrome installed to a machine, so using this command rights from the ruby code doesn't change anything fundamentally.&lt;/p&gt;

&lt;p&gt;That is why I decided to extract it into separated HTTP based micro-service for capturing web-page and returning an image.&lt;/p&gt;

&lt;p&gt;Here it is &lt;a href="https://github.com/dsalahutdinov/screenshot" rel="noopener noreferrer"&gt;https://github.com/dsalahutdinov/screenshot&lt;/a&gt;&lt;br&gt;
It could be touched with &lt;code&gt;curl&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;curl --output screenshot.png http://screenshot.local/screenshot\?width\=1180\&amp;amp;height\=768\&amp;amp;url\=https://amplifr.com/en
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or with browser as well:&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Internals
&lt;/h2&gt;

&lt;p&gt;Internally it is simple HTTP-wrapper for running &lt;code&gt;Chrome&lt;/code&gt; and return resulting image file. Nothing more on it. Screenshot tool is Kubernetes-ready, and you can get it up in a minute by running &lt;code&gt;kubectl&lt;/code&gt; or using predefined &lt;code&gt;helm&lt;/code&gt;-chart.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use Helm Chart
&lt;/h2&gt;

&lt;p&gt;The predefined helm-chart is located &lt;a href="https://github.com/dsalahutdinov/screenshot-helm" rel="noopener noreferrer"&gt;here&lt;/a&gt;. This repo also works as a helm-repository on top of Github pages. Add the repository to your helm settings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;helm repo add screenshot-helm https://dsalahutdinov.github.io/screenshot-helm/
&lt;span class="s2"&gt;"screenshot-helm"&lt;/span&gt; has been added to your repositories
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And deploy the chart into current Kubernetes cluster:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;helm &lt;span class="nb"&gt;install &lt;/span&gt;screenshot-helm/screenshot &lt;span class="nt"&gt;--name&lt;/span&gt; screenshot &lt;span class="nt"&gt;--namespace&lt;/span&gt; screenshot

NAME:   screenshot
LAST DEPLOYED: Sat Apr  4 16:59:02 2020
NAMESPACE: screenshot
STATUS: DEPLOYED

RESOURCES:
&lt;span class="o"&gt;==&amp;gt;&lt;/span&gt; v1/Deployment
NAME         AGE
screenshot  1s

&lt;span class="o"&gt;==&amp;gt;&lt;/span&gt; v1/Pod&lt;span class="o"&gt;(&lt;/span&gt;related&lt;span class="o"&gt;)&lt;/span&gt;
NAME                      AGE
screenshot-76948b-rrq77  1s

&lt;span class="o"&gt;==&amp;gt;&lt;/span&gt; v1/Service
NAME         AGE
screenshot  1s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since then, service will run as "internal" without any ingress resources. You can access it within the cluster by its internal DNS name, [release].[namespace].kubernetes.local, or with just short name [release].[namespace]:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;--output&lt;/span&gt; screenshot.png &lt;span class="se"&gt;\&lt;/span&gt;
     http://screenshot.screenshot/screenshot/?width&lt;span class="o"&gt;=&lt;/span&gt;1180&amp;amp;height&lt;span class="o"&gt;=&lt;/span&gt;768&amp;amp;url&lt;span class="o"&gt;=&lt;/span&gt;https://amplifr.com/en
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Run on Minikube
&lt;/h2&gt;

&lt;p&gt;Here is the manual for running screenshot service into &lt;a href="https://github.com/kubernetes/minikube" rel="noopener noreferrer"&gt;Minikube&lt;/a&gt; local cluster with using "external" domain name &lt;code&gt;screenshot.local&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;# starts minikube
minikube start

# gets minikube ip address and adds it as screenshot.local domain address
echo "$(minikube ip) screenshot.local" | sudo tee -a /etc/hosts

# adds helm repository
$ helm repo add screenshot-helm 

# installs screenshot service with the enabled ingress
$ helm install screenshot-helm/screenshot \
  --name screenshot \
  --namespace screenshot \
  --set ingress.enabled=True \
  --set ingress.hosts\[0\].host=screenshot.local \
  --set ingress.hosts\[0\].paths\[0\]=/screenshot

NAME:   screenshot
LAST DEPLOYED: Mon Apr  6 10:51:54 2020
NAMESPACE: screenshot
STATUS: DEPLOYED

RESOURCES:
==&amp;gt; v1/Deployment
NAME        AGE
screenshot  0s

==&amp;gt; v1/Pod(related)
NAME                         AGE
screenshot-58dd688598-ml9nw  0s

==&amp;gt; v1/Service
NAME        AGE
screenshot  0s

==&amp;gt; v1/ServiceAccount
NAME        AGE
screenshot  0s

==&amp;gt; v1beta1/Ingress
NAME        AGE
screenshot  0s


NOTES:
1. Get the application URL by running these commands:
  http://screenshot.local/screenshot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the start, service will be available to access with &lt;code&gt;amplifr.local&lt;/code&gt; domain. Other supported options of helm-chart, like resources, tls, affinity you can find &lt;a href="https://github.com/dsalahutdinov/screenshot-helm/blob/master/values.yaml" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Usage
&lt;/h2&gt;

&lt;p&gt;Using ruby is very straightforward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="ss"&gt;height: &lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;width: &lt;/span&gt;&lt;span class="mi"&gt;768&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;url: &lt;/span&gt;&lt;span class="s1"&gt;'https://amplifr.com/en'&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Faraday&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="s1"&gt;'http://screenshot.screenshot/screenshot?'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;=&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;v&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="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&amp;amp;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;

&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'screenshot.png'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"wb"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;The &lt;code&gt;screenshot&lt;/code&gt; tool is straightforward and solves the simple, practical task - web-page screenshot generation. You can run into a Kubernetes cluster in a minute if you would need a web-page screenshot generation one day.&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>ruby</category>
      <category>go</category>
      <category>kubernetes</category>
    </item>
    <item>
      <title>Monitoring external services with Prometheus and Grafana</title>
      <dc:creator>Dmitry Salahutdinov</dc:creator>
      <pubDate>Mon, 23 Mar 2020 05:07:21 +0000</pubDate>
      <link>https://forem.com/amplifr/monitoring-external-services-with-prometheus-and-grafana-5eh6</link>
      <guid>https://forem.com/amplifr/monitoring-external-services-with-prometheus-and-grafana-5eh6</guid>
      <description>&lt;h2&gt;
  
  
  Preface
&lt;/h2&gt;

&lt;p&gt;Time ago, web-applications depended only on self-written code or libraries. It was easy to diagnose any problem, plainly investigating how an application work.&lt;/p&gt;

&lt;p&gt;Today, modern web-applications tend to have many external dependencies, such as microservices or external SaaS. It makes your system depend not only on the internal factors but on the reliability of external services as well. When something fails, it's essential to localize a problem and find out which side caused it.&lt;/p&gt;

&lt;p&gt;That is why monitoring of external services call is crucial.&lt;/p&gt;

&lt;p&gt;I work for &lt;a href="https://amplifr.com" rel="noopener noreferrer"&gt;Amplifr&lt;/a&gt;, the social media automation platform. Our Ruby core application depends on many external APIs: Facebook, Instagram, LinkedIn, Pinterest, &lt;a href="https://bitly.com" rel="noopener noreferrer"&gt;Bitly&lt;/a&gt;, Stripe, Amplitude, Intercom, and many others. &lt;br&gt;
We also are integrated with many self-hosted micro-services, such as &lt;a href="https://github.com/imgproxy/imgproxy" rel="noopener noreferrer"&gt;imgproxy&lt;/a&gt; for resizing images, &lt;a href="https://github.com/dsalahutdinov/screenshot" rel="noopener noreferrer"&gt;browser screenshot generator&lt;/a&gt;, social services bots, and other internal domain-specific services.&lt;/p&gt;

&lt;p&gt;When an application has so many integrations, it's handy to have a quick  traffic details overview:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;how many requests application does&lt;/li&gt;
&lt;li&gt;how much time consumes an average request&lt;/li&gt;
&lt;li&gt;how many of the attempts fail&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That was my second point to set monitoring up and write my own solution.&lt;/p&gt;

&lt;h2&gt;
  
  
  Metrics
&lt;/h2&gt;

&lt;p&gt;As most APIs work over HTTP, we will monitor the HTTP requests.&lt;/p&gt;

&lt;p&gt;Meet my gem &lt;a href="https://github.com/yabeda-rb/yabeda-http_requests" rel="noopener noreferrer"&gt;yabeda-http_requests&lt;/a&gt;. It is the new one of the &lt;a href="https://github.com/yabeda-rb" rel="noopener noreferrer"&gt;Yabeda&lt;/a&gt; monitoring solution by &lt;a href="https://evilmartians.com/" rel="noopener noreferrer"&gt;Evil Martians&lt;/a&gt;. If you are not familiar with it, here is the &lt;a href="https://evilmartians.com/chronicles/meet-yabeda-modular-framework-for-instrumenting-ruby-applications" rel="noopener noreferrer"&gt;great article&lt;/a&gt; to start.&lt;/p&gt;

&lt;p&gt;In a nutshell, Yabeda - is the open-source Ruby framework for collection metrics of Ruby processes (Rack/Rails, Sidekiq,p Puma) and exporting them into Prometheus (or any other adapter).&lt;/p&gt;

&lt;p&gt;&lt;code&gt;yabeda-http_requiests&lt;/code&gt; wraps all the external HTTP requests with the help of &lt;a href="https://github.com/aderyabin/sniffer" rel="noopener noreferrer"&gt;sniffer gem&lt;/a&gt;. Sniffer patches &lt;a href="https://github.com/aderyabin/sniffer/tree/master/lib/sniffer/adapters" rel="noopener noreferrer"&gt;all popular HTTP libraries&lt;/a&gt; and logs performing requests. By using middleware, &lt;code&gt;yabeda-http_requests&lt;/code&gt; gem calculates and stores metrics. Then metrics could be exported with &lt;code&gt;yabeda-prometheus&lt;/code&gt; gem.&lt;/p&gt;

&lt;p&gt;To get started with it you need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;to add the gem into your dependencies (Gemfile)&lt;/li&gt;
&lt;li&gt;set up metrics exporter for Prometheus&lt;/li&gt;
&lt;li&gt;tune up Prometheus to scrape the metrics endpoint&lt;/li&gt;
&lt;li&gt;import predefined &lt;a href="https://grafana.com/grafana/dashboards/11959" rel="noopener noreferrer"&gt;dashboard&lt;/a&gt; into your Grafana config&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You could find all the details &lt;code&gt;yabeda-http_requests&lt;/code&gt;  &lt;a href="https://github.com/yabeda-rb/yabeda-http_requests#usage" rel="noopener noreferrer"&gt;at the README.md&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Let's have a look at providing metrics &amp;amp; charts.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to monitor?
&lt;/h2&gt;

&lt;p&gt;The first release of &lt;code&gt;yabeda-http_requests&lt;/code&gt; includes some basics:&lt;/p&gt;

&lt;h3&gt;
  
  
  RPM
&lt;/h3&gt;

&lt;p&gt;The basic metric is RPM (request per minute). It shows how many HTTP request application does.&lt;/p&gt;

&lt;p&gt;Formally, &lt;code&gt;yabeda-http_requests&lt;/code&gt; collect a &lt;code&gt;http_request_count&lt;/code&gt; as counter. It is separated by quest host, port and method:&lt;/p&gt;

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

# HELP http_response_total A counter of the total number
# of external HTTP responses.
http_response_total{host="twitter.com",port="443",method="GET",status="200"} 131.0
http_response_total{host="dev.to",port="443",method="GET",status="200"} 131.0


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

&lt;/div&gt;

&lt;p&gt;Than, using &lt;a href="https://prometheus.io/docs/prometheus/latest/querying/functions/#rate" rel="noopener noreferrer"&gt;rate&lt;/a&gt; function:&lt;/p&gt;

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

sum by (host) (rate(http_response_total[1m]) )


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

&lt;/div&gt;

&lt;p&gt;renders the RPM chart:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fu3j9smoqyxk04bel6043.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fu3j9smoqyxk04bel6043.png" alt="Request per minute (by host)"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It is easy to regroup the same chart by status:&lt;/p&gt;

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

&lt;p&gt;❗ "RPM by status" chart uses another metric, which is called &lt;code&gt;http_response_total&lt;/code&gt; which logs the HTTP responses.&lt;/p&gt;

&lt;h3&gt;
  
  
  Request duration
&lt;/h3&gt;

&lt;p&gt;When you want to investigate which one of the dependent components causes a slow-down, there is a histogram duration data and the corresponding chart. It shows how many times consumes the slowest request:&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Example Application
&lt;/h2&gt;

&lt;p&gt;This section is for practical approach lovers. Here is the &lt;a href="https://github.com/yabeda-rb/yabeda-http_requests/tree/master/example" rel="noopener noreferrer"&gt;example of web-application&lt;/a&gt;. It shows how all things work together: web-application, metrics collector, exporter, and Prometheus &amp;amp; Grafana.&lt;/p&gt;

&lt;p&gt;The example has a docker-compose config for automatic setup. Just run the following:&lt;/p&gt;

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

&lt;span class="nv"&gt;$ &lt;/span&gt;git clone https://github.com/yabeda-rb/yabeda-http_requests
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;yabeda-http_requests/example
&lt;span class="nv"&gt;$ &lt;/span&gt;docker-compose up


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

&lt;/div&gt;

&lt;p&gt;This command runs the rack-application, Sidekiq, Prometheus, and Grafana.&lt;br&gt;
Rack application adds random web scrapping jobs to the sidekiq. Navigate to &lt;code&gt;http://localhost:9292&lt;/code&gt; to enqueue some jobs.&lt;/p&gt;

&lt;p&gt;"Sidekiq instance" runs background scrapping tasks, collects metrics, and exposes an endpoint with data for the Prometheus. &lt;br&gt;
Here is the basic sidekiq configuration, showing how the sidekiq process run its web-server:&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;# sidekiq config&lt;/span&gt;
&lt;span class="no"&gt;Sidekiq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure_server&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;url: &lt;/span&gt;&lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'REDIS_URL'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;# configures all the Yabeda metrics&lt;/span&gt;
  &lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Yabeda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure!&lt;/span&gt;

  &lt;span class="c1"&gt;# expose 9394 port with /metrics path for prometheus&lt;/span&gt;
  &lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Yabeda&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Prometheus&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Exporter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start_metrics_server!&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;Navigate to &lt;code&gt;http://localhost:9394/metrics&lt;/code&gt; to see metrics endpoint, and you will see the metrics values formatted as text:&lt;/p&gt;

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

# TYPE http_request_total counter
# HELP http_request_total A counter of the total number of external HTTP requests.
http_request_total{host="twitter.com",port="443",method="GET"} 16922.0
http_request_total{host="dev.to",port="443",method="GET"} 17083.0
http_request_total{host="amplifr.com",port="443",method="GET"} 451.0
http_request_total{host="www.sitepoint.com",port="443",method="GET"} 428.0
# TYPE http_response_total counter
# HELP http_response_total A counter of the total number of external HTTP responses.
http_response_total{host="twitter.com",port="443",method="GET",status="200"} 16910.0
http_response_total{host="dev.to",port="443",method="GET",status="200"} 17080.0
http_response_total{host="amplifr.com",port="443",method="GET",status="301"} 451.0
http_response_total{host="www.sitepoint.com",port="443",method="GET",status="200"} 428.0
http_response_total{host="twitter.com",port="443",method="GET",status="503"} 1.0
# TYPE http_response_duration_milliseconds histogram
# HELP http_response_duration_milliseconds A histogram of the response                                                duration (milliseconds).
http_response_duration_milliseconds_bucket{host="twitter.com",port="443",method="GET",status="200",le="0.5"} 0.0
http_response_duration_milliseconds_bucket{host="twitter.com",port="443",method="GET",status="200",le="1"} 0.0
http_response_duration_milliseconds_bucket{host="twitter.com",port="443",method="GET",status="200",le="2.5"} 0.0
http_response_duration_milliseconds_bucket{host="twitter.com",port="443",method="GET",status="200",le="5"} 0.0
http_response_duration_milliseconds_bucket{host="twitter.com",port="443",method="GET",status="200",le="10"} 0.0
http_response_duration_milliseconds_bucket{host="twitter.com",port="443",method="GET",status="200",le="25"} 0.0
...


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

&lt;/div&gt;

&lt;p&gt;Prometheus scrapes &lt;code&gt;http://sidekiq:9394/metrics&lt;/code&gt; periodically and stores the metrics into its database.&lt;/p&gt;

&lt;p&gt;Here is the overview diagram of how it works together:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fyykdbbpezp7ivllfy88j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fyykdbbpezp7ivllfy88j.png" alt="HTTP requests monitoring example diagram"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Grafana visualizes the data. Navigate to &lt;code&gt;http://localhost:3000/d/OGd-oEXWz/yabeda-external-http-requests&lt;/code&gt;. Use &lt;code&gt;admin/foobar&lt;/code&gt; to login and see the template dashboard:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fyw2vtcaiw8x9myke9h24.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fyw2vtcaiw8x9myke9h24.png" alt="Yabeda External HTTP requests Grafana dashboard"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;❗You could import prepared Grafana dashboard  &lt;a href="https://grafana.com/grafana/dashboards/11959" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

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

&lt;p&gt;The monitoring of external HTTP calls is essential. It is handy to an overview of your outgoing application traffic. It also helps to diagnose slow-down issues of dependent services.&lt;/p&gt;

&lt;p&gt;Yabeda framework and the &lt;code&gt;yabeda-http_requests&lt;/code&gt; let to quickly set up metrics and visualize them using a predefined Grafana dashboard template.&lt;/p&gt;

&lt;p&gt;If you have any other ideas, please do not hesitate to contribute: by writing an Issue, sending PR, or leaving the comment.&lt;/p&gt;

&lt;p&gt;Thank you for reading.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>monitoring</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Sane Defaults</title>
      <dc:creator>Alex Musayev</dc:creator>
      <pubDate>Fri, 28 Feb 2020 22:32:57 +0000</pubDate>
      <link>https://forem.com/amplifr/sane-defaults-25pe</link>
      <guid>https://forem.com/amplifr/sane-defaults-25pe</guid>
      <description>&lt;p&gt;&lt;strong&gt;This post explains one of the practices we use to improve web service configuration management. As an example, we will use a Ruby on Rails application running on the Kubernetes environment. However, the general concept of sane defaults is technology-agnostic and entirely applicable to different programming languages and frameworks.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Imagine medium to large scale web service maintained by a team of 5 engineers. The production environment constitutes a distributed system of Rails servers, auxiliary microservices, and data storage. Everything is running on a Kubernetes cluster. As usual, besides the production, there is also a staging configuration — very similar, but not identical. It is smaller and cheaper. And there are also local development environments typically containing a minimal task-specific subset of services.&lt;/p&gt;

&lt;p&gt;To maintain multi-environment Rails application, we need it to be configurable. Most common approach here is to follow &lt;a href="https://12factor.net"&gt;The Twelve-Factor App&lt;/a&gt; methodology and store configuration in the system environment variables.&lt;/p&gt;

&lt;p&gt;Say, we use &lt;code&gt;dotenv&lt;/code&gt; gem to set some arbitrary configuration parameters for development environment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# .env
MAIN_DOMAIN=app.local
REDIS_URL=redis://127.0.0.1:6379/0
AVATAR_SIZE=100
IMGPROXY_URL=https://imgproxy.local
SECRETS_IMGPROXY_KEY=key
SECRETS_IMGPROXY_SALT=salt
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now when we can address configuration values like so: &lt;code&gt;ENV.fetch('MAIN_DOMAIN')&lt;/code&gt;. Dead-straight, cheap and easy? Sort of. Let's take a closer look at the potential shortcomings related to environment variables-based configuration in a mature Rails project.&lt;/p&gt;

&lt;h1&gt;
  
  
  Downsides of using environment variables
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Configuration bloat.&lt;/strong&gt; A more extensive project configuration may include hundreds of parameters. This fact itself is healthy — big configuration is better than hardcoding magic values all over the codebase, anyway. But you need to expect the config to require some gardening to prevent duplication, obsoletion, and other potential issues.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Each change in the configuration require manual effort.&lt;/strong&gt; If your project uses &lt;code&gt;dotenv&lt;/code&gt; on the development environment, each team member will have to maintain an up to date copy of the &lt;code&gt;.env&lt;/code&gt; file with all the necessary configuration variables.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;.env&lt;/code&gt; file may include individual settings — like a personal S3 bucket name you use for testing — and not supposed to be committed to the version control system. Therefore, it is not possible to automate config updates propagation amongst the team members.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Application startup errors.&lt;/strong&gt; Typically it is a good practice to check mandatory configuration parameters during the application initialization stage and raise an error if something is missing (note &lt;code&gt;#fetch&lt;/code&gt; method call in the above example). I will skip a detailed explanation here, assuming you already familiar with the &lt;a href="https://en.wikipedia.org/wiki/Fail-fast"&gt;fail-fast&lt;/a&gt; principle in system design. But in the practical context of this article, this validation means each configuration change may cause a hiccup in every team member's working process. Nobody likes it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fragile tests.&lt;/strong&gt; Sometimes you need to use your configuration during testing, directly or indirectly. That means your test suite execution result depends on the system it is running on. A successful test run on a developer's machine does not guarantee the same result on CI, even in a containerized environment. And the reasons for the failure could be much less evident than the wrong &lt;code&gt;DATABASE_URL&lt;/code&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Sane defaults
&lt;/h1&gt;

&lt;p&gt;Our experience of maintaining configuration for larger web services shows that a significant part of configuration parameters don't change too often. Based on this observation, it is possible to mitigate most of the issues listed above by complementing configuration parameters with default values:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# const/defaults.rb&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Defaults&lt;/span&gt;
  &lt;span class="no"&gt;AVATAR_SIZE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;
  &lt;span class="no"&gt;MAIN_DOMAIN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'amplifr.local'&lt;/span&gt;
  &lt;span class="no"&gt;IMGPROXY_MAX_SRC_RESOLUTION&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;9000&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Rails autoload mechanism will make &lt;code&gt;Defaults&lt;/code&gt; module resolvable from everywhere in your project, making it possible to use the constants as a safe fallback each time you need the corresponding configuration parameter:&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;ENV&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'AVATAR_SIZE'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="no"&gt;Defaults&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;AVATAR_SIZE&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Default constants make it safe to remove related environment variables from &lt;code&gt;.env&lt;/code&gt;, or &lt;code&gt;Dockerfile&lt;/code&gt;, or&lt;code&gt;docker-compose.yml&lt;/code&gt;, or whatever you use to keep your configuration. Configuration changes will automatically propagate over the version control system, preventing some of the application startup errors. And from now on, it is easy to keep an environment-agnostic test suite (just keep using &lt;code&gt;Defaults::*&lt;/code&gt; values instead of the &lt;code&gt;ENV&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Furthermore, I'd like to highlight that sane defaults plays well with the Twelve-Factor App methodology since environment variables always have higher priority over the constants.&lt;/p&gt;

&lt;p&gt;There are some limitations, though: not every configuration parameter could have a default. Each default value should match the following conditions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A sane default supposes to remain identical for all code execution environments, including Rails environments, individual developer machines, server instances, CI, etc.&lt;/li&gt;
&lt;li&gt;Default values should never be secret. In other words, defaults do not apply to API keys or other configuration parameters that you don't want to keep in the repository.&lt;/li&gt;
&lt;li&gt;A default does not change often or ever.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Making it easy to use
&lt;/h1&gt;

&lt;p&gt;We solved the issues of the environment variables-based configuration, but &lt;del&gt;at what cost?&lt;/del&gt; accessing configuration params with &lt;code&gt;fetch&lt;/code&gt; calls is bulky and cumbersome:&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;ENV&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'AVATAR_SIZE'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Defaults&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;AVATAR_SIZE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Also, it would be a bad idea to rely on the developer to remember about the defaults each time they use a related configuration. And what about required parameter validation during the application initialization?&lt;/p&gt;

&lt;p&gt;Luckily it is easy to solve all of these problems with &lt;a href="https://github.com/rubyconfig/config"&gt;config&lt;/a&gt; gem. It allows to define structured configuration with YAML files, similar to Rails' &lt;code&gt;database.yml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/settings.yml&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;main_domain&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;%= ENV.fetch('MAIN_DOMAIN') { Defaults::MAIN_DOMAIN } %&amp;gt;&lt;/span&gt;
&lt;span class="na"&gt;redis_url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;%= ENV.fetch('REDIS_URL') { Defaults::REDIS_URL } %&amp;gt;&lt;/span&gt;
&lt;span class="na"&gt;avatar_size&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;%= ENV.fetch('AVATAR_SIZE') { Defaults::AVATAR_SIZE } %&amp;gt;&lt;/span&gt;

&lt;span class="na"&gt;imgproxy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;base_url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;%= ENV.fetch('IMGPROXY_URL') %&amp;gt;&lt;/span&gt;
  &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;%= ENV.fetch('SECRETS_IMGPROXY_KEY') %&amp;gt;&lt;/span&gt;
  &lt;span class="na"&gt;salt&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;%= ENV.fetch('SECRETS_IMGPROXY_SALT') %&amp;gt;&lt;/span&gt;
  &lt;span class="na"&gt;max_src_resolution&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;%= ENV.fetch('IMGPROXY_MAX_SRC_RESOLUTION') { Defaults::IMGPROXY_MAX_SRC_RESOLUTION } %&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Being added to your &lt;code&gt;Gemfile&lt;/code&gt;, &lt;code&gt;config&lt;/code&gt; gem builds a globally available settings object during Rails application startup, providing a set of concise accessors to the configuration params, like &lt;code&gt;Settings.main_domain&lt;/code&gt; or &lt;code&gt;Settings.imgproxy.base_url&lt;/code&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Summary
&lt;/h1&gt;

&lt;p&gt;Here is a list of benefits you gain from complementing environment-based configuration with sane defaults:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Prevent configuration bloat.&lt;/li&gt;
&lt;li&gt;Automate shared configs propagation over developer machines.&lt;/li&gt;
&lt;li&gt;Prevent startup errors caused by the lack of shared configuration params.&lt;/li&gt;
&lt;li&gt;More stable tests.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>configuration</category>
      <category>practices</category>
      <category>rails</category>
      <category>kubernetes</category>
    </item>
    <item>
      <title>Ruby conferences to attend in 2020</title>
      <dc:creator>Dmitry Salahutdinov</dc:creator>
      <pubDate>Sat, 25 Jan 2020 19:02:46 +0000</pubDate>
      <link>https://forem.com/amplifr/ruby-conferences-to-attend-in-2020-2ckn</link>
      <guid>https://forem.com/amplifr/ruby-conferences-to-attend-in-2020-2ckn</guid>
      <description>&lt;p&gt;Do you think &lt;strong&gt;Ruby&lt;/strong&gt; is dying?&lt;br&gt;
Meanwhile, Rubyists get the sold-out at more than 20 conferences around the world.&lt;/p&gt;

&lt;p&gt;While looking for, I made a list of Ruby conferences you can attend in 2020. Here it is:&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://birminghamonrails.com/"&gt;Birmingham On Rails&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Date:&lt;/strong&gt; January 31&lt;br&gt;
&lt;strong&gt;Location:&lt;/strong&gt; 🇺🇸USA, Birmingham, AL&lt;/p&gt;

&lt;p&gt;Birmingham on Rails is a forward thinking Ruby on Rails conference with the purpose of building a network of software professionals and educating them on the latest developments and uses of the Rails platform.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://www.rubyfuza.org/"&gt;RubyFuza&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Date:&lt;/strong&gt; February 6-7&lt;br&gt;
&lt;strong&gt;Location:&lt;/strong&gt; 🇿🇦South Africa, Cape Town&lt;br&gt;
&lt;strong&gt;Follow&lt;/strong&gt;: &lt;a href="https://twitter.com/rubyfuza"&gt;@rubyfuza&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Rubyfuza is the longest run Ruby conference in South Africa. 2020 will be the 9th event held in Cape Town This year we will be opening the conference up!&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://2020.rubyparis.org/"&gt;ParisRB Conf&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Date:&lt;/strong&gt; February 18-19&lt;br&gt;
&lt;strong&gt;Location:&lt;/strong&gt; 🇫🇷France, Paris&lt;br&gt;
&lt;strong&gt;Follow&lt;/strong&gt;: &lt;a href="https://twitter.com/parisrb"&gt;@parisrb&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Meet fellow rubyists from around the world, and expand your Ruby horizons!&lt;/p&gt;

&lt;p&gt;The year 2020 marks the 25th Anniversary of the first release of Ruby and we are lucky (and filled with gratitude) to welcome Ruby creator Yukihiro Matsumoto who will help us open ParisRB with a Keynote speech.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://rubyconf.org.au/2020"&gt;RubyConf AU&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Date:&lt;/strong&gt; February 20-21&lt;br&gt;
&lt;strong&gt;Location:&lt;/strong&gt; 🇦🇺Australia, Melbourne&lt;br&gt;
&lt;strong&gt;Follow&lt;/strong&gt;: &lt;a href="https://twitter.com/rubyconf_au"&gt;@rubyconf_au&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With a mixture of local and international speakers, there will be two day of talks. We'll cover topics that interest, excite, and delight Rubyists and the Ruby-curious of all levels. There will also be social events hosted where you will get the opportunity to meet like-minded people!&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://wrocloverb.com/"&gt;Wroclove.rb&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Date:&lt;/strong&gt; March 20-22&lt;br&gt;
&lt;strong&gt;Location:&lt;/strong&gt; 🇵🇱Poland, Wroclaw&lt;br&gt;
&lt;strong&gt;Follow&lt;/strong&gt;: &lt;a href="https://twitter.com/wrocloverb"&gt;@wrocloverb&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The main goal of the conference is to help Ruby professionals become better at what they do.&lt;/p&gt;

&lt;p&gt;wroclove.rb serves best to people who are already fluent with Ruby. The ideas presented at our conference are very advanced. They are meant to inspire the way we write code, the way we help our clients and users. It’s not uncommon for us to present you ideas that come from the Java and .NET worlds, we believe that their ideas can have a great influence on our community. There are good  reasons, why our conference is called “the best Java conference in the Ruby world”.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="http://brug.by/"&gt;BRUG.by&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Date:&lt;/strong&gt; March 21&lt;br&gt;
&lt;strong&gt;Location:&lt;/strong&gt; 🇧🇾Belarus, Minks&lt;br&gt;
&lt;strong&gt;Follow&lt;/strong&gt;: &lt;a href="https://www.facebook.com/ByRoR/"&gt;@ByRoR&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;THE INTERNATIONAL TECH CONFERENCE FOR RUBY ENTHUSIASTS IN MINSK.&lt;/p&gt;

&lt;p&gt;During the conference, we want to focus on the topic of Engineering Happiness. What gets us satisﬁed with our work? What processes, tools, industry news make us happy? What does an engineer need to be happy? What tips and tricks do companies use to increase developers’ happiness? All in all, is happiness really so important?&lt;/p&gt;

&lt;p&gt;If you have something to say on that subject, we are looking forward to your 40-minute presentation (Keynote, PPT or any other) with slides or a live coding session. The QA block will also take 20 minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://2020.rubyday.it/"&gt;RubyDay&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Date:&lt;/strong&gt; April 2&lt;br&gt;
&lt;strong&gt;Location:&lt;/strong&gt; 🇮🇹Verona, Italy&lt;br&gt;
&lt;strong&gt;Follow&lt;/strong&gt;: &lt;a href="https://twitter.com/rubydayit"&gt;@rubyday&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;RubyDay 2020 is the 8th edition of the Italian Ruby conference, organized by GrUSP, organizers of events like phpday and jsday. Event is international and all sessions will be in English.&lt;/p&gt;

&lt;p&gt;The event’s goal is to allow all Rubyists to meet and share experience while having fun and networking in an enjoyable context.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://www.rubywine.org/"&gt;RubyWine&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Date:&lt;/strong&gt; April 4&lt;br&gt;
&lt;strong&gt;Location:&lt;/strong&gt; 🇲🇩Moldova, Chișinău&lt;br&gt;
&lt;strong&gt;Follow&lt;/strong&gt;: &lt;a href="https://twitter.com/RubyMeditation"&gt;@RubyMeditation&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Being first of its kind in Moldova, Ruby Wine is intended to promote Ruby programming language among country and fortify existing Ruby community. It will bring together IT professionals for knowledge sharing and let them explore new insights of Ruby's possibilities. This event is a perfect mix of top-notch talks by talented Ruby developers and amazing Moldovan atmosphere.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://www.rubywine.org/"&gt;RubyKaigi&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Date:&lt;/strong&gt; April 9-11&lt;br&gt;
&lt;strong&gt;Location:&lt;/strong&gt; 🇯🇵Japan, Matsumoto&lt;br&gt;
&lt;strong&gt;Follow&lt;/strong&gt;: &lt;a href="https://twitter.com/rubykaigi"&gt;@rubykaigi&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;RubyKaigi is an annual conference held in Japan for Ruby programmers that was held for the first time in 2006.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://rubyconference.by/"&gt;RubyConfBy&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Date:&lt;/strong&gt; April 18&lt;br&gt;
&lt;strong&gt;Location:&lt;/strong&gt; 🇧🇾Belarus, Minks&lt;br&gt;
&lt;strong&gt;Follow&lt;/strong&gt;: &lt;a href="https://twitter.com/RubyConfBY"&gt;@RubyConfBY&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The 6th large technological Ruby, Rails and related technologies conference. Leaders from Eastern+Western Europe and USA will gather to discuss technological aspects of development with Ruby.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://rubyconfindia.org/"&gt;RubyConfIndia&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Date:&lt;/strong&gt; April 25-26&lt;br&gt;
&lt;strong&gt;Location:&lt;/strong&gt; 🇮🇳India, Goa&lt;br&gt;
&lt;strong&gt;Follow&lt;/strong&gt;: &lt;a href="https://twitter.com/rubyconfindia"&gt;@rubyconfindia&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;RubyConf India is a global event complementing other RubyConf events across the world.&lt;/p&gt;

&lt;p&gt;This year our event is a 2 day, single track event focused on Ruby Language, Framework and Tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://railsconf.com/"&gt;RailsConf&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Date:&lt;/strong&gt; May 5-7&lt;br&gt;
&lt;strong&gt;Location:&lt;/strong&gt; 🇺🇸USA, Portland&lt;br&gt;
&lt;strong&gt;Follow&lt;/strong&gt;: &lt;a href="https://twitter.com/railsconf"&gt;@railsconf&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;RailsConf is the world’s largest gathering of Rails developers, brought together to further discussion and learning about building, managing, and testing Rails applications. With a specific focus on Rails, conference topics can range from new users to administration to advanced techniques.v&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://balkanruby.com/"&gt;BalkanRuby&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Date:&lt;/strong&gt; May 15-26&lt;br&gt;
&lt;strong&gt;Location:&lt;/strong&gt; 🇧🇬Bulgaria, Sofia&lt;br&gt;
&lt;strong&gt;Follow&lt;/strong&gt;: &lt;a href="https://twitter.com/BalkanRuby"&gt;@BalkanRuby&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Balkan Ruby's aim is to bring world-class talks to the Balkan area and allow the local community to interact with the wider Ruby community from Europe and around the world.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://spbrubyconf.ru/"&gt;Saint P Ruby Conf&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Date:&lt;/strong&gt; June 6-7&lt;br&gt;
&lt;strong&gt;Location:&lt;/strong&gt; 🇷🇺Russia, Saint Petersburg&lt;br&gt;
&lt;strong&gt;Follow&lt;/strong&gt;: &lt;a href="https://twitter.com/saintpruby"&gt;@saintpruby&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ruby community based in Saint Petersburg, Russia. We do meetups, drinkups and conferences.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://2020.rubyunconf.eu/"&gt;Ruby Unconf&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Date:&lt;/strong&gt; June 6-7&lt;br&gt;
&lt;strong&gt;Location:&lt;/strong&gt; 🇩🇪Germany, Hamburg&lt;br&gt;
&lt;strong&gt;Follow&lt;/strong&gt;: &lt;a href="https://twitter.com/RubyUnconfEU"&gt;@RubyUnconfEU&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What exactly is an Unconference? It is an Barcamp style event. It works like this: Everybody can propose a talk. Everybody votes for the talks they want to see. The most voted talks will be held. You decide what will happen!&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="http://www.reddotrubyconf.com/"&gt;RedDotRubyConference&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Date:&lt;/strong&gt; June? (is not know yet)&lt;br&gt;
&lt;strong&gt;Location:&lt;/strong&gt; 🇸🇬 Singapore&lt;br&gt;
&lt;strong&gt;Follow&lt;/strong&gt;: &lt;a href="https://twitter.com/reddotrubyconf"&gt;@reddotrubyconf&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;According to &lt;a href="https://twitter.com/reddotrubyconf/status/1189344579209908229"&gt;that tweet&lt;/a&gt; the conference will get back this year.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://brightonruby.com/"&gt;BrightonRuby&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Date:&lt;/strong&gt; July 3&lt;br&gt;
&lt;strong&gt;Location:&lt;/strong&gt; 🇬🇧UK, Brighton&lt;br&gt;
&lt;strong&gt;Follow&lt;/strong&gt;: &lt;a href="https://twitter.com/brightonruby"&gt;@brightonruby&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It’s a friendly, inclusive, single day, single track event for Rubyists and the Ruby-curious.&lt;/p&gt;

&lt;p&gt;We’re nice. The speakers are ace.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="http://rubyconf.nairuby.org/2020"&gt;RubyConf Kenya&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Date:&lt;/strong&gt; July 23-25&lt;br&gt;
&lt;strong&gt;Location:&lt;/strong&gt; 🇰🇪Kenia, Nairobi&lt;br&gt;
&lt;strong&gt;Follow&lt;/strong&gt;: &lt;a href="https://twitter.com/NairubyKE"&gt;@NairubyKE&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Nairobi Ruby community driven conference.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://euruko2020.org/"&gt;Euruko&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Date:&lt;/strong&gt; August 21-22&lt;br&gt;
&lt;strong&gt;Location:&lt;/strong&gt; 🇫🇮Finland, Helsinki&lt;br&gt;
&lt;strong&gt;Follow&lt;/strong&gt;: &lt;a href="https://twitter.com/euruko"&gt;@euruko&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Euruko is a Ruby conference organised annually, each year in a different European city. Join us in Helsinki, Finland on 21st–22nd August 2020 for two days of Ruby.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://grillrb.com/"&gt;GrillRB&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Date:&lt;/strong&gt; unknown, end of August&lt;br&gt;
&lt;strong&gt;Location:&lt;/strong&gt; 🇵🇱Poland, Wroclaw&lt;br&gt;
&lt;strong&gt;Follow&lt;/strong&gt;: &lt;a href="https://twitter.com/grill_rb"&gt;@grill_rb&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;GrillRB is Polish outdoor single-track community-driven conference fully dedicated to Ruby on Rails framework and Ruby language. During the two day event we are going to discuss the topics which are the most crucial for Ruby community, such as: best practices, development process and the future of Ruby. Come and exchange the knowledge and experience, talk over projects with other Rubyists in relaxing &amp;amp; enjoyable environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://rubyc.eu/"&gt;RubyC&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Date:&lt;/strong&gt; September 12-13&lt;br&gt;
&lt;strong&gt;Location:&lt;/strong&gt; 🇺🇦Ukraine, Kyiv&lt;br&gt;
&lt;strong&gt;Follow&lt;/strong&gt;: &lt;a href="https://twitter.com/rubyc_eu"&gt;@rubyc_eu&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;RubyC is a European conference devoted to Ruby, Rails and other related technologies.&lt;/p&gt;

&lt;p&gt;Every year we invite hundreds of Ruby enthusiasts and developers from various programming groups to exchange knowledge, discuss the latest news, learn from one another, and generally have a wonderful time in Kyiv, Ukraine.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://rubyrussia.club/"&gt;RubyRussia&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Date:&lt;/strong&gt; unknown, end of September&lt;br&gt;
&lt;strong&gt;Location:&lt;/strong&gt; 🇷🇺Russia, Moscow&lt;br&gt;
&lt;strong&gt;Follow&lt;/strong&gt;: &lt;a href="https://twitter.com/railsclub_ru"&gt;@railsclub_ru&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The conference is devoted to all aspects of Ruby and Rails development. We are the largest Ruby focused pro conference in Russia.&lt;br&gt;
Ruby Russia is a unique place where you can socialize with colleagues, receive and share experience, meet old friends and learn something new.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://rubyconfth.com/"&gt;RubyConf Thailand&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Date:&lt;/strong&gt; October 16 — 17&lt;br&gt;
&lt;strong&gt;Location:&lt;/strong&gt; 🇹🇭Thailand, Bangkok&lt;br&gt;
&lt;strong&gt;Follow&lt;/strong&gt;: &lt;a href="https://twitter.com/rubyconfth"&gt;@rubyconfth&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;RubyConf TH is a community and volunteer-powered event. We also depend on the awesomeness of the whole Ruby community to contribute to a terrific experience. So help us to make it a success!&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://rubyconf.org/"&gt;RubyConf&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Date:&lt;/strong&gt; November 18-20&lt;br&gt;
&lt;strong&gt;Location:&lt;/strong&gt; 🇺🇸USA, Nashville&lt;br&gt;
&lt;strong&gt;Follow&lt;/strong&gt;: &lt;a href="https://twitter.com/rubyconf"&gt;@rubyconf&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;RubyConf is the world’s largest and longest-running gathering of Ruby enthusiasts, practitioners, and companies.&lt;/p&gt;

&lt;p&gt;Please, let me know if I missed any event.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>events</category>
      <category>conferences</category>
    </item>
    <item>
      <title>Continuous deployment Ruby application to Minikube with Google's Skaffold</title>
      <dc:creator>Dmitry Salahutdinov</dc:creator>
      <pubDate>Tue, 21 Jan 2020 18:44:39 +0000</pubDate>
      <link>https://forem.com/amplifr/continuous-deployment-to-minikube-with-skaffold-18ib</link>
      <guid>https://forem.com/amplifr/continuous-deployment-to-minikube-with-skaffold-18ib</guid>
      <description>&lt;p&gt;&lt;a href="http://kubernetes.io/"&gt;Kubernetes&lt;/a&gt; has made it very easy to deploy and scale applications to the cloud than ever. Still, the development process has not evolved at the same speed.&lt;/p&gt;

&lt;p&gt;Today, most developers try to either run parts of the infrastructure locally with docker or test these integrations directly in the cluster via CI jobs or the "&lt;code&gt;docker build&lt;/code&gt;, &lt;code&gt;docker push&lt;/code&gt;, &lt;code&gt;kubectl apply&lt;/code&gt;" cycle. Manual deployment is painful and incredibly slow.&lt;/p&gt;

&lt;p&gt;In this article, we will see how to set up continuous deployment of simple Ruby application to Minikube to reduce the development cycle time using the &lt;a href="https://skaffold.dev/"&gt;Google's Skaffold tool&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Skaffold handles the workflow for building/pushing Docker images and deploying your application. It helps to reuse your actual application production configuration (Kubernetes manifests, Helm chart, Docker Compose config) for the local continuous development cycle. Skaffold also supports file sync to prevent the full container rebuild/redeploy.&lt;/p&gt;

&lt;h1&gt;
  
  
  Preparation
&lt;/h1&gt;

&lt;p&gt;First of all, let's install the required tooling.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install Minikube
&lt;/h2&gt;

&lt;p&gt;Here are the commands to run for Mac, more details could be found &lt;a href="https://kubernetes.io/docs/tasks/tools/install-minikube/"&gt;here&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;minikube
&lt;span class="nv"&gt;$ &lt;/span&gt;minikube version
minikube version: v1.5.2
commit: 792dbf92a1de583fcee76f8791cff12e0c9440ad-dirty
&lt;span class="nv"&gt;$ &lt;/span&gt;minikube start

&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;minikube docker-env&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl config use-context minikube

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

&lt;/div&gt;



&lt;p&gt;The line &lt;code&gt;eval $(minikube docker-env)&lt;/code&gt; set the docker client to use Minikube's Docker engine. It allows us to avoid docker image pushing and simplifies the full redeploy cycle.&lt;/p&gt;

&lt;p&gt;We've also changed to actual &lt;code&gt;kubectl&lt;/code&gt; context to minikube.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install Skaffold
&lt;/h2&gt;

&lt;p&gt;Here are the documentation details of &lt;a href="https://skaffold.dev/docs/install/"&gt;Skaffold installing&lt;/a&gt;, for running on Mac just use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;skaffold
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Ruby/Rack application
&lt;/h1&gt;

&lt;p&gt;We will use the simplest Rack-application from the &lt;a href="https://github.com/GoogleContainerTools/skaffold/tree/master/examples/ruby"&gt;Skaffold's github&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;git clone git@github.com:GoogleContainerTools/skaffold.git
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;examples/ruby
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Skaffold dev
&lt;/h2&gt;

&lt;p&gt;Just run &lt;code&gt;skaffold dev&lt;/code&gt; and you will see similar output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;skaffold dev
Listing files to watch...
 - ruby-example
Generating tags...
 - ruby-example -&amp;gt; ruby-example:v1.1.0-118-g92f37b200-dirty
Checking cache...
 - ruby-example: Not found. Building
Found &lt;span class="o"&gt;[&lt;/span&gt;minikube] context, using &lt;span class="nb"&gt;local &lt;/span&gt;docker daemon.
Building &lt;span class="o"&gt;[&lt;/span&gt;ruby-example]...
Sending build context to Docker daemon  6.144kB
Step 1/6 : FROM ruby:2.7
 &lt;span class="nt"&gt;---&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; fb53c5f433da
Step 2/6 : WORKDIR /app
 &lt;span class="nt"&gt;---&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Using cache
 &lt;span class="nt"&gt;---&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; 0c2d04fabca0
Step 3/6 : ADD Gemfile&lt;span class="k"&gt;*&lt;/span&gt; ./
 &lt;span class="nt"&gt;---&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Using cache
 &lt;span class="nt"&gt;---&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; 06d6623561fc
Step 4/6 : RUN bundle &lt;span class="nb"&gt;install&lt;/span&gt;
 &lt;span class="nt"&gt;---&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Using cache
 &lt;span class="nt"&gt;---&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; 9bbcf218dda5
Step 5/6 : ADD &lt;span class="nb"&gt;.&lt;/span&gt; ./
 &lt;span class="nt"&gt;---&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Using cache
 &lt;span class="nt"&gt;---&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; 478cca6b9c4f
Step 6/6 : CMD &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"bundle"&lt;/span&gt;,&lt;span class="s2"&gt;"exec"&lt;/span&gt;,&lt;span class="s2"&gt;"puma"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
 &lt;span class="nt"&gt;---&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Running &lt;span class="k"&gt;in &lt;/span&gt;434b1923c060
 &lt;span class="nt"&gt;---&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; 988d9e9e7bf4
Successfully built 988d9e9e7bf4
Successfully tagged ruby-example:v1.1.0-118-g92f37b200-dirty
Tags used &lt;span class="k"&gt;in &lt;/span&gt;deployment:
 - ruby-example -&amp;gt; ruby-example:988d9e9e7bf4b44beb62a8aaa7104cdf407a8523db0bdc7fe331600d374ba274
   &lt;span class="nb"&gt;local &lt;/span&gt;images can&lt;span class="s1"&gt;'t be referenced by digest. They are tagged and referenced by a unique ID instead
Starting deploy...
 - service/ruby created
 - deployment.apps/ruby created
Watching for changes...
[ruby-75c5b89895-rd2lb ruby] Puma starting in single mode...
[ruby-75c5b89895-rd2lb ruby] * Version 4.3.1 (ruby 2.7.0-p0), codename: Mysterious Traveller
[ruby-75c5b89895-rd2lb ruby] * Min threads: 0, max threads: 16
[ruby-75c5b89895-rd2lb ruby] * Environment: development
[ruby-75c5b89895-rd2lb ruby] * Listening on tcp://0.0.0.0:9292
[ruby-75c5b89895-rd2lb ruby] Use Ctrl-C to stop
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At that time Skaffold tool has built the Docker image, tagged it with a specific tag, and deployed application to Minikube cluster. Then it  started to wait for the source file changes.&lt;/p&gt;

&lt;p&gt;The result could be seen here:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;minikube service ruby &lt;span class="nt"&gt;--url&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
Hello Skaffold!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Hot reload
&lt;/h2&gt;

&lt;p&gt;As configured at the &lt;a href="https://github.com/GoogleContainerTools/skaffold/blob/master/examples/ruby/skaffold.yaml#L10-L11"&gt;Skaffold config&lt;/a&gt; - it watches for &lt;code&gt;*.rb&lt;/code&gt; files and reloads all the changes rights into the container sources. The sample Rack application also supports for reloading with help of the &lt;code&gt;rack-unreloader&lt;/code&gt; ruby-gem.&lt;/p&gt;

&lt;p&gt;If we change the &lt;code&gt;app.rb&lt;/code&gt; file to return other string, like&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;App&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"Content-Type"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"text/html"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Hello Skaffold!!!"&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;We will see skaffold sync files into the container:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Syncing 1 files &lt;span class="k"&gt;for &lt;/span&gt;ruby-example:988d9e9e7bf4b44beb62a8aaa7104cdf407a8523db0bdc7fe331600d374ba274
Watching &lt;span class="k"&gt;for &lt;/span&gt;changes...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As the Rack application supports file reloading, it will now produce the different output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;minikube service ruby &lt;span class="nt"&gt;--url&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
Hello Skaffold!!!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Automatic redeploy
&lt;/h2&gt;

&lt;p&gt;If we change others files, such as &lt;code&gt;backend/Dockerfile&lt;/code&gt;, Skaffold will autmatically rebuild the Docker image, and redeploy the application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"gem 'oj'"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; backend/Gemfile
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We'll see full redeploy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Generating tags...
 - ruby-example -&amp;gt; ruby-example:v1.1.0-118-g92f37b200-dirty
Checking cache...
 - ruby-example: Not found. Building
Found &lt;span class="o"&gt;[&lt;/span&gt;minikube] context, using &lt;span class="nb"&gt;local &lt;/span&gt;docker daemon.
Building &lt;span class="o"&gt;[&lt;/span&gt;ruby-example]...
Sending build context to Docker daemon  6.144kB
Step 1/6 : FROM ruby:2.7
 &lt;span class="nt"&gt;---&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; fb53c5f433da
Step 2/6 : WORKDIR /app
 &lt;span class="nt"&gt;---&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Using cache
 &lt;span class="nt"&gt;---&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; 0c2d04fabca0
Step 3/6 : ADD Gemfile&lt;span class="k"&gt;*&lt;/span&gt; ./
 &lt;span class="nt"&gt;---&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Using cache
 &lt;span class="nt"&gt;---&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; 7b4671e7469d
Step 4/6 : RUN bundle &lt;span class="nb"&gt;install&lt;/span&gt;
 &lt;span class="nt"&gt;---&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Using cache
 &lt;span class="nt"&gt;---&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; 0331311c04f3
Step 5/6 : ADD &lt;span class="nb"&gt;.&lt;/span&gt; ./
 &lt;span class="nt"&gt;---&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; 67e2cf138ed3
Step 6/6 : CMD &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"bundle"&lt;/span&gt;,&lt;span class="s2"&gt;"exec"&lt;/span&gt;,&lt;span class="s2"&gt;"puma"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
 &lt;span class="nt"&gt;---&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Running &lt;span class="k"&gt;in &lt;/span&gt;fa04b480dd8f
 &lt;span class="nt"&gt;---&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; 31f2e1f54283
Successfully built 31f2e1f54283
Successfully tagged ruby-example:v1.1.0-118-g92f37b200-dirty
Tags used &lt;span class="k"&gt;in &lt;/span&gt;deployment:
 - ruby-example -&amp;gt; ruby-example:31f2e1f5428397d5d4aa1f2d2328ff859e01d970f304f2f9dbb9f4ba8d0c8968
   &lt;span class="nb"&gt;local &lt;/span&gt;images can&lt;span class="s1"&gt;'t be referenced by digest. They are tagged and referenced by a unique ID instead
Starting deploy...
 - deployment.apps/ruby configured
Watching for changes...
[ruby-75c5b89895-rd2lb ruby] - Gracefully stopping, waiting for requests to finish
[ruby-75c5b89895-rd2lb ruby] === puma shutdown: 2020-01-20 11:49:02 +0000 ===
[ruby-75c5b89895-rd2lb ruby] - Goodbye!
[ruby-5b74868996-ckg24 ruby] Puma starting in single mode...
[ruby-5b74868996-ckg24 ruby] * Version 4.3.1 (ruby 2.7.0-p0), codename: Mysterious Traveller
[ruby-5b74868996-ckg24 ruby] * Min threads: 0, max threads: 16
[ruby-5b74868996-ckg24 ruby] * Environment: development
[ruby-5b74868996-ckg24 ruby] * Listening on tcp://0.0.0.0:9292
[ruby-5b74868996-ckg24 ruby] Use Ctrl-C to stop
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Change the &lt;code&gt;app.rb&lt;/code&gt; then to return json string:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'oj'&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;App&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;json&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;"message"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"hello"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"Content-Type"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"text/html"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;Oj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&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;After the &lt;code&gt;app.rb&lt;/code&gt; file synchronization, the app would return:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;minikube service ruby &lt;span class="nt"&gt;--url&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"message"&lt;/span&gt;:&lt;span class="s2"&gt;"hello"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Features
&lt;/h2&gt;

&lt;p&gt;File sync and automatic &lt;code&gt;rebuild/redeploy&lt;/code&gt; are the core.&lt;/p&gt;

&lt;p&gt;But, Skaffold has many essential features for Kubernetes-native development:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;policy-based image tagging&lt;/li&gt;
&lt;li&gt;resource port-forwarding&lt;/li&gt;
&lt;li&gt;supports multiple profiles&lt;/li&gt;
&lt;li&gt;supports Helm, Kustomize, Docker Compose manifests&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Skaffold is simple and lightweight tool, easy to get up and running for local development! It automates deployments, but for comfortable development I like it to have any other features, like backward file sync.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>kubernetes</category>
      <category>development</category>
      <category>skaffold</category>
    </item>
    <item>
      <title>Uibook—a tool for visual testing of React components with media queries</title>
      <dc:creator>Vitaly Rizo</dc:creator>
      <pubDate>Tue, 18 Jun 2019 17:49:50 +0000</pubDate>
      <link>https://forem.com/amplifr/uibook-a-tool-for-visual-testing-of-react-components-with-media-queries-2a3o</link>
      <guid>https://forem.com/amplifr/uibook-a-tool-for-visual-testing-of-react-components-with-media-queries-2a3o</guid>
      <description>&lt;p&gt;TL;DR: Uibook — a &lt;a href="https://amplifr.com/uikit/#AutopilotPopup:en"&gt;simple tool&lt;/a&gt; for visual testing of React components with real media queries. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ffoiJ5JM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/gpn7w8mnrc7agcbhp3m7.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ffoiJ5JM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/gpn7w8mnrc7agcbhp3m7.gif" alt="" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Hi! My name is Vitalii Rizo, I'm a front-end developer at Amplifr. I'm going to tell you about Uibook and how it can help you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why we did it and what's the point
&lt;/h2&gt;

&lt;p&gt;We have lots of components with plenty of states, and it’s necessary to test both the mobile and desktop versions of an app constantly. It is also easy to break one component while fixing another.&lt;/p&gt;

&lt;p&gt;That's why we decided to create a page where we could quickly test components.&lt;/p&gt;

&lt;p&gt;Uibook allows you to see components in all states and combinations of props quickly. Developers can display the desktop and mobile versions of components on a single page, thanks to the support of media queries. But Uibook isn't just for developers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Designers can look at all states of a component on their device without setting up a local server.&lt;/li&gt;
&lt;li&gt;Managers see that even a popup that seems simple at first glance can contain a bunch of boundary states that developers have to take into account—this helps them better understand the design of the product.&lt;/li&gt;
&lt;li&gt;Editors can check texts in real components using the Live Text Editing mode to make it look flawless.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Uibook in comparison with analogs
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Seys-nUX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/qn5a7hm18kf42cafzkg9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Seys-nUX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/qn5a7hm18kf42cafzkg9.png" alt="" width="800" height="246"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are Storybook, Styleguidist, and other similar solutions exist, but Uibook takes a different approach. I can point to three main differences:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Uibook supports media queries components out-of-box to check components in the mobile state.&lt;/li&gt;
&lt;li&gt;Uibook does not require a separate builder and easily integrates to an existing project.&lt;/li&gt;
&lt;li&gt;I suppose that your Uibook tests will be publicly available to your customers. Any user can find bugs and leave feedback.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We needed a tool for visual testing mainly, not for development, although developing UI components in Uibook is also convenient. Did you need to make global changes to the project? Run through all the Pages to make sure that all the components are displayed correctly.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ZoNV3c80--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/00tore2n7b4xfwa27jz6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ZoNV3c80--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/00tore2n7b4xfwa27jz6.png" alt="" width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Technical implementation
&lt;/h2&gt;

&lt;p&gt;Uibook is a React application. Developer creates a Page—this is a file in particular format with all states of Component. A single state is called Case. Each Case contains props for the Component or JSX if you want to describe a complicated case. You can also define screen width and height for each Case.&lt;/p&gt;

&lt;p&gt;Uibook renders the selected Page on the screen using two controllers: with and without media requests.&lt;/p&gt;

&lt;p&gt;Since it is impossible to emulate media queries with CSS and JavaScript, we decided to render the Component inside an &lt;code&gt;&amp;lt;iframe&amp;gt;&lt;/code&gt;, if the user has specified the width or height of the screen.&lt;/p&gt;

&lt;p&gt;The main Controller optionally puts the Component in a user’s wrapper. It allows the user to select values passed to the wrapper in the top navigation bar. The root controller also adds hotkeys and Live Text Edit mode.&lt;/p&gt;

&lt;p&gt;I didn't want to have separate bundlers for the project and visual testing. In the other case you have to store more files and dependencies. It takes more time to configure, to run, to build, and to deploy. Uibook integrates into the project’s builder as a Webpack plugin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
 &lt;span class="err"&gt;…&lt;/span&gt;
 &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;UibookPlugin&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
   &lt;span class="na"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../controllers/uibook.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;webpack.config.js&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Uibook adds a separate chunk and does not increase the size of the main application. It works using the webpack’s &lt;code&gt;SingleEntryPlugin&lt;/code&gt; or &lt;code&gt;MultiEntryPlugin&lt;/code&gt;. It includes CSS and scripts from the main application taking into account the cache buster. Here's how the plugin gets the list of files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;compilation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;uibook&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
 &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The next step is to generate an HTML file. Uibook doesn’t use extra dependencies on this step, because it is easy to do: take a template, add imports, add it to the output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt; &lt;span class="nx"&gt;compilation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;assets&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;outputPath&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/index.html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="err"&gt;…&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It’s necessary to exclude &lt;code&gt;uibook&lt;/code&gt; chunk if you have &lt;code&gt;HtmlWebpackPlugin&lt;/code&gt;. Uibook will nicely remind you, because DX matters.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--9737EnLd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/cf4gc3ppub5ibom852jj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9737EnLd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/cf4gc3ppub5ibom852jj.png" alt="" width="800" height="239"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Uibook is very simple
&lt;/h2&gt;

&lt;p&gt;There are only three dependencies: React, Webpack, and &lt;code&gt;create-react-class&lt;/code&gt;. It is written in ES5, so it will work even if you don't have a Babel in your project. Also, Uibook has built-in hints if there is something wrong with the configuration file.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ZGMLXkLr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/31qad5ta2qov4gg9akl6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ZGMLXkLr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/31qad5ta2qov4gg9akl6.png" alt="" width="800" height="370"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Uibook is flexible
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--s1JEDxK4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/x92clfvyose762dffe8r.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--s1JEDxK4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/x92clfvyose762dffe8r.gif" alt="" width="800" height="316"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can wrap all the components in your Controller. It might be a wrapper for Redux, Context, or both. Here is an example with a new Context API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nc"&gt;UibookStarter&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
 &lt;span class="na"&gt;wrapper&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
   &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Provider&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
     &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
   &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Context.Provider&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;,
&lt;/span&gt; &lt;span class="na"&gt;values&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="na"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;de&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
   &lt;span class="na"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dark&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;light&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
 &lt;span class="p"&gt;},&lt;/span&gt;
 &lt;span class="err"&gt;…&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Uibook displays the list of user keys and their values in the top navigation menu.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to integrate Uibook into a project
&lt;/h2&gt;

&lt;p&gt;For example, we want to add the Button Component (&lt;code&gt;src/button.js&lt;/code&gt;) to Uibook. We need to install the &lt;code&gt;uibook&lt;/code&gt; package first, then create a Controller file and a Page file. The Controller is used to import all your Uibook tests, and the Page is a set of Cases, or combinations of parameters for the Component.&lt;/p&gt;

&lt;p&gt;Here's how to do it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Let's get started, &lt;code&gt;npm install uibook --save&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;You can use &lt;code&gt;npm init uibook&lt;/code&gt; command here to create example files, or you can do everything manually. You’ll get the following structure:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;your-project
├── uibook
│   ├── button.uibook.js
│   └── uibook-controller.js
├── src
│   └── button.js
├── webpack.config.js
└── package.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Add the plugin in the Webpack configuration file:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;em&gt;webpack.config.js&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;UibookPlugin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;uibook/plugin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="err"&gt;…&lt;/span&gt;
 &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
   &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;UibookPlugin&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
     &lt;span class="na"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../src/uibook-controller.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
   &lt;span class="p"&gt;})&lt;/span&gt;
 &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Let's write the test (or Page) in &lt;code&gt;uibook/button.uibook.js&lt;/code&gt;. If you have taken advantage of a &lt;code&gt;init&lt;/code&gt; command, you have this example already:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;UibookCase&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;uibook/case&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Button&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../src/button.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;PROPS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;UibookCase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;onClick&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ButtonUibook&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="na"&gt;cases&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
     &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;UibookCase&lt;/span&gt; &lt;span class="na"&gt;props&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;PROPS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;isLarge&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
       Large Button
     &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;UibookCase&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;,&lt;/span&gt;
     &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;UibookCase&lt;/span&gt; &lt;span class="na"&gt;props&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;PROPS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;isDisabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
       Disabled Button
     &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;UibookCase&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
 &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;ButtonUibook&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Import and pass this Uibook Page to the Controller:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;UibookStarter&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;uibook/starter&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;ButtonUibook&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./button.uibook&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nc"&gt;UibookStarter&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
 &lt;span class="na"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="na"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ButtonUibook&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Done! You can start the project as usual (for example, &lt;code&gt;npm run start&lt;/code&gt;) and open &lt;code&gt;/uibook&lt;/code&gt; in a browser. You’ll see three Cases with the Button (if you have a component &lt;code&gt;/src/button.js&lt;/code&gt;, of course):&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---XNvYG2Y--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/a3ph9p7shqbmhmybko5o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---XNvYG2Y--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/a3ph9p7shqbmhmybko5o.png" alt="" width="800" height="362"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How does Uibook help us?
&lt;/h2&gt;

&lt;p&gt;We've been &lt;a href="https://amplifr.com/uikit/"&gt;using&lt;/a&gt; Uibook in our workplace for over a year. The front-end developer creates new components through Uibook only, simultaneously creating a test file with boundary props. This is much faster than writing a Controller to see the component in a real web application. Moreover, you can use this test file for further visual testing after any global changes.&lt;/p&gt;

&lt;p&gt;Andrey Sitnik, front-end lead at Evil Martians, stopped worrying about &lt;code&gt;node_modules&lt;/code&gt; updates after their migration to Uibook:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Uibook finally gave us confidence that &lt;code&gt;normalize.css&lt;/code&gt; update will not break anything. Uibook gives me a way to see all components in all states in a row. &lt;code&gt;@media&lt;/code&gt; support helps a lot in this task. We can see all the states on a single page. Developers have fewer worries, and managers see fewer bugs. Everyone is happy.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It also simplifies the whole testing process. When a developer needs to create a React component they create a Uibook page with all possible props. You can start testing UI before internal business logic (smart components, store or sagas) will be written. And you can deploy the component without importing it into the main application.&lt;/p&gt;

&lt;p&gt;Other developers review the component using local or production Uibook. They can click on all the buttons and check that it calls the callback.&lt;/p&gt;

&lt;p&gt;Damir Melnikov, front-end developer at Amplifr, likes how Uibook improves the communication process between designers and editors:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Uibook allows me to create and modify components quickly. I can check new styles, check the mobile version, check the component in different boundary conditions. Besides, Uibook allows you to instantly share your work with the designer, content editors, and other front-end developers.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Alexander Marfitsin, the content-lead in Amplifr, notices how Uibook has the process of interface text writing:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;When writing interface texts, you often work blindly and do not see how the content in the "live" product will look. Uibook solves this problem. You can proofread content both in existing components and in new ones by trying it in a real interface. All text elements are editable so that you can get a complete result—from the title to the smallest label.&lt;/p&gt;

&lt;p&gt;Also, you better understand the product and the importance of good text for the interface with Uibook.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;⌘⌘⌘&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I hope that you decide to try Uibook, and that you can see firsthand how it may enhance your project. If you have any questions, please refer to the &lt;a href="https://github.com/vrizo/uibook/"&gt;detailed instructions&lt;/a&gt; in the Github repository. Or &lt;a href="https://twitter.com/vitaliirizo"&gt;tweet&lt;/a&gt;/&lt;a href="//mailto:kb@kernel-it.ru"&gt;email me&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>webpack</category>
      <category>testing</category>
      <category>react</category>
    </item>
    <item>
      <title>Dockerize the multi-services application for local development</title>
      <dc:creator>Dmitry Salahutdinov</dc:creator>
      <pubDate>Mon, 15 Apr 2019 11:40:13 +0000</pubDate>
      <link>https://forem.com/amplifr/dockerize-the-multi-services-application-for-local-development-2oig</link>
      <guid>https://forem.com/amplifr/dockerize-the-multi-services-application-for-local-development-2oig</guid>
      <description>&lt;p&gt;As nowadays many complex web-applications run on production containerized, we keep developing them in 'old-school' way, installing the &lt;a href="https://www.postgresql.org/" rel="noopener noreferrer"&gt;Postgresql&lt;/a&gt;, &lt;a href="https://redis.io/" rel="noopener noreferrer"&gt;Redis&lt;/a&gt;, &lt;a href="https://www.ruby-lang.org" rel="noopener noreferrer"&gt;Ruby&lt;/a&gt; and other components on the local development machine.&lt;/p&gt;

&lt;p&gt;It is getting harder to &lt;strong&gt;maintain the process of development&lt;/strong&gt;, especially when the system becomes heterogeneous and expands into a large number of services, running with various versions of the dependent components. It becomes especially actual when the dependent component's versions are varied.&lt;/p&gt;

&lt;p&gt;In this article, I am going to review the local development containerization with the example of the &lt;a href="https://amplifr.com" rel="noopener noreferrer"&gt;Amplifr&lt;/a&gt;, the project I work on. With the help of docker-compose and docker networks, it's easy and efficient.&lt;/p&gt;

&lt;p&gt;As all the infrastructure are containerized, and are managed with Kubernetes on production, we will attend the setting up &lt;strong&gt;local development only&lt;/strong&gt;, following the one principle - &lt;strong&gt;the convenience of the development process&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Benefits of the local containerization
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;No need to install all the components, such as database engines, language interpreters on the local machine. It keeps local machine &lt;em&gt;clean&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;natural support of the different environments, g.e. running the services with different versions of Ruby, of Postgresql on the local machine&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Project overview
&lt;/h2&gt;

&lt;p&gt;However Amplifr's backend is running on Rails, the project also has the complicated frontend, serving by the standalone Node.js server and the Logux web-socket server, and other helper-services, written on Node.js, Ruby, and Golang.&lt;/p&gt;

&lt;p&gt;The following picture shows the simplified architecture of the project:&lt;/p&gt;

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

&lt;p&gt;I am going to quickly review some components of the overall system.&lt;/p&gt;

&lt;h3&gt;
  
  
  Backend server
&lt;/h3&gt;

&lt;p&gt;The backend is the classic Rails-application, doing all the business logic and performing many background jobs with Sidekiq.&lt;/p&gt;

&lt;h3&gt;
  
  
  Frontend server
&lt;/h3&gt;

&lt;p&gt;The frontend is the only public HTTP entry-point for the overall application. It serves the frontend assets and proxies other requests to the Rails backend.&lt;br&gt;
The backend is also integrated back with frontend server for sharing some data, &lt;a href="https://dev.to/amplifr/outdated-browser-detection-with-browserslist-10co"&gt;like &lt;code&gt;browsers.json&lt;/code&gt;&lt;/a&gt; file for proper rendering of HTML.&lt;/p&gt;

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

&lt;p&gt;The Logux is the server exposing the web-socket port, holding the bidirectional connection with the client's browsers. To perform the business logic, it has two ways of HTTP integration with the backend. It allows us to keep all the business logic in Rails-backend, and sending back the notifications from backend by hitting Logux with HTTP.&lt;/p&gt;

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

&lt;p&gt;The link shortener is the specific web-service, written with Golang. It aims for shortening a link, expanding them and manages the overall statistics about link expansions.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F31zguz9tbgwbiephm3l8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F31zguz9tbgwbiephm3l8.png" alt="Link shortener server integration with backend" width="751" height="191"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  "Preview" service
&lt;/h3&gt;

&lt;p&gt;The preview is the public service, used from the client browsers to render the OpenGraph representation of any link. It has public http endpoint only.&lt;/p&gt;
&lt;h3&gt;
  
  
  Other components
&lt;/h3&gt;

&lt;p&gt;Shortener - is the standalone service for shorting the url and keeping analytics data about link expansion. It is written with Golang. It has the external public endpoint to expand the shorted links, and internal endpoint to short the links while publication social content within backend's background jobs.&lt;/p&gt;

&lt;p&gt;And some other internal services, such as telegram and facebook bot, which have the backend only integrations.&lt;/p&gt;
&lt;h2&gt;
  
  
  Components dependents
&lt;/h2&gt;

&lt;p&gt;Most of the components are by itself the complex web-services, depending on underlying components, such as Postgres, Redis, and other services low-level system services.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzqhrh4c0k1brx5b3fqk4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzqhrh4c0k1brx5b3fqk4.png" alt="Internals of the backend component and how we are going to dockerize it" width="736" height="251"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Containarization
&lt;/h3&gt;

&lt;p&gt;💡We will containerize each service separately with the &lt;a href="https://docs.docker.com/compose/" rel="noopener noreferrer"&gt;Docker Compose&lt;/a&gt;. It is a tool for defining and running multi-container Docker applications, making it easy to start just by running up all the services together with only one command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker-compose up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;💡To make the services to integrate we will use the &lt;a href="https://docs.docker.com/network/" rel="noopener noreferrer"&gt;docker networks&lt;/a&gt;, that allows any docker containers to communicate with each other. We will use only one &lt;code&gt;internal&lt;/code&gt; docker network for all the components for simplicity. Being more accurate a reader will be able to set up the individual network for every service dependents and for every group of the connectivity.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Dockerize Ruby Backend
&lt;/h3&gt;

&lt;p&gt;Here we have the standard stack: Postgres, Redis, Rails web-server and the Sidekiq background. For all of them, we'll define the services in &lt;code&gt;docker-compose.yaml&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Here are the key points:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;for the Postgres and Redis, we will define the &lt;a href="https://docs.docker.com/storage/volumes/" rel="noopener noreferrer"&gt;persistent volumes&lt;/a&gt; to save the data between the runs&lt;/li&gt;
&lt;li&gt;we are not going to copy the Ruby source code into the container, instead of this - we will mount the Rails application source-code to the &lt;code&gt;/app&lt;/code&gt; folder&lt;/li&gt;
&lt;li&gt;we also will define the persistent storage for the bundle and other stuff to increase the next time starts&lt;/li&gt;
&lt;li&gt;we will define the &lt;code&gt;amplifr_internal&lt;/code&gt; network and add the interacting containers to that network&lt;/li&gt;
&lt;li&gt;the application should be ready to be configured with the environments variables, which we are going to set up in docker-compose file&lt;/li&gt;
&lt;li&gt;we will define the base application service in YAML file and then will use the Anchors and aliases of the YAML syntax not to repeat yourself.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;❗Keep in mind, that this configuration differs from the way of building docker image for production, where all the source code and all the dependency bundles are copied inside the docker image, to let it be all-sufficient and not to have external dependencies!&lt;/p&gt;

&lt;p&gt;Here is the full &lt;a href="https://gist.github.com/dsalahutdinov/2d8975347ec79e94e7674dd0d9d4c518" rel="noopener noreferrer"&gt;gist with all the config&lt;/a&gt;, but let me pay attention to the main points:&lt;/p&gt;

&lt;h3&gt;
  
  
  Describe the base-service to inherit from it
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nl"&gt;&amp;amp;app&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
      &lt;span class="na"&gt;dockerfile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Dockerfile.dev&lt;/span&gt;
      &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;PG_VERSION&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;9.6'&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;amplifr-dev:0.1.0&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.:/app:cached&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;bundle:/bundle&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# environment settings&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;BUNDLE_PATH=/bundle&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;BUNDLE_CONFIG=/app/.bundle/config&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;RAILS_ENV=${RAILS_ENV:-development}&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DATABASE_URL=postgresql://postgres@postgres/amplifr_${RAILS_ENV}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;REDIS_URL=redis://redis:6379/&lt;/span&gt;

      &lt;span class="c1"&gt;# service integrations&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;FRONTEND_URL=https://frontend-server:3001/&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;LOGUX_URL=http://logux-server:31338&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;postgres&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;redis&lt;/span&gt;
    &lt;span class="na"&gt;tmpfs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/tmp&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The base service's container will be build from the &lt;code&gt;Dockerfile.dev&lt;/code&gt; with the arguments - the Postgres version. All other Ruby based images will inherit the base. Here is the service inheritance diagram:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpswyimt56yboaqgpkdux.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpswyimt56yboaqgpkdux.png" alt="Service inheritance" width="686" height="236"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We also define the mapping of the current folder to the container's &lt;code&gt;/app&lt;/code&gt; directory and mount the docker volume for the bundles. It prevents every time dependencies installation.&lt;/p&gt;

&lt;p&gt;We also define two groups of the environments variables:&lt;br&gt;
1) &lt;code&gt;system&lt;/code&gt; variables, such as &lt;code&gt;BUNDLE_PATH&lt;/code&gt;, &lt;code&gt;REDIS_URL&lt;/code&gt; and &lt;code&gt;DATABASE_URL&lt;/code&gt; URLs.&lt;br&gt;
2) dependent services internal url for integration:&lt;br&gt;
&lt;code&gt;FRONTEND_URL&lt;/code&gt; - is the internal endpoint of the frontend server to get the supported browserslist.&lt;br&gt;
&lt;code&gt;LOGUX_URL&lt;/code&gt; - is the internal Logux HTTP endpoint for sending action from Rails-app to Logux.&lt;/p&gt;
&lt;h3&gt;
  
  
  Describe the 'runner'
&lt;/h3&gt;

&lt;p&gt;The runner service is for running maintaining commands, such as rake tasks, or generators in Rails-environment. It is console-oriented service, so we have to go set up &lt;code&gt;stdin_open&lt;/code&gt; and &lt;code&gt;tty&lt;/code&gt; options, which corresponds to the &lt;code&gt;-i&lt;/code&gt; and &lt;code&gt;--t&lt;/code&gt; option of docker and enable bash shell for the container start:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;runner&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;*backend&lt;/span&gt;
    &lt;span class="na"&gt;stdin_open&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;tty&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/bin/bash&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can use it in this way:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker-compose run runner bundle &lt;span class="nb"&gt;exec &lt;/span&gt;rake db:create

&lt;span class="c"&gt;# or run container and any command within the container&lt;/span&gt;
docker-compose run runner
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Compose the server
&lt;/h3&gt;

&lt;p&gt;Define the web-server. The critical point here is that we define additional docker network &lt;code&gt;internal&lt;/code&gt; and adds the web-server to it giving the &lt;code&gt;backend-server&lt;/code&gt; alias to the container host in this network. So the web container will be accessible with the &lt;code&gt;backend-server&lt;/code&gt; network name.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;*app&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bundle exec thin start&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;internal&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;aliases&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;backend-server&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3000:3000'&lt;/span&gt;

&lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;internal&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Compose the Sidekiq
&lt;/h3&gt;

&lt;p&gt;Easy, it just runs the sidekiq and inherits the base service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;sidekiq&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;*app&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sidekiq&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Compose Redis and Postgres
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="na"&gt;postgres&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:9.6&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;postgres:/var/lib/postgresql/data&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="m"&gt;5432&lt;/span&gt;

  &lt;span class="na"&gt;redis&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis:3.2-alpine&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;redis:/data&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="m"&gt;6379&lt;/span&gt;

&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;postgres&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;redis&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The main point here is that we mount the volumes for the container's paths, where the data is stored. It persists the data between runs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dockerfile
&lt;/h3&gt;

&lt;p&gt;We would not dive deep into writing the &lt;code&gt;Dockefile&lt;/code&gt;. You can find it &lt;a href="https://gist.github.com/dsalahutdinov/2d8975347ec79e94e7674dd0d9d4c518" rel="noopener noreferrer"&gt;here&lt;/a&gt;. Just notice, that it inherites from the standard ruby image, some required components such as Postgresql client and some other binaries to build the bundle.&lt;/p&gt;

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

&lt;p&gt;The usage is quite easy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker-compose run runner ./bin/setup &lt;span class="c"&gt;# runs the bin/setup in docker&lt;/span&gt;
docker-compose run runner bundle &lt;span class="nb"&gt;exec &lt;/span&gt;rake db:drop &lt;span class="c"&gt;# runs rake task&lt;/span&gt;
docker-compose up server &lt;span class="c"&gt;# get the web-server running&lt;/span&gt;
docker-compose up &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="c"&gt;# runs all the services (web, sidekiq)&lt;/span&gt;
docker-compose up rails db &lt;span class="c"&gt;# runs the postgres client&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Docker Compose also allows to specify the service dependencies and get the dependent service up if it is needed for the running service, g.e. Sidekiq requires the Redis and Postgres services to work correctly, that is why we define them in the &lt;code&gt;depends_on&lt;/code&gt; section of the service.&lt;/p&gt;

&lt;p&gt;And here is the service dependency diagram, showing how the services run:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5vgvrryqlcr9irnz9xsq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5vgvrryqlcr9irnz9xsq.png" alt="Service startup sequence in case of depencencies" width="371" height="244"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Summary
&lt;/h3&gt;

&lt;p&gt;We've got the Rails-application running locally for the development. It works the same way as the local: persists the database, runs the rake task. Also the commands like &lt;code&gt;rails db&lt;/code&gt;, &lt;code&gt;rails c&lt;/code&gt; works well within a container.&lt;/p&gt;

&lt;p&gt;The main advantage is that we can change the Postgres version or the Ruby version easily by changing one line, then rebuild image and try to run with the new environment.&lt;/p&gt;
&lt;h2&gt;
  
  
  Dockerize Node.js (frontend server)
&lt;/h2&gt;

&lt;p&gt;The primary key points here are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;use the base official &lt;code&gt;node&lt;/code&gt; docker images without any tuning&lt;/li&gt;
&lt;li&gt;add the &lt;code&gt;server&lt;/code&gt; service to the &lt;code&gt;amplifr_internal&lt;/code&gt; network&lt;/li&gt;
&lt;li&gt;define the &lt;code&gt;BACKEND_URL&lt;/code&gt; environment variable to map to the internal docker path of the backend service.&lt;/li&gt;
&lt;li&gt;mount the &lt;code&gt;mode_modules&lt;/code&gt; volume for the Node.js modules install path
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.4'&lt;/span&gt;

&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nl"&gt;&amp;amp;app&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;node:11&lt;/span&gt;
    &lt;span class="na"&gt;working_dir&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/app&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;NODE_ENV=development&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;BACKEND_URL=http://backend-server:3000&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.:/app:cached&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;node_modules:/app/node_modules&lt;/span&gt;

  &lt;span class="na"&gt;runner&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;*app&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/bin/bash&lt;/span&gt;
    &lt;span class="na"&gt;stdin_open&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;tty&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

  &lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;*app&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bash -c "yarn cache clean &amp;amp;&amp;amp; yarn install &amp;amp;&amp;amp; yarn start"&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;amplifr_internal&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;aliases&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;frontend-server&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3001:3001"&lt;/span&gt;

&lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;amplifr_internal&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;external&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;node_modules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

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

&lt;p&gt;The frontend server is now easy to start, by running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker-compose up server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But it needs the backend to start first because frontend service refers to the &lt;code&gt;internal&lt;/code&gt; network, which gets up while starting up the backend.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dockerize the Logux server
&lt;/h3&gt;

&lt;p&gt;In any simple case, Logux server has any databases dependencies and could be configured the same way as frontend. The only one difference, that Logux service has its environment variables, to set up the interaction with integrated services.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker-compose up server &lt;span class="c"&gt;# runs the server&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Dockerizing Golang (link shortener web service)
&lt;/h2&gt;

&lt;p&gt;The main idea is also the same:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;use the set up docker image with &lt;code&gt;Golang&lt;/code&gt;, mount the application source code there and run it with the &lt;code&gt;go run&lt;/code&gt; interpreter.&lt;/li&gt;
&lt;li&gt;share the service with docker networks for integrate with Ruby backend&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Our web-service has the Postgres and Redis dependencies. Lets start describing from the &lt;code&gt;Dockerfile&lt;/code&gt;, overall config sample can be found &lt;a href="https://gist.github.com/dsalahutdinov/47487f319eeaa12cb2026daf9c06f8f0" rel="noopener noreferrer"&gt;here&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; golang:1.11&lt;/span&gt;

&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; MIGRATE_VERSION=4.0.2&lt;/span&gt;

&lt;span class="c"&gt;# install postgres client for local development&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; postgresql-client

&lt;span class="c"&gt;# install dep tool to ensuring dependencies&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;go get &lt;span class="nt"&gt;-u&lt;/span&gt; github.com/golang/dep/cmd/dep

&lt;span class="c"&gt;# install migrate cli for running database migrations&lt;/span&gt;
&lt;span class="k"&gt;ADD&lt;/span&gt;&lt;span class="s"&gt; https://github.com/golang-migrate/migrate/releases/download/v${MIGRATE_VERSION}/migrate.linux-amd64.tar.gz /tmp&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;tar&lt;/span&gt; &lt;span class="nt"&gt;-xzf&lt;/span&gt; /tmp/migrate.linux-amd64.tar.gz &lt;span class="nt"&gt;-C&lt;/span&gt; /usr/local/bin &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;mv&lt;/span&gt; /usr/local/bin/migrate.linux-amd64 /usr/local/bin/migrate

&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; APP ${GOPATH}/src/github.com/evilmartians/ampgs&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; ${APP}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here are a couple of interesting details:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;we install postgres-client for local development image. It simplifies the access to the database, whenever you need it: &lt;code&gt;docker-compose run runner "psql $DATABASE_URL"&lt;/code&gt;. The same we have at the Ruby backend dockerization&lt;/li&gt;
&lt;li&gt;we install the &lt;code&gt;dep&lt;/code&gt; tool to install and ensure all the dependencies: &lt;code&gt;docker-compose run runner dep ensure&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;we install the migration tool to the image, to allow do database migrations right from the docker container: &lt;code&gt;docker-compose run runner "migrate -source file://migrations/ -database ${DATABASE_URL} up"&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;‼️ The most of those tool we do not need for the production environment docker image, because it will contain only compiled binary.&lt;/p&gt;

&lt;p&gt;We will use the same way of dockerizing to Golang service, as the Ruby service:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;extract the base &lt;code&gt;app&lt;/code&gt; service and the special &lt;code&gt;runner&lt;/code&gt; service for run the maintenance tasks&lt;/li&gt;
&lt;li&gt;add the Postgres and Redis dependencies with persistible data volumes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here are the significant parts of the &lt;code&gt;docker-compose.yml&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# base service definition&lt;/span&gt;
  &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nl"&gt;&amp;amp;app&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ampgs:0.3.1-development&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
      &lt;span class="na"&gt;dockerfile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker/development/Dockerfile&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;REDIS_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis://redis:6379/6&lt;/span&gt;
      &lt;span class="na"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres://postgres:postgres@postgres:5432/ampgs&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.:/go/src/github.com/evilmartians/ampgs&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;redis&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;postgres&lt;/span&gt;

  &lt;span class="na"&gt;runner&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;*app&lt;/span&gt;

  &lt;span class="na"&gt;web&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;*app&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;go&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;run&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;ampgs.go"&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;8000:8000'&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;amplifr_internal&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;aliases&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ampgs-server&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Wrap up
&lt;/h1&gt;

&lt;p&gt;Docker-compose is the powerful tool to simplify the managing of the complex services.&lt;br&gt;
Let me review the main principles for local development dockerization in context of using docker compose:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;mount the source code as the folder to the container instead of rebuilding docker&lt;/strong&gt; image with the copy of source code. It helps a lot of time for every local restart&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;use the docker networks to craft the communication between services&lt;/strong&gt;. It helps to test all the services together, but keeps their environments separately.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;services get to know of each other&lt;/strong&gt; by providing the environments variables to the docker container with the &lt;code&gt;docker-compose&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's it. Thanks for reading!&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>node</category>
      <category>go</category>
      <category>docker</category>
    </item>
    <item>
      <title>Monitoring Puma web server with Prometheus and Grafana</title>
      <dc:creator>Dmitry Salahutdinov</dc:creator>
      <pubDate>Tue, 02 Apr 2019 10:48:27 +0000</pubDate>
      <link>https://forem.com/amplifr/monitoring-puma-web-server-with-prometheus-and-grafana-5b5o</link>
      <guid>https://forem.com/amplifr/monitoring-puma-web-server-with-prometheus-and-grafana-5b5o</guid>
      <description>&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;Localizing web application performance problems and response latency could be tricky in the projects with complex infrastructure.&lt;/p&gt;

&lt;p&gt;And having &lt;strong&gt;monitoring&lt;/strong&gt; for all the services is highly crucial.&lt;/p&gt;

&lt;p&gt;Sometimes performance degradation might be induced &lt;em&gt;one step ahead of the main application&lt;/em&gt; because of the lack of the web server capacity. As the most popular web server for running Ruby web applications is &lt;strong&gt;Puma&lt;/strong&gt;, let me explain how to implement and tune up the simple monitoring for it. &lt;/p&gt;

&lt;h1&gt;
  
  
  Puma control application
&lt;/h1&gt;

&lt;p&gt;Puma has a &lt;strong&gt;built-in control application&lt;/strong&gt; for managing web-server and asking it's internal statistics. Control application actives by the following code, called from Puma configuration file &lt;code&gt;puma/config.rb&lt;/code&gt;:&lt;/p&gt;

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

&lt;span class="n"&gt;activate_control_app&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'tcp://127.0.01:9000'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;auth_token: &lt;/span&gt;&lt;span class="s1"&gt;'top_secret'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# or without any params, just activate_control_app&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;For serving it, Puma runs the &lt;a href="https://github.com/puma/puma/blob/master/lib/puma/runner.rb#L47" rel="noopener noreferrer"&gt;separate web-server instance&lt;/a&gt; with the specific &lt;a href="https://github.com/puma/puma/blob/master/lib/puma/app/status.rb" rel="noopener noreferrer"&gt;rack backend application&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This backend returns the worker statistics data in JSON format:&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;# GET /stats?secret=top_secret&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="s2"&gt;"workers"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;"phase"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;"booted_workers"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;"old_workers"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;"worker_status"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="s2"&gt;"pid"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"index"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"phase"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"booted"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"last_checkin"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"2019-03-31T13:04:28Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"last_status"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;"backlog"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"running"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"pool_capacity"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"max_threads"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;
       &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="s2"&gt;"pid"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"index"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"phase"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"booted"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"last_checkin"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"2019-03-31T13:04:28Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"last_status"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;"backlog"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"running"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"pool_capacity"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"max_threads"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;The exact answer schema depends on the Puma configuration: when it is in clustered mode (has more than one worker), the output describes each worker.&lt;br&gt;
If Puma is in non-clustered, the result describes only the single worker with rapid output:&lt;/p&gt;

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

&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="s2"&gt;"backlog"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;"running"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;"pool_capacity"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;"max_threads"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;Anyway, there are meaningful metrics for monitoring purposes, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;max_threads&lt;/code&gt; -  preconfigured maximum number of worker threads&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;running&lt;/code&gt; - the number of running threads (spawned threads) for any Puma worker&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pool_capacity&lt;/code&gt; - the number of requests that the server is capable of taking right now. More details are here.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;backlog&lt;/code&gt; - the number of connections in that worker's "todo" set waiting for a worker thread&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Using them, we can automate the monitoring system, which checks the values periodically any Puma cluster and show metrics in dynamic, such as shown below:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fva2b8h92fv787me9gu6l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fva2b8h92fv787me9gu6l.png" alt="Puma workers pool capacity chart"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Decreasing &lt;code&gt;pool_capacity&lt;/code&gt; means raising the load of the server. It is the starting point of rising the request processing time latency by capacity issues.&lt;/p&gt;

&lt;h2&gt;
  
  
  Yabeda framework
&lt;/h2&gt;

&lt;p&gt;For the Ruby world, we have the extendable framework for collecting and exporting metrics, which is called &lt;a href="https://github.com/yabeda-rb/yabeda" rel="noopener noreferrer"&gt;Yabeda&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It provides a simple DSL for describing the metrics and fetching their values with a simple lambda function. &lt;/p&gt;

&lt;p&gt;For now, &lt;a href="https://evilmartians.com/chronicles/meet-yabeda-modular-framework-for-instrumenting-ruby-applications" rel="noopener noreferrer"&gt;Yabeda framework&lt;/a&gt; provides solutions for monitoring &lt;a href="https://github.com/yabeda-rb/yabeda-rails" rel="noopener noreferrer"&gt;Rails&lt;/a&gt; and &lt;a href="https://github.com/yabeda-rb/yabeda-sidekiq" rel="noopener noreferrer"&gt;Sidekiq&lt;/a&gt; out of the box.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prometheus
&lt;/h2&gt;

&lt;p&gt;Monitoring considers periodically storing the metric values for future analysis. And one of the most popular and suitable solutions for that is &lt;a href="https://prometheus.io/" rel="noopener noreferrer"&gt;Prometheus&lt;/a&gt;. As Prometheus implements the "HTTP pull model," it expects the monitorable subject to expose some endpoint with the metrics value in the specific format.&lt;/p&gt;

&lt;p&gt;Yabeda framework allows exporting metrics with the help of &lt;a href="https://github.com/yabeda-rb/yabeda-prometheus" rel="noopener noreferrer"&gt;Prometheus Exporter&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Yabeda for Puma
&lt;/h2&gt;

&lt;p&gt;Now I am going to introduce one more new monitoring solution of the Yabeda family - &lt;a href="https://github.com/yabeda-rb/yabeda-puma-plugin" rel="noopener noreferrer"&gt;puma monitoring plugin&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It just needs to load the &lt;code&gt;yabeda-puma-plugin&lt;/code&gt; gem and to &lt;a href="https://github.com/yabeda-rb/example-prometheus/blob/master/rails_app/config/puma.rb#L19-L20" rel="noopener noreferrer"&gt;configure Puma web server&lt;/a&gt; with following lines in &lt;code&gt;puma/config.rb&lt;/code&gt; file:&lt;/p&gt;

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

&lt;span class="n"&gt;activate_control_app&lt;/span&gt;
&lt;span class="n"&gt;plugin&lt;/span&gt; &lt;span class="ss"&gt;:yabeda&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;That's it. After the Puma web server start, the plugin will do all the job for collection the metrics.&lt;/p&gt;

&lt;h1&gt;
  
  
  Get things together
&lt;/h1&gt;

&lt;p&gt;Here is the overall architecture of the Puma monitoring solution:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fynq6005bljq7n0zepgln.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fynq6005bljq7n0zepgln.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It gets all the metrics from Puma control application statistics and consolidates them to the Yabeda framework. Values could be exported by Prometheus rack-middleware, serving the &lt;code&gt;/metrics&lt;/code&gt; path of the web application and providing metrics values in prometheus-friendly format. Here is the sample response of metrics endpoint for Puma, configured with two workers:&lt;/p&gt;

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

GET /metrics

puma_backlog{index="0"} 0
puma_backlog{index="1"} 0
puma_running{index="0"} 5
puma_running{index="1"} 5
puma_pool_capacity{index="0"} 1
puma_pool_capacity{index="1"} 5
puma_max_threads{index="0"} 5
puma_max_threads{index="1"} 5
puma_workers 2
puma_booted_workers 2
puma_old_workers 0


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

&lt;/div&gt;
&lt;h2&gt;
  
  
  Visualization
&lt;/h2&gt;

&lt;p&gt;Depending on your needs, the data could be visualized in many ways; here is the example of basic summarized metrics values:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbvsvyvdmjbdi4nutsaqv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbvsvyvdmjbdi4nutsaqv.png" alt="Basic summarized values values visualization in Grafana"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This diagram shows the overall metrics values for all the Puma workers. Also, indicators could be displayed separately for all the workers, or all the Puma cluster instances.&lt;/p&gt;
&lt;h2&gt;
  
  
  "Application busy" metric
&lt;/h2&gt;

&lt;p&gt;Looking at all the raw Puma metrics might be not visually comfortable to make some quick overview of the system in general. More suitable way if to calculate the composite metric, describing the overall workload of the web-server in percentage. Let call it &lt;strong&gt;"Application busy"&lt;/strong&gt; or just &lt;strong&gt;busy-metric&lt;/strong&gt;. Formula evaluates the percentage of overall workload:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

(1 - pool_capacity / max_threads) * 100


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

&lt;/div&gt;

&lt;p&gt;It turns out to have the only chart instead of several:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffxih9ljc3ou6srgv5ikv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffxih9ljc3ou6srgv5ikv.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;busy-metric&lt;/strong&gt; looks to be more informative to overview the health of the system. It shows the actual workload of overall Puma cluster in a more friendly way. When busy-metrics sticks up, it means that the application is under high load, and it probably needs to tune up the Puma web server. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Busy-metric&lt;/strong&gt; allows to determine the problem state easily,  but for specific incident investigation, raw metrics might be more helpful and advisable.&lt;/p&gt;

&lt;h1&gt;
  
  
  Metrics playground
&lt;/h1&gt;

&lt;p&gt;Yabeda framework supply the example project with all the monitoring infrastructure set up for &lt;a href="https://github.com/yabeda-rb/example-prometheus" rel="noopener noreferrer"&gt;monitoring the Sidekiq, Rails, and Puma&lt;/a&gt;. It is easy to set it up with docker-compose.&lt;/p&gt;

&lt;h1&gt;
  
  
  Wrapup
&lt;/h1&gt;

&lt;p&gt;Setting up the monitoring infrastructure makes to build more stable and maintainable software, and sleep calmly at night. &lt;br&gt;
Monitoring is made easy with &lt;a href="https://github.com/yabeda-rb/yabeda" rel="noopener noreferrer"&gt;Yabeda&lt;/a&gt; framework.&lt;/p&gt;

&lt;p&gt;Check out the &lt;a href="https://github.com/yabeda-rb/yabeda-puma-plugin" rel="noopener noreferrer"&gt;yabeda-puma-plugin&lt;/a&gt; for getting ready to monitor Puma!&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>monitoring</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Outdated browser detection with Browserslist</title>
      <dc:creator>Dmitry Salahutdinov</dc:creator>
      <pubDate>Tue, 20 Nov 2018 19:43:22 +0000</pubDate>
      <link>https://forem.com/amplifr/outdated-browser-detection-with-browserslist-10co</link>
      <guid>https://forem.com/amplifr/outdated-browser-detection-with-browserslist-10co</guid>
      <description>&lt;p&gt;The standard way to configure target browsers with Node.js is &lt;a href="https://github.com/browserslist/browserslist" rel="noopener noreferrer"&gt;Browserslist&lt;/a&gt;. It is possible to add the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"browserslist"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"last 2 version"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"not dead"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to the &lt;code&gt;package.json&lt;/code&gt; or the &lt;code&gt;.browserslistrc&lt;/code&gt; config file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Browsers that we support
last 2 version
not dead
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Those two similar examples mean that the target browsers are two last version and the browser is not dead.&lt;/p&gt;

&lt;p&gt;This config is used by many front-end tools, such as &lt;a href="https://github.com/postcss/autoprefixer" rel="noopener noreferrer"&gt;Autoprefixer&lt;/a&gt;, &lt;a href="https://github.com/babel/babel/tree/master/packages/babel-preset-env" rel="noopener noreferrer"&gt;Babel&lt;/a&gt; and many others.&lt;/p&gt;

&lt;p&gt;But in this article I am going to write about the &lt;a href="https://github.com/browserslist/browserslist-useragent" rel="noopener noreferrer"&gt;Browserslist Useragent&lt;/a&gt; frontend tool for finding if a given user agent string satisfies a &lt;code&gt;Browserslist&lt;/code&gt; browsers:&lt;/p&gt;

&lt;p&gt;Install the &lt;code&gt;browserslist-useragent&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;browserslist-useragent
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and you can determine by &lt;code&gt;User-Agent&lt;/code&gt; string if you browser matches:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;matchesUA&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;browserslist-useragent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;matchesUA&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userAgentString&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// with browserslist config inferred&lt;/span&gt;
&lt;span class="nf"&gt;matchesUA&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Mozilla/5.0 (Windows NT 10.0; rv:54.0) Gecko/20100101 Firefox/54.0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;//returns boolean&lt;/span&gt;

&lt;span class="c1"&gt;// with explicit browserslist&lt;/span&gt;
&lt;span class="nf"&gt;matchesUA&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Mozilla/5.0 (Windows NT 10.0; rv:54.0) Gecko/20100101 Firefox/54.0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;browsers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Firefox &amp;gt; 53&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]})&lt;/span&gt;
&lt;span class="c1"&gt;// returns true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Imaging we have the &lt;code&gt;.browserslistrc&lt;/code&gt; config file 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;last 2 versions
not IE 11
not ExplorerMobile 11
not last 1 OperaMini version
not OperaMobile 12.1
not dead
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can get the array of detailed browsers rules with the help of browserslist:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;browserslist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;browserslist&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./browsers.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;browserslist&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the sample above it will produce the json file with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s2"&gt;"and_chr 67"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s2"&gt;"and_ff 60"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s2"&gt;"and_qq 1.2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s2"&gt;"and_uc 11.8"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s2"&gt;"android 67"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s2"&gt;"android 4.4.3-4.4.4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s2"&gt;"baidu 7.12"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s2"&gt;"chrome 69"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s2"&gt;"chrome 68"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s2"&gt;"edge 17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s2"&gt;"edge 16"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s2"&gt;"firefox 62"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s2"&gt;"firefox 61"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s2"&gt;"ios_saf 11.3-11.4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s2"&gt;"ios_saf 11.0-11.2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s2"&gt;"op_mob 46"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s2"&gt;"opera 55"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s2"&gt;"opera 54"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s2"&gt;"safari 11.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s2"&gt;"safari 11"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s2"&gt;"samsung 7.2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s2"&gt;"samsung 6.2"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the way to determine the browsers matchings with Node.js.&lt;/p&gt;

&lt;p&gt;Why do we need to check the browsers version on both sides: backend and frontend?&lt;br&gt;
In the case of your modern javascript frontend would not be loaded on the old browser - we can still use the backend rendering to write some HTML notifying user about the issue:&lt;/p&gt;

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

&lt;p&gt;❗This HTML block would work in any browser no matter how old it is.&lt;/p&gt;

&lt;p&gt;And if your backend is written with Ruby - use can still use the port of the original tool to the Ruby - &lt;a href="https://github.com/browserslist/browserslist-useragent-ruby" rel="noopener noreferrer"&gt;browserslist-useragent gem&lt;/a&gt;. It works the same way its Node.js version - recognises the family and the version from the &lt;code&gt;User-Agent&lt;/code&gt; header string and matches it with the &lt;code&gt;browserslist&lt;/code&gt;-rules produced by the &lt;code&gt;Browserslists&lt;/code&gt; tool.&lt;/p&gt;
&lt;h1&gt;
  
  
  Single project
&lt;/h1&gt;

&lt;p&gt;The usage is straightforward - it just needs you to generate the &lt;code&gt;browsers.json&lt;/code&gt; file before.&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;ApplicationController&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;supported_browser?&lt;/span&gt;
    &lt;span class="vi"&gt;@browsers&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="no"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;root&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="s2"&gt;"browsers.json"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;matcher&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;BrowserslistUseragent&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Match&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="vi"&gt;@browsers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;user_agent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;matcher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;browser?&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;matcher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;version?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;allow_higher: &lt;/span&gt;&lt;span class="kp"&gt;true&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;helper_method&lt;/span&gt; &lt;span class="ss"&gt;:supported_browser?&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then add this code to you Rails-application layout template:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight haml"&gt;&lt;code&gt;&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;supported_browser?&lt;/span&gt;
  &lt;span class="nc"&gt;.div&lt;/span&gt; 
div( style: "position: fixed; bottom: 0; right: 0; padding: 8px 10px; background: #e9502f; color: white; width: 100%; z-index: 10; text-align: center;" )
    &lt;span class="nc"&gt;.div&lt;/span&gt;
      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'unsupported_browser'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;❗This old-fashioned style is deliberately chosen: 'style'-attributes will work mostly everywhere!&lt;/p&gt;

&lt;p&gt;Here it is. It will work well for the Rails projects where all the frontend and backend live together as one &lt;a href="https://evilmartians.com/chronicles/evil-front-part-1" rel="noopener noreferrer"&gt;solid project&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Separated frontend and backend projects
&lt;/h2&gt;

&lt;p&gt;If you have separated projects for Ruby backend and Node.js frontend, you will prefer to get browsers.json over HTTP. You will need to do the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;serve the &lt;code&gt;/browsers.json&lt;/code&gt; path to render the browserslist output by putting it to the &lt;code&gt;public&lt;/code&gt; folder:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;public&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;browsers.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;browserslist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;..&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}))&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;get in over HTTP in the ruby-backend code:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;browsers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Faraday&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'http://frontend-domain.local/browsers.json'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;matcher&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;BrowserslistUseragent&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Match&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="n"&gt;browsers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;user_agent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;matcher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;browser?&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;matcher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;version?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;allow_higher: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or use the &lt;a href="https://github.com/plataformatec/faraday-http-cache" rel="noopener noreferrer"&gt;faraday-http-cache&lt;/a&gt; to cache the results of the http request. It will force to make one request per the Rails application instance only:&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;# caches http response locally with etag&lt;/span&gt;
&lt;span class="n"&gt;http_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Faraday&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&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;builder&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt; &lt;span class="no"&gt;Faraday&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;HttpCache&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;store: &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;cache&lt;/span&gt;
  &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;adapter&lt;/span&gt; &lt;span class="no"&gt;Faraday&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;default_adapter&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;browsers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;http_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'http://frontend-domain.local/browsers.json'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. This solution will use one &lt;code&gt;browserslist.rc&lt;/code&gt; config in the frontend repository, which will automatically be shared over the backend.&lt;/p&gt;

&lt;p&gt;More details abort the &lt;code&gt;browserslist_useragent&lt;/code&gt; gem you will find &lt;a href="https://github.com/browserslist/browserslist-useragent-ruby" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Thanks for reading!&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>node</category>
      <category>browserdetection</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Purify code with data integrity. Part 1: defaults and not-nulls</title>
      <dc:creator>Dmitry Salahutdinov</dc:creator>
      <pubDate>Mon, 19 Nov 2018 18:06:47 +0000</pubDate>
      <link>https://forem.com/amplifr/purify-code-with-data-integrity-part-1-defaults-and-not-nulls-492p</link>
      <guid>https://forem.com/amplifr/purify-code-with-data-integrity-part-1-defaults-and-not-nulls-492p</guid>
      <description>&lt;p&gt;Working with the data is the crucial point of any application. &lt;strong&gt;ActiveRecord&lt;/strong&gt; makes this process easier for Rails developer. It allows managing the data without getting deep into the underlying database. Sometimes it causes some code issues we would not have if pay more attention to the database structuring and maintaining data integrity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Data integrity?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Data_integrity" rel="noopener noreferrer"&gt;Data integrity&lt;/a&gt; is the process of maintaining the accuracy and consistency of the data.&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;ActiveRecord&lt;/code&gt; context I mean, that &lt;code&gt;data integrity&lt;/code&gt; - is the set of the principles for &lt;em&gt;keeping an underlying database in accuracy state&lt;/em&gt; by paying more attention to its schema design.&lt;/p&gt;

&lt;p&gt;In this article, I am going to review some well-known &lt;strong&gt;data integrity techniques&lt;/strong&gt; which are very helpful in improving the codebase but sometimes are undervalued for some reasons. We will see how easy they are to integrate and how they can help to write more clear code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Default values
&lt;/h2&gt;

&lt;p&gt;Have you seen the code 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;# Example 1&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;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;state: &lt;/span&gt;&lt;span class="s1"&gt;'new'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Example 2&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="n"&gt;validates&lt;/span&gt; &lt;span class="ss"&gt;:state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;presence: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;before_validation&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;state&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="s1"&gt;'new'&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;There is the same problem in both examples: the lack of default value preset. It causes for writing initialization "by hand" in a non-centralised way using one of the following options:&lt;/p&gt;

&lt;p&gt;1) 👎&lt;strong&gt;to set the value "in place"&lt;/strong&gt; (example one). This solution is fast and dirty. The main disadvantage is implicitness of the "correct" default value: someone who is not familiar with the project &lt;strong&gt;would not know the proposal default for setting up&lt;/strong&gt; in the future or &lt;strong&gt;expect the value should already present&lt;/strong&gt;. It may cause the errors.&lt;/p&gt;

&lt;p&gt;The second point is that such "code snippets" &lt;strong&gt;spread all over the codebase&lt;/strong&gt; and make it hard to maintain because of duplication and decentralization.&lt;/p&gt;

&lt;p&gt;2) 👎&lt;strong&gt;to set the default values in the "pre-save" model callback&lt;/strong&gt; &lt;br&gt;
 (example two from the top).But here we also have implicitness. Where should I find the correct initial value &lt;code&gt;before_save&lt;/code&gt;, &lt;code&gt;before_commit&lt;/code&gt;, &lt;code&gt;before_validation&lt;/code&gt;? The answer depends on the imagination of the author 😊.&lt;/p&gt;

&lt;p&gt;The second problem is that the default value is not initialized in the new model (example two), and even we set the default values before saving, we still have it uninitialized in the new model object:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;order&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="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;state&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;
&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;
&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;state&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'new'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;👍The best option is to preset the default value in the database schema and let ActiveRecord work well by design:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;create_table&lt;/span&gt; &lt;span class="ss"&gt;:orders&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="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;integer&lt;/span&gt; &lt;span class="ss"&gt;:state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="ss"&gt;:'new'&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# default value work by-default :)&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="nf"&gt;state&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'new'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It allows to have a new object with the column set by default. ActiveRecord will read the metadata of your table and preset the default values for any new model of the &lt;code&gt;User&lt;/code&gt; type.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting up the database
&lt;/h3&gt;

&lt;p&gt;The simple case is to set the default values while creating the new table. It is easy and non-blocking operation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;create_table&lt;/span&gt; &lt;span class="ss"&gt;:order&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="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;integer&lt;/span&gt; &lt;span class="ss"&gt;:state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;default: &lt;/span&gt;&lt;span class="s1"&gt;'new'&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Otherwise adding a column with default to the existing table is more complicated, because it will enforce setting the default value for all the rows. It will lock read/write operation for a while in the high-loaded databases.&lt;/p&gt;

&lt;p&gt;If you are lucky to be on the edge and run you application on Postgres 11 - things become much simpler. PostgreSQL 11 does not touch the existing row, but &lt;a href="https://leopard.in.ua/2016/09/20/safe-and-unsafe-operations-postgresql#.W_Oy4nozZbW" rel="noopener noreferrer"&gt;updates them 'lazily'&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you are not on Postgres 11, the safe way to set up default value constraint consists of two simple steps:&lt;br&gt;
1) creating the nullable column without setting default value&lt;br&gt;
2) changing the default value of the column&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;  &lt;span class="n"&gt;add_column&lt;/span&gt; &lt;span class="ss"&gt;:orders&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:state&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_default&lt;/span&gt; &lt;span class="ss"&gt;:orders&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'new'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that all new rows will have the default values and active record will use those metadata to automatically preset the default. All the existing rows in data base will still have the &lt;code&gt;NULL&lt;/code&gt; value and it is needed to fill them "by hands" to start to prettify the ruby code. Then you can rely on the presence of the column for every row.&lt;/p&gt;

&lt;p&gt;❗The main point is to maintain compatibility: change the database presets first and ship it. Then cut off the default value initialization out of Ruby-code, when you don't have NULL-values in the column anymore.&lt;/p&gt;

&lt;h2&gt;
  
  
  Not-Null Constraints
&lt;/h2&gt;

&lt;p&gt;Have you ever faced code 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="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ImageUploadService&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;
    &lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upload_tries_count&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upload_tries_count&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="k"&gt;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;The issue is similar to the lack of default value. When a column is nullable - web couldn't be sure that there is no &lt;code&gt;nil&lt;/code&gt; value. It enforces to write &lt;code&gt;nil&lt;/code&gt;-checking logic every time we have to use the column.&lt;/p&gt;

&lt;p&gt;If we guarantee that column does not have the &lt;code&gt;nil&lt;/code&gt; value, the code will become much simpler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upload_tries_count&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And here is the solution:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A not-null constraint specifies merely that a column must not assume the null value.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Create a new table and with not-null column
&lt;/h3&gt;

&lt;p&gt;Set the &lt;code&gt;default&lt;/code&gt; options and the column will have the value set by default:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;create_table&lt;/span&gt; &lt;span class="ss"&gt;:user&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="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;integer&lt;/span&gt; &lt;span class="ss"&gt;:points_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;null: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;default: &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Adding not-null column to existing table
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://medium.com/klaxit-techblog/zero-downtime-migrations-with-activerecord-47528abe5136" rel="noopener noreferrer"&gt;This article&lt;/a&gt; describes in detail the safe way to add the new column with default, but here are the summary steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fill all the null-values in the database with some value&lt;/li&gt;
&lt;li&gt;Set column not-null (by migration)&lt;/li&gt;
&lt;li&gt;Cut off the "in-place" model initialization code&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;❗The better way to fill values of the overloaded table is to update with batches (every batch in its transaction) and to stretch in time.&lt;/p&gt;

&lt;p&gt;The standard way to automate things in Ruby world is &lt;code&gt;Rake&lt;/code&gt;-tasks, and here is the sample of rake-task to update users table users with batches:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;desc&lt;/span&gt; &lt;span class="s1"&gt;'Fill user points count'&lt;/span&gt;
&lt;span class="n"&gt;namespace&lt;/span&gt; &lt;span class="ss"&gt;:migrations&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="ss"&gt;fill_user_points_count: :environment&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;total_updated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="kp"&gt;loop&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;updated_rows&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;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;points_count: &lt;/span&gt;&lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10_000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update_all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;points_count: &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_i&lt;/span&gt;
      &lt;span class="k"&gt;break&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;updated_rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;zero?&lt;/span&gt;

      &lt;span class="n"&gt;total_updated&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;updated_rows&lt;/span&gt;
      &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"updated &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;total_updated&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; users"&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;I recommend using rake-task instead of updating columns in migration, because of it &lt;br&gt;
gives &lt;strong&gt;more control to run it on production&lt;/strong&gt; (you can interrupt process at any time if something goes wrong). And here is some tricky migration to automate setting the default value for staging environment and the other developer machine:&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;FillOrderSourceAndSetNotNull&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;5.0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="n"&gt;disable_ddl_transaction!&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;up&lt;/span&gt;
    &lt;span class="k"&gt;if&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;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;production?&lt;/span&gt;
      &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Run bundle exec migrations:fill_user_points_count in production"&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="no"&gt;Rake&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'migrations:fill_user_points_count'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;invoke&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 migration disables transactional mode for the migration and updates users with batches of 10 000 rows. It allows to not to have the large table lock if the table updates in one transaction.&lt;/p&gt;

&lt;p&gt;After this step - we can easily set table not-null:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;change_column_null&lt;/span&gt; &lt;span class="ss"&gt;:users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:points_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and deploy changes to production.&lt;br&gt;
The last step is to cut out nil-checking code 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;user.points_count ||= 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Wrap up
&lt;/h2&gt;

&lt;p&gt;Following this two simple rules: 'set the default value' and 'set column not null' are very useful to write more clear code and avoid many of the problems.&lt;/p&gt;

&lt;p&gt;Thanks for reading! See you at the next topic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Additional links:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.postgresql.org/docs/10/ddl-constraints.html" rel="noopener noreferrer"&gt;Many details of PorstreSQL constraints in official domentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://leopard.in.ua/2016/09/20/safe-and-unsafe-operations-postgresql#.W-2GDzkSw0M" rel="noopener noreferrer"&gt;Safe and unsafe operations for high volume PostgreSQL&lt;/a&gt; by Alexey Vasiliev&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.bignerdranch.com/blog/coding-rails-with-data-integrity/" rel="noopener noreferrer"&gt;Coding Rails with Data Integrity&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/klaxit-techblog/zero-downtime-migrations-with-activerecord-47528abe5136" rel="noopener noreferrer"&gt;Zero downtime migrations with ActiveRecord&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>rails</category>
      <category>ruby</category>
      <category>database</category>
      <category>dataintegrity</category>
    </item>
    <item>
      <title>Concerns testing with anonymous controller</title>
      <dc:creator>Dmitry Salahutdinov</dc:creator>
      <pubDate>Wed, 10 Oct 2018 08:41:48 +0000</pubDate>
      <link>https://forem.com/amplifr/testing-concerns-in-anonymous-controller-2e71</link>
      <guid>https://forem.com/amplifr/testing-concerns-in-anonymous-controller-2e71</guid>
      <description>&lt;p&gt;Once you have a controller concern 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="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;BillingErrorRescuer&lt;/span&gt;
  &lt;span class="kp"&gt;extend&lt;/span&gt; &lt;span class="no"&gt;ActiveSupport&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Concern&lt;/span&gt;

  &lt;span class="n"&gt;included&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;rescue_from&lt;/span&gt; &lt;span class="no"&gt;Billing&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;LimitExceeded&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;with: :rescue_billing_limits&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;rescue_billing_limits&lt;/span&gt;
      &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;status: &lt;/span&gt;&lt;span class="mi"&gt;402&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;json: &lt;/span&gt;&lt;span class="no"&gt;Billing&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;PlanPresenter&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="vi"&gt;@account&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_json&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;The idea is to test is separately in anonymous controller:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'spec_helper'&lt;/span&gt;

&lt;span class="n"&gt;describe&lt;/span&gt; &lt;span class="no"&gt;BillingErrorRescuer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;type: :controller&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;controller&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;BillingErrorRescuer&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;action&lt;/span&gt;
      &lt;span class="vi"&gt;@account&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;FactoryBot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt; &lt;span class="ss"&gt;:account&lt;/span&gt;
      &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="no"&gt;Billing&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;LimitExceeded&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;before&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;draw&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;get&lt;/span&gt; &lt;span class="ss"&gt;:action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;to: &lt;/span&gt;&lt;span class="s2"&gt;"anonymous#action"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;get&lt;/span&gt; &lt;span class="ss"&gt;:action&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:account&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="ss"&gt;:account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;state: :trial&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s1"&gt;'renders json with billing data'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;match_json_schema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'controllers/billing_error_rescuer'&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;What does this test do?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Enables testing controller features in rspec&lt;/li&gt;
&lt;li&gt;Defines anonymous controller to include concern extension&lt;/li&gt;
&lt;li&gt;Draw the route for the action&lt;/li&gt;
&lt;li&gt;Calls the action with the default controller pipeline&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Thats it!&lt;br&gt;
Thanks &lt;code&gt;rspec-rails&lt;/code&gt; gem, that we have almost everything out of the box!&lt;/p&gt;

</description>
      <category>rails</category>
      <category>ruby</category>
      <category>testing</category>
      <category>rspec</category>
    </item>
    <item>
      <title>Stressless pull-request watching with Google Mail</title>
      <dc:creator>Dmitry Salahutdinov</dc:creator>
      <pubDate>Tue, 02 Oct 2018 17:12:16 +0000</pubDate>
      <link>https://forem.com/amplifr/stressless-pull-request-watching-with-google-mail-2ob9</link>
      <guid>https://forem.com/amplifr/stressless-pull-request-watching-with-google-mail-2ob9</guid>
      <description>&lt;p&gt;Let's share the experience of watching repositories, doing review and reacting to the messages.&lt;/p&gt;

&lt;p&gt;I am going to review some common tips of organising pull-request watching with Google Mail, that work for me.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why not Slack
&lt;/h2&gt;

&lt;p&gt;Slack is integrated merely with GitHub but has no grouping, no priorities. Messages go in a timeline feed, so comments of different pull-requests alternate and intermix.&lt;/p&gt;

&lt;h2&gt;
  
  
  Google Mail benefits
&lt;/h2&gt;

&lt;p&gt;Google Mail is very smart and helpful in organising email processing and have many features to be productive. We are going review some of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Messages chains
&lt;/h2&gt;

&lt;p&gt;Google Mail groups all the incoming messages into the chains and puts all the GitHub’s comments of one pull-request in one. No matter how many unread messages of one pull-request you have - they all appear as one unread chain (with the number of incomes).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fpa60gef46p0m7fqhv4vq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fpa60gef46p0m7fqhv4vq.png" alt="Google Mail message chains with the amount of messages count"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;❗ Chaining breaks after renaming pull-request, and all the new comments with getting in the new group. That is why do not recommend to name pull-request correctly for the first time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Labels
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://support.google.com/mail/answer/118708?hl=en&amp;amp;ref_topic=3394656" rel="noopener noreferrer"&gt;Labels&lt;/a&gt; allow organising all the emails in groups. They work like folders, but you can add multiple labels to a message. We will use them to specify the message types, e.g. &lt;em&gt;“For review”&lt;/em&gt;, &lt;em&gt;“My team”&lt;/em&gt;, &lt;em&gt;“OSS”&lt;/em&gt; and so on.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fk3imaqj2f9tedl5ohrgd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fk3imaqj2f9tedl5ohrgd.png" alt="Labelling Google Mail messages"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;❗ Labels are flexible, and you could reconfigure them at any time when you will need to reorganise your flow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Filters
&lt;/h2&gt;

&lt;p&gt;Google Mail uses &lt;a href="https://support.google.com/mail/answer/6579?hl=en&amp;amp;ref_topic=3394656" rel="noopener noreferrer"&gt;customisable rules&lt;/a&gt; to preprocess incoming messages. This is the excellent way to classify emails and apply the source-specific label to them.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F9v4yhewv7mujwg753xen.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F9v4yhewv7mujwg753xen.png" alt="Filters settings page"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Prioritisation
&lt;/h2&gt;

&lt;p&gt;Simple "prioritisation" could be maid manual by arranging labels decreasing the urgency of the messages you have to react.&lt;/p&gt;

&lt;h2&gt;
  
  
  Snoozing
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://support.google.com/mail/answer/7622010?co=GENIE.Platform%3DDesktop&amp;amp;hl=en" rel="noopener noreferrer"&gt;Snoozing&lt;/a&gt; is the new feature. It is handy in the context of pull-requests watching because it allows postponing the incoming mail immediately when you are not able to react at the moment.&lt;/p&gt;

&lt;p&gt;Now we are going to tune up those features to improve the organisation of watching and reviewing pull-requests process.&lt;/p&gt;

&lt;h1&gt;
  
  
  Google Mail features in action
&lt;/h1&gt;

&lt;h2&gt;
  
  
  1. Labels
&lt;/h2&gt;

&lt;p&gt;First of all, we have to think about repositories you are watching and the urgency of incoming data. Here is my sample. You could use it to start and make it more proper to your case:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fyhizw7sew036lf61dmm8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fyhizw7sew036lf61dmm8.png" alt="Basic labels for watching pull-requests"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Review&lt;/strong&gt; - holds the pull-requests from where someone is asking my review with the GitHub's "Reviewers" tool.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Backend&lt;/strong&gt; - is used to put messages from the backend team. Here might be some pull-requests which are in work now (WIP) and probably no review has been requested yet. As I am in the back-end team, it is essential to keep track of events of my team.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Frontend&lt;/strong&gt; labels used to mark frontend team's pull-requests. It is less important to watch those pull-requests but lets me observe what is happening in the frontend part of Amplifr.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Team&lt;/strong&gt;: here are all the pull-request of the all Amplifr working project repositories. Most of the are auxiliary and don't change too often.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Used gems&lt;/strong&gt;. At the backend projects, written with Ruby on Rails, we use many Ruby gems. And it is handy to follow all the updates, that is why I watch some repositories like &lt;a href="https://github.com/sferik/twitter" rel="noopener noreferrer"&gt;twitter gem&lt;/a&gt;.&lt;br&gt;
But this updates are not urgent and could be viewed at the free time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OSS&lt;/strong&gt; - this label is for following pull-requests of the open source projects I contribute to or like to track updates.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GITHUB&lt;/strong&gt; - I use this label to mark all the messages from GitHub to overview the total count.&lt;/p&gt;
&lt;h2&gt;
  
  
  Filters
&lt;/h2&gt;

&lt;p&gt;These filters work for me:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Matches: from:(notifications@github.com)
Do this: Apply label "GITHUB", Never send it to Spam

Matches: from:(noreply@github.com) subject:(Github Subscribed)
Do this: Mark as read, Delete it

Matches: from:(notifications@github.com) to:(review_requested@noreply.github.com)
Do this: Apply label "1) Review", Mark it as important

Matches: to:(evilmartians/amplifr &amp;lt;amplifr@noreply.github.com&amp;gt;)
Do this: Apply label "2) Backend", Mark it as important

Matches: to:(evilmartians/amplifr-front &amp;lt;amplifr@noreply.github.com&amp;gt;)
Do this: Apply label "3) Frontend"

Matches: (to:(gazay/gon) OR to:(sferik/twitter) OR to:(philnash/bitly) OR :to:(wilddima/logux_rails)
Do this: Apply label "5) Used gems"

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

&lt;/div&gt;



&lt;p&gt;❗ &lt;a href="https://gist.github.com/dsalahutdinov/d0d33ef0aaedea4c54071665572d1bd5#file-filters-xml" rel="noopener noreferrer"&gt;You could download the base filters from my Gist&lt;/a&gt;, import them into Google Mail, and start tuning them for your flow.&lt;/p&gt;

&lt;p&gt;After that, we can see all the messages grouped by the urgency of them. A quick view of the label's table let you see how many tasks are pending.&lt;/p&gt;

&lt;h2&gt;
  
  
  Google Mail Checker
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://jasonsavard.com/ru-RU/Checker-Plus-for-Gmail" rel="noopener noreferrer"&gt;"GMail Checker"&lt;/a&gt; is the excellent browser plugin for checking the mail and for getting notified about new messages.&lt;/p&gt;

&lt;p&gt;The main features, I use, are:&lt;/p&gt;

&lt;p&gt;1) "Do not disturb" for the full-screen mode.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F3pg92niya9ylqz9zg2ej.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F3pg92niya9ylqz9zg2ej.png" alt="DnD on full-screen"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This option is beneficial not to lose concentration when you are coding in the VIM or browsing something in full screen.&lt;/p&gt;

&lt;p&gt;2) Notify for the specified labels email:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F2nbq00crnzhdhdvnxr08.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F2nbq00crnzhdhdvnxr08.png" alt="Notify the most critical label's email"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This feature lets me react fast for the most crucial messages.&lt;/p&gt;

&lt;p&gt;The mail checker also has many features, which would be very useful to improve usage of the mail.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mental setup
&lt;/h2&gt;

&lt;p&gt;When automation works well, you only need to view comments starting from to most urgent and react to them. And here is the most critical part for human factor come out.&lt;/p&gt;

&lt;p&gt;Imaging the cause, you open the new email, read the comments, go to the GitHub, and then you look aside and switch to other activity, forgetting about this message. In this case, you miss the sight because you have the letter read in Google Mail but without no reaction.&lt;/p&gt;

&lt;p&gt;❗The primary mental setup - is to review incoming mail intently and always make a reaction:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;directly, with approving pull-request, doing the review or commenting&lt;/li&gt;
&lt;li&gt;implicitly, with snoozing the message (for the more suitable time)&lt;/li&gt;
&lt;li&gt;or with marking the email "unread" to leave it for the next session, you will work on the email.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Wrap up
&lt;/h1&gt;

&lt;p&gt;Spending one hour for organising incoming emails let to save more time in the future, It:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;makes email browsing smarter by using prioritisation and labels&lt;/li&gt;
&lt;li&gt;shorten the reaction time for the most urgent messages&lt;/li&gt;
&lt;li&gt;gives the flexibility - all the settings could be changed at any time for any situation to perform maximum&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thanks for reading.&lt;/p&gt;

</description>
      <category>github</category>
      <category>review</category>
      <category>discuss</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
