<?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: Dmitry Daw</title>
    <description>The latest articles on DEV Community by Dmitry Daw (@haukot).</description>
    <link>https://dev.to/haukot</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%2F190035%2F77c0bcfc-45c8-4eaa-b5a7-891b23a9b50f.jpg</url>
      <title>DEV Community: Dmitry Daw</title>
      <link>https://dev.to/haukot</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/haukot"/>
    <language>en</language>
    <item>
      <title>A way to cache responses in Grape API</title>
      <dc:creator>Dmitry Daw</dc:creator>
      <pubDate>Fri, 28 Jun 2024 19:42:34 +0000</pubDate>
      <link>https://dev.to/haukot/easy-response-caching-for-grape-api-5324</link>
      <guid>https://dev.to/haukot/easy-response-caching-for-grape-api-5324</guid>
      <description>&lt;p&gt;Caching is a great way to speed up slow pages and to make your API faster in general.&lt;/p&gt;

&lt;p&gt;Let's say we want to cache the response of our API.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key to cache invalidation
&lt;/h2&gt;

&lt;p&gt;First thing to think when adding a cache is the cache invalidation.&lt;/p&gt;

&lt;p&gt;Rails have handy methods for that: &lt;code&gt;ActiveRelation&lt;/code&gt; and &lt;code&gt;ActiveRecord&lt;/code&gt; methods &lt;code&gt;cache_key&lt;/code&gt; and &lt;code&gt;cache_key_with_version&lt;/code&gt;.&lt;br&gt;
While &lt;code&gt;cache_key&lt;/code&gt; returns only the id of a model, e.g.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"name like ?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"%Game%"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;last&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cache_key&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"products/124"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;cache_key_with_version&lt;/code&gt; additionally returns the timestamp of the last change, e.g.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;124&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;cache_key_with_version&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"products/124-20240624103815954181"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By default it works by the &lt;code&gt;updated_at&lt;/code&gt; column, but you can customize it like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;124&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;cache_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:last_reviewed_at&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;For relation&lt;/strong&gt; it works a bit differently, returning the key based on the SQL query hash. It takes into consideration params too.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"name like ?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"%Game%"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;cache_key&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; "products/query-1850ab3d302391b85b8693e941286659"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And &lt;code&gt;cache_key_with_version&lt;/code&gt; additionally returns the id and timestamp of the last entity&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"name like ?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"%Game%"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;cache_key_with_version&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; "products/query-e0db51fbb1a07ab9545d84d80aac3d16-124-20240628093023387346"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Two caching strategies
&lt;/h2&gt;

