<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Andrey Novikov</title>
    <description>The latest articles on DEV Community by Andrey Novikov (@envek).</description>
    <link>https://dev.to/envek</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F58821%2F6d876b13-e626-4de3-8061-45a68cc88bce.jpeg</url>
      <title>DEV Community: Andrey Novikov</title>
      <link>https://dev.to/envek</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/envek"/>
    <language>en</language>
    <item>
      <title>Fullstaq Ruby: First impressions, and how to migrate your Docker/Kubernetes Ruby apps today</title>
      <dc:creator>Andrey Novikov</dc:creator>
      <pubDate>Wed, 07 Aug 2019 14:15:13 +0000</pubDate>
      <link>https://dev.to/evilmartians/fullstaq-ruby-first-impressions-and-how-to-migrate-your-docker-kubernetes-ruby-apps-today-4fm7</link>
      <guid>https://dev.to/evilmartians/fullstaq-ruby-first-impressions-and-how-to-migrate-your-docker-kubernetes-ruby-apps-today-4fm7</guid>
      <description>&lt;h2&gt;
  
  
  What is Fullstaq Ruby?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://fullstaqruby.org/"&gt;Fullstaq Ruby&lt;/a&gt; is a custom build of standard MRI Ruby interpreter with memory allocator replaced, security patches applied, and more goodies on the way.&lt;/p&gt;

&lt;p&gt;If some old timers are here, they can remember REE–Ruby Enterprise Edition–from ancient times of Ruby 1.8.7 and Ruby on Rails 2.2 (almost ten years ago!). Ah, good ol’ times! You could install it via RVM or Rbenv, and some legacy applications are still running on it or have been just recently migrated. REE has a &lt;a href="https://github.com/rvm/rvm/tree/76f66ed8ad6552ad9730999fb3e706154f942f55/patches/ree"&gt;dozen of different patches&lt;/a&gt; on top of Ruby 1.8.7 to improve performance, reduce memory consumption, adjust obsolete security settings, and so on.&lt;/p&gt;

&lt;p&gt;In MRI 1.9.x most of these problems were solved, and, as it gained adoption, REE became obsolete. But even modern “vanilla” MRI still has quirks that can be fixed relatively easy. The most annoying of them is &lt;a href="https://www.speedshop.co/2017/12/04/malloc-doubles-ruby-memory.html"&gt;memory bloat due to memory fragmentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So it is not at all surprising that &lt;a href="https://www.joyfulbikeshedding.com/"&gt;Hongli Lai&lt;/a&gt;, the creator of REE, have released Fullstaq Ruby. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;REE is dead, long live Fullstaq Ruby!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Why do we need it?
&lt;/h2&gt;

&lt;p&gt;At one of our projects at &lt;a href="https://evilmartians.com/"&gt;Evil Martians&lt;/a&gt; we were experiencing severe memory bloat. Our application does a lot of IO, and we have a lot of Sidekiq processes with high concurrency setting (20 threads per process). This setting is optimal from the performance point of view because workers are mostly making requests to different remote APIs, our own database, and caches. But such a high level of concurrency also leads to high memory fragmentation. Our Sidekiq processes eat &lt;em&gt;several gigabytes&lt;/em&gt; of RAM each.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Read more about choosing Sidekiq concurrency setting in the &lt;a href="https://us11.campaign-archive.com/?u=1aa0f43522f6d9ef96d1c5d6f&amp;amp;id=997fbd1c2c"&gt;Sidekiq in Practice part 1&lt;/a&gt; by Nate Berkopec.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We have decided to replace our MRI 2.6.3 to Fullstaq Ruby 2.6.3 with &lt;a href="http://jemalloc.net/"&gt;jemalloc&lt;/a&gt; to see how it will perform.&lt;/p&gt;

&lt;h2&gt;
  
  
  Now that's the difference!
&lt;/h2&gt;

&lt;p&gt;We tried Fullstaq Ruby on a commercial application that runs in production and serves requests from paying clients around the clock. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;First of all: nothing broke. Zero downtime!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now, take a look at these monitoring graphs. Memory bloat of long-running processes has practically gone!&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Web application processes have become very stable in memory consumption (4 times less memory!). Bloat still occurs sporadically, but still the readings show that about 50% less memory is consumed during spikes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--gN3xSFNd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/kdku0n5tfx9t4yajc8sr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--gN3xSFNd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/kdku0n5tfx9t4yajc8sr.png" alt="web pods memory consumption"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Background job workers (we are using Sidekiq) also lost two thirds of their weight. From 1.5-2 GB before to 500-700 MB after the migration to Fullstaq Ruby.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--AzGzJS3i--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/t93jdbvzwrgp9n3tlsm9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--AzGzJS3i--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/t93jdbvzwrgp9n3tlsm9.png" alt="sidekiq pods memory consumption"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;There is no noticeable difference in memory consumption for short processes (e.g., cron jobs)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;We didn't notice any changes in response times or CPU utilization. &lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The graphs above prove that memory fragmentation was the reason for high memory consumption.&lt;/p&gt;

&lt;p&gt;And that’s it—quite an improvement for swapping one ruby binary for another, isn’t it?&lt;/p&gt;

&lt;h2&gt;
  
  
  Alternatives?
&lt;/h2&gt;

&lt;p&gt;If jemalloc isn't an option for you or you cannot afford to replace MRI with something else, you can try &lt;code&gt;MALLOC_ARENA_MAX=2&lt;/code&gt; &lt;em&gt;spell&lt;/em&gt; to adjust MRI's standard glibc malloc behavior. Results will be close enough to Fullstaq Ruby to treat them almost as equal.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--YFs8FjKE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/i4wc99f0xb5b2f39mbix.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YFs8FjKE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/i4wc99f0xb5b2f39mbix.png" alt="Fullstaq Ruby with jemalloc on the left and MRI with two malloc arenas on the right"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In our case, Ruby with limited number of malloc arenas (on the right) consumed about 50-100 MB more memory than Ruby with jemalloc (on the left).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Read more and see benchmarks of &lt;code&gt;MALLOC_ARENA_MAX=2&lt;/code&gt; in this post:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag__link"&gt;
  &lt;a href="/palkan_tula" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--nT6mAN_f--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/practicaldev/image/fetch/s--2KOdJPra--/c_fill%2Cf_auto%2Cfl_progressive%2Ch_150%2Cq_auto%2Cw_150/https://dev-to-uploads.s3.amazonaws.com/uploads/user/profile_image/48217/7985e690-6765-41f5-ad5a-74c44393c25c.jpg" alt="palkan_tula image"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="/evilmartians/cables-vs-malloctrim-or-yet-another-ruby-memory-usage-benchmark-3emo" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Cables vs. malloc_trim, or yet another Ruby memory usage benchmark&lt;/h2&gt;
      &lt;h3&gt;Vladimir Dementyev ・ Mar 19 '19 ・ 6 min read&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#ruby&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#benchmarks&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#rails&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;

&lt;/blockquote&gt;

&lt;p&gt;We decided to stick with Fullstaq Ruby.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to install?
&lt;/h2&gt;

&lt;p&gt;At the moment the only way to install Fullstaq Ruby is to use deb or rpm packages (either installing directly or via repositories). But we deploy our app to Kubernetes cluster, so we need a Docker image. As there is no "container edition" available on the official website yet, so let’s build our own image–actually, it is not that hard!&lt;/p&gt;

&lt;p&gt;Let's use Debian 9, as this is the Linux distribution being used by official Ruby Docker image, and define the Ruby version:&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; debian:stretch-slim&lt;/span&gt;

&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; RUBY_VERSION=2.6.3-jemalloc&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then install prerequisites, add Fullstaq Ruby APT repository, install Ruby itself and cleanup apt caches—all in a single command to reduce the Docker layer size:&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;RUN &lt;/span&gt;apt-get update &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get dist-upgrade &lt;span class="nt"&gt;--assume-yes&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &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;--assume-yes&lt;/span&gt; &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="nt"&gt;--no-install-recommends&lt;/span&gt; curl gnupg apt-transport-https ca-certificates &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; curl &lt;span class="nt"&gt;-SLf&lt;/span&gt; https://raw.githubusercontent.com/fullstaq-labs/fullstaq-ruby-server-edition/master/fullstaq-ruby.asc | apt-key add - &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"deb https://apt.fullstaqruby.org debian-9 main"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /etc/apt/sources.list.d/fullstaq-ruby.list &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get update &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &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;--assume-yes&lt;/span&gt; &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="nt"&gt;--no-install-recommends&lt;/span&gt; fullstaq-ruby-&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;RUBY_VERSION&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get autoremove &lt;span class="nt"&gt;--assume-yes&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-fr&lt;/span&gt; /var/cache/apt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fullstaq Ruby also installs Rbenv as dependency but we don't need it in Docker, so let's add ruby and gems binaries to system &lt;code&gt;$PATH&lt;/code&gt; in the &lt;a href="https://github.com/docker-library/ruby/blob/bffb6ff1fbe37874ed506a15eb1bb7faffca589b/2.6/stretch/slim/Dockerfile#L106-L111"&gt;same way that official Docker image for Ruby does&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;ENV&lt;/span&gt;&lt;span class="s"&gt; GEM_HOME /usr/local/bundle&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; BUNDLE_PATH="$GEM_HOME" \&lt;/span&gt;
    BUNDLE_SILENCE_ROOT_WARNING=1 \
    BUNDLE_APP_CONFIG="$GEM_HOME" \
    RUBY_VERSION=$RUBY_VERSION \
    LANG=C.UTF-8 LC_ALL=C.UTF-8

