<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://lefnord.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://lefnord.github.io/" rel="alternate" type="text/html" /><updated>2025-06-02T18:43:15+00:00</updated><id>https://lefnord.github.io/feed.xml</id><title type="html">personal thoughts</title><subtitle>on this and that</subtitle><entry><title type="html">Rails: Broadcast to specific target.</title><link href="https://lefnord.github.io/Rails-_Broadcast_to_specific_target" rel="alternate" type="text/html" title="Rails: Broadcast to specific target." /><published>2025-05-28T21:42:00+00:00</published><updated>2025-05-28T21:42:00+00:00</updated><id>https://lefnord.github.io/Rails:_Broadcast_to_specific_target.</id><content type="html" xml:base="https://lefnord.github.io/Rails-_Broadcast_to_specific_target"><![CDATA[<p>If you are working with <a href="https://turbo.hotwired.dev">Turbo</a> a while, there are comming the point you want to stream things only to a specific set users, say moderators. But how can this be acived without having the right <code class="language-plaintext highlighter-rouge">current_user</code> object. Cause the <code class="language-plaintext highlighter-rouge">current_user</code> on controller level isn’t the same as user as it is for the target in the context of broadcast.</p>

<p>So say you have a chat room and only the owner of a message is allowed to delet its message.</p>

<div class="language-haml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">-# app/views/messages/_room.html.haml
</span>
<span class="p">=</span> <span class="n">turbo_stream_from</span> <span class="n">dom_id</span><span class="p">(</span><span class="n">current_user</span><span class="p">)</span>
<span class="p">=</span> <span class="n">turbo_stream_from</span> <span class="n">dom_id</span><span class="p">(</span><span class="n">room</span><span class="p">)</span>
<span class="p">-</span> <span class="n">room</span><span class="p">.</span><span class="nf">messages</span><span class="p">.</span><span class="nf">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">message</span><span class="o">|</span>
  <span class="p">=</span> <span class="n">render</span> <span class="ss">partial: </span><span class="s1">'messages/message'</span><span class="p">,</span> <span class="ss">locals: </span><span class="p">{</span><span class="ss">message: </span><span class="n">message</span><span class="p">,</span> <span class="ss">owner: </span><span class="n">message</span><span class="p">.</span><span class="nf">user</span> <span class="o">==</span> <span class="n">current_user</span><span class="p">}</span>
</code></pre></div></div>

<p>Here we have two streams one for the current user, which is used to add the delete link, and the one for the chat room itself to add the newly create message.</p>

<p>The chat message partial looks like.</p>

<div class="language-haml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">-# app/views/messages/_message.html.haml
</span>
<span class="p">=</span> <span class="n">message</span><span class="p">.</span><span class="nf">initials</span>
<span class="p">=</span> <span class="n">message</span><span class="p">.</span><span class="nf">username</span>
<span class="p">=</span> <span class="no">I18n</span><span class="p">.</span><span class="nf">l</span> <span class="n">message</span><span class="p">.</span><span class="nf">created_at</span><span class="p">,</span> <span class="ss">format: :short</span>
<span class="p">=</span> <span class="n">raw</span> <span class="n">message</span><span class="p">.</span><span class="nf">message</span>

<span class="p">=</span> <span class="n">turbo_frame_tag</span> <span class="p">[</span><span class="n">dom_id</span><span class="p">(</span><span class="n">message</span><span class="p">),</span> <span class="s1">'delete'</span><span class="p">]</span> <span class="k">do</span>
  <span class="p">-</span> <span class="k">if</span> <span class="n">owner</span>
    <span class="p">=</span> <span class="n">render</span> <span class="ss">partial: </span><span class="s1">'messages/delete'</span><span class="p">,</span> <span class="ss">locals: </span><span class="p">{</span><span class="ss">message: </span><span class="n">message</span><span class="p">}</span>
</code></pre></div></div>

<p>Now then somebody sends a message we using in the model callback the message user to send also the delete link to the owner.</p>

<div class="language-rb highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># app/models/message.rb</span>

<span class="k">class</span> <span class="nc">Message</span> <span class="o">&lt;</span> <span class="no">ApplicationRecord</span>
  <span class="n">belongs_to</span> <span class="ss">:room</span><span class="p">,</span> <span class="ss">counter_cache: </span><span class="kp">true</span>
  <span class="n">belongs_to</span> <span class="ss">:user</span>

  <span class="n">after_create_commit</span> <span class="ss">:create_broadcast</span>

  <span class="k">def</span> <span class="nf">create_broadcast</span>
    <span class="n">broadcast_append_to</span> <span class="n">room</span><span class="p">,</span>
                        <span class="ss">partial: </span><span class="s1">'messages/message'</span><span class="p">,</span>
                        <span class="ss">locals: </span><span class="p">{</span><span class="ss">message: </span><span class="nb">self</span><span class="p">,</span> <span class="ss">owner: </span><span class="kp">false</span><span class="p">}</span>

    <span class="n">broadcast_replace_to</span> <span class="s2">"user_</span><span class="si">#{</span><span class="n">user</span><span class="p">.</span><span class="nf">id</span><span class="si">}</span><span class="s2">"</span><span class="p">,</span>
                         <span class="ss">target: </span><span class="s2">"message_</span><span class="si">#{</span><span class="nb">id</span><span class="si">}</span><span class="s2">_delete"</span><span class="p">,</span>
                         <span class="ss">partial: </span><span class="s1">'messages/delete'</span><span class="p">,</span>
                         <span class="ss">locals: </span><span class="p">{</span><span class="ss">message: </span><span class="nb">self</span><span class="p">}</span>
    <span class="k">end</span>
  <span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>