&lt;p&gt;Adding caching is easy, but the problem is the cache invalidation. There are two stategies for doing that&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Push method&lt;/strong&gt; - by some triggers (e.g., once an hour, or when a product changes), we prepare data and store it in the cache.&lt;br&gt;
The positives are that data always will be in the cache when a user calls an API.&lt;br&gt;
The negatives - we need to prepare data in all places where there are triggers, and be careful with cache invalidation (it's very easy to miss a place that occasionally changes your model).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Pull method&lt;/strong&gt; - the moment we need data from the cache, we check it, and if there is no data - we get the data in the usual way and store it in the cache for the future.&lt;br&gt;
It's easier in implementation, as we're worrying less about invalidation (though it still needs some work),&lt;br&gt;
But it works worse with cache misses - as some users will wait for a response full time.&lt;br&gt;
So to use that, your API should not be &lt;em&gt;too&lt;/em&gt; slow!&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For the push method, you are good to use &lt;code&gt;cache_key&lt;/code&gt;, but for the pull method we need to check if the cache is still correct, so &lt;code&gt;cache_key_with_version&lt;/code&gt; is our friend&lt;/p&gt;

&lt;p&gt;I'd say generally it's better to start with &lt;strong&gt;pull method&lt;/strong&gt;, and use &lt;strong&gt;push method&lt;/strong&gt; in cases when it's possible to update the cache at some time, e.g. once a day. &lt;/p&gt;

&lt;p&gt;But you should always take into consideration your requirements.&lt;/p&gt;

&lt;h2&gt;
  
  
  Going to the project
&lt;/h2&gt;

&lt;p&gt;We'll go with &lt;strong&gt;pull method&lt;/strong&gt; as an easier one, and use &lt;code&gt;cache_key_with_version&lt;/code&gt;. Let's add a helper method in Grape API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;  &lt;span class="n"&gt;helpers&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;present&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;namespace&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'MyAPI'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;caching: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_json&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;caching&lt;/span&gt;

      &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cache_key_with_version&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;namespace&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'-'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="ss"&gt;expires_in: &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;day&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="n"&gt;resourse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_json&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;Redis&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;TimeoutError&lt;/span&gt;
      &lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_json&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Besides &lt;code&gt;resource.cache_key_with_version&lt;/code&gt;, we also make separate caches for different API namespaces (we don't want users to see admin's output).&lt;br&gt;
In more complex examples, you could add more params, e.g., options for serializers if they change the output when SQL is the same.&lt;/p&gt;

&lt;p&gt;Then we could use it in the API like this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Edu::API::V2::Products&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Grape&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;API&lt;/span&gt;
  &lt;span class="n"&gt;resources&lt;/span&gt; &lt;span class="ss"&gt;:products&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;get&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;products&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;some_query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;present&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"API::V2::Products"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;caching: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The next thing is to make sure we invalidate products if the output depends on related models. As our cache depends on the &lt;code&gt;updated_at&lt;/code&gt; column of Products, and other models will not affect it by default.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Option&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="c1"&gt;# invalidate product cache if option changes &lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="ss"&gt;:product&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;touch: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="c1"&gt;# invalidate product cache if promos change&lt;/span&gt;
  &lt;span class="n"&gt;has_and_belongs_to_many&lt;/span&gt; &lt;span class="ss"&gt;:promos&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                          &lt;span class="ss"&gt;after_add: :touch_updated_at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                          &lt;span class="ss"&gt;after_remove: :touch_updated_at&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;touch_updated_at&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_promo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;touch&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;persisted?&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;h2&gt;
  
  
  Things to take into consideration
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;It's better to have a separate Redis database number for the cache. There could be a case that Redis will be full of cache entries and remove e.g., Sidekiq-related data. (it depends on the Redis eviction config)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Beware of untrusted input. There could be a case when an attacker could fill your cache up by sending different versions of parameters.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;E.g., if you have the parameter search, better not cache it as every cache will be different.&lt;/p&gt;

&lt;p&gt;You could easily disable cache for specific params&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;    &lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;optional&lt;/span&gt; &lt;span class="ss"&gt;:search&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="n"&gt;get&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;products&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;some_query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;caching&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:search&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;blank?&lt;/span&gt;
      &lt;span class="n"&gt;present&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"API::V2::Products"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;caching: &lt;/span&gt;&lt;span class="n"&gt;caching&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;p&gt;Documentation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://guides.rubyonrails.org/caching_with_rails.html" rel="noopener noreferrer"&gt;https://guides.rubyonrails.org/caching_with_rails.html&lt;/a&gt; Rails caching guide&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://apidock.com/rails/ActiveRecord/Base/cache_key" rel="noopener noreferrer"&gt;https://apidock.com/rails/ActiveRecord/Base/cache_key&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;
&lt;a href="https://apidock.com/rails/ActiveRecord/Relation/cache_key" rel="noopener noreferrer"&gt;https://apidock.com/rails/ActiveRecord/Relation/cache_key&lt;/a&gt; &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Source code:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/rails/rails/blob/b9d6759401c3d50a51e0a7650cb2331f4218d11f/activerecord/lib/active_record/integration.rb#L114" rel="noopener noreferrer"&gt;cache_key_with_version&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/rails/rails/blob/b9d6759401c3d50a51e0a7650cb2331f4218d11f/activerecord/lib/active_record/relation.rb#L436" rel="noopener noreferrer"&gt;How &lt;code&gt;cache_key&lt;/code&gt; computed from SQL&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Examples of caching in some projects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Spree &lt;a href="https://github.com/rails/rails/blob/b9d6759401c3d50a51e0a7650cb2331f4218d11f/activerecord/lib/active_record/relation.rb#L436" rel="noopener noreferrer"&gt;1&lt;/a&gt; &lt;a href="https://github.com/spree/spree/blob/4c3c662c686bafb8b636a6ebb11d5e8149bb9c71/api/app/serializers/spree/api/v2/base_serializer.rb#L7" rel="noopener noreferrer"&gt;2&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Gitlab &lt;a href="https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/models/concerns/reactive_caching.rb" rel="noopener noreferrer"&gt;1&lt;/a&gt; &lt;a href="https://docs.gitlab.com/ee/development/caching.html" rel="noopener noreferrer"&gt;2&lt;/a&gt; &lt;a href="https://docs.gitlab.com/ee/development/utilities.html#requestcache" rel="noopener noreferrer"&gt;3&lt;/a&gt; &lt;a href="https://gitlab.com/gitlab-org/gitlab/-/tree/master/lib/gitlab/cache?ref_type=heads" rel="noopener noreferrer"&gt;4&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;ActiveModel Serializers &lt;a href="https://github.com/rails-api/active_model_serializers/issues/1829#issuecomment-233425940" rel="noopener noreferrer"&gt;1&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Additional concepts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Russian-doll caching&lt;/li&gt;
&lt;li&gt;Push vs Pull caching strategies in System Design&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>performance</category>
      <category>caching</category>
    </item>
    <item>
      <title>How to fix a segfault in Ruby</title>
      <dc:creator>Dmitry Daw</dc:creator>
      <pubDate>Sat, 22 Jun 2024 19:16:22 +0000</pubDate>
      <link>https://dev.to/haukot/how-to-fix-a-segfault-in-ruby-3584</link>
      <guid>https://dev.to/haukot/how-to-fix-a-segfault-in-ruby-3584</guid>
      <description>&lt;p&gt;Let's say you got a "Segmentation fault" error in Ruby&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[BUG] Segmentation fault at 0x0000000000000028
ruby 3.4.0preview1 (2024-05-16 master 9d69619623) [x86_64-linux-musl]

-- Machine register context ------------------------------------------------
 RIP: 0x00007fefe4cd4886 RBP: 0x0000000000000001 RSP: 0x00007fefc95d3a10
 RAX: 0x0000000000000001 RBX: 0x00007fefc94212e0 RCX: 0x00007fefc95d0b70
 RDX: 0x0000000000000010 RDI: 0x0000000000000000 RSI: 0x00007fefc95d08f0
  R8: 0x0000000000000000  R9: 0x0000000000000000 R10: 0x0000000000000000
 R11: 0x0000000000000217 R12: 0x00007fefc9421340 R13: 0x00007fff5a0ec750
 R14: 0x00007fefe4649b10 R15: 0x00007fefc95d3b38 EFL: 0x0000000000010202

-- Other runtime information -----------------------------------------------

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

&lt;/div&gt;



&lt;p&gt;0x0000000000000028 near zero points out that something is NULL, but that's not much. To get more info, you could run your program under &lt;code&gt;gdb&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/app # gdb -q --args ruby test.rb

(gdb) 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we are in a debugger. To run your program write &lt;code&gt;run&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(gdb) run
Starting program: /usr/local/bin/ruby test.rb
warning: Error disabling address space randomization: Operation not permitted
[New LWP 36]
[New LWP 37]
[New LWP 38]
execution expired

Thread 4 "ruby" received signal SIGSEGV, Segmentation fault.
[Switching to LWP 38]
0x00007f0a2c33b886 in freeaddrinfo (p=0x0) at src/network/freeaddrinfo.c:10
warning: 10     src/network/freeaddrinfo.c: No such file or directory
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Okay, we see there is something with system's &lt;code&gt;src/network/freeaddrinfo.c&lt;/code&gt;.&lt;br&gt;
Let's get backtrace and check variables&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(gdb) bt
#0  0x00007f0a2c33b886 in freeaddrinfo (p=0x0) at src/network/freeaddrinfo.c:10
#1  0x00007f0a10c1e940 in do_getaddrinfo (ptr=0x7f0a10f61200) at raddrinfo.c:426
#2  0x00007f0a2c35c349 in start (p=0x7f0a10afaa88) at src/thread/pthread_create.c:207
#3  0x00007f0a2c35e95f in __clone () at src/thread/x86_64/clone.s:22
Backtrace stopped: frame did not save the PC

(gdb) info args
p = 0x0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Okay, now we see that the problem comes from ruby's &lt;code&gt;raddrinfo.c&lt;/code&gt;, and there is argument &lt;code&gt;p&lt;/code&gt; that is NULL.&lt;/p&gt;

&lt;p&gt;We could also go up in the stack, and check variables&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(gdb) info locals
cnt = 1
b = &amp;lt;optimized out&amp;gt;

(gdb) frame 1
#1  0x00007f1068ec6940 in do_getaddrinfo (ptr=0x7f1068cf0c40) at raddrinfo.c:426
warning: 426    raddrinfo.c: No such file or directory

(gdb) info args
ptr = 0x7f1068cf0c40

(gdb) info locals
arg = 0x7f1068cf0c40
err = &amp;lt;optimized out&amp;gt;
gai_errno = &amp;lt;optimized out&amp;gt;
need_free = 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we're prepared to look what is happening in the code. Lets check &lt;code&gt;raddrinfo.c&lt;/code&gt;, line 426&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ext/socket/raddrinfo.c&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arg&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;cancelled&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;freeaddrinfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arg&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;ai&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Indeed &lt;code&gt;freeaddrinfo&lt;/code&gt; is called. &lt;br&gt;
Now could make a bug in &lt;a href="https://bugs.ruby-lang.org/" rel="noopener noreferrer"&gt;https://bugs.ruby-lang.org/&lt;/a&gt;, or try to debug it by yourself.&lt;/p&gt;

&lt;p&gt;I've tried :) &lt;/p&gt;

&lt;p&gt;We're on Alpine, on &lt;code&gt;ruby:3.3.3-alpine&lt;/code&gt;. And on different system, e.g. &lt;code&gt;ruby:3.3.3&lt;/code&gt; is all okay, so it should be something with Alpine.&lt;/p&gt;

&lt;p&gt;Some search tells us that indeed: freeaddrinfo in Alpine's musl library does not accept NULL pointer(&lt;a href="https://git.musl-libc.org/cgit/musl/tree/src/network/freeaddrinfo.c" rel="noopener noreferrer"&gt;link&lt;/a&gt;), in difference with glibc(which is used e.g. in Ubuntu)(&lt;a href="https://github.com/bminor/glibc/blob/5aa2f79691ca6a40a59dfd4a2d6f7baff6917eb7/nss/getaddrinfo.c#L2619" rel="noopener noreferrer"&gt;link&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;So let's fix it.&lt;/p&gt;

&lt;p&gt;Firstly we need to build ruby. For convenience lets create a small Dockerfile&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FROM alpine:3.20

WORKDIR /usr/src/app

RUN apk update &amp;amp;&amp;amp; apk add autoconf gcc build-base ruby ruby-dev openssl openssl-dev yaml-dev zlib-dev yaml gdb

CMD sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Clone ruby&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ git clone --depth 1 git@github.com:ruby/ruby.git
$ cd ruby
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Go inside our alpine docker container&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ docker build -t my-ruby-develop .
$ docker run -it --rm -v $(pwd):/usr/src/app -w /usr/src/app  my-ruby-develop sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And build the latest ruby version - to check the bug is still present.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ ./autogen.sh
$ mkdir build &amp;amp;&amp;amp; cd build
$ mkdir rubies
$ ../configure --prefix="/usr/src/myapp/build/rubies/ruby-master" &amp;amp;&amp;amp; make &amp;amp;&amp;amp; make install
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we have our latest ruby in &lt;code&gt;/usr/src/myapp/build/rubies/ruby-master&lt;/code&gt; folder.&lt;/p&gt;

&lt;p&gt;Let's check the problem is still exist&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/app # /usr/src/app/build/rubies/ruby-master/bin/ruby test.rb
Operation timed out - user specified timeout
[BUG] Segmentation fault at 0x0000000000000028

-- Machine register context ------------------------------------------------
 RIP: 0x00007f561acf6886 RBP: 0x0000000000000001 RSP: 0x00007f55ff5d2a10
 RAX: 0x0000000000000001 RBX: 0x00007f55ff43ff30 RCX: 0x00007f55ff5cfb70
 RDX: 0x0000000000000010 RDI: 0x0000000000000000 RSI: 0x00007f55ff5cf8f0
  R8: 0x0000000000000000  R9: 0x0000000000000000 R10: 0x0000000000000000
 R11: 0x0000000000000217 R12: 0x00007f55ff43ff90 R13: 0x00007f55ff236040
 R14: 0x00007f55ff236b38 R15: 0x00007f55ff5d2b38 EFL: 0x0000000000010202
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is for sure. &lt;/p&gt;

&lt;p&gt;Then we need to&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;check in which case the problem is happening&lt;/li&gt;
&lt;li&gt;find if it should be changed inside Alpine or Ruby&lt;/li&gt;
&lt;li&gt;are there other places that could be related to the same problem&lt;/li&gt;
&lt;li&gt;make a reproducible example&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;etc etc - typical engineering work.&lt;/p&gt;

&lt;p&gt;In our case, it should be changed inside ruby. Let's make the change&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ext/socket/raddrinfo.c&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arg&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;cancelled&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arg&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;ai&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;freeaddrinfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arg&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;ai&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make the ruby(with &lt;code&gt;make clean&lt;/code&gt; first) and try again. It works!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/app # make clean &amp;amp;&amp;amp; ../configure --prefix="/usr/src/myapp/build/rubies/ruby-master" &amp;amp;&amp;amp; make &amp;amp;&amp;amp; make install
/app # /usr/src/app/build/rubies/ruby-master/bin/ruby test.rb
Good
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Great! Now we can run the tests, for related file, and the whole set&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ make test-all TESTS=../test/socket/test_addrinfo.rb
$ make test-all
$ make test-spec
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And if possible - write a test for your change(in this case it is hard to write a reliable test because of getaddrinfo internals).&lt;/p&gt;

&lt;p&gt;Don't forget to describe a bug in ruby's bugtracker, and attach all useful info(e.g. &lt;a href="https://bugs.ruby-lang.org/issues/20592" rel="noopener noreferrer"&gt;https://bugs.ruby-lang.org/issues/20592&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;And voila! You made the world a bit better &lt;a href="https://github.com/ruby/ruby/commit/fba8aff7af450e476e97b62385427dfa51850955" rel="noopener noreferrer"&gt;https://github.com/ruby/ruby/commit/fba8aff7af450e476e97b62385427dfa51850955&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Links
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/ruby/ruby/wiki/How-To-Contribute" rel="noopener noreferrer"&gt;https://github.com/ruby/ruby/wiki/How-To-Contribute&lt;/a&gt; How to contribute&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/ruby/ruby/wiki/Developer-How-To" rel="noopener noreferrer"&gt;https://github.com/ruby/ruby/wiki/Developer-How-To&lt;/a&gt; Developer How To&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/ko1/rubyhackchallenge/blob/master/EN/4_bug.md" rel="noopener noreferrer"&gt;https://github.com/ko1/rubyhackchallenge/blob/master/EN/4_bug.md&lt;/a&gt; How to work with bugs in ruby&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.ruby-lang.org/en/master/contributing/building_ruby_md.html" rel="noopener noreferrer"&gt;https://docs.ruby-lang.org/en/master/contributing/building_ruby_md.html&lt;/a&gt; How to build ruby&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.ruby-lang.org/en/master/contributing/testing_ruby_md.html" rel="noopener noreferrer"&gt;https://docs.ruby-lang.org/en/master/contributing/testing_ruby_md.html&lt;/a&gt; How to test ruby&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/ko1/rubyhackchallenge/blob/master/bib.md" rel="noopener noreferrer"&gt;https://github.com/ko1/rubyhackchallenge/blob/master/bib.md&lt;/a&gt; Materials on Ruby internals&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ruby</category>
      <category>gdb</category>
      <category>opensource</category>
      <category>rails</category>
    </item>
    <item>
      <title>How to use rbtrace from outside of docker container</title>
      <dc:creator>Dmitry Daw</dc:creator>
      <pubDate>Sat, 15 Jun 2024 09:02:45 +0000</pubDate>
      <link>https://dev.to/haukot/how-to-use-rbtracer-from-outside-of-docker-container-kdf</link>
      <guid>https://dev.to/haukot/how-to-use-rbtracer-from-outside-of-docker-container-kdf</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/tmm1/rbtrace" rel="noopener noreferrer"&gt;Rbtrace&lt;/a&gt; is a great tool to see which functions your app is calling at the time.&lt;/p&gt;

&lt;p&gt;In most cases you want it to be installed inside a docker container, but sometimes you can't. &lt;/p&gt;

&lt;p&gt;In this cases, you could run it from outside, but you need to share several things with the host:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;IPC(Inter-process communication) namespace. Rbtrace is using IPC to connect his inside app process with the outside tracer. &lt;/li&gt;
&lt;li&gt;PID namespace. Rbtrace is sending signals to PID, so you should be able to send these.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;/tmp&lt;/code&gt; folder. Rbtrace creates sockets to connect with process, like &lt;code&gt;/tmp/rbtrace-1688246.sock&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So, you can setup your app like this:&lt;/p&gt;

&lt;h3&gt;
  
  
  Inside the app
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;docker-compose.yml&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# ...&lt;/span&gt;
    &lt;span class="na"&gt;ipc&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;host&lt;/span&gt;
    &lt;span class="na"&gt;pid&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;host&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/tmp/:/tmp&lt;/span&gt;
      &lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Gemfile&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'rbtrace'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Outside the app
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# run as root&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;rbtrace &lt;span class="nt"&gt;-p&lt;/span&gt; 1688246 &lt;span class="nt"&gt;--firehose&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;where 1688246 - is PID of the process you want to trace. &lt;br&gt;
And voila! You could get your nice and shiny traces, e.g.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Kernel#public_send                                      
  Puma::ThreadPool#reap                                    
    Thread::Mutex#synchronize                                  
      Array#reject                                      
        Thread#alive? &amp;lt;0.000002&amp;gt;                                  
        Thread#alive? &amp;lt;0.000001&amp;gt;                              
        Thread#alive? &amp;lt;0.000001&amp;gt;                                 
        Thread#alive? &amp;lt;0.000001&amp;gt;                                  
        Thread#alive? &amp;lt;0.000002&amp;gt;                               
      Array#reject &amp;lt;0.000020&amp;gt;                                  
      Array#each &amp;lt;0.000002&amp;gt;
      Array#delete_if
        Array#include? &amp;lt;0.000002&amp;gt;
        Array#include? &amp;lt;0.000001&amp;gt;
        Array#include? &amp;lt;0.000001&amp;gt;
        Array#include? &amp;lt;0.000001&amp;gt;
        Array#include? &amp;lt;0.000002&amp;gt;
      Array#delete_if &amp;lt;0.000019&amp;gt;
    Thread::Mutex#synchronize &amp;lt;0.000053&amp;gt; 
  Puma::ThreadPool#reap &amp;lt;0.000058&amp;gt;
Kernel#public_send &amp;lt;0.000067&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Another great parameter for &lt;code&gt;rbtrace&lt;/code&gt; is &lt;code&gt;--slow&lt;/code&gt;, which shows only methods that took more than N ms.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rbtrace -p 1746688 --slow=500 &amp;gt; trace.slow.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; rbtrace uses PID in three ways - as PID to send signals, as a key for IPC message queue, and as a prefix for the socket file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links:
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://github.com/tmm1/rbtrace" rel="noopener noreferrer"&gt;rbtrace&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/tmm1/rbtrace/blob/master/lib/rbtrace/rbtracer.rb#L37" rel="noopener noreferrer"&gt;usage as pid&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/tmm1/rbtrace/blob/master/lib/rbtrace/rbtracer.rb#L56" rel="noopener noreferrer"&gt;usage as IPC key&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/tmm1/rbtrace/blob/master/lib/rbtrace/rbtracer.rb#L95" rel="noopener noreferrer"&gt;usage as socket path&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>ruby</category>
      <category>performance</category>
      <category>docker</category>
      <category>rails</category>
    </item>
    <item>
      <title>Base test profiling inside docker with test-prof</title>
      <dc:creator>Dmitry Daw</dc:creator>
      <pubDate>Thu, 30 May 2024 18:32:27 +0000</pubDate>
      <link>https://dev.to/haukot/base-test-profiling-inside-docker-with-test-prof-9d4</link>
      <guid>https://dev.to/haukot/base-test-profiling-inside-docker-with-test-prof-9d4</guid>
      <description>&lt;p&gt;Install &lt;code&gt;test-prof&lt;/code&gt; and &lt;code&gt;stackprof&lt;/code&gt; gems.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;group&lt;/span&gt; &lt;span class="ss"&gt;:test&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s2"&gt;"test-prof"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 1.0"&lt;/span&gt;
  &lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s2"&gt;"stackprof"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&amp;gt;= 0.2.9'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;require: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run test with flag &lt;code&gt;TEST_STACK_PROF=1&lt;/code&gt;. Better to run only part of tests or use &lt;code&gt;SAMPLE=10&lt;/code&gt; flag, otherwise you could get too big of a stack dump.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TEST_STACK_PROF=1 bundle exec rspec spec/my_test.rb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then you could visualize it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Visualize with speedscope
&lt;/h2&gt;

&lt;p&gt;Another way is to use &lt;a href="https://github.com/jlfwong/speedscope" rel="noopener noreferrer"&gt;speedscope&lt;/a&gt;, as it provides better interface. For this, generate &lt;code&gt;json&lt;/code&gt; output&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;stackprof &lt;span class="nt"&gt;--flamegraph&lt;/span&gt; tmp/test_prof/stack-prof-report-wall-raw-total.dump &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; tmp/test_prof/stack-prof-report-wall-raw-total.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And upload the file to speedscope, e.g. at &lt;a href="https://www.speedscope.app/" rel="noopener noreferrer"&gt;https://www.speedscope.app/&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Visualize with stackprof's viewer
&lt;/h2&gt;

&lt;p&gt;Then convert flamegraph dump to html&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;stackprof &lt;span class="nt"&gt;--flamegraph&lt;/span&gt; tmp/test_prof/stack-prof-report-wall-raw-total.dump &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; tmp/test_prof/stack-prof-report-wall-raw-total.html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then you could see the flamegraph with gem's viewer. From inside docker you could copy flamegraph.js and viewer.html to same shared folder, e.g. tmp folder in project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cp&lt;/span&gt; /usr/local/bundle/gems/stackprof-0.2.26/lib/stackprof/flamegraph/viewer.html ./tmp/test_prof/
&lt;span class="nb"&gt;cp&lt;/span&gt; /usr/local/bundle/gems/stackprof-0.2.26/lib/stackprof/flamegraph/flamegraph.js ./tmp/test_prof/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then open in the browser url like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;file:///home/my/path_to_project_folder/tmp/test_prof/viewer.html?data=/home/my/path_to_project_folder/tmp/test_prof/stack-prof-report-wall-raw-total.html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Links
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/test-prof/test-prof" rel="noopener noreferrer"&gt;https://github.com/test-prof/test-prof&lt;/a&gt; Test-prof gem&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://evilmartians.com/chronicles/testprof-a-good-doctor-for-slow-ruby-tests" rel="noopener noreferrer"&gt;https://evilmartians.com/chronicles/testprof-a-good-doctor-for-slow-ruby-tests&lt;/a&gt; article about usage&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>rails</category>
      <category>testing</category>
      <category>performance</category>
    </item>
    <item>
      <title>Is ActiveRecord right in omitting parentheses in queries? (and how ChatGPT lies again)</title>
      <dc:creator>Dmitry Daw</dc:creator>
      <pubDate>Tue, 16 Apr 2024 11:45:19 +0000</pubDate>
      <link>https://dev.to/haukot/is-activerecord-right-in-omitting-parentheses-in-queries-and-how-chatgpt-lies-again-2dao</link>
      <guid>https://dev.to/haukot/is-activerecord-right-in-omitting-parentheses-in-queries-and-how-chatgpt-lies-again-2dao</guid>
      <description>&lt;p&gt;I need to get a selection of users with an SQL query like this:&lt;/p&gt;

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

&lt;span class="n"&gt;sql&lt;/span&gt; &lt;span class="o"&gt;=&amp;lt;&amp;lt;~&lt;/span&gt;&lt;span class="no"&gt;SQL&lt;/span&gt;&lt;span class="sh"&gt;
    token IS NULL OR
    (
      token = 'some_token'
      AND (
        state = 'cancelled' AND created_at &amp;gt; ?
        OR state = 'submitted'
      )
    )
&lt;/span&gt;&lt;span class="no"&gt;  SQL&lt;/span&gt;
&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;I don't like to have SQL in my queries — all hidden code in scopes is now pops up, and it is hard to compose.&lt;/p&gt;

&lt;p&gt;But could I rewrite this query with ActiveRecord? Let's try:&lt;/p&gt;

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

&lt;span class="no"&gt;User&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;token: &lt;/span&gt;&lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;or&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;token: &lt;/span&gt;&lt;span class="s1"&gt;'some_token'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;and&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;state: &lt;/span&gt;&lt;span class="s1"&gt;'cancelled'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;or&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'created_at &amp;gt; ?'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;Looks better, but does it generate the same SQL?&lt;/p&gt;

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

&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="nv"&gt;"users"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;"users"&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;"users"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"token"&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
       &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="nv"&gt;"users"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"token"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'some_token'&lt;/span&gt;
       &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;"users"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"state"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'cancelled'&lt;/span&gt;
            &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'2024-04-16 10:46:43.129109'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;Hm, where do the parentheses after &lt;code&gt;IS NULL OR&lt;/code&gt; go? Doesn't it look like a different condition?&lt;/p&gt;

&lt;p&gt;Tests are passing, but if ActiveRecord omits parentheses - wouldn't it be problematic in some cases?&lt;/p&gt;

&lt;p&gt;For example, if we have an expression like this &lt;code&gt;a || (b &amp;amp;&amp;amp; (c || d))&lt;/code&gt; - would it be the same if there were no parentheses, like &lt;code&gt;a || b &amp;amp;&amp;amp; c || d&lt;/code&gt;?&lt;/p&gt;

&lt;p&gt;We are programmers in the modern era, so let's ask ChatGPT.&lt;/p&gt;

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

&lt;p&gt;So it seems all is okay. But let's (just for the sake of the experiment) check manually too:&lt;/p&gt;

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

&lt;span class="n"&gt;booleans&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;combinations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;booleans&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;product&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;booleans&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;booleans&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;booleans&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;combinations&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;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;expression1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="n"&gt;expression2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;expression1&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;expression2&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"a: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, b: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, c: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, Expression1: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;expression1&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, Expression2: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;expression2&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="ss"&gt;a: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;b: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;c: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Expression1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Expression2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="ss"&gt;a: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;b: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;c: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Expression1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Expression2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;So, it's actually not. This robotic liar!&lt;/p&gt;

&lt;p&gt;Okay, &lt;code&gt;a &amp;amp;&amp;amp; (b || (c &amp;amp;&amp;amp; d))&lt;/code&gt; and &lt;code&gt;a &amp;amp;&amp;amp; b || c &amp;amp;&amp;amp; d&lt;/code&gt; are not equivalent.&lt;br&gt;
Let's check another one:&lt;/p&gt;

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

&lt;span class="n"&gt;combinations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;booleans&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;product&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;booleans&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;booleans&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;booleans&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;booleans&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                &lt;span class="n"&gt;booleans&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;booleans&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;booleans&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;booleans&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;booleans&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;combinations&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;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;expression1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
  &lt;span class="n"&gt;expression2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;expression1&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;expression2&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"a: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, b: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, c: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, Expression1: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;expression1&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, Expression2: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;expression2&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;These are the same! But how? So many groups we have!&lt;/p&gt;

&lt;p&gt;How's that? &lt;strong&gt;It's because of logical operators' precedence&lt;/strong&gt;: logical AND evaluates before logical OR.&lt;/p&gt;

&lt;p&gt;So in &lt;code&gt;a || b &amp;amp;&amp;amp; c&lt;/code&gt;, it will first evaluate &lt;code&gt;b &amp;amp;&amp;amp; c&lt;/code&gt;, and then &lt;code&gt;||&lt;/code&gt;. We could see it like this: &lt;code&gt;a || (b &amp;amp;&amp;amp; c)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;And it's the same in most languages - in Ruby and PostgreSQL for sure.&lt;/p&gt;

&lt;p&gt;To make it easier to understand, we could rewrite expressions as multiplication and addition: because logical AND with binary values works exactly like normal arithmetic multiplication, and logical OR works in many senses as arithmetic addition.&lt;/p&gt;

&lt;p&gt;E.g. &lt;code&gt;a + (b * c)&lt;/code&gt; is the same as &lt;code&gt;a + b * c&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;And in our expressions:&lt;/p&gt;

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

  &lt;span class="c1"&gt;# these are not the same&lt;/span&gt;
  &lt;span class="n"&gt;expression1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="n"&gt;expression2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;

  &lt;span class="c1"&gt;# these are the same&lt;/span&gt;
  &lt;span class="n"&gt;expression3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
  &lt;span class="n"&gt;expression4&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;Now it's starting to make sense - ActiveRecord is in its right to omit parentheses in our SQL example, as it is similar to &lt;code&gt;expression3&lt;/code&gt;. And it's actually an optimization to send less data over the network.&lt;/p&gt;

&lt;p&gt;But let's check, does it work correctly with expressions like &lt;code&gt;a &amp;amp;&amp;amp; (b || (c &amp;amp;&amp;amp; d))&lt;/code&gt;?&lt;/p&gt;

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

&lt;span class="no"&gt;User&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;token: &lt;/span&gt;&lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;and&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;token: &lt;/span&gt;&lt;span class="s1"&gt;'some_token'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;or&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;state: &lt;/span&gt;&lt;span class="s1"&gt;'cancelled'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'created_at &amp;gt; ?'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;


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

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

&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="nv"&gt;"users"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;"users"&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="nv"&gt;"users"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"token"&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;"users"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"token"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'some_token'&lt;/span&gt;
       &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="nv"&gt;"users"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"state"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'cancelled'&lt;/span&gt;
       &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'2024-04-16 11:15:36.584382'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;Good - parentheses after &lt;code&gt;AND&lt;/code&gt; are in their place! &lt;/p&gt;

&lt;p&gt;And if we rewrite &lt;code&gt;OR&lt;/code&gt; to &lt;code&gt;AND&lt;/code&gt;?&lt;/p&gt;

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

&lt;span class="no"&gt;User&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;token: &lt;/span&gt;&lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;and&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;token: &lt;/span&gt;&lt;span class="s1"&gt;'some_token'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;and&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;state: &lt;/span&gt;&lt;span class="s1"&gt;'cancelled'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'created_at &amp;gt; ?'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;


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

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

&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="nv"&gt;"users"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;"users"&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="nv"&gt;"users"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"token"&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="nv"&gt;"users"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"token"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'some_token'&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="nv"&gt;"users"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"state"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'cancelled'&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'2024-04-16 11:20:36.296399'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;It omits all parentheses—and rightfully so.&lt;/p&gt;

&lt;p&gt;Okay, but we could use not only the &lt;code&gt;.or&lt;/code&gt; method but also &lt;code&gt;OR&lt;/code&gt; directly, like &lt;code&gt;.where("... OR ...")&lt;/code&gt;. Does ActiveRecord handle this? &lt;br&gt;
This is easy — &lt;code&gt;.where(sql)&lt;/code&gt; always wraps the expression inside it in parentheses:&lt;/p&gt;

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

&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;token: &lt;/span&gt;&lt;span class="s1"&gt;'some_token'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"state = 'cancelled' OR created_at &amp;gt; ?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


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

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

&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="nv"&gt;"users"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;"users"&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="nv"&gt;"users"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"token"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'some_token'&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'cancelled'&lt;/span&gt;
       &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'2024-04-16 11:23:00.877269'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;Even if it's only one condition&lt;/p&gt;

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

&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;token: &lt;/span&gt;&lt;span class="s1"&gt;'some_token'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"state = 'cancelled'"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_sql&lt;/span&gt;


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

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

&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="nv"&gt;"users"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;"users"&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="nv"&gt;"users"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"token"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'some_token'&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'cancelled'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;So, it seems all is good, and we could use our nice and clean ActiveRecord! Nice!&lt;/p&gt;

&lt;p&gt;References:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.postgresql.org/docs/7.2/sql-precedence.html" rel="noopener noreferrer"&gt;Operator precedence in PostgreSQL&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ruby-doc.org/core-2.6.2/doc/syntax/precedence_rdoc.html" rel="noopener noreferrer"&gt;Operator precedence in Ruby&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>postgres</category>
      <category>chatgpt</category>
    </item>
    <item>
      <title>Deploy Anycable with Kamal(formely MRSK)</title>
      <dc:creator>Dmitry Daw</dc:creator>
      <pubDate>Thu, 03 Aug 2023 13:43:17 +0000</pubDate>
      <link>https://dev.to/haukot/deploy-anycable-with-mrsk-4gm2</link>
      <guid>https://dev.to/haukot/deploy-anycable-with-mrsk-4gm2</guid>
      <description>&lt;p&gt;Here we'll deploy &lt;a href="https://anycable.io/" rel="noopener noreferrer"&gt;Anycable&lt;/a&gt; wih &lt;a href="https://github.com/basecamp/kamal" rel="noopener noreferrer"&gt;Kamal(MRSK before)&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We need our Rails code for anycable-rpc, so anycable-rpc will clearly be a role.&lt;br&gt;
The Anycable websocket server (in our case, anycable-go) could be installed as an accessory, or as a role.&lt;/p&gt;

&lt;p&gt;To simplify the setup, we'll utilize &lt;a href="https://docs.anycable.io/ruby/cli?id=running-websocket-server-along-with-rpc" rel="noopener noreferrer"&gt;--server-command&lt;/a&gt; option of &lt;code&gt;anycable&lt;/code&gt;. &lt;br&gt;
This will run the specified command after anycable is launched.&lt;br&gt;
In our case - it'll initiate anycable-go.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bundle &lt;span class="nb"&gt;exec &lt;/span&gt;anycable &lt;span class="nt"&gt;--server-command&lt;/span&gt; &lt;span class="s2"&gt;"anycable-go"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To run anycable-go, we need to install its binary directly in our Rails app's Docker container:&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="c"&gt;# Dockerfile&lt;/span&gt;
&lt;span class="c"&gt;# ...&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://github.com/anycable/anycable-go/releases/download/v1.4.1/anycable-go-linux-amd64 &lt;span class="nt"&gt;-o&lt;/span&gt; anycable-go &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;chmod&lt;/span&gt; +x anycable-go
    &amp;amp;&amp;amp; cp anycable-go /usr/local/bin/anycable-go
&lt;span class="c"&gt;# ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  MRSK config
&lt;/h2&gt;

&lt;p&gt;We have websockets on the same host as our main web app. Therefore, we need to set up Traefik so that the /cable path will be handled by the anycable-go server, not the web.&lt;/p&gt;

&lt;p&gt;To do this, we can utilize this config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;    &lt;span class="c1"&gt;# role's options in config/deploy.yml&lt;/span&gt;
    &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;traefik.enable&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="na"&gt;traefik.http.routers.my_service-anycable.rule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;PathPrefix(`/cable`)'&lt;/span&gt;
      &lt;span class="na"&gt;traefik.http.routers.my_service-anycable.entrypoints&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;web'&lt;/span&gt;
      &lt;span class="na"&gt;traefik.http.services.my_service-anycable.loadbalancer.server.port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8080&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In &lt;code&gt;my_service-anycable&lt;/code&gt;, &lt;code&gt;my_service&lt;/code&gt; is the service name in MRSK, and &lt;code&gt;anycable&lt;/code&gt; is the role name in MRSK. &lt;br&gt;
This could also contain a destination, if you're using it.&lt;br&gt;
This name should be the same as the role's &lt;a href="https://github.com/mrsked/mrsk/blob/299b166db7f42263552170bd8d4a2e961b86e6ee/lib/mrsk/configuration/role.rb#L113" rel="noopener noreferrer"&gt;traefik_service&lt;/a&gt; name.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;traefik.http.routers.my_service-anycable.rule: 'PathPrefix(&lt;/code&gt;&lt;code&gt;/cable&lt;/code&gt;&lt;code&gt;)'&lt;/code&gt; adds a rule that we only want requests from the &lt;code&gt;/cable&lt;/code&gt; path.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;traefik.http.routers.my_service-anycable.entrypoints: 'web'&lt;/code&gt; sets the entrypoint to the default &lt;code&gt;web&lt;/code&gt; entrypoint at port &lt;code&gt;80&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;traefik.http.services.my_service-anycable.loadbalancer.server.port: 8080&lt;/code&gt; sets the port to which Traefik should send requests to our container.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then, we can add the necessary Anycable environment variables and set up the MRSK deploy config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/deploy.yml&lt;/span&gt;

&lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my_service&lt;/span&gt;
&lt;span class="na"&gt;servers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;web&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;10.0.0.155&lt;/span&gt;
  &lt;span class="c1"&gt;# anycable-rpc together with anycable-go&lt;/span&gt;
  &lt;span class="na"&gt;anycable&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;hosts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;10.0.0.155&lt;/span&gt;
    &lt;span class="na"&gt;cmd&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bundle exec anycable --server-command "anycable-go"&lt;/span&gt;
    &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;clear&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;ANYCABLE_RPC_HOST&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;localhost:50051&lt;/span&gt;
        &lt;span class="na"&gt;ANYCABLE_HOST&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0.0.0.0"&lt;/span&gt;
        &lt;span class="na"&gt;ANYCABLE_PORT&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8080&lt;/span&gt;
        &lt;span class="na"&gt;ANYCABLE_REDIS_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;redis://:&amp;lt;%=&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;ENV['REDIS_PASSWORD']&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;%&amp;gt;@&amp;lt;%=&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;ENV['REDIS_HOST']&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;%&amp;gt;:6379/0"&lt;/span&gt;
        &lt;span class="na"&gt;ANYCABLE_RPC_HOST&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;localhost:50051"&lt;/span&gt;
    &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;traefik.enable&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="na"&gt;traefik.http.routers.my_service-anycable.rule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;PathPrefix(`/cable`)'&lt;/span&gt;
      &lt;span class="na"&gt;traefik.http.routers.my_service-anycable.entrypoints&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;web'&lt;/span&gt;
      &lt;span class="na"&gt;traefik.http.services.my_service-anycable.loadbalancer.server.port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8080&lt;/span&gt;

&lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;clear&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;ANYCABLE_REDIS_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;redis://:&amp;lt;%=&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;ENV['REDIS_PASSWORD']&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;%&amp;gt;@&amp;lt;%=&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;ENV['REDIS_HOST']&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;%&amp;gt;:6379/0"&lt;/span&gt;
    &lt;span class="na"&gt;REDIS_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;redis://:&amp;lt;%=&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;ENV['REDIS_PASSWORD']&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;%&amp;gt;@&amp;lt;%=&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;ENV['REDIS_HOST']&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;%&amp;gt;:6379/1"&lt;/span&gt;
    &lt;span class="na"&gt;ANYCABLE_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;wss://&amp;lt;%=&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;ENV['ACTIONCABLE_HOST']&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;%&amp;gt;/cable"&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;That's all! This sets up anycable-rpc with anycable-go on your server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Other deploy variants
&lt;/h2&gt;

&lt;p&gt;If you want to separate anycable-rpc and anycable-go, you have several options to do so.&lt;/p&gt;

&lt;h3&gt;
  
  
  Separate hosts
&lt;/h3&gt;

&lt;p&gt;You could deploy anycable-go as an accessory on another host, then you could use a different Docker image and not install it in the Rails app's Docker image.&lt;/p&gt;

&lt;p&gt;But in this setup, you need to publish ports for both anycable-rpc and anycable-go.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my_service&lt;/span&gt;
&lt;span class="na"&gt;servers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;web&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;10.0.0.155&lt;/span&gt;
  &lt;span class="na"&gt;anycable-rpc&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;hosts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;10.0.0.155&lt;/span&gt;
    &lt;span class="na"&gt;cmd&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bundle exec anycable&lt;/span&gt;
    &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;50051&lt;/span&gt;
    &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;clear&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;ANYCABLE_RPC_HOST&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;0.0.0.0:50051&lt;/span&gt;
    &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;traefik.tcp.routers.my_service-anycable-rpc.rule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;HostSNI(`*`)'&lt;/span&gt;
      &lt;span class="na"&gt;traefik.tcp.routers.my_service-anycable-rpc.entrypoints&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rpc&lt;/span&gt;
      &lt;span class="na"&gt;traefik.tcp.services.my_service-anycable-rpc.loadbalancer.server.port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;50051&lt;/span&gt;

&lt;span class="na"&gt;traefik&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;publish&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;50051:50051&lt;/span&gt;
  &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;entrypoints.web.address&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;:80"&lt;/span&gt;
    &lt;span class="na"&gt;entrypoints.rpc.address&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;:50051"&lt;/span&gt;
    &lt;span class="na"&gt;accesslog&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

&lt;span class="na"&gt;accessories&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;ws&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;anycable/anycable-go:1.4&lt;/span&gt;
    &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10.0.0.166&lt;/span&gt;
    &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;80:8080"&lt;/span&gt;
    &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;clear&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;ANYCABLE_HOST&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0.0.0.0"&lt;/span&gt;
        &lt;span class="na"&gt;ANYCABLE_PORT&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8080&lt;/span&gt;
        &lt;span class="na"&gt;ANYCABLE_REDIS_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;redis://:&amp;lt;%=&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;ENV['REDIS_PASSWORD']&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;%&amp;gt;@&amp;lt;%=&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;ENV['REDIS_HOST']&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;%&amp;gt;:6379/0"&lt;/span&gt;
        &lt;span class="na"&gt;ANYCABLE_RPC_HOST&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;10.0.0.155:50051"&lt;/span&gt;
        &lt;span class="na"&gt;ANYCABLE_DEBUG&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Setup anycable-go as accessory on the same host
&lt;/h3&gt;

&lt;p&gt;You (most likely) could use anycable-go as an accessory, and write Traefik configs in the labels of the accessory.&lt;br&gt;
But after doing this, you'll need to use the host's IP address or the Docker internal network (as mentioned below in other options).&lt;/p&gt;
&lt;h3&gt;
  
  
  Use the host's IP address
&lt;/h3&gt;

&lt;p&gt;Just publish the ports to the outside and use the host's IP address from inside the container.&lt;/p&gt;
&lt;h3&gt;
  
  
  Use the host's network
&lt;/h3&gt;

&lt;p&gt;Add the internal host IP as the Docker internal host.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;servers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;web&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;hosts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;10.0.0.155&lt;/span&gt;
  &lt;span class="na"&gt;anycable-rpc&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;hosts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;10.0.0.155&lt;/span&gt;
    &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;add-host"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;host.docker.internal:host-gateway&lt;/span&gt;
    &lt;span class="c1"&gt;# ... env, cmd, etc&lt;/span&gt;
  &lt;span class="na"&gt;anycable-go&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;hosts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;10.0.0.155&lt;/span&gt;
    &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;add-host"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;host.docker.internal:host-gateway&lt;/span&gt;
    &lt;span class="c1"&gt;# ... env, cmd, etc&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(&lt;a href="https://github.com/mrsked/mrsk/issues/41#issuecomment-1483857362" rel="noopener noreferrer"&gt;source&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Then you could use &lt;code&gt;host.docker.internal&lt;/code&gt; as host in anycable-rpc and anycable-go containers &lt;/p&gt;

&lt;h3&gt;
  
  
  Create docker internal network by hands and then use it
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Create the network first by ssh'ing into machine or using something like Ansible&lt;/span&gt;

&lt;span class="na"&gt;servers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;web&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;hosts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;10.0.1.183&lt;/span&gt;
    &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;network"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;my-network"&lt;/span&gt;

&lt;span class="na"&gt;traefik&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;network&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;my-network"&lt;/span&gt;

&lt;span class="na"&gt;accessories&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:14&lt;/span&gt;
    &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10.0.1.183&lt;/span&gt;
    &lt;span class="s"&gt;... etc&lt;/span&gt;
    &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;network&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;my-network"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(&lt;a href="https://github.com/mrsked/mrsk/issues/41#issuecomment-1522501391" rel="noopener noreferrer"&gt;source&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;And then use hosts from this network&lt;/p&gt;

</description>
      <category>rails</category>
      <category>anycable</category>
      <category>kamal</category>
      <category>ruby</category>
    </item>
    <item>
      <title>How to port VS Code plugins to your favorite editor(e.g. Emacs), with the example of Github Copilot Labs.</title>
      <dc:creator>Dmitry Daw</dc:creator>
      <pubDate>Sat, 01 Jul 2023 22:05:31 +0000</pubDate>
      <link>https://dev.to/haukot/how-to-use-vs-code-plugins-in-your-favorite-editoreg-emacs-with-the-example-of-github-copilot-labs-50pn</link>
      <guid>https://dev.to/haukot/how-to-use-vs-code-plugins-in-your-favorite-editoreg-emacs-with-the-example-of-github-copilot-labs-50pn</guid>
      <description>&lt;h3&gt;
  
  
  TLDR: Proof of concept code is here &lt;a href="https://github.com/haukot/copilot_labs_plugin_base" rel="noopener noreferrer"&gt;https://github.com/haukot/copilot_labs_plugin_base&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;VS Code plugins are essentially JS files that VS Code runs with its own callbacks. So we can write our own wrapper, which will define the functions needed for the plugin, and stub all other functions.&lt;/p&gt;

&lt;p&gt;A simple example is the &lt;a href="https://marketplace.visualstudio.com/items?itemName=GitHub.copilot-labs" rel="noopener noreferrer"&gt;Github Copilot Labs&lt;/a&gt; extension, because it only needs selection and command.&lt;/p&gt;

&lt;p&gt;VS Code extension is essentially a js module, which exports several functions. We are interested in &lt;code&gt;init()&lt;/code&gt; and &lt;code&gt;activate()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now, let's download it, unzip it, and move it to the &lt;code&gt;extension&lt;/code&gt; folder.&lt;/p&gt;

&lt;p&gt;Then we can initialize it&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;extension&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./extension/extension/dist/extension.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;extension&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;extension&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;activate&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It fails, because it needs vscode to run(obviously!). So let's create our own VS Code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// vscode/index.js&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;vscode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="nx"&gt;vscode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;MINE&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;vscode&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// package.json&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dependencies&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vscode&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;file:vscode&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now our extension will use our own VS Code! It still fails, though.&lt;br&gt;
We need to stub the methods the extension waits for, but most of them will be simple dummy methods like this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;vscode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Uri&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can see all of the methods &lt;a href="https://github.com/haukot/copilot_labs_plugin_base/blob/main/vscode/index.js" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now let's initialize extension(we have also added several dummy settings in &lt;code&gt;activate&lt;/code&gt;)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;extension&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;extension&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;activate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;extension&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;packageJSON&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vscode-copilot&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;subscriptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
  &lt;span class="na"&gt;globalState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;setKeysForSync&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Good, no errors! But we want output.&lt;br&gt;
Github Copilot Labs main feature is "Use brush", which is works as an executeCommand in VS Code.&lt;/p&gt;

&lt;p&gt;Let's look at our stub for commands&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// NOTE: this is our variable, not vscode's&lt;/span&gt;
&lt;span class="nx"&gt;vscode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_registeredCommands&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="nx"&gt;vscode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commands&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;registerCommand&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;command&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;vscode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_registeredCommands&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;command&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="c1"&gt;// NOTE: you need to replace this method to implement your functionality&lt;/span&gt;
  &lt;span class="na"&gt;executeCommand&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;command&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;vscode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_registeredCommands&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;vscode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_registeredCommands&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;](...&lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;NotRegistered&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It doesn't do anything complex, just registers the command from extension.js, and runs it by executeCommand.&lt;/p&gt;

&lt;p&gt;Let's change it for Copilot Labs. Its command accepts a brush name and changes current selection.&lt;br&gt;
So, let's redefine executeCommand so it'll accept text from an external source!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;vscode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commands&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;executeCommand&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ourParams&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;command&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;vscode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_registeredCommands&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;vscode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;activeTextEditor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;vscode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;activeTextEditor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;...{&lt;/span&gt;
          &lt;span class="na"&gt;document&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;getText&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentSelection&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="c1"&gt;// we already return content, so don't need to use currentSelection&lt;/span&gt;
              &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;ourParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fileContent&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="na"&gt;languageId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ourParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;languageId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;edit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;toChange&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="na"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;selection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;newContent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// we'll return whole content, so don't need to use selection&lt;/span&gt;
                &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newContent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
              &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="nf"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;toChange&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nx"&gt;vscode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_registeredCommands&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;](...&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;NotRegistered&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What's happening here:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We redefine the &lt;code&gt;window.activeTextEditor.document.getText&lt;/code&gt; function so it'll return our code.&lt;/li&gt;
&lt;li&gt;We redefine the &lt;code&gt;window.activeTextEditor.edit&lt;/code&gt; function, which will be called after the extension completes the command and tries to change current selection.&lt;/li&gt;
&lt;li&gt;We run &lt;code&gt;vscode._registeredCommands[name](...args)&lt;/code&gt;, which will run command inside extension.js and start the whole process.&lt;/li&gt;
&lt;li&gt;We wrap all this in a Promise, so we can get the result of the execution.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And now we could execute the command like this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;fileContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;def hello():&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s1"&gt;  print("Hello, world!")&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s1"&gt;hello()&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;languageId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;python&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;vscode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commands&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;executeCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;copilot-labs.use-brush&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;fileContent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;languageId&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;debug&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;RESULT&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ERROR&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Good! To integrate it with an IDE, we need some interface to run it, e.g. a jsonrpc server. IDE plugin will start it and send commands to it.&lt;br&gt;
It's not so interesting, so I'll just link the implementations: &lt;a href="https://github.com/haukot/copilot_labs_plugin_base/blob/main/index.js" rel="noopener noreferrer"&gt;IDE plugin's side&lt;/a&gt; and &lt;a href="https://github.com/haukot/copilot_labs_plugin_base/blob/main/agent.js" rel="noopener noreferrer"&gt;jsonrpc-server&lt;/a&gt;. (Sorry for many comments, for now I don't have to clean all this up :')&lt;/p&gt;

&lt;p&gt;This is a simple example with a simple extension, but it may pave the way for integrating more complex extensions too.&lt;/p&gt;

&lt;p&gt;An example of a similar approach I found in in the &lt;a href="https://github.com/yioneko/vtsls/" rel="noopener noreferrer"&gt;LSP wrapper for typescript extension of vscode&lt;/a&gt;.&lt;br&gt;
It goes much farther and uses vscode itself(see files &lt;a href="https://github.com/yioneko/vtsls/blob/d79cb577a277437cda9fe6b2ad30e20377f85f44/packages/service/src/shims/workspace.ts" rel="noopener noreferrer"&gt;1&lt;/a&gt;, &lt;a href="https://github.com/yioneko/vtsls/blob/d79cb577a2/packages/service/scripts/build.js#L50" rel="noopener noreferrer"&gt;2&lt;/a&gt;, &lt;a href="https://github.com/yioneko/vtsls/blob/d79cb577a277437cda9fe6b2ad30e20377f85f44/packages/service/vitest.config.ts#L21C1-L21C1" rel="noopener noreferrer"&gt;3&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;It seems like overkill for an extension like Copilot Labs, but maybe it could be more useful for wrapping some complex extensions.&lt;/p&gt;

&lt;p&gt;Goodbye, and happy hacking!&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>githubcopilot</category>
      <category>emacs</category>
      <category>vim</category>
    </item>
    <item>
      <title>Trick to cleaner use Sidekiq::Testing.inline! in tests</title>
      <dc:creator>Dmitry Daw</dc:creator>
      <pubDate>Mon, 12 Jun 2023 16:31:26 +0000</pubDate>
      <link>https://dev.to/haukot/trick-to-cleaner-use-sidekiqtestinginline-in-tests-3pa0</link>
      <guid>https://dev.to/haukot/trick-to-cleaner-use-sidekiqtestinginline-in-tests-3pa0</guid>
      <description>&lt;p&gt;By default, Sidekiq jobs don't run in tests. But sometimes, e.g. in e2e tests, you'd want them to run. Sidekiq provides a method &lt;code&gt;Sidekiq::Testing.inline!&lt;/code&gt; that enables job processing.&lt;/p&gt;

&lt;p&gt;If you run this method once, it enables job processing in all tests; if you run it with a block, it processes jobs only within that block. But it's not very clean to have blocks everywhere in your tests. So there's a trick to isolate &lt;code&gt;Sidekiq::Testing.inline!&lt;/code&gt; within the scope of your full test or specific contexts.&lt;/p&gt;

&lt;p&gt;You can utilize RSpec's configuration options to control when &lt;code&gt;Sidekiq::Testing.inline!&lt;/code&gt; is invoked. You can set it up to run based on the presence of specific metadata in your tests, like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# spec/spec_helper.rb&lt;/span&gt;
&lt;span class="no"&gt;RSpec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="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;around&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;example&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:sidekiq_inline&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
      &lt;span class="no"&gt;Sidekiq&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Testing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;inline!&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;example&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="k"&gt;else&lt;/span&gt;
      &lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this configuration, &lt;code&gt;Sidekiq::Testing.inline!&lt;/code&gt; will only run when the &lt;code&gt;:sidekiq_inline&lt;/code&gt; metadata is present and set to true in a test. This can be done as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="s1"&gt;'my context'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:sidekiq_inline&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="c1"&gt;# your tests go here&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;describe&lt;/span&gt; &lt;span class="s1"&gt;'my describe'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:sidekiq_inline&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="c1"&gt;# your tests go here&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s1"&gt;'my test'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:sidekiq_inline&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="c1"&gt;# your test goes here&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this approach, you can better control when Sidekiq jobs are processed during testing, leading to cleaner tests.&lt;/p&gt;

</description>
      <category>rails</category>
      <category>ruby</category>
      <category>sidekiq</category>
      <category>testing</category>
    </item>
    <item>
      <title>How to cache Kamal(MRSK) deployments in CI</title>
      <dc:creator>Dmitry Daw</dc:creator>
      <pubDate>Thu, 01 Jun 2023 17:04:39 +0000</pubDate>
      <link>https://dev.to/haukot/how-to-cache-mrsk-deployments-in-ci-52h9</link>
      <guid>https://dev.to/haukot/how-to-cache-mrsk-deployments-in-ci-52h9</guid>
      <description>&lt;p&gt;Caching is a key strategy in optimizing Docker deployments. But because CI don't saves previous docker layers, and, unfortunalety, MRSK doesn't have options like &lt;code&gt;--cache-to&lt;/code&gt;, MRSK(now Kamal) rebuilds each Docker container from scratch, leading to a slower deploys. &lt;/p&gt;

&lt;p&gt;There are two methods to implement caching with MRSK in GitHub Actions: pulling the Docker image before deployment and using the --skip_push option with the build command.&lt;/p&gt;

&lt;h2&gt;
  
  
  Method 1: Pull the Docker Image before Deployment
&lt;/h2&gt;

&lt;p&gt;This approach is suitable for simple Dockerfiles. &lt;br&gt;
First, the Docker image is pulled from the registry, then the MRSK deploy command is run. By pulling the Docker image first, MRSK can use the layers from the previously built image, reducing the need for a complete rebuild.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Login to DockerHub&lt;/span&gt;
  &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker/login-action@v2&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.DOCKERHUB_USERNAME }}&lt;/span&gt;
    &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.DOCKERHUB_PASSWORD }}&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Pull Docker Image&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;docker pull ${{ secrets.DOCKERHUB_USERNAME }}/your-image-name || &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy with MRSK&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;mrsk deploy&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this code snippet, the docker pull command pulls the Docker image from the registry. The || true part ensures that the workflow doesn't fail if the image doesn't exist yet (for instance, during the first run).&lt;/p&gt;