&lt;span class="c"&gt;# path recommendation: https://github.com/bundler/bundler/pull/6469#issuecomment-383235438&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; PATH $GEM_HOME/bin:$BUNDLE_PATH/gems/bin:/usr/lib/fullstaq-ruby/versions/${RUBY_VERSION}/bin:$PATH&lt;/span&gt;

&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; [ "irb" ]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that's it!&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;We’ve already built and published this image. You can pull it from &lt;a href="https://quay.io/repository/evl.ms/fullstaq-ruby?tab=tags"&gt;our repository&lt;/a&gt; at quay.io&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker pull quay.io/evl.ms/fullstaq-ruby:2.6.3-jemalloc-stretch-slim
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dockerfiles are available on GitHub: &lt;a href="https://github.com/evilmartians/fullstaq-ruby-docker"&gt;https://github.com/evilmartians/fullstaq-ruby-docker&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Now we can just replace base image in our application Dockerfile:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;-ARG RUBY_VERSION=2.6.3
&lt;/span&gt;&lt;span class="gi"&gt;+ARG RUBY_VERSION=2.6.3-jemalloc
&lt;/span&gt;
-FROM ruby:${RUBY_VERSION}-stretch-slim
&lt;span class="gi"&gt;+FROM quay.io/evl.ms/fullstaq-ruby:${RUBY_VERSION}-stretch-slim
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And deploy it to staging and then to production.&lt;/p&gt;

&lt;p&gt;Feel free to do the same!&lt;/p&gt;

&lt;h2&gt;
  
  
  Recap
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Migration is smooth. Just reinstall Ruby and gems, and everything should just work.&lt;/li&gt;
&lt;li&gt;Application servers and background jobs worker processes should reduce memory consumption drastically.&lt;/li&gt;
&lt;li&gt;There is no noticeable difference in memory consumption for short processes (like cron jobs or scripts).&lt;/li&gt;
&lt;li&gt;Performance may slightly improve, but it depends on your workload profile.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ruby</category>
      <category>docker</category>
      <category>performance</category>
      <category>memory</category>
    </item>
    <item>
      <title>Reporting non-nullable violations in graphql-ruby properly</title>
      <dc:creator>Andrey Novikov</dc:creator>
      <pubDate>Fri, 19 Jul 2019 07:37:08 +0000</pubDate>
      <link>https://dev.to/evilmartians/reporting-non-nullable-violations-in-graphql-ruby-properly-2k3b</link>
      <guid>https://dev.to/evilmartians/reporting-non-nullable-violations-in-graphql-ruby-properly-2k3b</guid>
      <description>&lt;p&gt;GraphQL encourages you to decouple your frontend from your backend. You can think more about your types’ “correctness” and less about particular view layout. To achieve that GraphQL provides strong type system. GraphQL doesn’t allow to return String instead of Integer and also doesn’t allow return &lt;code&gt;null&lt;/code&gt;s where API declares that type is &lt;em&gt;non-nullable&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;But sometimes your data will accidentally contain empty values where even you don’t expect them. Some external APIs will return incorrect or missing data, even if its &lt;em&gt;contract&lt;/em&gt; disallows that. Sometimes it will be your own bug. But in any case, it is your responsibility to track for such failures and correct them.&lt;/p&gt;

&lt;p&gt;If you use Ruby for developing GraphQL API, the chances are high that you use graphql-ruby. It is standard de-facto in Ruby because it is a very mature and feature-rich gem with a whole ecosystem of plugins. But “out of the box” there it doesn’t inform you about violations of these “non-nullable type” rules and just silently returns a partial response with error messages to the client.&lt;/p&gt;

&lt;p&gt;But we want to know about non-null violations because it is our responsibility to develop bullet-proof applications. And according to the graphql-ruby’s &lt;a href="https://graphql-ruby.org/errors/type_errors"&gt;documentation about type errors&lt;/a&gt;, there is a special hook to catch such errors. Let’s use it!&lt;/p&gt;

&lt;p&gt;We’re using Honeybadger, so we’ll report this offense there:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;YourAppSchema&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;GraphQL&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Schema&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;type_error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exception&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;query_context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;exception&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_a?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;GraphQL&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;InvalidNullError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="no"&gt;Honeybadger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;notify&lt;/span&gt; &lt;span class="n"&gt;exception&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;context: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;query: &lt;/span&gt;&lt;span class="n"&gt;query_context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query_string&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;super&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;So far, so good. Soon we’ll able to see our first error in error tracker UI.&lt;/p&gt;

&lt;p&gt;But wait, what’s it? There are errors about non-null violations in multiple different fields mixed in one error field. Aren’t they different bugs? What’s wrong?&lt;/p&gt;

&lt;p&gt;It turns out that by default &lt;a href="https://docs.honeybadger.io/lib/ruby/getting-started/customizing-error-grouping.html"&gt;Honeybadger groups errors together&lt;/a&gt; by exception class, component (controller and action), and backtrace fragment. Please note that the exception message doesn’t count. In the case of GraphQL, it is always a single controller and action, so exceptions for absolutely different queries are getting grouped together. Let’s fix that!&lt;/p&gt;

&lt;p&gt;According to &lt;a href="https://docs.honeybadger.io/lib/ruby/getting-started/customizing-error-grouping.html"&gt;the same documentation page&lt;/a&gt;, we need to calculate unique &lt;em&gt;fingerprint&lt;/em&gt; string for every different error. So, what we need to include to this fingerprint for non-null violation?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Exception class? Yes, we need to distinguish &lt;code&gt;GraphQL::InvalidNullError&lt;/code&gt; from other types of errors&lt;/li&gt;
&lt;li&gt;Controller and action? No, they’re always the same. And even if an error will occur during triggering subscription, will it make any difference?&lt;/li&gt;
&lt;li&gt;Backtrace? Hmm… I’m not sure. What do you think?&lt;/li&gt;
&lt;li&gt;Type and field names, of course! We need to separate errors in different fields.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s use an exception class name, GraphQL type name, and field name:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ApplicationSchema&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;GraphQL&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Schema&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;type_error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exception&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;query_context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;fingerprint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;honeybadger_fingerprint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exception&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="no"&gt;Honeybadger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;notify&lt;/span&gt; &lt;span class="n"&gt;exception&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;fingerprint: &lt;/span&gt;&lt;span class="n"&gt;fingerprint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;context: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="ss"&gt;query:   &lt;/span&gt;&lt;span class="n"&gt;query_context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query_string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="ss"&gt;user_id: &lt;/span&gt;&lt;span class="n"&gt;query_context&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:user&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="k"&gt;super&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

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

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;honeybadger_fingerprint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;
      &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="no"&gt;GraphQL&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;InvalidNullError&lt;/span&gt;
        &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;class&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;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parent_type&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&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;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;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;And that’s it: from now on exceptions for different fields lives in different Honeybadger errors.&lt;/p&gt;

&lt;p&gt;Thank you for your attention! Let’s build reliable APIs!&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>graphql</category>
      <category>errors</category>
      <category>monitoring</category>
    </item>
    <item>
      <title>Lefthook, Crystalball, and git magic for smooth development experience</title>
      <dc:creator>Andrey Novikov</dc:creator>
      <pubDate>Wed, 10 Jul 2019 11:51:39 +0000</pubDate>
      <link>https://dev.to/evilmartians/lefthook-crystalball-and-git-magic-for-smooth-development-experience-33mc</link>
      <guid>https://dev.to/evilmartians/lefthook-crystalball-and-git-magic-for-smooth-development-experience-33mc</guid>
      <description>&lt;p&gt;From this step-by-step tutorial, you will learn how to setup &lt;a href="https://github.com/Arkweid/lefthook"&gt;Lefthook&lt;/a&gt; git hooks manager, &lt;a href="https://github.com/toptal/crystalball"&gt;Crystalball&lt;/a&gt; test selection library, and also how to automatically install missing gems and migrate your database when you're switching to a feature branch and back to the &lt;code&gt;master&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Every project should have a CI installed. But CI builds sometimes can queue up, you need to wait for the notification. And anyway, is there a method to reduce the time for "implement-test-fix" cycle, save keystrokes and mouse clicks, but don't let broken code to get into the project repository? Yes, it exists long ago and is well-known: git hooks. Plain executable scripts in your local &lt;code&gt;.git/hooks/&lt;/code&gt; directory. But it is so bothersome to set them up, to update them. and to sync with your collaborators because you can't commit them to the repository itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up Lefthook
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/Arkweid/lefthook"&gt;Lefthook&lt;/a&gt; is our own git hook manager written in Go. Single dependency-free binary. Fast, reliable, feature-rich, language-agnostic.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vJ70wriM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://practicaldev-herokuapp-com.freetls.fastly.net/assets/github-logo-ba8488d21cd8ee1fee097b8410db9deaa41d0ca30b004c0c63de0a479114156f.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/Arkweid"&gt;
        Arkweid
      &lt;/a&gt; / &lt;a href="https://github.com/Arkweid/lefthook"&gt;
        lefthook
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Fast and powerful Git hooks manager for any type of projects.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://camo.githubusercontent.com/ef6d3c7ccef1a40874c10f8a85b08b58639da288/68747470733a2f2f6170692e7472617669732d63692e6f72672f41726b776569642f6c656674686f6f6b2e7376673f6272616e63683d6d6173746572"&gt;&lt;img src="https://camo.githubusercontent.com/ef6d3c7ccef1a40874c10f8a85b08b58639da288/68747470733a2f2f6170692e7472617669732d63692e6f72672f41726b776569642f6c656674686f6f6b2e7376673f6272616e63683d6d6173746572" alt="Build Status"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;
Lefthook&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;The fastest polyglot Git hooks manager out there&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://raw.githubusercontent.com/Arkweid/lefthook/master/./logo_sign.svg"&gt;&lt;img width="147" height="100" title="Lefthook logo" src="https://res.cloudinary.com/practicaldev/image/fetch/s--We-W2sPl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/Arkweid/lefthook/master/./logo_sign.svg"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Fast and powerful Git hooks manager for Node.js, Ruby or any other type of projects.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fast.&lt;/strong&gt; It is written in Go. Can run commands in parallel.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Powerful.&lt;/strong&gt; With a few lines in the config you can check only the changed files on &lt;code&gt;pre-push&lt;/code&gt; hook.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simple.&lt;/strong&gt; It is single dependency-free binary which can work in any environment.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;📖 &lt;a href="https://evilmartians.com/chronicles/lefthook-knock-your-teams-code-back-into-shape?utm_source=lefthook" rel="nofollow"&gt;Read the introduction post&lt;/a&gt;&lt;/p&gt;
&lt;div class="highlight highlight-source-yaml"&gt;&lt;pre&gt;&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; On `git push` lefthook will run spelling and links check for all of the changed files&lt;/span&gt;
&lt;span class="pl-ent"&gt;pre-push&lt;/span&gt;
  &lt;span class="pl-ent"&gt;parallel&lt;/span&gt;: &lt;span class="pl-c1"&gt;true&lt;/span&gt;
  &lt;span class="pl-ent"&gt;commands&lt;/span&gt;
    &lt;span class="pl-ent"&gt;spelling&lt;/span&gt;
      &lt;span class="pl-ent"&gt;files&lt;/span&gt;: &lt;span class="pl-s"&gt;git diff --name-only HEAD @{push}&lt;/span&gt;
      &lt;span class="pl-ent"&gt;glob&lt;/span&gt;: &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;*.md&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;
      &lt;span class="pl-ent"&gt;run&lt;/span&gt;: &lt;span class="pl-s"&gt;npx yaspeller {files}&lt;/span&gt;
    &lt;span class="pl-ent"&gt;check-links&lt;/span&gt;:
      &lt;span class="pl-ent"&gt;files&lt;/span&gt;: &lt;span class="pl-s"&gt;git diff --name-only HEAD @{push}&lt;/span&gt;
      &lt;span class="pl-ent"&gt;glob&lt;/span&gt;: &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;*.md&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;
      &lt;span class="pl-ent"&gt;run&lt;/span&gt;: &lt;span class="pl-s"&gt;npx markdown-link-check {files}&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;a href="https://evilmartians.com/?utm_source=lefthook" rel="nofollow"&gt;