<p>The trick is to send the delete broadcast to the user stream, with the delete target defined by the turbo frame above.</p>]]></content><author><name>LeFnord</name></author><category term="rails" /><category term="turbo" /></entry><entry><title type="html">How To … ViewComponents and Stimulus</title><link href="https://lefnord.github.io/how_to-_ViewComponents_and_Stimulus/" rel="alternate" type="text/html" title="How To … ViewComponents and Stimulus" /><published>2025-02-09T16:42:00+00:00</published><updated>2025-02-09T16:42:00+00:00</updated><id>https://lefnord.github.io/how_to:_ViewComponents_and_Stimulus</id><content type="html" xml:base="https://lefnord.github.io/how_to-_ViewComponents_and_Stimulus/"><![CDATA[<p>By using <a href="https://viewcomponent.org/guide/generators.html#generate-a-stimulus-controller">ViewComponents</a>
one finds the possinility to also create a <a href="https://stimulus.hotwired.dev/reference/controllers">Stimulus Controller</a>.</p>

<p>It took a while for me to find it out, how to make us of it in a Rails 8 application,
together with <a href="https://github.com/rails/propshaft">propshaft</a> and <a href="https://github.com/rails/importmap-rails">importmaps</a>.</p>

<p>At the end it was quite easy, simply add following:</p>
<div class="language-rb highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># config/initializers/assets.rb</span>
<span class="no">Rails</span><span class="p">.</span><span class="nf">application</span><span class="p">.</span><span class="nf">config</span><span class="p">.</span><span class="nf">assets</span><span class="p">.</span><span class="nf">paths</span> <span class="o">&lt;&lt;</span> <span class="no">Rails</span><span class="p">.</span><span class="nf">root</span><span class="p">.</span><span class="nf">join</span><span class="p">(</span><span class="s2">"app/components"</span><span class="p">)</span>
<span class="no">Rails</span><span class="p">.</span><span class="nf">application</span><span class="p">.</span><span class="nf">config</span><span class="p">.</span><span class="nf">importmap</span><span class="p">.</span><span class="nf">cache_sweepers</span> <span class="o">&lt;&lt;</span> <span class="no">Rails</span><span class="p">.</span><span class="nf">root</span><span class="p">.</span><span class="nf">join</span><span class="p">(</span><span class="s2">"app/components"</span><span class="p">)</span>
</code></pre></div></div>
<div class="language-rb highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># config/importmap.rb</span>
<span class="n">pin_all_from</span> <span class="s2">"app/components"</span><span class="p">,</span> <span class="ss">under: </span><span class="s2">"controllers"</span><span class="p">,</span> <span class="ss">to: </span><span class="s2">""</span>
</code></pre></div></div>]]></content><author><name>LeFnord</name></author><category term="how_to" /><category term="rails" /><category term="stimulus" /><category term="view_components" /></entry><entry><title type="html">Hints … Switching from Intel to M2</title><link href="https://lefnord.github.io/Hints_-_Switching_from_Intel_to_M2/" rel="alternate" type="text/html" title="Hints … Switching from Intel to M2" /><published>2022-08-23T20:07:00+00:00</published><updated>2022-08-23T20:07:00+00:00</updated><id>https://lefnord.github.io/Hints_-_Switching_from_Intel_to_M2</id><content type="html" xml:base="https://lefnord.github.io/Hints_-_Switching_from_Intel_to_M2/"><![CDATA[<p>Yeah my new MBP 13” M2 arrived yesterday. And I had to migrate from my old Intel one.</p>

<p>Here some lessons that I learned.</p>

<h2 id="brew">Brew</h2>

<h4 id="️-do-this-only-if-you-dont-need-legacy-apps">⚠️ Do this only, if you don’t need “legacy” apps.</h4>

<p>What do I mean: In my case, I need use rbenv to manage my rubys,
in one project an oracle access is required, will be done with <code class="language-plaintext highlighter-rouge">ruby-oci8</code>. This gems needs the Oracle Instantclient, but this one is only for Mac Intel available.</p>

<p>on your old machine do:</p>

<ul>
  <li>save your delevopment data, like PG databases</li>
  <li>list all installed formulaes and save the <em>Brewfile</em> with
    <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>brew bundle dump
</code></pre></div>    </div>
  </li>
  <li>remove Brew completely with
    <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/bin/bash <span class="nt">-c</span> <span class="s2">"</span><span class="si">$(</span>curl <span class="nt">-fsSL</span> https://raw.githubusercontent.com/Homebrew/install/master/uninstall.sh<span class="si">)</span><span class="s2">"</span>
</code></pre></div>    </div>
  </li>
</ul>

<p>on your new machine:</p>

<ul>
  <li>ensure you have the xcode command line tools installed</li>
  <li>install brew, see above, but now <code class="language-plaintext highlighter-rouge">install.sh</code></li>
  <li>reinstall your formulas with
    <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>brew bundle
</code></pre></div>    </div>
  </li>
</ul>

<h4 id="i-did-it--">I did it … 🤬</h4>

<p>to fix it  had to do following steps</p>

<ol>
  <li>go in <em>Finder</em> to the <em>Termninal</em> and open “Information” (⌘+i)</li>
  <li>check “open with Rosetta” (or similar)</li>
  <li>install homebrew but now with given architecture
    <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">arch</span> <span class="nt">-x86_64</span> /bin/bash <span class="nt">-c</span> <span class="s2">"</span><span class="si">$(</span>curl <span class="nt">-fsSL</span> https://raw.githubusercontent.com/Homebrew/install/master/install.sh<span class="si">)</span><span class="s2">"</span>
</code></pre></div>    </div>
  </li>
  <li>and reinstall your formulas as above</li>
</ol>

<p>How to use Mx Apps?</p>

<p>You have to create the folder structure under <code class="language-plaintext highlighter-rouge">/opt</code>, see here <a href="https://docs.brew.sh/Installation#untar-anywhere">Untar anywhere</a></p>

<p>And specify the architecture then, e.g.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>arch -arm64 brew install tiles
</code></pre></div></div>
<p>or even better, create your own alias … 😉</p>

<p>… to be continued</p>]]></content><author><name>LeFnord</name></author><category term="hints" /></entry><entry><title type="html">How To … Searchkick</title><link href="https://lefnord.github.io/How_To_-_Searchkick/" rel="alternate" type="text/html" title="How To … Searchkick" /><published>2021-11-06T07:09:00+00:00</published><updated>2021-11-06T07:09:00+00:00</updated><id>https://lefnord.github.io/How_To_%E2%80%A6_Searchkick</id><content type="html" xml:base="https://lefnord.github.io/How_To_-_Searchkick/"><![CDATA[<p>Working with <a href="https://www.are.na">Are.na</a> on upgrading, or better replacing,
the search infrastructure, meaning from <a href="https://www.elastic.co/">ElasticSearch</a> 1.7.x together
with <a href="https://github.com/karmi/retire">re|tire</a> to use <a href="https://www.opensearch.org">OpenSearch</a>
with <a href="https://github.com/ankane/searchkick">Searchkick</a>.</p>