&lt;p&gt;But the problem with this method is that it's not working for workflows where only final Docker image is pushed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Method 2: Use the --skip_push Option and Build Command before Deployment
&lt;/h2&gt;

&lt;p&gt;This method is for complex workflows, for example for Dockerfiles which have separated final and builder images. Because in such Dockerfiles we push only final image, we can't get builder cache from pulling latest image.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;--skip_push&lt;/code&gt; option in MRSK not only skips the push, but also the Docker build process. Therefore, we can perform the build manually and then use MRSK for deployment only.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;MRSK envify&lt;/span&gt;
  &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;VERSION&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.sha }}&lt;/span&gt; &lt;span class="c1"&gt;# set version to ensure we'll build the same version&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;mrsk&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;envify&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-v'&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Set up Docker Buildx.&lt;/span&gt;
  &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker/setup-buildx-action@v2&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build and push&lt;/span&gt;
  &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker/build-push-action@v4&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
    &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.DOCKERHUB_USERNAME }}/your-image-name:${{ github.sha }},${{ secrets.DOCKERHUB_USERNAME }}/your-image-name:latest&lt;/span&gt;
    &lt;span class="na"&gt;cache-from&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;type=gha&lt;/span&gt; &lt;span class="c1"&gt;# https://docs.docker.com/build/cache/backends/gha/&lt;/span&gt;
    &lt;span class="na"&gt;cache-to&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;type=gha,mode=max&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;MRSK deploy&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;mrsk&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;deploy&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-vvvv&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;--skip_push'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In that way we could fully embrace CI cache and speedup deployments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://cloud.google.com/build/docs/optimize-builds/speeding-up-builds#using_a_cached_docker_image" rel="noopener noreferrer"&gt;https://cloud.google.com/build/docs/optimize-builds/speeding-up-builds#using_a_cached_docker_image&lt;/a&gt; Best practices to speedup deploys with Docker&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/mrsked/mrsk/pull/159" rel="noopener noreferrer"&gt;https://github.com/mrsked/mrsk/pull/159&lt;/a&gt; Closed PR about --cache-to option in MRSK&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/mrsked/mrsk/pull/111" rel="noopener noreferrer"&gt;https://github.com/mrsked/mrsk/pull/111&lt;/a&gt; Discussion about --skip_push option &lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.docker.com/build/cache/backends/gha/" rel="noopener noreferrer"&gt;https://docs.docker.com/build/cache/backends/gha/&lt;/a&gt; Github Actions backend for cache&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>kamal</category>
      <category>githubactions</category>
    </item>
    <item>
      <title>What to do if Sidekiq job is stuck</title>
      <dc:creator>Dmitry Daw</dc:creator>
      <pubDate>Fri, 27 Jan 2023 09:55:37 +0000</pubDate>
      <link>https://dev.to/haukot/what-to-do-if-sidekiq-job-is-stuck-1j1j</link>
      <guid>https://dev.to/haukot/what-to-do-if-sidekiq-job-is-stuck-1j1j</guid>
      <description>&lt;p&gt;If your Sidekiq job is stuck, your first intention may be to try to stop this job. But Sidekiq &lt;a href="https://github.com/mperham/sidekiq/issues/2907" rel="noopener noreferrer"&gt;doesn't have such mechanism&lt;/a&gt;, and it may make your worker stuck even futher, and to clean current processing job.&lt;/p&gt;