&lt;img src="https://camo.githubusercontent.com/608ad776fcb2da2b33f4f8265889cc5f1b8a6bad/68747470733a2f2f6576696c6d61727469616e732e636f6d2f6261646765732f73706f6e736f7265642d62792d6576696c2d6d61727469616e732e737667" alt="Sponsored by Evil Martians" width="236" height="54"&gt;&lt;/a&gt;
&lt;h2&gt;
Usage&lt;/h2&gt;
&lt;p&gt;Choose your environment:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://raw.githubusercontent.com/Arkweid/lefthook/master/./docs/node.md"&gt;Node.js&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://raw.githubusercontent.com/Arkweid/lefthook/master/./docs/ruby.md"&gt;Ruby&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://raw.githubusercontent.com/Arkweid/lefthook/master/./docs/other.md"&gt;Other environments&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Then you can find all Lefthook features in &lt;a href="https://raw.githubusercontent.com/Arkweid/lefthook/master/./docs/full_guide.md"&gt;the full&lt;/a&gt;…&lt;/p&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/Arkweid/lefthook"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Install it with &lt;a href="https://github.com/Arkweid/lefthook/blob/master/docs/ruby.md"&gt;&lt;code&gt;gem&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://github.com/Arkweid/lefthook/blob/master/docs/node.md"&gt;&lt;code&gt;npm&lt;/code&gt;&lt;/a&gt;, or &lt;a href="https://github.com/Arkweid/lefthook/blob/master/docs/other.md"&gt;from source or your OS package manager&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Define your hooks in config file &lt;code&gt;lefthook.yml&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;pre-push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;parallel&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;commands&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;rubocop&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;backend&lt;/span&gt;
      &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bundle exec rubocop&lt;/span&gt;
    &lt;span class="na"&gt;rspec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rspec backend&lt;/span&gt;
      &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bundle exec rspec --fail-fast&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;And run &lt;code&gt;lefthook install&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And voila, starting from now rspec and rubocop will be run in parallel on every &lt;code&gt;git push&lt;/code&gt; and push will be aborted if they would find any issues.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;But why you've chosen pre-push over pre-commit?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;First of all, sometimes, during refactorings, you want to make a lot of small commits locally. Most of them won't pass linters. Why execute all this machinery if  you know that it won't pass? Afterward, you will squash and reorder them with &lt;code&gt;git rebase --interactive&lt;/code&gt; and push clean code to the repo.&lt;/p&gt;

&lt;p&gt;More importantly, some things are not executing fast. Wait a minute on every &lt;code&gt;git commit&lt;/code&gt; is meh. You lose speed. So why not to move long operations to the much more rare push event?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;However, running whole test suite may take a too long time (and we have CI exactly for that anyway), so we need some method to run only specs that we probably might break by our changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up Crystalball
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/toptal/crystalball"&gt;Crystalball&lt;/a&gt; is a Ruby library by Toptal which implements &lt;a href="https://tenderlovemaking.com/2015/02/13/predicting-test-failues.html"&gt;Regression Test Selection mechanism&lt;/a&gt;. Its main purpose is to select a minimal subset of your test suite, which should be run to ensure your changes didn't break anything. It is a tricky problem in Ruby applications in general and especially in Rails applications because of &lt;a href="https://guides.rubyonrails.org/autoloading_and_reloading_constants.html"&gt;Ruby on Rails constant autoloading mechanism&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Crystalball solves this problem by tracing code execution and tracking dependencies between files when your test suite is running. Using this profiling data, it could tell which files affect which. Take a look at &lt;a href="https://speakerdeck.com/p0deje/crystalball-predicting-test-failures"&gt;slides about Crystalball&lt;/a&gt; from the talk at RubyKaigi 2019.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vJ70wriM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://practicaldev-herokuapp-com.freetls.fastly.net/assets/github-logo-ba8488d21cd8ee1fee097b8410db9deaa41d0ca30b004c0c63de0a479114156f.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/toptal"&gt;
        toptal
      &lt;/a&gt; / &lt;a href="https://github.com/toptal/crystalball"&gt;
        crystalball
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Regression Test Selection library for your RSpec test suite
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;h1&gt;
Crystalball&lt;/h1&gt;
&lt;p&gt;Crystalball is a Ruby library which implements &lt;a href="https://tenderlovemaking.com/2015/02/13/predicting-test-failues.html" rel="nofollow"&gt;Regression Test Selection mechanism&lt;/a&gt; originally published by Aaron Patterson
Its main purpose is to select a minimal subset of your test suite which should be run to ensure your changes didn't break anything.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://travis-ci.org/toptal/crystalball" rel="nofollow"&gt;&lt;img src="https://camo.githubusercontent.com/38a00472a53d185408802e43ff61b9421b5c3b5b/68747470733a2f2f7472617669732d63692e6f72672f746f7074616c2f6372797374616c62616c6c2e7376673f6272616e63683d6d6173746572" alt="Build Status"&gt;&lt;/a&gt;
&lt;a href="https://codeclimate.com/github/toptal/crystalball/maintainability" rel="nofollow"&gt;&lt;img src="https://camo.githubusercontent.com/371fa80d2d5cc40b2c0bf558c0f2e5c8acb72b5b/68747470733a2f2f6170692e636f6465636c696d6174652e636f6d2f76312f6261646765732f63386266633235613433613161326563663936342f6d61696e7461696e6162696c697479" alt="Maintainability"&gt;&lt;/a&gt;
&lt;a href="https://codeclimate.com/github/toptal/crystalball/test_coverage" rel="nofollow"&gt;&lt;img src="https://camo.githubusercontent.com/bfd96c45d197c3ec9aa6d011239cd16109a4c449/68747470733a2f2f6170692e636f6465636c696d6174652e636f6d2f76312f6261646765732f63386266633235613433613161326563663936342f746573745f636f766572616765" alt="Test Coverage"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
Installation&lt;/h2&gt;
&lt;p&gt;Add this line to your application's Gemfile:&lt;/p&gt;
&lt;div class="highlight highlight-source-ruby"&gt;&lt;pre&gt;&lt;span class="pl-en"&gt;group&lt;/span&gt; &lt;span class="pl-pds"&gt;:test&lt;/span&gt; &lt;span class="pl-k"&gt;do&lt;/span&gt;
  &lt;span class="pl-en"&gt;gem&lt;/span&gt; &lt;span class="pl-s"&gt;'crystalball'&lt;/span&gt;
