Jekyll2024-01-31T01:23:20+05:30https://rubyinrails.com/feed.xmlRuby in RailsRuby, Rails, Ruby on Rails TutorialsRead secrets using AWS secret manager in Rails2022-08-04T09:00:00+05:302022-08-04T09:00:00+05:30https://rubyinrails.com/2022/08/04/read-secrets-using-aws-secret-manager-in-rails<p>Storing secrets in a version control system is not a good idea.
Rails provides a way to store
<a href="https://edgeguides.rubyonrails.org/security.html#custom-credentials">credentials</a>
in an encrypted manner.
In such approach,
we need to just store secret key (called as <code class="language-plaintext highlighter-rouge">master.key</code>)
separately which is used to decrypt credentials.</p>
<p>AWS has released a feature called AWS
<a href="https://aws.amazon.com/secrets-manager/">secret manager</a>.
It solves a few problems.</p>
<ol>
<li>Storing environment credentials in a secure manner</li>
<li><a href="https://aws.amazon.com/blogs/security/rotate-amazon-rds-database-credentials-automatically-with-aws-secrets-manager/#:~:text=To%20enable%20rotation%2C%20I%20select,this%20secret%20on%20your%20behalf.">Auto rotate secrets</a> using AWS Lambda in an automated manner</li>
<li>Storage and access of secrets in environment specific manner</li>
</ol>
<p>Read more about AWS secrets <a href="https://aws.amazon.com/secrets-manager/">here</a>.
To create secrets using AWS secrets manager follow this
<a href="https://docs.aws.amazon.com/secretsmanager/latest/userguide/create_secret.html">article</a></p>
<p>In this article,
we will learn how to use secrets created using AWS secret manager
and
using those in the Rails application.</p>
<h4 id="step-1-add-aws-sdk-secretsmanager-gem">Step 1: Add aws-sdk-secretsmanager gem</h4>
<p>To access services specific to AWS secret manager,
add the gem in Rails application.</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">gem</span> <span class="s1">'aws-sdk-secretsmanager'</span></code></pre></figure>
<h4 id="step-2-create-secret-manager-initializer">Step 2: Create secret manager initializer</h4>
<p>Create an initializer file in <code class="language-plaintext highlighter-rouge">config/initializers</code> directory.
Let’s name it as <code class="language-plaintext highlighter-rouge">secret_manager.rb</code></p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="nb">require</span> <span class="s1">'aws-sdk-secretsmanager'</span>
<span class="k">def</span> <span class="nf">set_aws_managed_secrets</span>
<span class="c1"># secret name created in aws secret manager</span>
<span class="n">secret_name</span> <span class="o">=</span> <span class="s2">"</span><span class="si">#{</span><span class="no">ENV</span><span class="p">[</span><span class="s1">'RAILS_ENV'</span><span class="p">]</span><span class="si">}</span><span class="s2">/repository_name/postgres/username"</span>
<span class="c1"># region name</span>
<span class="n">region_name</span> <span class="o">=</span> <span class="s1">'ap-east-1'</span>
<span class="n">client</span> <span class="o">=</span> <span class="no">Aws</span><span class="o">::</span><span class="no">SecretsManager</span><span class="o">::</span><span class="no">Client</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="ss">region: </span><span class="n">region_name</span><span class="p">)</span>
<span class="k">begin</span>
<span class="n">get_secret_value_response</span> <span class="o">=</span> <span class="n">client</span><span class="p">.</span><span class="nf">get_secret_value</span><span class="p">(</span><span class="ss">secret_id: </span><span class="n">secret_name</span><span class="p">)</span>
<span class="k">rescue</span> <span class="no">Aws</span><span class="o">::</span><span class="no">SecretsManager</span><span class="o">::</span><span class="no">Errors</span><span class="o">::</span><span class="no">DecryptionFailure</span> <span class="o">=></span> <span class="n">e</span>
<span class="k">raise</span>
<span class="k">rescue</span> <span class="no">Aws</span><span class="o">::</span><span class="no">SecretsManager</span><span class="o">::</span><span class="no">Errors</span><span class="o">::</span><span class="no">InternalServiceError</span> <span class="o">=></span> <span class="n">e</span>
<span class="k">raise</span>
<span class="k">rescue</span> <span class="no">Aws</span><span class="o">::</span><span class="no">SecretsManager</span><span class="o">::</span><span class="no">Errors</span><span class="o">::</span><span class="no">InvalidParameterException</span> <span class="o">=></span> <span class="n">e</span>
<span class="k">raise</span>
<span class="k">rescue</span> <span class="no">Aws</span><span class="o">::</span><span class="no">SecretsManager</span><span class="o">::</span><span class="no">Errors</span><span class="o">::</span><span class="no">InvalidRequestException</span> <span class="o">=></span> <span class="n">e</span>
<span class="k">raise</span>
<span class="k">rescue</span> <span class="no">Aws</span><span class="o">::</span><span class="no">SecretsManager</span><span class="o">::</span><span class="no">Errors</span><span class="o">::</span><span class="no">ResourceNotFoundException</span> <span class="o">=></span> <span class="n">e</span>
<span class="k">raise</span>
<span class="k">else</span>
<span class="k">if</span> <span class="n">get_secret_value_response</span><span class="p">.</span><span class="nf">secret_string</span>
<span class="n">secret_json</span> <span class="o">=</span> <span class="n">get_secret_value_response</span><span class="p">.</span><span class="nf">secret_string</span>
<span class="n">secret_hash</span> <span class="o">=</span> <span class="no">JSON</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="n">secret_json</span><span class="p">)</span>
<span class="no">ENV</span><span class="p">[</span><span class="s1">'DATABASE_HOST'</span><span class="p">]</span> <span class="o">=</span> <span class="n">secret_hash</span><span class="p">[</span><span class="s1">'host'</span><span class="p">]</span>
<span class="no">ENV</span><span class="p">[</span><span class="s1">'DATABASE_USERNAME'</span><span class="p">]</span> <span class="o">=</span> <span class="n">secret_hash</span><span class="p">[</span><span class="s1">'username'</span><span class="p">]</span>
<span class="no">ENV</span><span class="p">[</span><span class="s1">'DATABASE_PASSWORD'</span><span class="p">]</span> <span class="o">=</span> <span class="n">secret_hash</span><span class="p">[</span><span class="s1">'password'</span><span class="p">]</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span></code></pre></figure>
<p>Below are a few noteworthy things:</p>
<p>We have created secrets specific to environment name in AWS secret manager. e. g.</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">staging</span><span class="o">/</span><span class="n">example</span><span class="o">/</span><span class="n">postgres</span><span class="o">/</span><span class="n">test_username</span></code></pre></figure>
<p>And the code below is used to define the secret name based on rails environment.</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">secret_name</span> <span class="o">=</span> <span class="s2">"</span><span class="si">#{</span><span class="no">ENV</span><span class="p">[</span><span class="s1">'RAILS_ENV'</span><span class="p">]</span><span class="si">}</span><span class="s2">/repository_name/postgres/username"</span></code></pre></figure>
<p>We receive the json response in lines given below.</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">secret_json</span> <span class="o">=</span> <span class="n">get_secret_value_response</span><span class="p">.</span><span class="nf">secret_string</span>
<span class="n">secret_hash</span> <span class="o">=</span> <span class="no">JSON</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="n">secret_json</span><span class="p">)</span></code></pre></figure>
<p><code class="language-plaintext highlighter-rouge">secret_hash</code> is a <a href="https://ruby-doc.org/core-3.1.2/Hash.html">Hash</a> with
values we have stored in aws secret manager.</p>
<p>Now, we just make those values available in <code class="language-plaintext highlighter-rouge">ENV</code> variable in lines given below.</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="no">ENV</span><span class="p">[</span><span class="s1">'DATABASE_HOST'</span><span class="p">]</span> <span class="o">=</span> <span class="n">secret_hash</span><span class="p">[</span><span class="s1">'host'</span><span class="p">]</span>
<span class="no">ENV</span><span class="p">[</span><span class="s1">'DATABASE_USERNAME'</span><span class="p">]</span> <span class="o">=</span> <span class="n">secret_hash</span><span class="p">[</span><span class="s1">'username'</span><span class="p">]</span>
<span class="no">ENV</span><span class="p">[</span><span class="s1">'DATABASE_PASSWORD'</span><span class="p">]</span> <span class="o">=</span> <span class="n">secret_hash</span><span class="p">[</span><span class="s1">'password'</span><span class="p">]</span></code></pre></figure>
<h4 id="3-use-secrets-from-env">3. Use secrets from ENV</h4>
<p>Now, wherever we need those values in the codebase (e.g. <code class="language-plaintext highlighter-rouge">config/database.yml</code>),
we can directly access them with <code class="language-plaintext highlighter-rouge">ENV['DATABASE_HOST']</code>, <code class="language-plaintext highlighter-rouge">ENV['DATABASE_USERNAME']</code>
and so on.</p>
<p>Posting sample <code class="language-plaintext highlighter-rouge">database.yml</code> file below.</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1"># config/database.yml.sample</span>
<span class="ss">default: </span><span class="o">&</span><span class="n">default</span>
<span class="ss">adapter: </span><span class="n">postgresql</span>
<span class="ss">encoding: </span><span class="n">utf8</span>
<span class="ss">username: </span><span class="s2">"<%= ENV['DATABASE_USERNAME'] %>"</span>
<span class="ss">password: </span><span class="s2">"<%= ENV['DATABASE_PASSWORD'] %>"</span>
<span class="ss">host: </span><span class="s2">"<%= ENV['DATABASE_HOST'] %>"</span>
<span class="ss">database: </span><span class="s2">"<%= ENV['DATABASE_NAME'] %>"</span>
<span class="ss">port: </span><span class="mi">5432</span>
<span class="ss">development:
</span><span class="o"><<</span><span class="p">:</span> <span class="o">*</span><span class="n">default</span>
<span class="ss">staging:
</span><span class="o"><<</span><span class="p">:</span> <span class="o">*</span><span class="n">default</span>
<span class="ss">test:
</span><span class="o"><<</span><span class="p">:</span> <span class="o">*</span><span class="n">default</span>
<span class="ss">production:
</span><span class="o"><<</span><span class="p">:</span> <span class="o">*</span><span class="n">default</span></code></pre></figure>Akshay MohiteStoring secrets in a version control system is not a good idea. Rails provides a way to store credentials in an encrypted manner. In such approach, we need to just store secret key (called as master.key) separately which is used to decrypt credentials.Find performance bottlenecks using Rails logs2022-08-03T09:00:00+05:302022-08-03T09:00:00+05:30https://rubyinrails.com/2022/08/03/find-performance-bottlenecks-using-rails-logs<p>Rails logs provide useful information indicating where queries are being fired from along with any custom logging information. We can utilise the query logs indicating line numbers to identify performance bottlenecks.</p>
<p>There are quite a few gems available to find out performance bottlenecks.
<a href="https://github.com/flyerhzm/bullet">Bullet</a> helps identify N+1 queries in the application.
It also help with removing unused <a href="https://guides.rubyonrails.org/active_record_querying.html#eager-loading-associations">eager loading</a> of associations.
While such gems help identify performance bottlenecks,
sometimes it’s rather difficult to identify bottlenecks
if the queries being fired are not N+1 queries,
but they are being called multiple times from different workflows.</p>
<p>Rails provides a configuration option
<code class="language-plaintext highlighter-rouge">verbose_query_logs</code>
on <code class="language-plaintext highlighter-rouge">ActiveRecord::Base</code>.
It is enabled by default in applications > Rails 5.2
It gives
<a href="https://guides.rubyonrails.org/debugging_rails_applications.html#verbose-query-logs">query logs</a>
with line numbers firing the query in Rails logs.</p>
<p>Below is the sample log generated in log file.</p>
<figure class="highlight"><pre><code class="language-sql" data-lang="sql"> <span class="n">Post</span> <span class="k">Load</span> <span class="p">(</span><span class="mi">0</span><span class="p">.</span><span class="mi">2</span><span class="n">ms</span><span class="p">)</span> <span class="k">SELECT</span> <span class="nv">"posts"</span><span class="p">.</span><span class="o">*</span> <span class="k">FROM</span> <span class="nv">"posts"</span>
<span class="err">↳</span> <span class="n">app</span><span class="o">/</span><span class="n">models</span><span class="o">/</span><span class="n">post</span><span class="p">.</span><span class="n">rb</span><span class="p">:</span><span class="mi">5</span>
<span class="k">Comment</span> <span class="k">Load</span> <span class="p">(</span><span class="mi">0</span><span class="p">.</span><span class="mi">1</span><span class="n">ms</span><span class="p">)</span> <span class="k">SELECT</span> <span class="nv">"comments"</span><span class="p">.</span><span class="o">*</span> <span class="k">FROM</span> <span class="nv">"comments"</span> <span class="k">WHERE</span> <span class="nv">"comments"</span><span class="p">.</span><span class="nv">"post_id"</span> <span class="o">=</span> <span class="o">?</span> <span class="p">[[</span><span class="nv">"post_id"</span><span class="p">,</span> <span class="mi">1</span><span class="p">]]</span>
<span class="err">↳</span> <span class="n">app</span><span class="o">/</span><span class="n">models</span><span class="o">/</span><span class="n">post</span><span class="p">.</span><span class="n">rb</span><span class="p">:</span><span class="mi">6</span>
<span class="k">Comment</span> <span class="k">Load</span> <span class="p">(</span><span class="mi">0</span><span class="p">.</span><span class="mi">1</span><span class="n">ms</span><span class="p">)</span> <span class="k">SELECT</span> <span class="nv">"comments"</span><span class="p">.</span><span class="o">*</span> <span class="k">FROM</span> <span class="nv">"comments"</span> <span class="k">WHERE</span> <span class="nv">"comments"</span><span class="p">.</span><span class="nv">"post_id"</span> <span class="o">=</span> <span class="o">?</span> <span class="p">[[</span><span class="nv">"post_id"</span><span class="p">,</span> <span class="mi">2</span><span class="p">]]</span>
<span class="err">↳</span> <span class="n">app</span><span class="o">/</span><span class="n">models</span><span class="o">/</span><span class="n">post</span><span class="p">.</span><span class="n">rb</span><span class="p">:</span><span class="mi">6</span>
<span class="k">Comment</span> <span class="k">Load</span> <span class="p">(</span><span class="mi">0</span><span class="p">.</span><span class="mi">1</span><span class="n">ms</span><span class="p">)</span> <span class="k">SELECT</span> <span class="nv">"comments"</span><span class="p">.</span><span class="o">*</span> <span class="k">FROM</span> <span class="nv">"comments"</span> <span class="k">WHERE</span> <span class="nv">"comments"</span><span class="p">.</span><span class="nv">"post_id"</span> <span class="o">=</span> <span class="o">?</span> <span class="p">[[</span><span class="nv">"post_id"</span><span class="p">,</span> <span class="mi">3</span><span class="p">]]</span>
<span class="err">↳</span> <span class="n">app</span><span class="o">/</span><span class="n">models</span><span class="o">/</span><span class="n">post</span><span class="p">.</span><span class="n">rb</span><span class="p">:</span><span class="mi">6</span></code></pre></figure>
<p>In this case, it’s easier to identify the source of the problem.
i.e. we need to look at <code class="language-plaintext highlighter-rouge">app/models.post.rb#6</code>.
We can add eager loading to resolve this particular issue.</p>
<p>It can be quite overwhelming to find out which parts of our code are causing bottlenecks.</p>
<p>In such cases, we can use a simple bash command given below to find out which lines are getting called most number of times.</p>
<figure class="highlight"><pre><code class="language-shell" data-lang="shell"> <span class="nb">cat </span>log/development.log | <span class="nb">sort</span> | <span class="nb">uniq</span> <span class="nt">-c</span> | <span class="nb">sort</span> <span class="nt">-n</span></code></pre></figure>
<p>Assuming we are looking in log files of <code class="language-plaintext highlighter-rouge">development</code> environment.
The line above lists down log lines in sorted manner
with line numbers which are logged most number of times
at the last with count showing how many times it’s being called.</p>
<p>Posting a sample output of the command given above.</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"> <span class="mi">5170</span> <span class="no">User</span> <span class="no">Load</span> <span class="p">(</span><span class="mf">0.1</span><span class="n">ms</span><span class="p">)</span> <span class="no">SELECT</span> <span class="s2">"users"</span><span class="p">.</span><span class="nf">*</span> <span class="no">FROM</span> <span class="s2">"users"</span> <span class="no">WHERE</span> <span class="s2">"users"</span><span class="o">.</span><span class="s2">"type"</span> <span class="o">=</span> <span class="vg">$1</span> <span class="no">ORDER</span> <span class="no">BY</span> <span class="s2">"users"</span><span class="o">.</span><span class="s2">"id"</span> <span class="no">ASC</span> <span class="no">LIMIT</span> <span class="vg">$2</span> <span class="p">[[</span><span class="s2">"type"</span><span class="p">,</span> <span class="s2">"SuperAdmin"</span><span class="p">],</span> <span class="p">[</span><span class="s2">"LIMIT"</span><span class="p">,</span> <span class="mi">1</span><span class="p">]]</span>
<span class="mi">5276</span> <span class="no">Product</span> <span class="no">Load</span> <span class="p">(</span><span class="mf">0.1</span><span class="n">ms</span><span class="p">)</span> <span class="no">SELECT</span> <span class="s2">"products"</span><span class="p">.</span><span class="nf">*</span> <span class="no">FROM</span> <span class="s2">"products"</span> <span class="no">WHERE</span> <span class="s2">"products"</span><span class="o">.</span><span class="s2">"id"</span> <span class="o">=</span> <span class="vg">$1</span> <span class="no">LIMIT</span> <span class="vg">$2</span> <span class="p">[[</span><span class="s2">"id"</span><span class="p">,</span> <span class="mi">1</span><span class="p">],</span> <span class="p">[</span><span class="s2">"LIMIT"</span><span class="p">,</span> <span class="mi">1</span><span class="p">]]</span>
<span class="mi">5981</span> <span class="no">Portal</span> <span class="no">Load</span> <span class="p">(</span><span class="mf">0.1</span><span class="n">ms</span><span class="p">)</span> <span class="no">SELECT</span> <span class="s2">"portals"</span><span class="p">.</span><span class="nf">*</span> <span class="no">FROM</span> <span class="s2">"portals"</span> <span class="no">WHERE</span> <span class="s2">"portals"</span><span class="o">.</span><span class="s2">"id"</span> <span class="o">=</span> <span class="vg">$1</span> <span class="no">LIMIT</span> <span class="vg">$2</span> <span class="p">[[</span><span class="s2">"id"</span><span class="p">,</span> <span class="mi">5</span><span class="p">],</span> <span class="p">[</span><span class="s2">"LIMIT"</span><span class="p">,</span> <span class="mi">1</span><span class="p">]]</span>
<span class="mi">9775</span> <span class="no">Cost</span> <span class="no">Load</span> <span class="p">(</span><span class="mf">0.1</span><span class="n">ms</span><span class="p">)</span> <span class="no">SELECT</span> <span class="s2">"costs"</span><span class="p">.</span><span class="nf">*</span> <span class="no">FROM</span> <span class="s2">"costs"</span> <span class="no">WHERE</span> <span class="s2">"costs"</span><span class="o">.</span><span class="s2">"id"</span> <span class="o">=</span> <span class="vg">$1</span> <span class="no">LIMIT</span> <span class="vg">$2</span> <span class="p">[[</span><span class="s2">"id"</span><span class="p">,</span> <span class="mi">19</span><span class="p">],</span> <span class="p">[</span><span class="s2">"LIMIT"</span><span class="p">,</span> <span class="mi">1</span><span class="p">]]</span>
<span class="mi">10072</span> <span class="no">Cost</span> <span class="no">Load</span> <span class="p">(</span><span class="mf">0.1</span><span class="n">ms</span><span class="p">)</span> <span class="no">SELECT</span> <span class="s2">"costs"</span><span class="p">.</span><span class="nf">*</span> <span class="no">FROM</span> <span class="s2">"costs"</span> <span class="no">WHERE</span> <span class="s2">"costs"</span><span class="o">.</span><span class="s2">"id"</span> <span class="o">=</span> <span class="vg">$1</span> <span class="no">LIMIT</span> <span class="vg">$2</span> <span class="p">[[</span><span class="s2">"id"</span><span class="p">,</span> <span class="mi">18</span><span class="p">],</span> <span class="p">[</span><span class="s2">"LIMIT"</span><span class="p">,</span> <span class="mi">1</span><span class="p">]]</span></code></pre></figure>
<p>As we can see it clearly states which query is being fired huge number of times in the application.
We can utilize this information effectively to find out which lines are hogging our application.</p>
<p>Often times, we need to optimize performance of certain parts / workflows of our application.
In such case, we can respwan the log file.</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">rm</span> <span class="n">log</span><span class="o">/</span><span class="n">development</span><span class="p">.</span><span class="nf">log</span>
<span class="n">touch</span> <span class="n">log</span><span class="o">/</span><span class="n">development</span><span class="p">.</span><span class="nf">log</span></code></pre></figure>
<p>Execute the workflow by restarting <code class="language-plaintext highlighter-rouge">rails server</code> and execute the command given above to find out issues
in a particular session.</p>Akshay MohiteRails logs provide useful information indicating where queries are being fired from along with any custom logging information. We can utilise the query logs indicating line numbers to identify performance bottlenecks.Split Rails routes into multiple files2022-08-02T09:00:00+05:302022-08-02T09:00:00+05:30https://rubyinrails.com/2022/08/02/split-rails-routes-into-multiple-files<p>Rails
<a href="https://guides.rubyonrails.org/routing.html">routes</a> file can become lengthy if the application becomes huge. It can be difficult to manage routes especially if we have multiple namespace and route action names are similar across namespaces. Splitting Rails routes helps us manage routes easily in an application.</p>
<p>Rails routes file is available at <code class="language-plaintext highlighter-rouge">config/routes.rb</code> by default.
It generally follows the pattern given below.</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="no">Rails</span><span class="p">.</span><span class="nf">application</span><span class="p">.</span><span class="nf">routes</span><span class="p">.</span><span class="nf">draw</span> <span class="k">do</span>
<span class="c1"># route 1</span>
<span class="c1"># route 2</span>
<span class="c1"># route .</span>
<span class="c1"># route .</span>
<span class="c1"># route N</span>
<span class="k">end</span></code></pre></figure>
<p>As we can see, <code class="language-plaintext highlighter-rouge">Rails.application.routes</code> calls method <code class="language-plaintext highlighter-rouge">draw</code> by passing a block of routes defined in this file.
<code class="language-plaintext highlighter-rouge">Rails.application.routes</code> is an instance of
<a href="https://github.com/rails/rails/blob/c6cbf8fc5602b7c1a04555a92263044e47b74225/actionpack/lib/action_dispatch/routing/route_set.rb">ActionDispatch::Routing::RouteSet</a>.</p>
<p>Let’s take an example <code class="language-plaintext highlighter-rouge">config/routes.rb</code> file with some routes.</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1"># config/routes.rb</span>
<span class="no">Rails</span><span class="p">.</span><span class="nf">application</span><span class="p">.</span><span class="nf">routes</span><span class="p">.</span><span class="nf">draw</span> <span class="k">do</span>
<span class="n">namespace</span> <span class="ss">:admin</span> <span class="k">do</span>
<span class="c1"># ..</span>
<span class="k">end</span>
<span class="n">namespace</span> <span class="ss">:customer</span> <span class="k">do</span>
<span class="c1"># ..</span>
<span class="k">end</span>
<span class="n">namespace</span> <span class="ss">:third_party</span> <span class="k">do</span>
<span class="c1"># ..</span>
<span class="k">end</span>
<span class="k">end</span></code></pre></figure>
<p>To split this <code class="language-plaintext highlighter-rouge">routes.rb</code> file into multiple files:</p>
<h6 id="1-create-a-directory-configroutes">1. Create a directory <code class="language-plaintext highlighter-rouge">config/routes</code>.</h6>
<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="nb">mkdir</span> <span class="nt">-p</span> config/routes</code></pre></figure>
<h6 id="2-create-new-files-in-the-configroutes-directory-in-this-case-admin_routesrb-customer_routesrb-third_party_routesrb">2. Create new files in the <code class="language-plaintext highlighter-rouge">config/routes</code> directory. In this case, <code class="language-plaintext highlighter-rouge">admin_routes.rb</code>, <code class="language-plaintext highlighter-rouge">customer_routes.rb</code>, <code class="language-plaintext highlighter-rouge">third_party_routes.rb</code></h6>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1"># config/routes/admin_routes.rb</span>
<span class="k">module</span> <span class="nn">AdminRoutes</span>
<span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">extended</span><span class="p">(</span><span class="n">router</span><span class="p">)</span>
<span class="n">router</span><span class="p">.</span><span class="nf">instance_exec</span> <span class="k">do</span>
<span class="c1"># put admin namespace routes here</span>
<span class="n">namespace</span> <span class="ss">:admin</span> <span class="k">do</span>
<span class="c1"># ..</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span></code></pre></figure>
<p>Similarly, define <code class="language-plaintext highlighter-rouge">customer_routes.rb</code> and <code class="language-plaintext highlighter-rouge">third_party_routes.rb</code> files.</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1"># config/routes/customer_routes.rb</span>
<span class="k">module</span> <span class="nn">CustomerRoutes</span>
<span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">extended</span><span class="p">(</span><span class="n">router</span><span class="p">)</span>
<span class="n">router</span><span class="p">.</span><span class="nf">instance_exec</span> <span class="k">do</span>
<span class="c1"># put customer namespace routes here</span>
<span class="n">namespace</span> <span class="ss">:customer</span> <span class="k">do</span>
<span class="c1"># ..</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span></code></pre></figure>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1"># config/routes/third_party_routes.rb</span>
<span class="k">module</span> <span class="nn">ThirdPartyRoutes</span>
<span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">extended</span><span class="p">(</span><span class="n">router</span><span class="p">)</span>
<span class="n">router</span><span class="p">.</span><span class="nf">instance_exec</span> <span class="k">do</span>
<span class="c1"># put third_party namespace routes here</span>
<span class="n">namespace</span> <span class="ss">:third_party</span> <span class="k">do</span>
<span class="c1"># ..</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span></code></pre></figure>
<p>Make sure these newly created route files are
<a href="https://guides.rubyonrails.org/autoloading_and_reloading_constants.html">auto loaded</a>.
Add <code class="language-plaintext highlighter-rouge">config/routes</code> directory files in autoload paths in <code class="language-plaintext highlighter-rouge">config/application.rb</code></p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">config</span><span class="p">.</span><span class="nf">autoload_paths</span> <span class="o">+=</span> <span class="no">Dir</span><span class="p">[</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="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="s1">'config'</span><span class="p">,</span> <span class="s1">'routes'</span><span class="p">,</span> <span class="s1">'**/'</span><span class="p">)</span> <span class="p">]</span></code></pre></figure>
<p>This auto loads modules defined in <code class="language-plaintext highlighter-rouge">config/routes</code> directory and can be used in
<code class="language-plaintext highlighter-rouge">config/routes.rb</code> file. Let’s load these routes in <code class="language-plaintext highlighter-rouge">config/routes.rb</code> file as given below.</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="no">Rails</span><span class="p">.</span><span class="nf">application</span><span class="p">.</span><span class="nf">routes</span><span class="p">.</span><span class="nf">draw</span> <span class="k">do</span>
<span class="kp">extend</span> <span class="no">AdminRoutes</span>
<span class="kp">extend</span> <span class="no">CustomerRoutes</span>
<span class="kp">extend</span> <span class="no">ThirdPartyRoutes</span>
<span class="k">end</span></code></pre></figure>
<p>Run the rails application to verify that routes splitted in multiple files
are loaded correctly and they work as expected.</p>Akshay MohiteRails routes file can become lengthy if the application becomes huge. It can be difficult to manage routes especially if we have multiple namespace and route action names are similar across namespaces. Splitting Rails routes helps us manage routes easily in an application.Rails N+1 query optimization to get records counts in PSQL2022-08-01T09:00:00+05:302022-08-01T09:00:00+05:30https://rubyinrails.com/2022/08/01/rails-n-1-query-optimization-record-counts-psql<p>Performance of the Rails application becomes slower if the application has a lot of N+1 queries.
In this article,
we will go through a performance optimization that removes N+1 queries
to fetch recourd counts based on certain conditions.</p>
<p>Let’s consider a schema for <code class="language-plaintext highlighter-rouge">Address</code> model / table as gives below.</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"> <span class="c1"># id</span>
<span class="c1"># user_id</span>
<span class="c1"># address_1</span>
<span class="c1"># address_2</span>
<span class="c1"># address_3</span>
<span class="c1"># created_at</span>
<span class="c1"># updated_at</span>
<span class="k">class</span> <span class="nc">Address</span> <span class="o"><</span> <span class="no">ApplicationRecord</span>
<span class="k">end</span></code></pre></figure>
<p>Let’s say, we want to get</p>
<ul>
<li>The number of records that do not have <code class="language-plaintext highlighter-rouge">address_1</code> value</li>
<li>The number of records that do not have <code class="language-plaintext highlighter-rouge">address_2</code> value</li>
<li>The number of records that do not have <code class="language-plaintext highlighter-rouge">address_3</code> value</li>
</ul>
<p>This can be written with Ruby on Rails ORM as given below.</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"> <span class="n">column_names</span> <span class="o">=</span> <span class="sx">%i(address_1 address_2 address_3)</span>
<span class="n">column_names</span><span class="p">.</span><span class="nf">inject</span><span class="p">({})</span> <span class="k">do</span> <span class="o">|</span><span class="n">column_counts</span><span class="p">,</span> <span class="n">column_name</span><span class="o">|</span>
<span class="n">count</span> <span class="o">=</span> <span class="no">Address</span><span class="p">.</span><span class="nf">where</span><span class="p">(</span><span class="n">column_name</span> <span class="o">=></span> <span class="p">[</span><span class="kp">nil</span><span class="p">,</span> <span class="s1">''</span><span class="p">]).</span><span class="nf">select</span><span class="p">(</span><span class="n">column_name</span><span class="p">).</span><span class="nf">count</span>
<span class="n">column_counts</span><span class="p">[</span><span class="n">column_name</span><span class="p">]</span> <span class="o">=</span> <span class="n">count</span>
<span class="n">column_counts</span>
<span class="k">end</span></code></pre></figure>
<p>If we run this piece of code,
we get following output
on a sample database with some records in <code class="language-plaintext highlighter-rouge">addresses</code> table.</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="p">{</span>
<span class="ss">:address_1</span> <span class="o">=></span> <span class="mi">23</span><span class="p">,</span>
<span class="ss">:address_2</span> <span class="o">=></span> <span class="mi">51</span><span class="p">,</span>
<span class="ss">:address_3</span> <span class="o">=></span> <span class="mi">1233</span>
<span class="p">}</span></code></pre></figure>
<p>We’ve obtained the required information from the database using Rails ORM.</p>
<p>Let’s look at the queries being fired to get this data.</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">count</span> <span class="o">=</span> <span class="no">Address</span><span class="p">.</span><span class="nf">where</span><span class="p">(</span><span class="n">column_name</span> <span class="o">=></span> <span class="p">[</span><span class="kp">nil</span><span class="p">,</span> <span class="s1">''</span><span class="p">]).</span><span class="nf">select</span><span class="p">(</span><span class="n">column_name</span><span class="p">).</span><span class="nf">count</span></code></pre></figure>
<p>And the line is being called in a loop.
When inspected, we can see following queries.</p>
<figure class="highlight"><pre><code class="language-sql" data-lang="sql"><span class="k">SELECT</span> <span class="k">COUNT</span><span class="p">(</span><span class="nv">"addresses"</span><span class="p">.</span><span class="nv">"address_1"</span><span class="p">)</span> <span class="k">FROM</span> <span class="nv">"addresses"</span> <span class="k">WHERE</span> <span class="nv">"addresses"</span><span class="p">.</span><span class="nv">"deleted_at"</span> <span class="k">IS</span> <span class="k">NULL</span> <span class="k">AND</span> <span class="p">(</span><span class="nv">"addresses"</span><span class="p">.</span><span class="nv">"address_1"</span> <span class="o">=</span> <span class="err">$</span><span class="mi">1</span> <span class="k">OR</span> <span class="nv">"addresses"</span><span class="p">.</span><span class="nv">"address_1"</span> <span class="k">IS</span> <span class="k">NULL</span><span class="p">)</span> <span class="p">[[</span><span class="nv">"address_1"</span><span class="p">,</span> <span class="nv">""</span><span class="p">]]</span>
<span class="k">SELECT</span> <span class="k">COUNT</span><span class="p">(</span><span class="nv">"addresses"</span><span class="p">.</span><span class="nv">"address_2"</span><span class="p">)</span> <span class="k">FROM</span> <span class="nv">"addresses"</span> <span class="k">WHERE</span> <span class="nv">"addresses"</span><span class="p">.</span><span class="nv">"deleted_at"</span> <span class="k">IS</span> <span class="k">NULL</span> <span class="k">AND</span> <span class="p">(</span><span class="nv">"addresses"</span><span class="p">.</span><span class="nv">"address_2"</span> <span class="o">=</span> <span class="err">$</span><span class="mi">1</span> <span class="k">OR</span> <span class="nv">"addresses"</span><span class="p">.</span><span class="nv">"address_2"</span> <span class="k">IS</span> <span class="k">NULL</span><span class="p">)</span> <span class="p">[[</span><span class="nv">"address_2"</span><span class="p">,</span> <span class="nv">""</span><span class="p">]]</span>
<span class="k">SELECT</span> <span class="k">COUNT</span><span class="p">(</span><span class="nv">"addresses"</span><span class="p">.</span><span class="nv">"address_3"</span><span class="p">)</span> <span class="k">FROM</span> <span class="nv">"addresses"</span> <span class="k">WHERE</span> <span class="nv">"addresses"</span><span class="p">.</span><span class="nv">"deleted_at"</span> <span class="k">IS</span> <span class="k">NULL</span> <span class="k">AND</span> <span class="p">(</span><span class="nv">"addresses"</span><span class="p">.</span><span class="nv">"address_3"</span> <span class="o">=</span> <span class="err">$</span><span class="mi">1</span> <span class="k">OR</span> <span class="nv">"addresses"</span><span class="p">.</span><span class="nv">"address_3"</span> <span class="k">IS</span> <span class="k">NULL</span><span class="p">)</span> <span class="p">[[</span><span class="nv">"address_3"</span><span class="p">,</span> <span class="nv">""</span><span class="p">]]</span></code></pre></figure>
<p>As we can see it has fired 3 queries. Now, if this piece of / method gets called huge number of times in an application,
this can become a bottleneck quickly.</p>
<p>General rule of thumb can be considered as:</p>
<blockquote>
<p>If any line of code which fires a query is written in a loop, there may be a scope of performance optmization to remove those N+1 queries.</p>
</blockquote>
<h3 id="remove-n1-querying-using-a-sql">Remove N+1 querying using a SQL</h3>
<p>Upon taking a close look at the SQL queries being fired,
we can get the required data in a single SQL query
using
<a href="https://www.postgresql.org/docs/current/functions-conditional.html">PostgreSQL’s Conditional Expresions</a></p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">column_names</span> <span class="o">=</span> <span class="sx">%i(address_1 address_2 address_3)</span>
<span class="n">sql</span> <span class="o">=</span> <span class="o"><<-</span><span class="no">SQL</span><span class="sh">
SELECT </span><span class="si">#{</span>
<span class="n">column_names</span><span class="p">.</span><span class="nf">map</span> <span class="k">do</span> <span class="o">|</span><span class="n">column_name</span><span class="o">|</span>
<span class="s2">"COALESCE(SUM(CASE WHEN </span><span class="si">#{</span><span class="n">column_name</span><span class="si">}</span><span class="s2"> IS NULL THEN 1 ELSE 0 END), 0) AS </span><span class="si">#{</span><span class="n">column_name</span><span class="si">}</span><span class="s2">"</span>
<span class="k">end</span><span class="p">.</span><span class="nf">join</span><span class="p">(</span><span class="s1">', '</span><span class="p">)</span>
<span class="si">}</span><span class="sh">
FROM addresses
</span><span class="no">SQL</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">sql</span><span class="p">).</span><span class="nf">first</span></code></pre></figure>
<p>Here we are getting the count of records
for each column in a select clause
based on condition in <code class="language-plaintext highlighter-rouge">case .. when</code> expressions.
The record is counted if the value is <code class="language-plaintext highlighter-rouge">NULL</code>.
We have used
<a href="https://www.postgresql.org/docs/current/functions-conditional.html#FUNCTIONS-COALESCE-NVL-IFNULL">COALESCE</a>
in order to ensure default value <code class="language-plaintext highlighter-rouge">0</code> is returned if there are no records in <code class="language-plaintext highlighter-rouge">addresses</code> table.</p>
<p>It gives the output as given below.</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"> <span class="ss">:address_1</span> <span class="o">=></span> <span class="mi">23</span><span class="p">,</span>
<span class="ss">:address_2</span> <span class="o">=></span> <span class="mi">51</span><span class="p">,</span>
<span class="ss">:address_3</span> <span class="o">=></span> <span class="mi">1233</span></code></pre></figure>
<p>This is exactly the result that we wanted.
But, using PostgreSQL’s features,
we have obtained this result in a single query.</p>Akshay MohitePerformance of the Rails application becomes slower if the application has a lot of N+1 queries. In this article, we will go through a performance optimization that removes N+1 queries to fetch recourd counts based on certain conditions.How to fix user controlled method execution - Brakeman2021-12-24T09:00:00+05:302021-12-24T09:00:00+05:30https://rubyinrails.com/2021/12/24/how-to-fix-user-controlled-method-execution-brakeman<p><a href="https://github.com/presidentbeef/brakeman">Brakeman</a> is a free static vulnerability scanner tool that helps you find security issues in your Rails application. In this tutorial, we will learn how to fix user controlled method execution security warning.
Let’s consider an example.</p>
<p>Brakeman detects security warnings by statically analyzing the codebase for potential security vulnerabilities. Official documentation would give us potential suggestions on how to fix the security warnings. It is a good practice to keep an eye on security warnings in your application. This can be done by integrating brakeman with the <a href="https://en.wikipedia.org/wiki/CI/CD">CI/CD pipeline</a>.</p>
<p>Consider a use case where we want to perform some changes to a <code class="language-plaintext highlighter-rouge">User</code> in the application. Let’s say, we have options to perform the following actions:</p>
<ul>
<li>Activate user - which marks user as active.</li>
<li>Deactivate user - which marks user as inactive.</li>
<li>Archive user - which archives the user.</li>
</ul>
<p>These options are displayed as a dropdown to the end user. The end user selects one of the action and hits <code class="language-plaintext highlighter-rouge">Submit</code> button. And any of the activity mentioned above is triggered on <code class="language-plaintext highlighter-rouge">Submit</code> button click.</p>
<p>Assuming, in case we have a single controller action <code class="language-plaintext highlighter-rouge">update</code>
to process such change.</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">class</span> <span class="nc">UsersController</span> <span class="o"><</span> <span class="no">ApplicationController</span>
<span class="n">before_action</span> <span class="ss">:set_user</span><span class="p">,</span> <span class="ss">only: </span><span class="p">[</span><span class="ss">:update</span><span class="p">]</span>
<span class="k">def</span> <span class="nf">update</span>
<span class="n">public_send</span><span class="p">(</span><span class="n">params</span><span class="p">[</span><span class="ss">:status</span><span class="p">])</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">activate</span>
<span class="vi">@user</span><span class="p">.</span><span class="nf">update</span><span class="p">(</span><span class="ss">status: :active</span><span class="p">)</span>
<span class="c1"># Do some more work required after inactivating user</span>
<span class="c1"># ...</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">deactivate</span>
<span class="vi">@user</span><span class="p">.</span><span class="nf">update</span><span class="p">(</span><span class="ss">status: :inactive</span><span class="p">)</span>
<span class="c1"># Do some more work required after deactivating user</span>
<span class="c1"># ...</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">archive</span>
<span class="vi">@user</span><span class="p">.</span><span class="nf">update</span><span class="p">(</span><span class="ss">status: :archive</span><span class="p">)</span>
<span class="c1"># Do some more work required after archiving user</span>
<span class="c1"># ...</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">set_user</span>
<span class="vi">@user</span> <span class="o">=</span> <span class="no">User</span><span class="p">.</span><span class="nf">find</span><span class="p">(</span><span class="n">params</span><span class="p">[</span><span class="ss">:id</span><span class="p">])</span>
<span class="k">end</span>
<span class="k">end</span></code></pre></figure>
<p><em>Note: Why these methods in controller? This is for illustration of the concept.
This could be a piece of code in some service class or somewhere else.</em></p>
<p>Now, if you run brakeman security analysis over this controller,
it will give <a href="https://brakemanscanner.org/docs/warning_types/dangerous_send/">a security warning</a> as given below.</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="no">Confidence</span><span class="p">:</span> <span class="no">High</span>
<span class="no">Category</span><span class="p">:</span> <span class="no">Dangerous</span> <span class="no">Send</span>
<span class="no">Check</span><span class="p">:</span> <span class="no">Send</span>
<span class="no">Message</span><span class="p">:</span> <span class="no">User</span> <span class="n">controlled</span> <span class="nb">method</span> <span class="n">execution</span>
<span class="no">Code</span><span class="p">:</span> <span class="n">public_send</span><span class="p">(</span><span class="n">params</span><span class="p">[</span><span class="ss">:status</span><span class="p">])</span>
<span class="no">File</span><span class="p">:</span> <span class="n">app</span><span class="o">/</span><span class="n">controllers</span><span class="o">/</span><span class="n">users_controller</span><span class="p">.</span><span class="nf">rb</span>
<span class="no">Line</span><span class="p">:</span> <span class="mi">5</span></code></pre></figure>
<p>This is considered as security vulnerability,
because if <code class="language-plaintext highlighter-rouge">params[:status]</code> is altered,
it could lead to running any method dangerously.</p>
<h4 id="fix-dangerous-send-method-execution">Fix dangerous send method execution</h4>
<p>This could be fixed by whitelisting method that needs to be called.
For the case we described above, we can rewrite <code class="language-plaintext highlighter-rouge">update</code> method as given below.</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">def</span> <span class="nf">update</span>
<span class="k">case</span> <span class="n">params</span><span class="p">[</span><span class="ss">:status</span><span class="p">]</span>
<span class="k">when</span> <span class="s1">'activate'</span>
<span class="n">activate</span>
<span class="k">when</span> <span class="s1">'deactivate'</span>
<span class="n">deactivate</span>
<span class="k">when</span> <span class="s1">'archive'</span>
<span class="n">archive</span>
<span class="k">end</span>
<span class="k">end</span></code></pre></figure>
<p>This whitelists methods that are alllowed to be called. If <code class="language-plaintext highlighter-rouge">params[:status]</code> is altered, it will have no effect as allowed methods are only <code class="language-plaintext highlighter-rouge">activate</code>, <code class="language-plaintext highlighter-rouge">deactivate</code> and <code class="language-plaintext highlighter-rouge">archive</code>.</p>
<p>Similarly, we see occurrences where we derive class name dynamically based on the input parameter should be handled in similar manner.</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">class_name</span> <span class="o">=</span> <span class="n">params</span><span class="p">[</span><span class="ss">:class_name</span><span class="p">]</span>
<span class="n">model</span> <span class="o">=</span> <span class="n">class_name</span><span class="p">.</span><span class="nf">classify</span><span class="p">.</span><span class="nf">constantize</span></code></pre></figure>
<p>This should be avoided as <code class="language-plaintext highlighter-rouge">params[:class_name]</code> is an user input. Instead, use whitelisting strategy discussed above.</p>Akshay MohiteBrakeman is a free static vulnerability scanner tool that helps you find security issues in your Rails application. In this tutorial, we will learn how to fix user controlled method execution security warning. Let’s consider an example.Define keyword arguments when defining methods with boolean arguments2021-12-20T18:00:00+05:302021-12-20T18:00:00+05:30https://rubyinrails.com/2021/12/20/define-keyword-arguments-when-defining-methods-with-boolean-arguments<p>When calling methods with boolean arguments does not explain what exactly is the argument about.
<a href="https://github.com/rubocop/rubocop">Rubocop</a> has
<a href="https://github.com/rubocop/rubocop/pull/8412">a cop</a>
that suggests to define keyword arguments when defining methods with boolean arguments.</p>
<p>Let’s consider an example.</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">def</span> <span class="nf">feature_status</span><span class="p">(</span><span class="n">is_admin</span> <span class="o">=</span> <span class="kp">false</span><span class="p">)</span>
<span class="k">if</span> <span class="n">is_admin</span>
<span class="nb">puts</span> <span class="s2">"feature is enabled"</span>
<span class="k">else</span>
<span class="nb">puts</span> <span class="s2">"feature is disabled"</span>
<span class="k">end</span>
<span class="k">end</span></code></pre></figure>
<p>Now, whenever we have to call such method,
it’ll be something like</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">feature_status</span><span class="p">(</span><span class="kp">true</span><span class="p">)</span></code></pre></figure>
<p>Now, by looking at the code given above,
it is not very much clear what that argument is about.</p>
<p>Thus, Rubocop has introduced a cop to
<a href="https://www.rubydoc.info/gems/rubocop/RuboCop/Cop/Style/OptionalBooleanParameter">define these boolean arguments as keyword arguments</a>.</p>
<p>The same method given above can be written as</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">def</span> <span class="nf">feature_status</span><span class="p">(</span><span class="ss">is_admin: </span><span class="kp">false</span><span class="p">)</span>
<span class="k">if</span> <span class="n">is_admin</span>
<span class="nb">puts</span> <span class="s2">"feature is enabled"</span>
<span class="k">else</span>
<span class="nb">puts</span> <span class="s2">"feature is disabled"</span>
<span class="k">end</span>
<span class="k">end</span></code></pre></figure>
<p>This can be called as given below.</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">feature_status</span><span class="p">(</span><span class="ss">is_admin: </span><span class="kp">true</span><span class="p">)</span></code></pre></figure>
<p>As we can see, it is pretty clear what that argument is about.
It’ll be easier to understand what argument needs to passed here precisely.</p>
<h4 id="allowed-mehthods">Allowed mehthods</h4>
<p>Rubocop has made it possible to allow some methods
if you want to make an exception for this rule.
It can be defined as given below.</p>
<figure class="highlight"><pre><code class="language-yml" data-lang="yml"><span class="na">allowed_methods</span><span class="pi">:</span> <span class="pi">[</span><span class="s1">'</span><span class="s">feature_status'</span><span class="pi">]</span></code></pre></figure>
<p>If this configuration is set in <code class="language-plaintext highlighter-rouge">.rubocop.yml</code>,
it won’t give a warning for the method <code class="language-plaintext highlighter-rouge">feature_status</code> with boolean argument anymore.</p>Akshay MohiteWhen calling methods with boolean arguments does not explain what exactly is the argument about. Rubocop has a cop that suggests to define keyword arguments when defining methods with boolean arguments.Fetch records in a custom order with ActiveRecord in Rails2021-12-14T18:00:00+05:302021-12-14T18:00:00+05:30https://rubyinrails.com/2021/12/14/fetch-records-in-custom-order-with-rails-activerecord<p>Sometimes, you may want to fetch records in some arbitrary order of values of a column. This tutorial illustrates how to do fetch records based on some arbitrary order of values in a rails application with PostgreSQL or MySQL as a database.</p>
<p>Let’s consider an example. Suppose we have a table named <code class="language-plaintext highlighter-rouge">users</code> with the following columns:</p>
<table>
<thead>
<tr>
<th>id</th>
<th>name</th>
<th>age</th>
<th>status</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>Timothy Collier</td>
<td>20</td>
<td>active</td>
</tr>
<tr>
<td>2</td>
<td>Hassan Tillman</td>
<td>30</td>
<td>active</td>
</tr>
<tr>
<td>3</td>
<td>Kaitlyn Schmitt</td>
<td>40</td>
<td>in_progress</td>
</tr>
<tr>
<td>4</td>
<td>Allison Ebert</td>
<td>50</td>
<td>stale</td>
</tr>
<tr>
<td>5</td>
<td>Justin Kuvalis</td>
<td>60</td>
<td>inactive</td>
</tr>
</tbody>
</table>
<p>Consider a use-case where we want to fetch the list of users
with age less than 50
in
some specified order of status values
as given below.</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="no">SORT_ORDER</span> <span class="o">=</span> <span class="sx">%w(stale inactive in_progress active )</span></code></pre></figure>
<p>Now, there is an easy way to fetch these records from database and then perform
<a href="https://apidock.com/ruby/Array/sort">sort</a>
in the ruby.
It could be done as given below.html</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="no">User</span><span class="p">.</span><span class="nf">where</span><span class="p">(</span><span class="s1">'age < ?'</span><span class="p">,</span> <span class="mi">50</span><span class="p">).</span><span class="nf">all</span><span class="p">.</span><span class="nf">sort_by</span> <span class="p">{</span> <span class="o">|</span><span class="n">user</span><span class="o">|</span> <span class="no">SORT_ORDER</span><span class="p">.</span><span class="nf">index</span><span class="p">(</span><span class="n">user</span><span class="p">.</span><span class="nf">status</span><span class="p">)</span> <span class="p">}</span></code></pre></figure>
<p>This shall give us the list of users with age less than 50 in the sort order specified by the constant <code class="language-plaintext highlighter-rouge">SORT_ORDER</code>.</p>
<p>But, performing operations in Ruby can be avoided if we could fetch the same result directly from the database.
Let’s try to write a query for the same.</p>
<h3 id="with-postgresql-database">With PostgreSQL database</h3>
<figure class="highlight"><pre><code class="language-sql" data-lang="sql"><span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">users</span>
<span class="k">WHERE</span> <span class="n">age</span> <span class="o"><</span> <span class="mi">50</span>
<span class="k">ORDER</span> <span class="k">BY</span> <span class="k">case</span> <span class="n">status</span>
<span class="k">WHEN</span> <span class="s1">'stale'</span> <span class="k">THEN</span> <span class="mi">1</span>
<span class="k">WHEN</span> <span class="s1">'inactive'</span> <span class="k">THEN</span> <span class="mi">2</span>
<span class="k">WHEN</span> <span class="s1">'in_progress'</span> <span class="k">THEN</span> <span class="mi">3</span>
<span class="k">WHEN</span> <span class="s1">'active'</span> <span class="k">THEN</span> <span class="mi">4</span>
<span class="k">END</span></code></pre></figure>
<p>As we can see in the code above, we have written a <a href="https://www.postgresql.org/docs/7.4/functions-conditional.html">case conditional expression</a> for each status value to perform ordering as expected.</p>
<p>Now, we can transform this to Rails ActiveRecord scope to be used on our <code class="language-plaintext highlighter-rouge">User</code> model.
Let’s define a <a href="https://guides.rubyonrails.org/active_record_querying.html#scopes">scope</a>
<code class="language-plaintext highlighter-rouge">order_by_status</code> for this functionality.</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"> <span class="n">scope</span> <span class="ss">:order_by_status</span><span class="p">,</span> <span class="nb">lambda</span> <span class="p">{</span>
<span class="n">order_clause</span> <span class="o">=</span> <span class="s1">'CASE status '</span>
<span class="no">SORT_ORDER</span><span class="p">.</span><span class="nf">each_with_index</span> <span class="k">do</span> <span class="o">|</span><span class="n">value</span><span class="p">,</span> <span class="n">index</span><span class="o">|</span>
<span class="n">order_clause</span> <span class="o"><<</span> <span class="n">sanitize_sql_array</span><span class="p">([</span><span class="s1">'WHEN ? THEN ? '</span><span class="p">,</span> <span class="n">value</span><span class="p">,</span> <span class="n">index</span><span class="p">])</span>
<span class="k">end</span>
<span class="n">order_clause</span> <span class="o"><<</span> <span class="n">sanitize_sql_array</span><span class="p">([</span><span class="s1">'ELSE ? END'</span><span class="p">,</span> <span class="no">SORT_ORDER</span><span class="p">.</span><span class="nf">length</span><span class="p">])</span>
<span class="n">order</span><span class="p">(</span><span class="no">Arel</span><span class="p">.</span><span class="nf">sql</span><span class="p">(</span><span class="n">order_clause</span><span class="p">))</span>
<span class="p">}</span></code></pre></figure>
<p>We are considering the custom SQL based on the values in the <code class="language-plaintext highlighter-rouge">SORT_ORDER</code>.</p>
<h3 id="with-mysql-database">With MySQL database</h3>
<p>MySQL has a built in function <a href="https://dev.mysql.com/doc/refman/8.0/en/string-functions.html#function_field">field</a>
which can be used to fetch records in arbitary order. Let’s try to use this function to fetch the same result.</p>
<figure class="highlight"><pre><code class="language-sql" data-lang="sql"><span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">users</span>
<span class="k">WHERE</span> <span class="n">age</span> <span class="o"><</span> <span class="mi">50</span>
<span class="k">ORDER</span> <span class="k">BY</span> <span class="n">field</span><span class="p">(</span><span class="n">status</span><span class="p">,</span> <span class="s1">'stale'</span><span class="p">,</span> <span class="s1">'inactive'</span><span class="p">,</span> <span class="s1">'in_progress'</span><span class="p">,</span> <span class="s1">'active'</span><span class="p">)</span></code></pre></figure>
<p>Now, we can transform this to Rails ActiveRecord scope to be used on our <code class="language-plaintext highlighter-rouge">User</code> model.
Let’s define a <a href="https://guides.rubyonrails.org/active_record_querying.html#scopes">scope</a>
<code class="language-plaintext highlighter-rouge">order_by_status</code> for this functionality.</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"> <span class="n">scope</span> <span class="ss">:order_by_status</span><span class="p">,</span> <span class="nb">lambda</span> <span class="p">{</span>
<span class="n">sanitized_statuses</span> <span class="o">=</span> <span class="no">SORT_ORDER</span><span class="p">.</span><span class="nf">map</span><span class="p">{</span> <span class="o">|</span><span class="n">status</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">status</span><span class="p">)</span> <span class="p">}.</span><span class="nf">join</span><span class="p">(</span><span class="s1">', '</span><span class="p">)</span>
<span class="n">order</span><span class="p">(</span><span class="no">Arel</span><span class="p">.</span><span class="nf">sql</span><span class="p">(</span><span class="s2">"field(status, </span><span class="si">#{</span><span class="n">sanitized_statuses</span><span class="si">}</span><span class="s2">)"</span><span class="p">))</span>
<span class="p">}</span></code></pre></figure>
<h3 id="usage-to-fetch-custom-ordered-records">Usage to fetch custom ordered records</h3>
<p>This can be used as given below.</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="no">User</span><span class="p">.</span><span class="nf">where</span><span class="p">(</span><span class="s1">'age < ?'</span><span class="p">,</span> <span class="mi">50</span><span class="p">).</span><span class="nf">order_by_status</span><span class="p">.</span><span class="nf">all</span></code></pre></figure>
<p>Moreover, if you would such ordering across multiple models,
you can consider moving this <a href="https://guides.rubyonrails.org/active_record_querying.html#scopes">scope</a>
to
<a href="https://api.rubyonrails.org/v6.1.4/classes/ActiveSupport/Concern.html">ActiveSupport Concern</a>.</p>
<h3 id="rails-7-in_order_of-method">Rails 7 in_order_of method</h3>
<p>Rails introduces <a href="https://github.com/rails/rails/pull/42061">in_order_of</a> method
to
<a href="https://edgeapi.rubyonrails.org/classes/ActiveRecord/QueryMethods.html#method-i-in_order_of">ActiveRecord::QueryMethods</a>.
This allows to fetch records in the custom order as we wanted.
This can be done as given below.html</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="no">User</span><span class="p">.</span><span class="nf">where</span><span class="p">(</span><span class="s1">'age < ?'</span><span class="p">,</span> <span class="mi">50</span><span class="p">).</span><span class="nf">in_order_of</span><span class="p">(</span><span class="ss">:status</span><span class="p">,</span> <span class="no">SORT_ORDER</span><span class="p">).</span><span class="nf">all</span></code></pre></figure>
<p>This gives the same result as we have done above.
Here, we don’t have to worry about making sure
we build up SQL based on the database being used with the Rails application. Rails will take care of it for us.</p>Akshay MohiteSometimes, you may want to fetch records in some arbitrary order of values of a column. This tutorial illustrates how to do fetch records based on some arbitrary order of values in a rails application with PostgreSQL or MySQL as a database.How does rails decide if migrations are pending?2021-12-01T18:00:00+05:302021-12-01T18:00:00+05:30https://rubyinrails.com/2021/12/01/rails-pending-migrations<p>Ever wondered how rails decides if any migration is pending? If not, this tutorial will explain how it is done using a table named schema_migrations.</p>
<p>Rails provides a <a href="https://guides.rubyonrails.org/configuring.html#initializers">configuration option</a> to indicate if we want to be alerted if any migration is pending.</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1"># Raise an error on page load if there are pending migrations.</span>
<span class="n">config</span><span class="p">.</span><span class="nf">active_record</span><span class="p">.</span><span class="nf">migration_error</span> <span class="o">=</span> <span class="ss">:page_load</span></code></pre></figure>
<p>This will raise an error of the pending migrations and notify the user as shown in the image below.</p>
<p><img class="aligncenter wp-image-1069" alt="rails pending migrations" src="/assets/images/pending-migrations.png" data-recalc-dims="1" /></p>
<p>This can be disabled with the <code class="language-plaintext highlighter-rouge">false</code> value
for the option as shown below.</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">config</span><span class="p">.</span><span class="nf">active_record</span><span class="p">.</span><span class="nf">migration_error</span> <span class="o">=</span> <span class="kp">false</span></code></pre></figure>
<h3 id="migration-file-name-format">Migration file name format</h3>
<p>There is a convention for naming the migration files.</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="no">YYYYMMDDHHMMSS_name_of_migration</span><span class="p">.</span><span class="nf">rb</span></code></pre></figure>
<p>It uses values from the current time for <code class="language-plaintext highlighter-rouge">YYYYMMDDHHMMSS</code> and the name of the migration.</p>
<p>Once migrations are run, the value <code class="language-plaintext highlighter-rouge">YYYYMMDDHHMMSS</code> in migration file,
is inserted in a table named <code class="language-plaintext highlighter-rouge">schema_migrations</code>.</p>
<h3 id="pending-migration-check">Pending migration check</h3>
<p>Rails provides a method to check if any migration is pending. Listing relevant methods from <a href="https://www.rubydoc.info/gems/activerecord/ActiveRecord/MigrationContext">ActiveRecord::MigrationContext</a> class below.</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1"># Listing only relevant methods for the illustration</span>
<span class="k">class</span> <span class="nc">ActiveRecord</span>
<span class="k">class</span> <span class="nc">MigrationContext</span>
<span class="k">def</span> <span class="nf">check_pending!</span><span class="p">(</span><span class="n">connection</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="k">raise</span> <span class="no">ActiveRecord</span><span class="o">::</span><span class="no">PendingMigrationError</span> <span class="k">if</span> <span class="n">connection</span><span class="p">.</span><span class="nf">migration_context</span><span class="p">.</span><span class="nf">needs_migration?</span>
<span class="k">end</span>
</code></pre></figure>
<p>This piece of code raises an exception <code class="language-plaintext highlighter-rouge">ActiveRecord::PendingMigrationError</code> if any migration is pending.</p>
<p>In the next section, we will see the logic used for <code class="language-plaintext highlighter-rouge">needs_migration?</code> method.</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"> <span class="k">def</span> <span class="nf">needs_migration?</span> <span class="c1"># :nodoc:</span>
<span class="n">pending_migration_versions</span><span class="p">.</span><span class="nf">size</span> <span class="o">></span> <span class="mi">0</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">pending_migration_versions</span> <span class="c1"># :nodoc:</span>
<span class="n">migrations</span><span class="p">.</span><span class="nf">collect</span><span class="p">(</span><span class="o">&</span><span class="ss">:version</span><span class="p">)</span> <span class="o">-</span> <span class="n">get_all_versions</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">migrations</span> <span class="c1"># :nodoc:</span>
<span class="n">migrations</span> <span class="o">=</span> <span class="n">migration_files</span><span class="p">.</span><span class="nf">map</span> <span class="k">do</span> <span class="o">|</span><span class="n">file</span><span class="o">|</span>
<span class="n">version</span><span class="p">,</span> <span class="nb">name</span><span class="p">,</span> <span class="n">scope</span> <span class="o">=</span> <span class="n">parse_migration_filename</span><span class="p">(</span><span class="n">file</span><span class="p">)</span>
<span class="k">raise</span> <span class="no">IllegalMigrationNameError</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="n">file</span><span class="p">)</span> <span class="k">unless</span> <span class="n">version</span>
<span class="n">version</span> <span class="o">=</span> <span class="n">version</span><span class="p">.</span><span class="nf">to_i</span>
<span class="nb">name</span> <span class="o">=</span> <span class="nb">name</span><span class="p">.</span><span class="nf">camelize</span>
<span class="no">MigrationProxy</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="nb">name</span><span class="p">,</span> <span class="n">version</span><span class="p">,</span> <span class="n">file</span><span class="p">,</span> <span class="n">scope</span><span class="p">)</span>
<span class="k">end</span>
<span class="n">migrations</span><span class="p">.</span><span class="nf">sort_by</span><span class="p">(</span><span class="o">&</span><span class="ss">:version</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">get_all_versions</span> <span class="c1"># :nodoc:</span>
<span class="k">if</span> <span class="n">schema_migration</span><span class="p">.</span><span class="nf">table_exists?</span>
<span class="n">schema_migration</span><span class="p">.</span><span class="nf">all_versions</span><span class="p">.</span><span class="nf">map</span><span class="p">(</span><span class="o">&</span><span class="ss">:to_i</span><span class="p">)</span>
<span class="k">else</span>
<span class="p">[]</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span></code></pre></figure>
<h3 id="logic">Logic</h3>
<ul>
<li><code class="language-plaintext highlighter-rouge">migrations</code> method returns versions (<code class="language-plaintext highlighter-rouge">YYYYMMDDHHMMSS</code>) of all the migration files available in the application.</li>
<li><code class="language-plaintext highlighter-rouge">get_all_versions</code> method returns all the versions available in <code class="language-plaintext highlighter-rouge">schema_migrations</code> table</li>
<li><code class="language-plaintext highlighter-rouge">pending_migration_versions</code> method returns the difference between the two arrays mentioned above.</li>
</ul>
<blockquote>
<p>If there is any file with a version that is not availble in the database <strong>needs migration</strong></p>
</blockquote>
<p>This is the basis of the logic followed for figuring out if any migration is pending. If you liked reading, please subscribe to the newsletter.</p>Akshay MohiteEver wondered how rails decides if any migration is pending? If not, this tutorial will explain how it is done using a table named schema_migrations.Setup Rspec parallel tests with Circle CI2021-11-30T15:40:00+05:302021-11-30T15:40:00+05:30https://rubyinrails.com/2021/11/30/setup-rspec-parallel-tests-with-circle-ci<p>While working on Rspec tests on a Ruby on Rails project,
I realised the <a href="https://en.wikipedia.org/wiki/Test_suite#:~:text=In%20software%20development%2C%20a%20test,some%20specified%20set%20of%20behaviours.">test suite</a> is taking a lot of time.
This was causing the pipelines run slow and resulting in slower development cycle.
We had to make it a bit faster.</p>
<p>While Rails has <a href="https://edgeguides.rubyonrails.org/testing.html#parallel-testing">inbuilt support for parallelization</a> now,
it is available for Minitest testing framework.
To work with Rspec, we need to use gems like parallel_spec, turbo_test (a solution came out of project <a href="https://github.com/discourse/discourse">discource</a>).
In this article, we will explore working with
<a href="https://github.com/grosser/parallel_tests">parallel_tests</a> gem and integrate it with Circle CI as well.</p>
<h4 id="setup-gem">Setup gem</h4>
<p>Add gem to Gemfile</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">gem</span> <span class="s1">'parallel_tests'</span><span class="p">,</span> <span class="ss">group: </span><span class="p">[</span><span class="ss">:test</span><span class="p">]</span></code></pre></figure>
<p>Now, install the gem to use it with your testing framework.</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">bundle</span> <span class="n">install</span></code></pre></figure>
<h4 id="run-specs-in-parallel">Run specs in parallel</h4>
<p>Now, you can run specs in parallel using following command.</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">bundle</span> <span class="nb">exec</span> <span class="n">rake</span> <span class="n">parallel</span><span class="ss">:spec</span></code></pre></figure>
<p>This will divide the specs (examples) in your application
in number of processes.
The number of processes are figured out based on
the <a href="https://en.wikipedia.org/wiki/Multi-core_processor">number of cores</a> available on the machine running tests.</p>
<p>We can specify number of processes to be used explicitly as given below.</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">bundle</span> <span class="nb">exec</span> <span class="n">rake</span> <span class="n">parallel</span><span class="ss">:spec</span><span class="s2">"[2]"</span></code></pre></figure>
<p>This will use <code class="language-plaintext highlighter-rouge">2</code> processes to divide total number of specs.
It will print the metrics showing number of tests run per process as given below.</p>
<figure class="highlight"><pre><code class="language-plaintext" data-lang="plaintext">2 processes for 118 specs, ~ 59 specs per process</code></pre></figure>
<h4 id="update-circleci-configyml-for-parllelization">Update CircleCI config.yml for parllelization</h4>
<p>Create a temporary rspec directory to store output of the tests run.
Run specs as explained in the step above.</p>
<figure class="highlight"><pre><code class="language-yaml" data-lang="yaml"><span class="na">run_tests</span><span class="pi">:</span> <span class="nl">&run_tests</span>
<span class="na">run</span><span class="pi">:</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">Run tests</span>
<span class="na">command</span><span class="pi">:</span> <span class="pi">|</span>
<span class="s">mkdir ~/rspec</span>
<span class="s">bundle exec rake parallel:spec"[8]"</span></code></pre></figure>
<h4 id="add-rspecjunitformatter-gem">Add RspecJunitFormatter gem</h4>
<p>We use <a href="https://github.com/sj26/rspec_junit_formatter">RspecJunitFormatter</a>
to generate XML output of specs run.</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">gem</span> <span class="s1">'rspec_junit_formatter'</span></code></pre></figure>
<p>Add gem to the Gemfile and perform <code class="language-plaintext highlighter-rouge">bundle install</code> if not done already.</p>
<p>The XML output generate by <code class="language-plaintext highlighter-rouge">RspecJunitFormatter</code> is compatible with CirlceCI data collection for tests.
It can be used with <a href="https://circleci.com/docs/2.0/configuration-reference/#store_test_results">store_test_results</a> step of CircleCI configuration.</p>
<h4 id="configure-rspec-or-rspec_parallel">Configure .rspec or .rspec_parallel</h4>
<p>Update <code class="language-plaintext highlighter-rouge">.rspec</code> or <code class="language-plaintext highlighter-rouge">.rspec_parallel</code> stating formatter to be used,
XML files with test env number based on parallel processes run and output directory for rspec test output as given below.</p>
<figure class="highlight"><pre><code class="language-plaintext" data-lang="plaintext">--color
--require spec_helper
--format documentation
--format progress --format RspecJunitFormatter --out ~/rspec/rspec<%= ENV["TEST_ENV_NUMBER"] %>.xml</code></pre></figure>
<h4 id="update-circleci-configyml-to-store-test-results">Update CircleCI config.yml to store test results</h4>
<p>Add a step in your build job like below.</p>
<figure class="highlight"><pre><code class="language-yaml" data-lang="yaml"> <span class="pi">-</span> <span class="na">store_test_results</span><span class="pi">:</span>
<span class="na">path</span><span class="pi">:</span> <span class="s">tmp/rspec</span></code></pre></figure>
<p>This will upload XML output files generated in the step above,
to CircleCI and it will start generating and showing passed / failed test metrics on It’s UI.</p>Akshay MohiteWhile working on Rspec tests on a Ruby on Rails project, I realised the test suite is taking a lot of time. This was causing the pipelines run slow and resulting in slower development cycle. We had to make it a bit faster.How to test a Ruby module?2021-11-29T20:55:00+05:302021-11-29T20:55:00+05:30https://rubyinrails.com/2021/11/29/how-to-test-ruby-module<p>Ruby <a href="https://ruby-doc.org/core-2.5.0/Module.html">module</a>
can be used to group methods
and
constants together.
This tutorial illustrates testing of ruby module either in isolation
or
based on the class where it is included
or
extended.</p>
<h3 id="testing-module--self-methods">Testing module / self methods</h3>
<p>Let’s take a simple example of a Ruby module
which contains self (module) methods.</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">module</span> <span class="nn">MyModule</span>
<span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">hello</span>
<span class="nb">puts</span> <span class="s2">"Hello"</span>
<span class="k">end</span>
<span class="k">end</span></code></pre></figure>
<p>Now, testing of such a module can be done
in isolation
by calling the class method directly
from module.</p>
<p>Below is the <a href="https://rspec.info/">rspec</a>
test code for the above module.</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="nb">require</span> <span class="s1">'spec_helper'</span>
<span class="no">RSpec</span><span class="p">.</span><span class="nf">describe</span> <span class="no">MyModule</span> <span class="k">do</span>
<span class="n">it</span> <span class="s2">"should say hello"</span> <span class="k">do</span>
<span class="n">expect</span><span class="p">(</span><span class="no">MyModule</span><span class="p">.</span><span class="nf">hello</span><span class="p">).</span><span class="nf">to</span> <span class="n">eq</span><span class="p">(</span><span class="s2">"Hello"</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span></code></pre></figure>
<h3 id="testing-instance-methods">Testing instance methods</h3>
<p>Now, let’s take a look at the same example
with a class which includes the module
to test <a href="https://ruby-doc.org/core-2.5.0/Module.html#public-instance-method-details">instance methods</a> on a module.</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">module</span> <span class="nn">MyCalendar</span>
<span class="k">def</span> <span class="nf">number_of_months</span>
<span class="mi">12</span>
<span class="k">end</span>
<span class="k">end</span></code></pre></figure>
<p>Now, to test this module,
we shall include this module in a dummy class.
Then,
we test the behaviour of the module method
by calling the method
on
an object of the dummy class.</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="nb">require</span> <span class="s1">'spec_helper'</span>
<span class="k">class</span> <span class="nc">DummyCalendar</span>
<span class="kp">include</span> <span class="no">MyCalendar</span>
<span class="k">end</span>
<span class="no">RSpec</span><span class="p">.</span><span class="nf">describe</span> <span class="no">MyModule</span> <span class="k">do</span>
<span class="n">it</span> <span class="s2">"should say number of months"</span> <span class="k">do</span>
<span class="n">calendar</span> <span class="o">=</span> <span class="no">DummyCalendar</span><span class="p">.</span><span class="nf">new</span>
<span class="n">expect</span><span class="p">(</span><span class="n">calendar</span><span class="p">.</span><span class="nf">number_of_months</span><span class="p">).</span><span class="nf">to</span> <span class="n">eq</span><span class="p">(</span><span class="mi">12</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span></code></pre></figure>
<p>This will test the module method based on a dummy class.
In this way, we don’t need to test the behavior of all the classes
where this module is included.</p>Akshay MohiteRuby module can be used to group methods and constants together. This tutorial illustrates testing of ruby module either in isolation or based on the class where it is included or extended.