&lt;p&gt;So the first thing that should be - to get logs about what is happening. To do that, send the TTIN signal to dump backtraces to the log, with command &lt;code&gt;kill -TTIN [Sidekiq pid here. In docker container it most probably 1]&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ ps ax | grep sidekiq
PID TTY      STAT   TIME COMMAND                                                                                                                                            
  1 ?        Ssl   94:02 sidekiq 5.2.9 app [1 of 20 busy] 
$ kill -TTIN 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It will dump the logs to stdout. Depending on your application settings, you may want to check logs on your logs collector service, e.g. in Kibana dashboard.&lt;/p&gt;

&lt;p&gt;If the Sidekiq process is not responding, you can use GDB to dump backtraces for all threads with command &lt;code&gt;gdb [ruby exec] [process number]&lt;/code&gt;, like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ which ruby                                                                                                                                                               
#=&amp;gt; /usr/local/bin/ruby
$ gdb /usr/local/bin/ruby 1

(gdb) info threads
  Id   Target Id         Frame 
  37   Thread 0x7f8b289d8700 (LWP 7994) "ruby-timer-thr" 0x00007f8b27a20d13 in *__GI___poll (fds=&amp;lt;optimized out&amp;gt;, fds@entry=0x7f8b289d7ec0, nfds=&amp;lt;optimized out&amp;gt;, 
    nfds@entry=1, timeout=timeout@entry=100) at ../sysdeps/unix/sysv/linux/poll.c:87
  36   Thread 0x7f8b23eb0700 (LWP 7995) "ruby" pthread_cond_wait@@GLIBC_2.3.2 () at ../nptl/sysdeps/unix/sysv/linux/x86_64/pthread_cond_wait.S:162
  35   Thread 0x7f8b23c2e700 (LWP 7996) "ruby" pthread_cond_wait@@GLIBC_2.3.2 () at ../nptl/sysdeps/unix/sysv/linux/x86_64/pthread_cond_wait.S:162
  34   Thread 0x7f8b239ac700 (LWP 7997) "ruby" pthread_cond_wait@@GLIBC_2.3.2 () at ../nptl/sysdeps/unix/sysv/linux/x86_64/pthread_cond_wait.S:162
  33   Thread 0x7f8b237aa700 (LWP 7998) "ruby" pthread_cond_wait@@GLIBC_2.3.2 () at ../nptl/sysdeps/unix/sysv/linux/x86_64/pthread_cond_wait.S:162
  32   Thread 0x7f8b28844700 (LWP 8002) "SignalSender" sem_wait () at ../nptl/sysdeps/unix/sysv/linux/x86_64/sem_wait.S:86
  31   Thread 0x7f8b1e1bf700 (LWP 8003) "ruby" pthread_cond_wait@@GLIBC_2.3.2 () at ../nptl/sysdeps/unix/sysv/linux/x86_64/pthread_cond_wait.S:162
  30   Thread 0x7f8b1e0be700 (LWP 8006) "ruby" pthread_cond_wait@@GLIBC_2.3.2 () at ../nptl/sysdeps/unix/sysv/linux/x86_64/pthread_cond_wait.S:162
  29   Thread 0x7f8b1dd81700 (LWP 8009) "ruby" pthread_cond_wait@@GLIBC_2.3.2 () at ../nptl/sysdeps/unix/sysv/linux/x86_64/pthread_cond_wait.S:162
  28   Thread 0x7f8b1dc80700 (LWP 8010) "ruby" pthread_cond_wait@@GLIBC_2.3.2 () at ../nptl/sysdeps/unix/sysv/linux/x86_64/pthread_cond_wait.S:162
  27   Thread 0x7f8b1db7f700 (LWP 8011) "ruby" pthread_cond_wait@@GLIBC_2.3.2 () at ../nptl/sysdeps/unix/sysv/linux/x86_64/pthread_cond_wait.S:162