&lt;span class="pl-k"&gt;end&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And then execute:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ bundle
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or install it yourself as:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ gem install crystalball
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;
Usage&lt;/h2&gt;
&lt;p&gt;Please see our &lt;a href="https://toptal.github.io/crystalball/" rel="nofollow"&gt;official documentation&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;
Versioning&lt;/h3&gt;
&lt;p&gt;We use &lt;a href="https://semver.org/" rel="nofollow"&gt;semantic versioning&lt;/a&gt; for our &lt;a href="https://github.com/toptal/crystalball/releases"&gt;releases&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
Development&lt;/h2&gt;
&lt;p&gt;After checking out the repo, run &lt;code&gt;bin/setup&lt;/code&gt; to install dependencies. Then, run &lt;code&gt;rake spec&lt;/code&gt; to run the tests. You can also run &lt;code&gt;bin/console&lt;/code&gt; for an interactive prompt that will allow you to experiment.&lt;/p&gt;
&lt;p&gt;To install this gem onto your local machine, run &lt;code&gt;bundle exec rake install&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;
Contributing&lt;/h2&gt;
&lt;p&gt;Bug reports and pull requests are welcome on GitHub at &lt;a href="https://github.com/toptal/crystalball"&gt;https://github.com/toptal/crystalball&lt;/a&gt;
This project is intended…&lt;/p&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/toptal/crystalball"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;Install it
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;   &lt;span class="c1"&gt;# Gemfile&lt;/span&gt;
   &lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s2"&gt;"crystalball"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;Configure for our pre-push case (by default crystalball is configured to be used in pre-commit hooks)
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;   &lt;span class="c1"&gt;# config/crystalball.yml&lt;/span&gt;
   &lt;span class="s"&gt;---&lt;/span&gt;
   &lt;span class="s"&gt;map_expiration_period&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;604800&lt;/span&gt; &lt;span class="c1"&gt;# 1 week&lt;/span&gt;
   &lt;span class="na"&gt;diff_from&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;origin/master&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;Setup your test suite to collect code coverage information:
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;   &lt;span class="c1"&gt;# spec/spec_helper.rb&lt;/span&gt;
   &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'CRYSTALBALL'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'true'&lt;/span&gt;
     &lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'crystalball'&lt;/span&gt;
     &lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'crystalball/rails'&lt;/span&gt;

     &lt;span class="no"&gt;Crystalball&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;MapGenerator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start!&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;register&lt;/span&gt; &lt;span class="no"&gt;Crystalball&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;MapGenerator&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;CoverageStrategy&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;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt; &lt;span class="no"&gt;Crystalball&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;MapGenerator&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;I18nStrategy&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;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt; &lt;span class="no"&gt;Crystalball&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;MapGenerator&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;DescribedClassStrategy&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;end&lt;/span&gt;
   &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;Generate code execution maps:
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;   &lt;span class="nv"&gt;CRYSTALBALL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true &lt;/span&gt;bundle &lt;span class="nb"&gt;exec &lt;/span&gt;rspec
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;Replace RSpec with crystalball in &lt;code&gt;lefthook.yml&lt;/code&gt;:
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight diff"&gt;&lt;code&gt;   -      run: bundle exec rspec --fail-fast
   +      run: bundle exec crystalball --fail-fast
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;And from now every push will be accelerated &lt;em&gt;dramatically&lt;/em&gt; if your changes are small.&lt;/p&gt;

&lt;p&gt;But crystalball needs up-to-date code execution maps to work correctly. Can we automate these maps refreshing, too? Sure, we can!&lt;/p&gt;
&lt;h2&gt;
  
  
  Keeping Crystalball up-to-date
&lt;/h2&gt;

&lt;p&gt;For that sake git's &lt;code&gt;post-checkout&lt;/code&gt; hook fits very well. We can run code with updating crystalball data logic. “Logic” implies complexity as there is no single command for that. To cover such cases, Lefthook allows having separate executable script files. We can put our logic to &lt;code&gt;.lefthook/post-checkout/crystalball-update&lt;/code&gt; file, make it executable, and declare in lefthook configuration like this:&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;# lefthook.yml&lt;/span&gt;

&lt;span class="na"&gt;post-checkout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;scripts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;crystalball-update&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rspec backend&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;And there, in &lt;code&gt;crystalball-update&lt;/code&gt; script, we need a bit of magic.&lt;/p&gt;

&lt;p&gt;First of all, we don't need to do anything when a developer uses &lt;code&gt;git checkout -- path&lt;/code&gt; command to reject some changes from working tree. Because this is not switching between commits and repository is possibly "dirty." Yes, Git CLI sometimes can feel weird as &lt;code&gt;checkout&lt;/code&gt; command is used for two different tasks.&lt;/p&gt;

&lt;p&gt;Per git docs, git will always pass to post-checkout hook three arguments: previous HEAD commit identifier (SHA1 sum), current HEAD commit identifier and flag whether it was checkout between branches (&lt;code&gt;1&lt;/code&gt;) or file checkout to the state of another commit (&lt;code&gt;0&lt;/code&gt;). Lefthook will catch these arguments and will carefully pass them to every managed script.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/usr/bin/env ruby

_prev_head, _curr_head, branch_change, * = ARGV

exit if branch_change == "0" # Don't run on file checkouts
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Next, we want to update crystalball profiling data only on the &lt;code&gt;master&lt;/code&gt; branch as recommended in the &lt;a href="https://toptal.github.io/crystalball/"&gt;Crystalball docs&lt;/a&gt;. To do so, we need to ask git what branch we've checked out:&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;# Rails.root if we look from .lefthook/post-checkout dir&lt;/span&gt;
&lt;span class="n"&gt;app_dir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expand_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"../.."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;__dir__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"BUNDLE_GEMFILE"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app_dir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Gemfile"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"bundler/setup"&lt;/span&gt;

&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"git"&lt;/span&gt;
&lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="no"&gt;Git&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="n"&gt;app_dir&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;current_branch&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"master"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;code&gt;git&lt;/code&gt; gem is a dependency of Crystalball, so we don't have to install it.&lt;/p&gt;

&lt;p&gt;And finally we need to do most heavy part: ask Crystalball, “Are your profiling data up-to-date?”&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"crystalball"&lt;/span&gt;
&lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Crystalball&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;RSpec&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Runner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;
&lt;span class="n"&gt;prediction_builder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Crystalball&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;RSpec&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Runner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prediction_builder&lt;/span&gt;

&lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="k"&gt;if&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;exists?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"execution_map_path"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;prediction_builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expired_map?&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;And if it is not fresh we need to run the whole test suite with special environment variable set:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Crystalball Ruby code execution maps are out of date. Performing full test suite to update them…"&lt;/span&gt;

&lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"CRYSTALBALL"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"true"&lt;/span&gt;
&lt;span class="no"&gt;RSpec&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Core&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Runner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;app_dir&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;And we're done. Are we?&lt;/p&gt;
&lt;h2&gt;
  
  
  Automate other routine tasks
&lt;/h2&gt;

&lt;p&gt;But running specs require that we have:&lt;/p&gt;

&lt;p&gt;a. installed gems and&lt;br&gt;
 b. actual database state.&lt;/p&gt;

&lt;p&gt;And in actively developing application gems are frequently updated, added, and removed, database schema sometimes can be changed several times a day in different branches. It is so typical to pull fresh master at morning and get updated gems and new database migrations. In that case, RSpec would fail, and Crystalball execution path maps won't be complete. So we need to ensure that our specs always can run beforehand.&lt;/p&gt;
&lt;h3&gt;
  
  
  Install missing gems on a &lt;code&gt;git checkout&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This task is quite simple and can be achieved by a simple bash script. Most of it will consist of checks to avoid calling bundler when it's not needed. Bundler is quite heavy as it runs by noticeable time.&lt;/p&gt;

&lt;p&gt;Two first of these checks are same, but just rewritten to shell: is this branch checkout? Did we actually move between commits?&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="nv"&gt;BRANCH_CHANGE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$3&lt;/span&gt;
&lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nv"&gt;$BRANCH_CHANGE&lt;/span&gt; &lt;span class="nt"&gt;-eq&lt;/span&gt; 0 &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;exit

&lt;/span&gt;&lt;span class="nv"&gt;PREV_HEAD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;
&lt;span class="nv"&gt;CURR_HEAD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$2&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;$PREV_HEAD&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nv"&gt;$CURR_HEAD&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;exit&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Next one is more tricky:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Don't run bundler if there were no changes in gems&lt;/span&gt;
git diff &lt;span class="nt"&gt;--quiet&lt;/span&gt; &lt;span class="nt"&gt;--exit-code&lt;/span&gt; &lt;span class="nv"&gt;$PREV_HEAD&lt;/span&gt; &lt;span class="nv"&gt;$CURR_HEAD&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; Gemfile.lock &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We're asking here, “Does a set of required gems changed between commits?” If &lt;code&gt;Gemfile.lock&lt;/code&gt; was changed, we need to check do we have all of the gems installed by invoking bundler.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;bundle check &lt;span class="o"&gt;||&lt;/span&gt; bundle &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Again, if you have up-to-date gems (and in most checkouts, it will be so), only &lt;code&gt;bundle check&lt;/code&gt; will be executed.&lt;/p&gt;
&lt;h3&gt;
  
  
  Automatically rollback and apply database migrations
&lt;/h3&gt;

&lt;p&gt;Next task is much more interesting. &lt;/p&gt;

&lt;p&gt;When we're switching from branch A to branch B, we need to ensure that database schema actually is compatible with our specs. To do so, we must rollback every migration that exists in branch A but not in branch B and then to apply every migration that exists in B and still isn't applied. Rollback part is required because migrations that remove or rename columns and tables are not backward compatible.&lt;/p&gt;

&lt;p&gt;The problem here is that there is no &lt;code&gt;pre-checkout&lt;/code&gt; hook in git, only &lt;code&gt;post-checkout&lt;/code&gt; one. And after checkout, there are no more migration files left that existed only in the branch we're switched &lt;em&gt;from&lt;/em&gt;. How to rollback them?&lt;/p&gt;