<p>In this process we found some unexpected solutions, for existend behaviour.
Unexpected because they are not documented. Here we want to share these finding.</p>

<h2 id="findings">Findings</h2>

<h3 id="1-mixing">1 Mixing</h3>

<p>According to the <a href="https://github.com/ankane/searchkick#partial-matches">Searchkick</a> documentation,
you have several possibilities to influence the search behavior, by specifying the analyzer to use,
so for our example with</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">Product</span> <span class="o">&lt;</span> <span class="no">ApplicationRecord</span>
  <span class="n">searchkick</span> <span class="ss">text_middle: </span><span class="p">[</span><span class="ss">:name</span><span class="p">],</span>
             <span class="ss">word_start: </span><span class="p">[</span><span class="ss">:content</span><span class="p">]</span>
<span class="k">end</span>
</code></pre></div></div>

<p>and use then the same setting in the <code class="language-plaintext highlighter-rouge">match</code> field … 🤔</p>

<p>But we wanted to search for both fields at once.
This was not documented, so we had to dive into the code (→ <a href="https://github.com/ankane/searchkick/blob/0d18831ae96988ed7089ffc85cd0ee36952ce254/lib/searchkick/query.rb#L360">idea</a>),
where the field option will be evaluated, so it seems an Array of Hashes will be accepted.</p>

<p>The solution is, to provide for each field the appropiated analyzer.</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="no">Product</span><span class="p">.</span><span class="nf">search</span> <span class="n">query</span><span class="p">,</span> <span class="ss">fields: </span><span class="p">[</span>
  <span class="p">{</span> <span class="s1">'name^3'</span><span class="p">:</span> <span class="ss">:text_middle</span> <span class="p">},</span> <span class="c1"># use togehter with boost</span>
  <span class="p">{</span> <span class="ss">content: :word_start</span> <span class="p">},</span>
<span class="p">]</span>
</code></pre></div></div>

<blockquote>
  <p>By using this method, avoid using the <code class="language-plaintext highlighter-rouge">match</code> key, cause it overwrites above analyzers.</p>
</blockquote>

<h3 id="2-settings">2 Settings</h3>

<p>Above we saw how to use the options. But what are these options?
That are <a href="https://github.com/ankane/searchkick/blob/0d18831ae96988ed7089ffc85cd0ee36952ce254/lib/searchkick/index_options.rb#L38">shortcuts</a>
for predefined Anlayzers, to use for processing the String/Text on indexing.</p>

<p>One can see, that each of them is built up the three keys <code class="language-plaintext highlighter-rouge">type</code>, <code class="language-plaintext highlighter-rouge">tokenizer</code> and <code class="language-plaintext highlighter-rouge">filter</code>.
It specifies the <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis.html">Analysis</a>, which should be applied to this field.</p>

<p>Another finding are the sizes of the <code class="language-plaintext highlighter-rouge">min_gram</code> and <code class="language-plaintext highlighter-rouge">max_gram</code>.
They are good for most cases, as are all predefined analyzers.
But we wanted to change it to our needs, cause our content can be a very long description,
so using a <code class="language-plaintext highlighter-rouge">max_ngram</code> of 50, will result in a very big index,
without improving the search quality itself.</p>