...

(gdb) set logging file gdb_output.txt
(gdb) set logging on
(gdb) set height 10000
(gdb) t a a bt
(gdb) quit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It will save logs to gdb_output.txt.&lt;/p&gt;

&lt;p&gt;You also can puts ruby logs to stdout, like so&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(gdb) call (void)rb_backtrace()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that, you can try to stop job in Sidekiq Admin with 'Stop' button, or with signals(TSTP to ask it to not pick up any new jobs, and TERM next).&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/mperham/sidekiq/wiki/Problems-and-Troubleshooting#frozen-processes" rel="noopener noreferrer"&gt;1&lt;/a&gt; Sidekiq FAQ about frozen processes&lt;br&gt;
&lt;a href="https://www.mikeperham.com/2013/05/03/dealing-with-stuck-workers/" rel="noopener noreferrer"&gt;2&lt;/a&gt; Another guide about frozen processes&lt;br&gt;
&lt;a href="https://newrelic.com/blog/best-practices/debugging-stuck-ruby-processes-what-to-do-before-you-kill-9" rel="noopener noreferrer"&gt;3&lt;/a&gt; How to use GDB with ruby processes&lt;br&gt;
&lt;a href="https://github.com/mperham/sidekiq/wiki/FAQ#how-do-i-cancel-a-sidekiq-job" rel="noopener noreferrer"&gt;4&lt;/a&gt; How to stop Sidekiq jobs(as your application mechanic)&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>gratitude</category>
      <category>tooling</category>
      <category>development</category>
    </item>
    <item>
      <title>Troubleshooting weird DB connection errors in Rails with PostgreSQL</title>
      <dc:creator>Dmitry Daw</dc:creator>
      <pubDate>Wed, 18 Jan 2023 13:28:08 +0000</pubDate>
      <link>https://dev.to/haukot/troubleshooting-weird-db-connection-errors-in-rails-with-postgresql-3g4e</link>
      <guid>https://dev.to/haukot/troubleshooting-weird-db-connection-errors-in-rails-with-postgresql-3g4e</guid>
      <description>&lt;p&gt;There are a number of errors that occur when attempting to use a database connection that has suddenly become dead.&lt;/p&gt;