&lt;p&gt;But this is git! The files are out there. Why not just &lt;em&gt;take&lt;/em&gt; them from git itself?&lt;/p&gt;

&lt;p&gt;To do so programmatically let's use gem &lt;code&gt;git&lt;/code&gt; to access our git repository. Crystalball already uses it under the hood so there will be no new dependency, but it is a good idea to add it to the &lt;code&gt;Gemfile&lt;/code&gt; explicitly.&lt;/p&gt;

&lt;p&gt;Let's start from the check that we really have any migrations to run (either up or down):&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"git"&lt;/span&gt;

&lt;span class="c1"&gt;# Rails.root if we look from .lefthook/post-checkout dir&lt;/span&gt;
&lt;span class="n"&gt;app_dir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expand_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"../.."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;__dir__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;git&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Git&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="n"&gt;app_dir&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Don't run if there were no database changes between revisions&lt;/span&gt;
&lt;span class="n"&gt;diff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;git&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;diff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prev_head&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;curr_head&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"db/migrate"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;diff&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;zero?&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Then, to be able to use migrations, we need to load our rails application and connect to the database:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&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;expand_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"config/boot"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;app_dir&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;require&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;expand_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"config/application"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;app_dir&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"rake"&lt;/span&gt;
&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load_tasks&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="s2"&gt;"db:load_config"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;invoke&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Then we can take files and save them somewhere:&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;# migrations added in prev_head (deleted in curr_head) that we need to rollback&lt;/span&gt;
&lt;span class="n"&gt;rollback_migration_files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;diff&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"deleted"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;rollback_migration_files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;any?&lt;/span&gt;
  &lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"tmpdir"&lt;/span&gt;
  &lt;span class="no"&gt;MigrationFilenameRegexp&lt;/span&gt; &lt;span class="o"&gt;=&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="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;MigrationFilenameRegexp&lt;/span&gt;
  &lt;span class="n"&gt;versions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

  &lt;span class="no"&gt;Dir&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mktmpdir&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;directory&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;rollback_migration_files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;diff_file&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;filename&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;basename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;diff_file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;contents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;git&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gblob&lt;/span&gt;&lt;span class="p"&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;prev_head&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;diff_file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;path&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;contents&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;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;directory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;MigrationFilenameRegexp&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;
      &lt;span class="n"&gt;versions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;And now, when we have files for migrations that need to be rolled back we can rollback them:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;begin&lt;/span&gt;
  &lt;span class="n"&gt;old_migration_paths&lt;/span&gt; &lt;span class="o"&gt;=&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;Migrator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;migrations_paths&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;Migrator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;migrations_paths&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;directory&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;versions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reverse_each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"VERSION"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;version&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="s2"&gt;"db:migrate:down"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;ensure&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;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"VERSION"&lt;/span&gt;&lt;span class="p"&gt;)&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;Migrator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;migrations_paths&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;old_migration_paths&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Here we're adding our temporary directory with another branch migrations to the ActiveRecord's &lt;code&gt;migrations_paths&lt;/code&gt;. This setting is available since Rails 5, but not widely known. Now ActiveRecord can see our ghost migration files, and we can simply invoke &lt;code&gt;rake db:migrate:down VERSION=number&lt;/code&gt; for every migration to rollback it.&lt;/p&gt;

&lt;p&gt;And after that we can &lt;em&gt;just&lt;/em&gt; migrate not yet applied migrations:&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;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="s2"&gt;"db:migrate"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;invoke&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;And that's it!&lt;/p&gt;
&lt;h3&gt;
  
  
  Composing it together
&lt;/h3&gt;

&lt;p&gt;Now we only need to invoke these scripts in the right order: install gems, run migrations and run specs (if required). To do so we need to name files in alphabetical order, place them in &lt;code&gt;.lefthook/post-checkout&lt;/code&gt; directory and declare them in &lt;code&gt;lefthook.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="na"&gt;post-checkout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;piped&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;scripts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;01-bundle-checkinstall&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;backend&lt;/span&gt;
    &lt;span class="na"&gt;02-db-migrate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;backend&lt;/span&gt;
    &lt;span class="na"&gt;03-crystalball-update&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rspec backend&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The &lt;code&gt;piped&lt;/code&gt; option will abort the rest of the commands if the preceding command fails. For example, if you forget to launch a database server, the second step will fail, and lefthook will skip the third step altogether.&lt;/p&gt;

&lt;p&gt;Frontend devs, not interested in running RSpec can exclude &lt;code&gt;rspec&lt;/code&gt; tag in their &lt;code&gt;lefthook.local.yml&lt;/code&gt;, and they will only get always installed gems and migrated database. Automagically.&lt;/p&gt;
&lt;h2&gt;
  
  
  Any gotchas?
&lt;/h2&gt;

&lt;p&gt;From now on you always have to write reversible migrations. Look at example application to learn how to do it.&lt;/p&gt;
&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Now we not only have checks that will prevent us from pushing broken code to the repository (and will check this really fast) but also, as a side-effect, we always will have installed gems, and our database will be in migrated state. You will forget about &lt;code&gt;bundle install&lt;/code&gt; (unless you are the one who update gems). And no more “Okay, now I need to rollback X and Y first, and checkout back to master only then.”&lt;/p&gt;

&lt;p&gt;Experiment with example application published on GitHub: &lt;a href="https://github.com/Envek/lefthook-crystalball-example"&gt;https://github.com/Envek/lefthook-crystalball-example&lt;/a&gt;&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vJ70wriM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://practicaldev-herokuapp-com.freetls.fastly.net/assets/github-logo-ba8488d21cd8ee1fee097b8410db9deaa41d0ca30b004c0c63de0a479114156f.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/Envek"&gt;
        Envek
      &lt;/a&gt; / &lt;a href="https://github.com/Envek/lefthook-crystalball-example"&gt;
        lefthook-crystalball-example
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Learn how to make git hooks to do most routine tasks for you: install gems, migrate the database, run tests, and linters.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;h1&gt;
Example application for Lefthook + Crystalball guide&lt;/h1&gt;
&lt;p&gt;Please read “&lt;a href="https://dev.to/evilmartians/lefthook-crystalball-and-git-magic-for-smooth-development-experience-33mc" title="Learn how to make git hooks to do most routine tasks for you: install gems, migrate the database, run tests, and linters." rel="nofollow"&gt;Lefthook, Crystalball, and git magic for smooth development experience&lt;/a&gt;” blog post first.&lt;/p&gt;
&lt;a href="https://evilmartians.com/" rel="nofollow"&gt;
&lt;img src="https://camo.githubusercontent.com/608ad776fcb2da2b33f4f8265889cc5f1b8a6bad/68747470733a2f2f6576696c6d61727469616e732e636f6d2f6261646765732f73706f6e736f7265642d62792d6576696c2d6d61727469616e732e737667" alt="Sponsored by Evil Martians" width="236" height="54"&gt;
&lt;/a&gt;
&lt;h2&gt;
Installation&lt;/h2&gt;
&lt;ol start="0"&gt;
&lt;li&gt;
&lt;p&gt;Ensure that you have Ruby 2.6.3 and SQLite installed (with development libraries)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fork and clone this repository locally:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell"&gt;&lt;pre&gt;git clone https://github.com/[YOUR-NAME]/lefthook-crystalball-example&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Go to directory with it:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell"&gt;&lt;pre&gt;&lt;span class="pl-c1"&gt;cd&lt;/span&gt; lefthook-crystalball-example&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Install lefthook from Rubygems explicitly:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell"&gt;&lt;pre&gt;gem install lefthook&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Install missing hooks to your local &lt;code&gt;.git/hooks/&lt;/code&gt; directory:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell"&gt;&lt;pre&gt;lefthook install&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Every developer need to do this only once. After that &lt;a href="https://github.com/Arkweid/lefthook" title="Git hooks manager"&gt;Lefthook&lt;/a&gt; will sync hooks after changes in &lt;code&gt;lefthook.yml&lt;/code&gt; automatically.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Install other dependencies:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell"&gt;&lt;pre&gt;bundle install
yarn install --check-files&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Prepare database&lt;/p&gt;
&lt;div class="highlight highlight-source-shell"&gt;&lt;pre&gt;rails db:setup&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Generate code execution maps for &lt;a href="https://github.com/toptal/crystalball" title="Regression Test Selection library for your RSpec test suite"&gt;Crystalball&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell"&gt;&lt;pre&gt;CRYSTALBALL=true bundle &lt;span class="pl-c1"&gt;exec&lt;/span&gt; rspec&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This need to be done only once too. &lt;code&gt;post-checkout&lt;/code&gt; hook will run this automatically once a week.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
Usage&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Checkout to &lt;code&gt;feature/multiaccount&lt;/code&gt; branch, see how migrations are applied:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell"&gt;&lt;pre&gt;git checkout feature/multiaccount&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Checkout to &lt;code&gt;feature/variations&lt;/code&gt; branch, see how new gems…&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/Envek/lefthook-crystalball-example"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;Happy coding!&lt;/p&gt;