<p>So we have to adapt it to our needs, and yes you guessed it … 😉
But the <a href="https://github.com/ankane/searchkick/blob/0d18831ae96988ed7089ffc85cd0ee36952ce254/lib/searchkick/index_options.rb#L184">settings</a> are used, so it must be possible to specify them as well.
With a little trial and error, we found it</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="no">Searchkick</span><span class="p">.</span><span class="nf">model_options</span> <span class="o">=</span> <span class="p">{</span>
  <span class="ss">settings: </span><span class="p">{</span>
    <span class="ss">analysis: </span><span class="p">{</span>
      <span class="ss">filter: </span><span class="p">{</span>
        <span class="ss">searchkick_edge_ngram: </span><span class="p">{</span> <span class="ss">type: </span><span class="s1">'edge_ngram'</span><span class="p">,</span> <span class="ss">min_gram: </span><span class="mi">1</span><span class="p">,</span> <span class="ss">max_gram: </span><span class="mi">17</span> <span class="p">},</span>
        <span class="ss">searchkick_ngram: </span><span class="p">{</span> <span class="ss">type: </span><span class="s1">'ngram'</span><span class="p">,</span> <span class="ss">min_gram: </span><span class="mi">1</span><span class="p">,</span> <span class="ss">max_gram: </span><span class="mi">17</span> <span class="p">}</span>
      <span class="p">}</span>
    <span class="p">},</span>
    <span class="ss">index: </span><span class="p">{</span>
      <span class="ss">max_ngram_diff: </span><span class="mi">23</span>
    <span class="p">}</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>But what, if we want to define our own analyzer?</p>

<blockquote>
  <p>It must be said that this is not recommended, unless you know exactly what you are doing,
because as already said, the available ones are very good and sufficient for most cases.</p>
</blockquote>

<p>Ok, one can not add own ones to the <a href="https://github.com/ankane/searchkick/blob/0d18831ae96988ed7089ffc85cd0ee36952ce254/lib/searchkick/index_options.rb#L38">predefined analyzer</a> list.
But one can overwrite an existend one … for example changing the above <code class="language-plaintext highlighter-rouge">text_middle</code> analyzer</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="no">Searchkick</span><span class="p">.</span><span class="nf">model_options</span> <span class="o">=</span> <span class="p">{</span>
  <span class="ss">settings: </span><span class="p">{</span>
    <span class="ss">analysis: </span><span class="p">{</span>
      <span class="ss">analyzer: </span><span class="p">{</span>
        <span class="ss">searchkick_text_middle_index: </span><span class="p">{</span>
          <span class="ss">type: </span><span class="s1">'custom'</span><span class="p">,</span>
          <span class="ss">tokenizer: </span><span class="s1">'whitespace'</span><span class="p">,</span>
          <span class="ss">filter: </span><span class="sx">%w[lowercase snowball_german_umlaut unique name_ngram]</span>
        <span class="p">},</span>
      <span class="p">},</span>
      <span class="ss">filter: </span><span class="p">{</span>
        <span class="ss">name_ngram: </span><span class="p">{</span> <span class="ss">type: </span><span class="s1">'ngram'</span><span class="p">,</span> <span class="ss">min_gram: </span><span class="mi">1</span><span class="p">,</span> <span class="ss">max_gram: </span><span class="mi">3</span><span class="p">,</span> <span class="ss">preserve_original: </span><span class="kp">true</span> <span class="p">},</span>
        <span class="ss">snowball_german_umlaut: </span><span class="p">{</span> <span class="ss">type: </span><span class="s1">'snowball'</span><span class="p">,</span> <span class="ss">name: </span><span class="s1">'German2'</span> <span class="p">},</span>
      <span class="p">}</span>
    <span class="p">}</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>it doesn’t make so much sense, but you get the idea.
For possible Analyzer options refer to the
<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis.html">Analysis</a>
documentation.</p>

<p>By the way, it also works with OpenSearch, but actual the <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis.html">ES documentation</a>, especially the <a href="https://lucene.apache.org">Lucene</a> related one is much better.</p>

<blockquote>
  <p>Don’t forget to reindex each time, after one of the obove settings are changed!</p>
</blockquote>]]></content><author><name>LeFnord</name></author><category term="how_to" /><category term="searchkick" /></entry><entry><title type="html">How to … Estimate instead count(*)</title><link href="https://lefnord.github.io/How_to_-_Estimate_instead_count/" rel="alternate" type="text/html" title="How to … Estimate instead count(*)" /><published>2020-11-02T11:02:00+00:00</published><updated>2020-11-02T11:02:00+00:00</updated><id>https://lefnord.github.io/How_to_-_Estimate_instead_count</id><content type="html" xml:base="https://lefnord.github.io/How_to_-_Estimate_instead_count/"><![CDATA[<p>Again working on a frontend for visualizing a big amount of data, think you know the situation,
a kind of index view, or a GET all. So you start to implement something clever,
not to fetch all the data at once, ending up by a pagination or something similar
to utilise raw SQL <code class="language-plaintext highlighter-rouge">LIMIT</code> and <code class="language-plaintext highlighter-rouge">OFFSET</code> to speed things up.</p>

<p>This requires mostly the knowledge of the count of items,
so the <code class="language-plaintext highlighter-rouge">OFFSET</code> can be calculated correctly.</p>

<p>In following some log output for the count and select statements,
results table contains ~4,5M rows …</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">(</span>129.8ms<span class="o">)</span>  SELECT COUNT<span class="o">(</span><span class="k">*</span><span class="o">)</span> FROM <span class="s2">"results"</span>
…
Result Load <span class="o">(</span>0.4ms<span class="o">)</span>  SELECT <span class="s2">"results"</span>.<span class="k">*</span> FROM <span class="s2">"results"</span> LIMIT <span class="nv">$1</span> OFFSET <span class="nv">$2</span>  <span class="o">[[</span><span class="s2">"LIMIT"</span>, 100], <span class="o">[</span><span class="s2">"OFFSET"</span>, 0]]
</code></pre></div></div>

<p>one can see that <code class="language-plaintext highlighter-rouge">COUNT</code> statement needs multiple times more then the <code class="language-plaintext highlighter-rouge">SELECT</code> statement.<br />
But do we need every time we are fetching the next span of data a new <code class="language-plaintext highlighter-rouge">COUNT</code>?</p>

<p>Think, NO.</p>

<h3 id="separate-endpoints-for-count-and-data-fetching">Separate endpoints for count and data fetching</h3>

<p>Add another endpoint for getting the count only when its needed,
for example on page load.<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup></p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">get</span> <span class="s1">'count'</span> <span class="k">do</span>
  <span class="n">results</span> <span class="o">=</span> <span class="o">::</span><span class="no">Result</span><span class="p">.</span><span class="nf">filter</span><span class="p">(</span><span class="n">params</span><span class="p">)</span>

  <span class="p">{</span> <span class="ss">count: </span><span class="n">results</span><span class="p">.</span><span class="nf">count</span> <span class="p">}</span>
<span class="k">end</span>

<span class="n">get</span> <span class="s1">'filter'</span> <span class="k">do</span>
  <span class="n">limit</span> <span class="o">=</span> <span class="n">params</span><span class="p">[</span><span class="ss">:limit</span><span class="p">]</span> <span class="o">||</span> <span class="mi">500</span>
  <span class="n">offset</span> <span class="o">=</span> <span class="n">params</span><span class="p">[</span><span class="ss">:offset</span><span class="p">]</span> <span class="o">||</span> <span class="mi">0</span>
  <span class="n">results</span> <span class="o">=</span> <span class="o">::</span><span class="no">Result</span><span class="p">.</span><span class="nf">filter</span><span class="p">(</span><span class="n">params</span><span class="p">).</span><span class="nf">limit</span><span class="p">(</span><span class="n">limit</span><span class="p">).</span><span class="nf">offset</span><span class="p">(</span><span class="n">offset</span><span class="p">)</span>

  <span class="p">{</span> <span class="ss">items: </span><span class="n">results</span> <span class="p">}</span>
<span class="k">end</span>
</code></pre></div></div>

<p>Ok this results in an decreased count of executing of the count statement, cause of hitting the count endpoint only,
when parameters are changed, but doesn’t speed it up.</p>

<h2 id="estimate">Estimate</h2>

<p>By thinking and researching about, what can be improved,
I found this inspiring post <a href="https://www.citusdata.com/blog/2016/10/12/count-performance">count-performance</a>.</p>

<p>Ok, why not … the exact count isn’t really needed, so I decided to give it a try.</p>

<h3 id="the-function">The Function</h3>

<p>First we have to add a SQL function, which does the estimation.
For that, the function from the post above will be packed into a migration.</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">CreateEstimateFunction</span> <span class="o">&lt;</span> <span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Migration</span><span class="p">[</span><span class="mf">6.0</span><span class="p">]</span>
  <span class="k">def</span> <span class="nf">up</span>
    <span class="n">function</span> <span class="o">=</span> <span class="s2">"CREATE FUNCTION count_estimate(query text) RETURNS integer AS $$
      DECLARE
        rec   record;
        rows  integer;
      BEGIN
        FOR rec IN EXECUTE 'EXPLAIN ' || query LOOP
          rows := substring(rec.</span><span class="se">\"</span><span class="s2">QUERY PLAN</span><span class="se">\"</span><span class="s2"> FROM ' rows=([[:digit:]]+)');
          EXIT WHEN rows IS NOT NULL;
        END LOOP;


        RETURN rows;
      END;
      $$ LANGUAGE plpgsql VOLATILE STRICT;"</span>

    <span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Base</span><span class="p">.</span><span class="nf">connection</span><span class="p">.</span><span class="nf">execute</span><span class="p">(</span><span class="n">function</span><span class="p">)</span>
  <span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>

<p>Remember the name of the function (here: <code class="language-plaintext highlighter-rouge">count_estimate</code>) for later usage.</p>

<p>Now we have the function place, we should make usage of it.
So we are changing the endpoint into …</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">get</span> <span class="s1">'count'</span> <span class="k">do</span>
  <span class="n">count</span> <span class="o">=</span> <span class="o">::</span><span class="no">Result</span><span class="p">.</span><span class="nf">counting</span><span class="p">(</span><span class="n">params</span><span class="p">)</span>

  <span class="p">{</span> <span class="ss">count: </span><span class="n">count</span> <span class="p">}</span>
<span class="k">end</span>
</code></pre></div></div>

<p>to call the new model method. Note, that it also take the same parameters as for filter endpoint.</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">filter</span><span class="p">(</span><span class="n">params</span><span class="p">)</span>
  <span class="c1"># filter implementation</span>
  <span class="c1"># returns an ActiveRecord::Relation object</span>
<span class="k">end</span>

<span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">counting</span><span class="p">(</span><span class="n">params</span><span class="p">)</span>
  <span class="n">query</span> <span class="o">=</span> <span class="k">if</span> <span class="o">&lt;</span><span class="n">search</span> <span class="n">params</span> <span class="n">given?</span><span class="o">&gt;</span>
            <span class="n">insert</span> <span class="o">=</span> <span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Base</span><span class="p">.</span><span class="nf">connection</span><span class="p">.</span><span class="nf">quote</span><span class="p">(</span><span class="n">filter</span><span class="p">(</span><span class="n">params</span><span class="p">).</span><span class="nf">to_sql</span><span class="p">)</span>
            <span class="n">search_total_query</span><span class="p">(</span><span class="n">insert</span><span class="p">)</span>
          <span class="k">else</span>
            <span class="n">total_query</span>
          <span class="k">end</span>

  <span class="n">result</span> <span class="o">=</span> <span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Base</span><span class="p">.</span><span class="nf">connection</span><span class="p">.</span><span class="nf">exec_query</span><span class="p">(</span><span class="n">query</span><span class="p">)</span>
  <span class="n">result</span><span class="p">.</span><span class="nf">rows</span><span class="p">.</span><span class="nf">flatten</span><span class="p">.</span><span class="nf">first</span><span class="p">.</span><span class="nf">to_i</span>
<span class="k">end</span>

<span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">search_total_query</span><span class="p">(</span><span class="n">query</span><span class="p">)</span>
  <span class="s2">"SELECT count_estimate("</span> <span class="o">+</span> <span class="n">query</span> <span class="o">+</span> <span class="s2">");"</span>
<span class="k">end</span>

<span class="c1"># helper methods</span>
<span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">total_query</span>
  <span class="s2">"SELECT
    (reltuples/relpages) * (pg_relation_size('results') /
    (current_setting('block_size')::integer)) AS count
  FROM pg_class where relname = 'results';"</span>
<span class="k">end</span>
</code></pre></div></div>

<p>To be consequent, we will use both estimations and decide via <code class="language-plaintext highlighter-rouge">search params given?</code>, which one to choose.
This is useful if want to make search queries which are decreasing the result space,
so the estimation want not differ too much.</p>

<p>For that the value of the <code class="language-plaintext highlighter-rouge">filter(params)</code> method have to be an <code class="language-plaintext highlighter-rouge">ActiveRecord::Relation</code> object,
so that we can reuse it here to generate the SQL string.</p>

<p>First some comparisions:</p>

<table class="table">
  <thead>
    <tr>
      <th>timings</th>
      <th>count</th>
      <th>estimate</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>no fiter</td>
      <td>116.8ms</td>
      <td>0.7ms</td>
    </tr>
    <tr>
      <td>fiter</td>
      <td>9130.1ms</td>
      <td>7.8ms</td>
    </tr>
  </tbody>
</table>

<p>The difference is evident.</p>

<hr />

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p>For the examples I use <a href="https://github.com/ruby-grape/grape">grape</a> mounted in a Rails project. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>LeFnord</name></author><category term="how_to" /><category term="rails" /><category term="pg" /></entry><entry><title type="html">How to … Minimise running Sidekiq jobs</title><link href="https://lefnord.github.io/How_to_-_Minimise-running-Sidekiq-jobs/" rel="alternate" type="text/html" title="How to … Minimise running Sidekiq jobs" /><published>2020-10-19T22:15:00+00:00</published><updated>2020-10-19T22:15:00+00:00</updated><id>https://lefnord.github.io/How_to_-_Minimise-running-Sidekiq-jobs</id><content type="html" xml:base="https://lefnord.github.io/How_to_-_Minimise-running-Sidekiq-jobs/"><![CDATA[<p>I’m working for a customer project, and stand for the task to build a view of many models<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup> —
yeap, the data model was/is a poor one —, so I came up, inspired
by this <a href="https://pganalyze.com/blog/materialized-views-ruby-rails">post</a>,
with <a href="https://github.com/scenic-views/scenic">Scenic</a> to use a <code class="language-plaintext highlighter-rouge">materialized view</code> as solution.</p>

<p>All was fine, cause the data once imported will never change, only one thing to have in mind is,
refreshing the view after data import. Naming the model in this example <code class="language-plaintext highlighter-rouge">Result</code> ends up in this worker:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">ResultRefreshJob</span>
  <span class="kp">include</span> <span class="no">Sidekiq</span><span class="o">::</span><span class="no">Worker</span>
  <span class="n">sidekiq_options</span> <span class="ss">queue: :views</span><span class="p">,</span> <span class="ss">retry: </span><span class="kp">false</span>

  <span class="k">def</span> <span class="nf">perform</span>
    <span class="no">Result</span><span class="p">.</span><span class="nf">refresh</span>
  <span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>

<p>it calls the refresh method of our materialized view as it is documented
<a href="https://github.com/scenic-views/scenic#what-about-materialized-views">here</a>.</p>

<p>All fine, but some months later, a feature request comes in, to add another column.
Sounds after a low-hanging-fruit, but these data can occur <code class="language-plaintext highlighter-rouge">0..n</code> times and it ends up
in a flatten pivot table — presenting it as pivot table was not wanted —, means,
the data grows exponential with count of entries.
And a second problem was, the data can be changed, very quickly.</p>

<p>Its naïve to think the above solution can handle the boost of jobs,
which blocking itself.</p>

<blockquote>
  <h4 id="goal">Goal</h4>
  <p>So the count of running jobs must be minimised<br />
and it have to be ensured the view is refreshed.</p>
</blockquote>

<p>I came up with a second job, which checks if refresh jobs are running,
if not it enqueues a new one, and if one exists,
an error will be raised and it will be enqueued again.</p>

<p>It looks like following:</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">ResultEnqueueJob</span>
  <span class="kp">include</span> <span class="no">Sidekiq</span><span class="o">::</span><span class="no">Worker</span>
  <span class="n">sidekiq_options</span> <span class="ss">queue: :views</span><span class="p">,</span> <span class="ss">retry: </span><span class="mi">3</span>

  <span class="c1"># TODO: this should be observed and maybe adapted</span>
  <span class="k">def</span> <span class="nf">perform</span>
    <span class="n">running_worker</span> <span class="o">=</span> <span class="no">Sidekiq</span><span class="o">::</span><span class="no">Workers</span><span class="p">.</span><span class="nf">new</span><span class="p">.</span><span class="nf">map</span><span class="p">(</span><span class="o">&amp;</span><span class="ss">:last</span><span class="p">).</span><span class="nf">select</span> <span class="p">{</span> <span class="o">|</span><span class="n">x</span><span class="o">|</span> <span class="n">x</span><span class="p">[</span><span class="s1">'queue'</span><span class="p">]</span> <span class="o">==</span> <span class="s1">'views'</span> <span class="p">}.</span><span class="nf">last</span>
    <span class="n">can_run</span> <span class="o">=</span> <span class="k">if</span> <span class="n">running_worker</span>
                <span class="n">run_at</span> <span class="o">=</span> <span class="no">DateTime</span><span class="p">.</span><span class="nf">strptime</span><span class="p">(</span><span class="s2">"</span><span class="si">#{</span><span class="n">running_worker</span><span class="p">[</span><span class="s1">'run_at'</span><span class="p">]</span><span class="si">}</span><span class="s2">"</span><span class="p">,</span> <span class="s1">'%s'</span><span class="p">)</span>
                <span class="n">run_at</span> <span class="o">&lt;</span> <span class="no">DateTime</span><span class="p">.</span><span class="nf">current</span> <span class="o">-</span> <span class="mi">1</span><span class="p">.</span><span class="nf">minutes</span>
              <span class="k">else</span>
                <span class="kp">true</span>
              <span class="k">end</span>

    <span class="k">if</span> <span class="n">can_run</span>
      <span class="no">ResultRefreshJob</span><span class="p">.</span><span class="nf">perform_async</span>
    <span class="k">else</span>
      <span class="k">raise</span> <span class="no">StandardError</span><span class="p">,</span> <span class="s2">"---&gt; another ResultRefreshJob is running"</span>
    <span class="k">end</span>
  <span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>

<p>ok, looks quite simple, but what’s the trick here?</p>

<p>We have here 3 parameters, number one the <em>retry count</em> (<code class="language-plaintext highlighter-rouge">rc</code>),
number two the <em>timespan we go back</em> (<code class="language-plaintext highlighter-rouge">ts</code>)
and number three, the unknown one, we can only estimate and observe,
the <em>time to rebuild the view</em> (<code class="language-plaintext highlighter-rouge">tr</code>).</p>

<p>The actual values are <code class="language-plaintext highlighter-rouge">tc = 3</code>, <code class="language-plaintext highlighter-rouge">ts = 1min</code> and we estimate <code class="language-plaintext highlighter-rouge">tr</code> to round <code class="language-plaintext highlighter-rouge">2min</code>,
now we have to optimize these values, to met the above goal and to adapt it on changes.</p>

<p>Have in mind, that the time to retry will exponential growing,
see: <a href="https://github.com/mperham/sidekiq/wiki/Error-Handling#automatic-job-retry">automatic job retry</a>.</p>

<p>Example:</p>

<p>t<sub>x</sub> : job<sub>1</sub> checks if can start → no job running, starts<br />
t<sub>x+ts</sub> : job<sub>2</sub> checks if can start → job<sub>1</sub> is running, can not<br />
t<sub>x+ts+30s</sub> : job<sub>2</sub>, checks again → job<sub>1</sub> is running, can not<br />
t<sub>x+ts+30s+46s</sub> : job<sub>2</sub>, checks again → job<sub>1</sub> finished, starts</p>

<hr />

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p>joining 6 tables, resulting in some 100m rows, this takes around 1-2min to perform <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>LeFnord</name></author><category term="how_to" /><category term="sidekiq" /></entry><entry><title type="html">How To … Declare array parameters in grape</title><link href="https://lefnord.github.io/how_to_array_params/" rel="alternate" type="text/html" title="How To … Declare array parameters in grape" /><published>2020-10-12T20:31:00+00:00</published><updated>2020-10-12T20:31:00+00:00</updated><id>https://lefnord.github.io/how_to_array_params</id><content type="html" xml:base="https://lefnord.github.io/how_to_array_params/"><![CDATA[<p>People are often confused about, <em>how to declare array parameters</em> in <a href="https://github.com/ruby-grape/grape">grape</a> and represent it correct in the OpenAPI documentation via the <a href="https://github.com/ruby-grape/grape-swagger">grape-swagger</a> gem.</p>

<p>For the example here an API skeleton will be build with <a href="https://github.com/LeFnord/grape-starter">grape-starter</a>, so in the result you have a running API app. There are two namespaces created, one for simple arrays and on for arrays of objects. For both POST and PUT endpoints will be implemented.</p>

<p>One thing to emphasize is the <code class="language-plaintext highlighter-rouge">param_type</code>, it is set to <code class="language-plaintext highlighter-rouge">body</code>, this is needed for JSON requests. If it will not be specified, a FormData parameter will be expected and documented.</p>

<h1 id="array-of-simple-datatypes">Array of simple Datatypes</h1>

<p>The implementation self:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">namespace</span> <span class="ss">:arrays</span> <span class="k">do</span>
  <span class="n">params</span> <span class="k">do</span>
    <span class="n">optional</span> <span class="ss">:names</span><span class="p">,</span> <span class="ss">type: </span><span class="no">Array</span><span class="p">[</span><span class="no">String</span><span class="p">],</span>
             <span class="ss">documentation: </span><span class="p">{</span> <span class="ss">param_type: </span><span class="s1">'body'</span> <span class="p">}</span>
  <span class="k">end</span>
  <span class="n">post</span> <span class="k">do</span>
    <span class="n">present</span> <span class="ss">:names</span><span class="p">,</span> <span class="n">params</span><span class="p">[</span><span class="ss">:names</span><span class="p">]</span>
  <span class="k">end</span>

  <span class="n">params</span> <span class="k">do</span>
    <span class="n">requires</span> <span class="ss">:id</span>
  <span class="k">end</span>
  <span class="n">route_param</span> <span class="ss">:id</span> <span class="k">do</span>
    <span class="n">params</span> <span class="k">do</span>
      <span class="n">optional</span> <span class="ss">:names</span><span class="p">,</span> <span class="ss">type: </span><span class="no">Array</span><span class="p">[</span><span class="no">String</span><span class="p">],</span>
               <span class="ss">documentation: </span><span class="p">{</span> <span class="ss">param_type: </span><span class="s1">'body'</span> <span class="p">}</span>
    <span class="k">end</span>
    <span class="n">put</span> <span class="k">do</span>
      <span class="n">present</span> <span class="ss">:names</span><span class="p">,</span> <span class="n">params</span><span class="p">[</span><span class="ss">:names</span><span class="p">]</span>
    <span class="k">end</span>
  <span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>

<h2 id="usage">usage</h2>

<ul>
  <li>POST request to: <code class="language-plaintext highlighter-rouge">/arrays</code></li>
  <li>PUT request to: <code class="language-plaintext highlighter-rouge">/arrays/1</code></li>
</ul>

<p>with following JSON body:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"names"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="s2">"a"</span><span class="p">,</span><span class="w"> </span><span class="s2">"b"</span><span class="w">
  </span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<h1 id="array-of-json-objects">Array of JSON objects</h1>

<p>The implementation self:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">namespace</span> <span class="ss">:complex</span> <span class="k">do</span>
  <span class="n">params</span> <span class="k">do</span>
    <span class="n">requires</span> <span class="ss">:addresses</span><span class="p">,</span> <span class="ss">type: </span><span class="no">Array</span><span class="p">[</span><span class="no">JSON</span><span class="p">],</span>
             <span class="ss">documentation: </span><span class="p">{</span> <span class="ss">param_type: </span><span class="s1">'body'</span> <span class="p">}</span> <span class="k">do</span>
               <span class="n">requires</span> <span class="ss">:street</span>
               <span class="n">requires</span> <span class="ss">:city</span>
               <span class="n">requires</span> <span class="ss">:code</span>
             <span class="k">end</span>
  <span class="k">end</span>
  <span class="n">post</span> <span class="k">do</span>
    <span class="n">present</span> <span class="ss">:addresses</span><span class="p">,</span> <span class="n">params</span><span class="p">[</span><span class="ss">:addresses</span><span class="p">]</span>
  <span class="k">end</span>

  <span class="n">params</span> <span class="k">do</span>
    <span class="n">requires</span> <span class="ss">:id</span>
  <span class="k">end</span>
  <span class="n">route_param</span> <span class="ss">:id</span> <span class="k">do</span>
    <span class="n">params</span> <span class="k">do</span>
      <span class="n">requires</span> <span class="ss">:addresses</span><span class="p">,</span> <span class="ss">type: </span><span class="no">Array</span><span class="p">[</span><span class="no">JSON</span><span class="p">],</span>
               <span class="ss">documentation: </span><span class="p">{</span> <span class="ss">param_type: </span><span class="s1">'body'</span> <span class="p">}</span> <span class="k">do</span>
                 <span class="n">requires</span> <span class="ss">:street</span>
                 <span class="n">requires</span> <span class="ss">:city</span>
                 <span class="n">requires</span> <span class="ss">:code</span>
               <span class="k">end</span>
    <span class="k">end</span>
    <span class="n">put</span> <span class="k">do</span>
      <span class="n">present</span> <span class="ss">:addresses</span><span class="p">,</span> <span class="n">params</span><span class="p">[</span><span class="ss">:addresses</span><span class="p">]</span>
    <span class="k">end</span>
  <span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>

<h2 id="usage-1">usage</h2>

<ul>
  <li>POST request to: <code class="language-plaintext highlighter-rouge">/complex</code></li>
  <li>PUT request to: <code class="language-plaintext highlighter-rouge">/complex/1</code></li>
</ul>

<p>with following JSON body:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"addresses"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="p">{</span><span class="w">
      </span><span class="nl">"street"</span><span class="p">:</span><span class="w"> </span><span class="s2">"some street a"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"city"</span><span class="p">:</span><span class="w"> </span><span class="s2">"city a"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"code"</span><span class="p">:</span><span class="w"> </span><span class="s2">"12345"</span><span class="w">
    </span><span class="p">},</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"street"</span><span class="p">:</span><span class="w"> </span><span class="s2">"another street"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"city"</span><span class="p">:</span><span class="w"> </span><span class="s2">"city b"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"code"</span><span class="p">:</span><span class="w"> </span><span class="s2">"67890"</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>The repo can be found <a href="https://github.com/LeFnord/grape-arrays">here</a>.</p>]]></content><author><name>LeFnord</name></author><category term="how_to" /><category term="grape" /></entry><entry><title type="html">How To … Potsgres fulltext search.</title><link href="https://lefnord.github.io/PG-Fulltext-Search-with-Grape/" rel="alternate" type="text/html" title="How To … Potsgres fulltext search." /><published>2020-08-02T12:42:00+00:00</published><updated>2020-08-02T12:42:00+00:00</updated><id>https://lefnord.github.io/PG-Fulltext-Search-with-Grape</id><content type="html" xml:base="https://lefnord.github.io/PG-Fulltext-Search-with-Grape/"><![CDATA[<p>I came across in a play project to want to implement a search functionality on a postgres text column.</p>

<p>For <a href="https://rubyonrails.org">Rails</a> developers, there is a nice gem – <a href="https://github.com/Casecommons/pg_search">pg_search</a> – out there, it does all what is needed for it.
But this is deeply bundled to Rails itself.</p>

<p>So I had to do it by hand, means:</p>

<ol>
  <li>Understanding what it is and how is done in PG.</li>
  <li>How can this be integrated into my App?</li>
</ol>

<h4 id="for-1">For 1.</h4>

<p>… this post <a href="https://pganalyze.com/blog/full-text-search-ruby-rails-postgres">full-text-search-ruby-rails-postgres</a> is very informative for the beginning and understanding of the basics of the topic.</p>

<h4 id="for-2">For 2.</h4>

<p>For my app, it means a simple <a href="https://github.com/ruby-grape/grape">grape</a> API, for the building of it <a href="https://github.com/LeFnord/grape-starter">grape-starter</a> will be used. There I use <a href="https://github.com/rails/rails/tree/master/activerecord">ActiveRecord</a> for accessing the database.</p>

<p>So that was the setup …</p>

<p>At the end of it, a model file exists, where the search will be implemented<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup>.</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">search</span><span class="p">(</span><span class="n">query</span><span class="p">)</span>
  <span class="n">find_by_sql</span><span class="p">([</span><span class="n">sql</span><span class="p">,</span> <span class="n">query</span><span class="p">,</span> <span class="n">query</span><span class="p">])</span>
<span class="k">end</span>
</code></pre></div></div>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">sql</span>
  <span class="o">&lt;&lt;-</span><span class="no">SQL</span><span class="p">.</span><span class="nf">strip_heredoc</span><span class="sh">
    SELECT
      id,
      data,
      created_at,
      updated_at,
      ts_rank(
        to_tsvector(',language', 'data'),
        to_tsquery(',language', '&lt;query&gt;')
      ) AS rank
    FROM zettel
    WHERE
      to_tsvector(',language', 'data') @@
      to_tsquery(',language', '&lt;query&gt;')
    ORDER BY rank DESC
    LIMIT 3
</span><span class="no">  SQL</span>
<span class="k">end</span>
</code></pre></div></div>

<hr />
<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p>IMHO … I personally prefer to split it up into two methods, first one – <code class="language-plaintext highlighter-rouge">search</code> – to accept the parameter and maybe doing something with it and provide it as input for the raw sql, and a second one – <code class="language-plaintext highlighter-rouge">query</code> – to represent in fact the raw SQL query, that will be called in the first method. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>LeFnord</name></author><category term="how_to" /><category term="pg" /></entry><entry><title type="html">How To … Add new post</title><link href="https://lefnord.github.io/usage/" rel="alternate" type="text/html" title="How To … Add new post" /><published>2019-02-13T21:44:00+00:00</published><updated>2019-02-13T21:44:00+00:00</updated><id>https://lefnord.github.io/usage</id><content type="html" xml:base="https://lefnord.github.io/usage/"><![CDATA[<p>(It’s more for me, to not to forget the handling 😉 )</p>

<ol>
  <li>Ensure you have a folder <code class="language-plaintext highlighter-rouge">_drafts</code>.</li>
  <li>Run <code class="language-plaintext highlighter-rouge">bin/new_post.rb &lt;Title, with whitespaces&gt;</code>
this generates a Markdown file with name
<code class="language-plaintext highlighter-rouge">&lt;2020-10-10-Title,_with_whitespaces&gt;.md</code> in <code class="language-plaintext highlighter-rouge">_drafts</code> folder,
if exists, else in project folder.</li>
  <li>To run it local <code class="language-plaintext highlighter-rouge">bundle exec jekyll serve --draft</code> or <code class="language-plaintext highlighter-rouge">bin/run</code></li>
  <li>If you are ready/finishing writing, move it into to <code class="language-plaintext highlighter-rouge">_posts</code> folder to publish.</li>
  <li>push the changes</li>
</ol>]]></content><author><name>LeFnord</name></author><category term="how_to" /></entry></feed>