&lt;p&gt;Errors like &lt;code&gt;PG::ConnectionBad: PQsocket() can't get socket descriptor:&lt;/code&gt;, &lt;code&gt;PG::UnableToSend: no connection to the server&lt;/code&gt;, &lt;code&gt;PG::ConnectionBad: PQconsumeInput() server closed the connection unexpectedly&lt;/code&gt;, &lt;code&gt;PG::UnableToSend: SSL connection has been closed unexpectedly&lt;/code&gt;, and so on.&lt;/p&gt;

&lt;p&gt;They may be caused by a few different factors, e.g. long command run, or long SSH connection, or closed connection in separate thread(like in &lt;a href="https://github.com/grosser/parallel/issues/62" rel="noopener noreferrer"&gt;'parallel' gem&lt;/a&gt;), or long code processing before save.&lt;/p&gt;

&lt;h2&gt;
  
  
  The cause
&lt;/h2&gt;

&lt;p&gt;If you see errors like that, and do have some slow processing, there are several things to check:&lt;/p&gt;

&lt;p&gt;1) Rails's database &lt;code&gt;idle_timeout&lt;/code&gt; setting, which is 300 seconds by default. This means that if a connection is idle for more than 300 seconds, the server will close it.&lt;br&gt;
2) PgBouncer &lt;code&gt;client_idle_timeout&lt;/code&gt; setting. This setting controls how long a connection can be idle before PgBouncer closes it. If this setting is set too low, it can cause the same errors as Rails's idle_timeout.&lt;br&gt;
3) PostgreSQL &lt;code&gt;idle_session_timeout&lt;/code&gt; settings (from PostgreSQL 14) and &lt;code&gt;idle_in_transaction_session_timeout&lt;/code&gt;(for sessions that were idle in transaction).&lt;br&gt;
4) TCP or other network or firewall timeouts(e.g. there is a case of the timeout in Azure Cloud)&lt;/p&gt;
&lt;h2&gt;
  
  
  The solution