</description>
      <category>rails</category>
      <category>git</category>
      <category>productivity</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>The silence of the Ruby exceptions: a Rails/PostgreSQL database transaction thriller</title>
      <dc:creator>Andrey Novikov</dc:creator>
      <pubDate>Tue, 24 Jul 2018 08:46:23 +0000</pubDate>
      <link>https://dev.to/evilmartians/the-silence-of-the-ruby-exceptions-a-railspostgresql-database-transaction-thriller-5e30</link>
      <guid>https://dev.to/evilmartians/the-silence-of-the-ruby-exceptions-a-railspostgresql-database-transaction-thriller-5e30</guid>
      <description>&lt;p&gt;&lt;em&gt;Here comes a tale on why you should&lt;/em&gt; &lt;strong&gt;&lt;em&gt;never&lt;/em&gt;&lt;/strong&gt; &lt;em&gt;silence errors inside database transactions. Learn how to use transactions properly and what to do when using them is not an option. Spoiler: use PostgreSQL advisory locks!&lt;/em&gt; &lt;/p&gt;

&lt;p&gt;I was working on a project where users could import a bunch of heavy entities (let's call them &lt;em&gt;products&lt;/em&gt;) from an external service into our application. Each product brought in even more associated data from external APIs. It is not uncommon for a user to import hundreds of products, and each product has dependencies that need to be fetched too, so you can imagine that the whole process takes a while (30 to 60 seconds for each product). As a user may get tired of waiting and hit the "Cancel" button at any moment, the application should still be usable with only those records that made it.&lt;/p&gt;

&lt;p&gt;Here's how our "interruptable import” was implemented: at the start of an import a temporary record in a separate database table was created for each enqueued product. Then for each of those records, a background job was spawned that pulled in all external information, persisted it in the right places (by creating associations, if necessary), and finally deleted a temporary record. If by the time the background job started the record was not found (when a user cancels the import, all temporary records are deleted)—the job just did nothing and exited silently. &lt;/p&gt;

&lt;p&gt;Interrupted import or not—the absence of temporary records meant we were done.&lt;/p&gt;

&lt;p&gt;Our design seemed simple and reliable, but it did not always work exactly as planned. A common bug description stated: "After canceling an import, a user is presented with a list of imported records. However, the next time the page is refreshed, the list has more records than shown initially". &lt;/p&gt;

&lt;p&gt;The reason for that was clear: as background jobs took up to a minute to finish, even a canceled import had an "afterglow".&lt;/p&gt;

&lt;p&gt;Nothing wrong with the design, but it led to confusing user experience, so we needed to address it in two possible ways: either somehow identify and cancel jobs already in progress, or wait for the last imports to finish before confirming that the whole process is in fact "canceled". I chose the latter.&lt;/p&gt;

&lt;h2&gt;
  
  
  Transaction locks to the rescue!
&lt;/h2&gt;

&lt;p&gt;For anyone dealing with relational databases often, the answer is clear: use transactions! According to a &lt;a href="http://api.rubyonrails.org/classes/ActiveRecord/Transactions/ClassMethods.html"&gt;description&lt;/a&gt; in Rails docs, they are "&lt;em&gt;protective blocks where SQL statements are only permanent if they can all succeed as one atomic action&lt;/em&gt;". As documentation states, "&lt;em&gt;you should use transaction blocks whenever you have a number of statements that must be executed together or not at all.&lt;/em&gt;" &lt;/p&gt;

&lt;p&gt;It is good to keep in mind that in most RDBMS records being updated inside a transaction will be &lt;em&gt;locked&lt;/em&gt; and unmodifiable by other processes until the transaction is finished. The same is true when you fetch the records with &lt;a href="https://api.rubyonrails.org/classes/ActiveRecord/Locking/Pessimistic.html"&gt;&lt;code&gt;SELECT FOR UPDATE&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Exactly our case! I used a single database transaction to wrap complex import tasks for individual products and to lock task records from being changed or deleted:&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;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transaction&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Import&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="nf"&gt;lock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;id: &lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# SELECT … FOR UPDATE means “I want to change it later, keep it for me”&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="c1"&gt;# it was already deleted so we're free&lt;/span&gt;
  &lt;span class="c1"&gt;# do some heavy lifting&lt;/span&gt;
  &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;destroy&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Now whenever the whole import is canceled, this code should wait for all individual tasks to finish:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;import_tasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delete_all&lt;/span&gt; &lt;span class="c1"&gt;# waits while all imports will be finished&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Simple and elegant! I've run everything locally and in the staging environment, found no problems and deployed to production.&lt;/p&gt;
&lt;h2&gt;
  
  
  Not so fast...
&lt;/h2&gt;

&lt;p&gt;Satisfied with my work, I was surprised to wake up to the myriad of log errors and complaints from colleagues. Many products weren't being imported at all. In some cases, only a single product would appear after the import is canceled. &lt;/p&gt;

&lt;p&gt;Error messages did not make much sense: &lt;code&gt;PG::InFailedSqlTransaction&lt;/code&gt; from code that executed innocent &lt;code&gt;SELECT&lt;/code&gt;s. What's going on?&lt;/p&gt;

&lt;p&gt;After an entire day of debugging, I've identified three root causes of problems:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Concurrent inserts of conflicting records into a database.&lt;/li&gt;
&lt;li&gt;Automatic transaction rollback in PostgreSQL.&lt;/li&gt;
&lt;li&gt;Silenced Ruby exceptions.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;First problem: Concurrent inserts of conflicting records&lt;/strong&gt;&lt;br&gt;
As each product's import job takes up to a minute, and there are multiple jobs per user, we run them in parallel to save time. Dependent records that are created when those jobs run can overlap, meaning different products can have the same dependency, first created, then reused. &lt;/p&gt;

&lt;p&gt;There are checks for these cases on the application level, but now that we use transactions they are rendered useless: if transaction A has created a dependent record, but has not committed yet, transaction B won't be aware of it and will try to create a duplicate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Second problem: Transaction rollback on a DB-level error&lt;/strong&gt; &lt;br&gt;
We can prevent creation of the duplicate records at the database level, with the following DDL:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;deps&lt;/span&gt; &lt;span class="k"&gt;ADD&lt;/span&gt; &lt;span class="k"&gt;UNIQUE&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chars&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Now if transaction A had inserted a new record and the transaction B, running in parallel, tries to insert the same record—we will get an error:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;BEGIN&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;deps&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chars&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'{"same": "value"}'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;-- Now it will block until first transaction will be finished&lt;/span&gt;
&lt;span class="n"&gt;ERROR&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="n"&gt;duplicate&lt;/span&gt; &lt;span class="k"&gt;key&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="n"&gt;violates&lt;/span&gt; &lt;span class="k"&gt;unique&lt;/span&gt; &lt;span class="k"&gt;constraint&lt;/span&gt; &lt;span class="nv"&gt;"deps_user_id_chars_key"&lt;/span&gt;
&lt;span class="n"&gt;DETAIL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="k"&gt;Key&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chars&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="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;"same"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;"value"&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;already&lt;/span&gt; &lt;span class="k"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="c1"&gt;-- And will throw an error when first transaction have commited and it is become clear that we have a conflict&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;One point that is commonly overlooked is that the transaction B, upon encountering an error, will roll-back entirely and the sequence of steps leading up to the error will be wasted. An invalid transaction, however, &lt;strong&gt;will still be open&lt;/strong&gt; until all the code inside runs its course. Every attempt to use records in question will result in another error:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;ERROR&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="k"&gt;current&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="n"&gt;aborted&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;commands&lt;/span&gt; &lt;span class="n"&gt;ignored&lt;/span&gt; &lt;span class="k"&gt;until&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;It goes without saying that no information (doesn't matter if you dealt with it before or after the error) will be persisted at all:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;COMMIT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;-- If we even try to save what we've done&lt;/span&gt;
&lt;span class="k"&gt;ROLLBACK&lt;/span&gt; &lt;span class="c1"&gt;-- RDBMS will reject our attempt&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Third problem: Silenced exceptions&lt;/strong&gt;&lt;br&gt;
At that point, it became clear that adding such a common and robust concept as database transaction to the application logic effectively broke it. I had no choice but to dig into other people's code. This is when I started seeing snippets such as this one:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_stuff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="c1"&gt;# Magic, lots of magic&lt;/span&gt;
&lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;StandardError&lt;/span&gt;
  &lt;span class="kp"&gt;nil&lt;/span&gt; &lt;span class="c1"&gt;# Happy debugging&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 the author is trying to say here is "We tried something that failed, but we don't care, so we keep doing stuff". While the reasons behind that approach may be justified (there are some things you can’t validate for on the application level), it makes transaction-based logic unimplementable: a silenced exception will never bubble up to a &lt;code&gt;transaction&lt;/code&gt; block and will not trigger a rollback (because ActiveRecord catches all exceptions, rolls back a transaction and re-raises them).&lt;/p&gt;
&lt;h2&gt;
  
  
  A perfect storm
&lt;/h2&gt;

&lt;p&gt;Here's how those three factors came together to create a perfect &lt;del&gt;storm&lt;/del&gt; bug:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Transaction tries to insert a conflicting row into a database and encounters a "duplicate key" error from Postgres. This error, however, never causes a transaction rollback, as it is silenced in Ruby.&lt;/li&gt;
&lt;li&gt;Transaction becomes invalid but keeps running. When the application tries to access the DB for whatever reason (even with an innocent &lt;code&gt;SELECT&lt;/code&gt; to list products), it triggers "current transaction is aborted" error. But that exception may be silenced too...&lt;/li&gt;
&lt;li&gt;You get the idea. While things keep breaking inside the application, no one will know about it until the error bubbles up to the first piece of code that does not use a &lt;code&gt;rescue&lt;/code&gt; block. As it might be quite far away from the code that failed originally, debugging becomes a nightmare.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  PostgreSQL's built-in alternative to transaction-level locks
&lt;/h2&gt;

&lt;p&gt;Hunting for &lt;code&gt;rescues&lt;/code&gt; in the application code and rewriting the whole import logic was not an option, so I needed a quick win and Postgres gave me one! It has a built-in locking tool that can be used as an alternative for locking records in transactions. Meet &lt;a href="https://www.postgresql.org/docs/current/static/explicit-locking.html#ADVISORY-LOCKS"&gt;session-level advisory locks&lt;/a&gt;. This is how I used them:&lt;/p&gt;

&lt;p&gt;First, I removed all transaction wrappers that I added in the first place. Doing API interactions or any other non-database side-effects from a code within a transaction is a bad idea anyway because even if database changes are successfully rolled back, side-effects will already be applied and that may leave your application in an undesired state. Take a look at the &lt;a href="https://github.com/palkan/isolator"&gt;isolator&lt;/a&gt; gem that will help you make sure your side-effects are well isolated.&lt;/p&gt;

&lt;p&gt;Then I associated every import task with a &lt;em&gt;shared&lt;/em&gt; lock based on some unique key (created from a user ID and a hash of operation name, for instance):&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;pg_advisory_lock_shared&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Shared locks may be obtained simultaneously by as many sessions as you want.&lt;/p&gt;

&lt;p&gt;"Cancel import" operation deletes all pending tasks from the database and tries to take an &lt;em&gt;exclusive&lt;/em&gt; lock for the same key. This will have to wait till all &lt;em&gt;shared&lt;/em&gt; locks are released:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;pg_advisory_lock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;And that is it!&lt;/p&gt;

&lt;p&gt;Now canceling an import will automatically wait for all running import tasks to finish.&lt;br&gt;
Moreover, we can use a little hack to set a timeout for our exclusive lock, because we don't want to wait for too long, blocking a web server thread, if some task hangs for whatever reason:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;transaction&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"SET LOCAL lock_timeout = '30s'"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"SELECT pg_advisory_lock(42, user.id)"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;LockWaitTimeout&lt;/span&gt;
  &lt;span class="kp"&gt;nil&lt;/span&gt; &lt;span class="c1"&gt;# we are tired of waiting and give up (transaction is already rolled back at this point)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Rescuing exception &lt;em&gt;outside&lt;/em&gt; a &lt;code&gt;transaction&lt;/code&gt; block is safe because &lt;a href="https://github.com/rails/rails/blob/v5.2.0/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb#L228-L236"&gt;ActiveRecord would already roll back&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  What to do with concurrent inserts of the same record?
&lt;/h2&gt;

&lt;p&gt;Unfortunately, I am not aware of a solution that can be used together with &lt;em&gt;concurrent&lt;/em&gt; inserts. Here are all the approaches that I know about, but all of them will lock concurrent inserts until the first transaction commits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;INSERT … ON CONFLICT UPDATE&lt;/code&gt; (available since PostgreSQL 9.5) to wait until the first transaction commits and returns a record inserted by it.&lt;/li&gt;
&lt;li&gt;Lock some common record in the database before running validations for a new record: this will wait until record inserted by the first transaction will become visible and validations will work as expected.&lt;/li&gt;
&lt;li&gt;Take some common advisory lock before running validations for a new record.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you’re not afraid of dealing with Postgres errors, you can also do this:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;import_all_the_things&lt;/span&gt;
  &lt;span class="c1"&gt;# Start transaction here and at some point do&lt;/span&gt;
  &lt;span class="no"&gt;Dep&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chars&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;RecordNotUnique&lt;/span&gt;
  &lt;span class="k"&gt;retry&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;And make sure this code is not wrapped inside a &lt;code&gt;transaction&lt;/code&gt; block.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Why will it lock?&lt;/p&gt;

&lt;p&gt;UNIQUE and EXCLUDE constraints would prevent potential conflicts to be written at the same time. E.g., if you have unique integer column and one transaction inserts 5, that will prevent other transactions from inserting 5 concurrently, but will not interfere with a transaction inserting 6. Since &lt;a href="https://www.postgresql.org/docs/current/static/transaction-iso.html"&gt;the least strict transaction isolation level supported by PostgreSQL&lt;/a&gt; is &lt;code&gt;READ COMMITED&lt;/code&gt;, the second transaction is not allowed to see results of the first transaction that are not yet committed. &lt;code&gt;INSERT&lt;/code&gt; of conflicting value must wait until the first transaction either commits (then it will fail) or rollbacks (then it will succeed). Read more in &lt;a href="http://thoughts.davisjeff.com/2010/09/25/exclusion-constraints-are-generalized-sql-unique/"&gt;this article&lt;/a&gt; from the author of &lt;code&gt;EXCLUDE&lt;/code&gt; constraints.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  Preventing future havoc
&lt;/h2&gt;

&lt;p&gt;If you know that some code can not be used inside a transaction for reasons described above, make sure it can never be wrapped in one. Here's how you can detect open transactions and fail fast:&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;# Here's how you define it&lt;/span&gt;
&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;NoTransactionAllowed&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;InTransactionError&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;RuntimeError&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&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="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;super&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;in_transaction?&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="no"&gt;InTransactionError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; doesn't work reliably within a DB transaction"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;in_transaction?&lt;/span&gt;
    &lt;span class="n"&gt;connection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connection&lt;/span&gt;
    &lt;span class="c1"&gt;# service transactions (tests and database_cleaner) are not joinable&lt;/span&gt;
    &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transaction_open?&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;current_transaction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;joinable?&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# And here's how you use it&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Deps&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Import&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;BaseService&lt;/span&gt;
  &lt;span class="n"&gt;prepend&lt;/span&gt; &lt;span class="no"&gt;NoTransactionAllowed&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;
    &lt;span class="n"&gt;do_import&lt;/span&gt;
  &lt;span class="k"&gt;rescue&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;RecordNotUnique&lt;/span&gt;
    &lt;span class="k"&gt;retry&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;Now, whenever anyone tries to wrap a risky service in a transaction, they will get an exception (unless they’ve silenced it, of course).&lt;/p&gt;
&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;One lesson to be taken from this tale: be careful with exceptions. Only catch those you really know how to deal with, and nothing more. Never silence exceptions unless you are absolutely sure. The sooner error will be noticed—the easier it will be to debug.&lt;br&gt;
And don't overuse database transactions!&lt;/p&gt;

&lt;p&gt;Our gems &lt;a href="https://github.com/palkan/isolator"&gt;isolator&lt;/a&gt; and &lt;a href="https://github.com/Envek/after_commit_everywhere/"&gt;after_commit_everywhere&lt;/a&gt; will make your transactions completely fool-proof.&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qF2jUiUG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://practicaldev-herokuapp-com.freetls.fastly.net/assets/github-logo-6a5bca60a4ebf959a6df7f08217acd07ac2bc285164fae041eacb8a148b1bab9.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/palkan"&gt;
        palkan
      &lt;/a&gt; / &lt;a href="https://github.com/palkan/isolator"&gt;
        isolator
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Detect non-atomic interactions within DB transactions
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="instapaper_body md"&gt;
&lt;p&gt;&lt;a href="http://cultofmartians.com/tasks/isolator.html" rel="nofollow"&gt;&lt;img src="https://camo.githubusercontent.com/03dac7833b3d619c60d72d6a2006e47ddc5c0c07/687474703a2f2f63756c746f666d61727469616e732e636f6d2f6173736574732f6261646765732f62616467652e737667" alt="Cult Of Martians"&gt;&lt;/a&gt;
&lt;a href="https://badge.fury.io/rb/isolator" rel="nofollow"&gt;&lt;img src="https://camo.githubusercontent.com/e233d35095078f449c9f12bb000f2e471bfc45c1/68747470733a2f2f62616467652e667572792e696f2f72622f69736f6c61746f722e737667" alt="Gem Version"&gt;&lt;/a&gt;
&lt;a href="https://travis-ci.org/palkan/isolator" rel="nofollow"&gt;&lt;img src="https://camo.githubusercontent.com/4b136263187eb5e78b39d68d42aafb836ed8ef73/68747470733a2f2f7472617669732d63692e6f72672f70616c6b616e2f69736f6c61746f722e7376673f6272616e63683d6d6173746572" alt="Build Status"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;
Isolator&lt;/h1&gt;
&lt;p&gt;Detect non-atomic interactions within DB transactions.&lt;/p&gt;
&lt;p&gt;Examples:&lt;/p&gt;
&lt;div class="highlight highlight-source-ruby"&gt;&lt;pre&gt;&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; HTTP calls within transaction&lt;/span&gt;
&lt;span class="pl-c1"&gt;User&lt;/span&gt;.transaction &lt;span class="pl-k"&gt;do&lt;/span&gt;
  user &lt;span class="pl-k"&gt;=&lt;/span&gt; &lt;span class="pl-c1"&gt;User&lt;/span&gt;.&lt;span class="pl-k"&gt;new&lt;/span&gt;(user_params)
  user.save
  &lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; HTTP API call&lt;/span&gt;
  &lt;span class="pl-c1"&gt;PaymentsService&lt;/span&gt;.charge!(user)
&lt;span class="pl-k"&gt;end&lt;/span&gt;
&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt;=&amp;gt; raises Isolator::HTTPError&lt;/span&gt;
&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; background job&lt;/span&gt;
&lt;span class="pl-c1"&gt;User&lt;/span&gt;.transaction &lt;span class="pl-k"&gt;do&lt;/span&gt;
  user.update!(&lt;span class="pl-c1"&gt;confirmed_at:&lt;/span&gt; &lt;span class="pl-c1"&gt;Time&lt;/span&gt;.now)
  &lt;span class="pl-c1"&gt;UserMailer&lt;/span&gt;.successful_confirmation(user).deliver_later
&lt;span class="pl-k"&gt;end&lt;/span&gt;

&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt;=&amp;gt; raises Isolator::BackgroundJobError&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Of course, Isolator can detect &lt;em&gt;implicit&lt;/em&gt; transactions too. Consider this pretty common bad practice–enqueueing background job from &lt;code&gt;after_create&lt;/code&gt; callback:&lt;/p&gt;
&lt;div class="highlight highlight-source-ruby"&gt;&lt;pre&gt;&lt;span class="pl-k"&gt;class&lt;/span&gt; &lt;span class="pl-en"&gt;Comment&lt;span class="pl-e"&gt; &amp;lt; ApplicationRecord&lt;/span&gt;&lt;/span&gt;
  &lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; the good way is to use after_create_commit&lt;/span&gt;
  &lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; (or not use callbacks at all)&lt;/span&gt;
  after_create &lt;span class="pl-c1"&gt;:notify_author&lt;/span&gt;

  &lt;span class="pl-k"&gt;private&lt;/span&gt;

  &lt;span class="pl-k"&gt;def&lt;/span&gt; &lt;span class="pl-en"&gt;notify_author&lt;/span&gt;
    &lt;span class="pl-c1"&gt;CommentMailer&lt;/span&gt;.comment_created(&lt;span class="pl-c1"&gt;self&lt;/span&gt;).deliver_later
  &lt;span class="pl-k"&gt;end&lt;/span&gt;
&lt;span class="pl-k"&gt;end&lt;/span&gt;

&lt;span class="pl-c1"&gt;Comment&lt;/span&gt;.create(&lt;span class="pl-c1"&gt;text:&lt;/span&gt; &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;Mars is watching you!&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;)
&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt;=&amp;gt; raises Isolator::BackgroundJobError&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Isolator is supposed to be used in tests and on staging.&lt;/p&gt;
&lt;h2&gt;
Installation&lt;/h2&gt;
&lt;p&gt;Add this line to your application's Gemfile:&lt;/p&gt;
&lt;div class="highlight highlight-source-ruby"&gt;
&lt;pre&gt;&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; We suppose that Isolator is used in development and test&lt;/span&gt;
&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; environments.&lt;/span&gt;
group &lt;span class="pl-c1"&gt;:development&lt;/span&gt;, &lt;span class="pl-c1"&gt;:test&lt;/span&gt;&lt;/pre&gt;…&lt;/div&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/palkan/isolator"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;



&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qF2jUiUG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://practicaldev-herokuapp-com.freetls.fastly.net/assets/github-logo-6a5bca60a4ebf959a6df7f08217acd07ac2bc285164fae041eacb8a148b1bab9.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/Envek"&gt;
        Envek
      &lt;/a&gt; / &lt;a href="https://github.com/Envek/after_commit_everywhere"&gt;
        after_commit_everywhere
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Use ActiveRecord transactional callbacks outside of models, literally everywhere in your application.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="instapaper_body md"&gt;
&lt;p&gt;&lt;a href="https://rubygems.org/gems/after_commit_everywhere" rel="nofollow"&gt;&lt;img src="https://camo.githubusercontent.com/d49051eb5b26424e29cd4039b7c3dc643f0c4282/68747470733a2f2f62616467652e667572792e696f2f72622f61667465725f636f6d6d69745f657665727977686572652e737667" alt="Gem Version"&gt;&lt;/a&gt;
&lt;a href="https://travis-ci.org/Envek/after_commit_everywhere" rel="nofollow"&gt;&lt;img src="https://camo.githubusercontent.com/c41cd3ac5818a54f4580d75d8527af751027bd34/68747470733a2f2f7472617669732d63692e6f72672f456e76656b2f61667465725f636f6d6d69745f657665727977686572652e7376673f6272616e63683d6d6173746572" alt="Build Status"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;
&lt;code&gt;after_commit&lt;/code&gt; everywhere&lt;/h1&gt;
&lt;p&gt;Allows to use ActiveRecord transactional callbacks outside of ActiveRecord models, literally everywhere in your application.&lt;/p&gt;
&lt;p&gt;Inspired by these articles:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/evilmartians/rails-aftercommit-everywhere--4j9g" rel="nofollow"&gt;https://dev.to/evilmartians/rails-aftercommit-everywhere--4j9g&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.arkency.com/2015/10/run-it-in-background-job-after-commit/" rel="nofollow"&gt;https://blog.arkency.com/2015/10/run-it-in-background-job-after-commit/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href="https://evilmartians.com/?utm_source=after_commit_everywhere&amp;amp;utm_campaign=project_page" rel="nofollow"&gt;&lt;img src="https://camo.githubusercontent.com/608ad776fcb2da2b33f4f8265889cc5f1b8a6bad/68747470733a2f2f6576696c6d61727469616e732e636f6d2f6261646765732f73706f6e736f7265642d62792d6576696c2d6d61727469616e732e737667" alt="Sponsored by Evil Martians" width="236" height="54"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
Installation&lt;/h2&gt;
&lt;p&gt;Add this line to your application's Gemfile:&lt;/p&gt;
&lt;div class="highlight highlight-source-ruby"&gt;&lt;pre&gt;gem &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;'&lt;/span&gt;after_commit_everywhere&lt;span class="pl-pds"&gt;'&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And then execute:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ bundle
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or install it yourself as:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ gem install after_commit_everywhere
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;
Usage&lt;/h2&gt;
&lt;p&gt;Recommended usage is to include it to your base service class or anything:&lt;/p&gt;
&lt;div class="highlight highlight-source-ruby"&gt;&lt;pre&gt;&lt;span class="pl-k"&gt;class&lt;/span&gt; &lt;span class="pl-en"&gt;ServiceObjectBtw&lt;/span&gt;
  &lt;span class="pl-k"&gt;include&lt;/span&gt; &lt;span class="pl-c1"&gt;AfterCommitEverywhere&lt;/span&gt;
  &lt;span class="pl-k"&gt;def&lt;/span&gt; &lt;span class="pl-en"&gt;call&lt;/span&gt;
    &lt;span class="pl-c1"&gt;ActiveRecord&lt;/span&gt;::&lt;span class="pl-c1"&gt;Base&lt;/span&gt;.transaction &lt;span class="pl-k"&gt;do&lt;/span&gt;
      after_commit { &lt;span class="pl-c1"&gt;puts&lt;/span&gt; &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;We're all done!&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt; }
    &lt;span class="pl-k"&gt;end&lt;/span&gt;
  &lt;span class="pl-k"&gt;end&lt;/span&gt;
&lt;span class="pl-k"&gt;end&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Or just extend it whenever you need it:&lt;/p&gt;
&lt;div class="highlight highlight-source-ruby"&gt;&lt;pre&gt;&lt;span class="pl-k"&gt;extend&lt;/span&gt; &lt;span class="pl-c1"&gt;AfterCommitEverywhere&lt;/span&gt;
&lt;span class="pl-c1"&gt;ActiveRecord&lt;/span&gt;::&lt;span class="pl-c1"&gt;Base&lt;/span&gt;.transaction &lt;span class="pl-k"&gt;do&lt;/span&gt;
  after_commit { &lt;span class="pl-c1"&gt;puts&lt;/span&gt; &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;We're all done!&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt; }
&lt;span class="pl-k"&gt;end&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That's it!&lt;/p&gt;
&lt;p&gt;But the main benefit is that it works with nested &lt;code&gt;transaction&lt;/code&gt; blocks (may be even spread across many files in your codebase):&lt;/p&gt;
&lt;div class="highlight highlight-source-ruby"&gt;
&lt;pre&gt;&lt;span class="pl-k"&gt;include&lt;/span&gt; &lt;span class="pl-c1"&gt;AfterCommitEverywhere&lt;/span&gt;
&lt;span class="pl-c1"&gt;ActiveRecord&lt;/span&gt;::&lt;span class="pl-c1"&gt;Base&lt;/span&gt;.transaction &lt;span class="pl-k"&gt;do&lt;/span&gt;
  &lt;span class="pl-c1"&gt;puts&lt;/span&gt; &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;We're in transaction now&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;
  &lt;span class="pl-c1"&gt;ActiveRecord&lt;/span&gt;&lt;/pre&gt;…&lt;/div&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/Envek/after_commit_everywhere"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;



&lt;h2&gt;
  
  
  Further reading
&lt;/h2&gt;

&lt;p&gt;&lt;a href="http://exceptionalruby.com/"&gt;&lt;em&gt;Exceptional Ruby&lt;/em&gt;&lt;/a&gt; &lt;em&gt;by Avdi Grimm&lt;/em&gt;. This book will teach you how to work with existing Ruby exceptions, and how to design your own. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://brandur.org/http-transactions"&gt;&lt;em&gt;Using Atomic Transactions to Power an Idempotent API&lt;/em&gt;&lt;/a&gt; &lt;em&gt;by @Brandur&lt;/em&gt;. &lt;a href="https://brandur.org/"&gt;His blog&lt;/a&gt; is full of awesome articles about software reliability, Ruby, and PostgreSQL.&lt;/p&gt;




&lt;p&gt;Read more developer articles by &lt;a href="https://evilmartians.com/"&gt;Evil Martians&lt;/a&gt; on &lt;a href="https://dev.to/evilmartians"&gt;dev.to&lt;/a&gt; and in &lt;a href="https://evilmartians.com/chronicles"&gt;Martian Chronicles&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>rails</category>
      <category>postgres</category>
      <category>ruby</category>
      <category>errors</category>
    </item>
  </channel>
</rss>