&lt;/h2&gt;

&lt;p&gt;To solve this problem, there are a few possible solutions:&lt;/p&gt;

&lt;p&gt;0) Speed up slow processing&lt;br&gt;
1) Increase the &lt;code&gt;idle_timeout&lt;/code&gt;(or another guilty timeout) setting to a higher value to prevent a server from closing idle connections too soon.&lt;br&gt;
2) Add a database setting called &lt;code&gt;reaping_frequency: 10&lt;/code&gt;, which will clear and restore connections more frequently to prevent them from becoming idle.&lt;br&gt;
3) Add manual reconnection, like the code snippet below, which releases the connection and re-establishes it before saving a record to the database.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    ActiveRecord::Base.connection_pool.release_connection
    ActiveRecord::Base.connection_pool.with_connection do
      # save record in database
    end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;p&gt;Links:&lt;br&gt;
&lt;a href="https://api.rubyonrails.org/v5.1/classes/ActiveRecord/ConnectionAdapters/ConnectionPool.html" rel="noopener noreferrer"&gt;(1)&lt;/a&gt;&lt;a href="https://apidock.com/rails/ActiveRecord/DatabaseConfigurations/HashConfig/reaping_frequency" rel="noopener noreferrer"&gt;(2)&lt;/a&gt;&lt;a href="https://github.com/mperham/sidekiq/wiki/Problems-and-Troubleshooting#postgres-connection-corruption" rel="noopener noreferrer"&gt;(3)&lt;/a&gt;&lt;a href="https://github.com/rails/rails/blob/v5.2.4.3/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb#L283" rel="noopener noreferrer"&gt;(4)&lt;/a&gt; Info about &lt;code&gt;reaping_frequency&lt;/code&gt;&lt;br&gt;
&lt;a href="https://juanitofatas.com/series/rails/active-record-connection-pool" rel="noopener noreferrer"&gt;(5)&lt;/a&gt;&lt;a href="https://api.rubyonrails.org/v7.0/classes/ActiveRecord/ConnectionAdapters/ConnectionPool.html" rel="noopener noreferrer"&gt;(6)&lt;/a&gt; Info about Rails's &lt;code&gt;idle_timeout&lt;/code&gt;&lt;br&gt;
&lt;a href="https://github.com/mperham/sidekiq/issues/3128" rel="noopener noreferrer"&gt;(7)&lt;/a&gt;&lt;a href="https://github.com/grosser/parallel/issues/62" rel="noopener noreferrer"&gt;(8)&lt;/a&gt;&lt;a href="https://stackoverflow.com/a/65242944/11736429" rel="noopener noreferrer"&gt;(9)&lt;/a&gt; Solutions with manual reconnection&lt;/p&gt;

</description>
      <category>welcome</category>
      <category>frontend</category>
      <category>community</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How to query Ancestry fast</title>
      <dc:creator>Dmitry Daw</dc:creator>
      <pubDate>Thu, 29 Apr 2021 20:26:12 +0000</pubDate>
      <link>https://dev.to/haukot/how-to-query-ancestry-fast-3873</link>
      <guid>https://dev.to/haukot/how-to-query-ancestry-fast-3873</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/stefankroes/ancestry" rel="noopener noreferrer"&gt;Ancestry&lt;/a&gt; is a great library to organize models in a tree structure.&lt;/p&gt;

&lt;p&gt;For example, if you have Location model, which may be City, State, and Country, and you want to easily organize and query between all those entities.&lt;/p&gt;

&lt;p&gt;By default Ancestry makes requests with LIKE query, e.g.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-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;locations&lt;/span&gt;&lt;span class="p"&gt;.&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;locations&lt;/span&gt; 
&lt;span class="k"&gt;WHERE&lt;/span&gt;
  &lt;span class="nv"&gt;"locations"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"ancestry"&lt;/span&gt; &lt;span class="k"&gt;LIKE&lt;/span&gt; &lt;span class="s1"&gt;'4a5b7a5d-1147-4d09-8a5d-a1a476c5be50/88fd88b7-eecd-4fc0-b18d-e2b6a077ab66/550f9809-e875-4fab-8cac-db8e6d59e0e5/%'&lt;/span&gt; 
&lt;span class="k"&gt;OR&lt;/span&gt; 
  &lt;span class="nv"&gt;"locations"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"ancestry"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'4a5b7a5d-1147-4d09-8a5d-a1a476c5be50/88fd88b7-eecd-4fc0-b18d-e2b6a077ab66/550f9809-e875-4fab-8cac-db8e6d59e0e5'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sometimes you need to write a query to check all descendants, like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-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;locations&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;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;distinct&lt;/span&gt; &lt;span class="n"&gt;cities&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;span class="k"&gt;FROM&lt;/span&gt; 
  &lt;span class="n"&gt;locations&lt;/span&gt; 
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;locations&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;cities&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; 
  &lt;span class="n"&gt;cities&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ancestry&lt;/span&gt; &lt;span class="k"&gt;LIKE&lt;/span&gt; &lt;span class="s1"&gt;'%'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;locations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;'%'&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt;
  &lt;span class="n"&gt;cities&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;location_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'CITY'&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt;
  &lt;span class="n"&gt;locations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(in this example &lt;code&gt;locations.id&lt;/code&gt; is &lt;code&gt;uuid&lt;/code&gt;, so we can use simplified LIKE query with '%' || locations.id || '%'. With &lt;code&gt;int id&lt;/code&gt; it requires a different request)&lt;/p&gt;

&lt;p&gt;But this request is running too long.&lt;br&gt;
PostgreSQL does have indexes that may speed up LIKE queries, for example &lt;code&gt;GIN&lt;/code&gt; and &lt;code&gt;GiST&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;But before using index, we can change that query to this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-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;locations&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;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;distinct&lt;/span&gt; &lt;span class="n"&gt;cities&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;span class="k"&gt;FROM&lt;/span&gt; 
  &lt;span class="n"&gt;locations&lt;/span&gt; 
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;locations&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;cities&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; 
  &lt;span class="n"&gt;locations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;ANY&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;string_to_array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cities&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ancestry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'/'&lt;/span&gt;&lt;span class="p"&gt;)::&lt;/span&gt;&lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;[]))&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt;
  &lt;span class="n"&gt;cities&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;location_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'CITY'&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt;
  &lt;span class="n"&gt;locations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And it runs MUCH faster. On our data, we get an improvement from 5 seconds to 6 ms(!).&lt;/p&gt;

&lt;p&gt;And it's possible to use it with &lt;code&gt;int&lt;/code&gt; ids, just change &lt;code&gt;uuid[]&lt;/code&gt; to &lt;code&gt;int[]&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;After we can also add GIN index to it, if we want to.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;add_index&lt;/span&gt; &lt;span class="ss"&gt;:locations&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"(string_to_array(ancestry, '/')::uuid[])"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;using: :gin&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But it already runs much faster.&lt;/p&gt;

&lt;p&gt;Another way to speed up Ancestry queries is to use &lt;code&gt;ltree&lt;/code&gt; index, but it requires a diffent syntax.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sources:
&lt;/h3&gt;

&lt;p&gt;string_to_array: &lt;a href="https://github.com/stefankroes/ancestry/issues/466" rel="noopener noreferrer"&gt;https://github.com/stefankroes/ancestry/issues/466&lt;/a&gt;&lt;br&gt;
GIN and GiST index documentation: &lt;a href="https://www.postgresql.org/docs/9.1/textsearch-indexes.html" rel="noopener noreferrer"&gt;https://www.postgresql.org/docs/9.1/textsearch-indexes.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ltree: &lt;a href="https://github.com/stefankroes/ancestry/issues/102" rel="noopener noreferrer"&gt;https://github.com/stefankroes/ancestry/issues/102&lt;/a&gt;&lt;br&gt;
ltree index documentation: &lt;a href="https://www.postgresql.org/docs/9.1/ltree.html" rel="noopener noreferrer"&gt;https://www.postgresql.org/docs/9.1/ltree.html&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
