<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">



<title type="text">Take your Rails development to the next level</title>
<generator uri="https://github.com/mojombo/jekyll">Jekyll</generator>
<link rel="self" type="application/atom+xml" href="http://blog.widefix.com/atom.xml" />
<link rel="alternate" type="text/html" href="http://blog.widefix.com/" />
<updated>2025-06-17T20:07:14-04:00</updated>
<id>http://blog.widefix.com/</id>
<author>
  <name>Andrei Kaleshka</name>
  <uri>http://blog.widefix.com/</uri>
  <email>ka8725@gmail.com</email>
</author>


<entry>
  <title type="html"><![CDATA[New UI Features for Schema Tracking and Migration Management in ActualDbSchema ]]></title>
  <link rel="alternate" type="text/html" href="http://blog.widefix.com/new-ui-features-for-schema-tracking-and-migration-management-in-actualdbschema/"/>
  <id>http://blog.widefix.com/new-ui-features-for-schema-tracking-and-migration-management-in-actualdbschema</id>
  <updated>2025-02-28 14:28:19 +0400T00:00:00-00:00</updated>
  <published>2025-02-28T00:00:00-05:00</published>
  
  <author>
    <name>Andrei Kaleshka</name>
    <uri>http://blog.widefix.com</uri>
    <email>ka8725@gmail.com</email>
  </author>
  <category scheme="http://blog.widefix.com/tags/#actual_db_schema" term="actual_db_schema" /><category scheme="http://blog.widefix.com/tags/#rails" term="rails" /><category scheme="http://blog.widefix.com/tags/#rails-development" term="rails-development" /><category scheme="http://blog.widefix.com/tags/#migrations" term="migrations" />
  <content type="html">
  
    &lt;p&gt;Hello, Rails developers! We’re thrilled to announce the release of ActualDbSchema v0.8.3! 🎉 This update brings new tools to visualize schema changes directly in your browser, clean up broken migrations, and customize your workflow. Let’s explore what’s new!&lt;/p&gt;

&lt;h2 id=&quot;view-schema-with-migration-annotations-in-the-ui&quot;&gt;View Schema with Migration Annotations in the UI&lt;/h2&gt;

&lt;p&gt;Ever wanted to inspect your schema and trace which migration introduced a specific change directly from the browser? Now you can!&lt;/p&gt;

&lt;p&gt;Navigate to the migrations page (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://localhost:3000/rails/migrations&lt;/code&gt;) and click the &lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;View Schema&lt;/code&gt;&lt;/strong&gt; button to see the schema diff rendered in the UI, annotated with the migrations that created each change. In case there is no pending changes, the UI shows the entire schema.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key Features:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Diff Highlighting&lt;/strong&gt;: See schema changes alongside their originating migrations.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Filtering&lt;/strong&gt;: Search for specific tables using a built-in filter.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Migration Links&lt;/strong&gt;: Click any annotation to jump to the corresponding migration details.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Schema Page on the UI&lt;/strong&gt;
&lt;img src=&quot;/images/schema_page.png&quot; alt=&quot;Schema Page on the UI&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Schema Page with Filter&lt;/strong&gt;
&lt;img src=&quot;/images/schema_page_with_filter.png&quot; alt=&quot;Schema Page with Filter&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;clean-up-broken-migrations&quot;&gt;Clean Up Broken Migrations&lt;/h2&gt;

&lt;p&gt;Managing old migrations can be challenging, especially when migration files go missing. In this release, we’ve introduced a &lt;strong&gt;Broken Versions&lt;/strong&gt; page and rake tasks to clean up these records.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What Are Broken Migrations?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A migration is considered &lt;strong&gt;broken&lt;/strong&gt; if it exists in the database but the corresponding file is missing. This can happen if migrations were deleted manually after being applied.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to Delete Broken Migrations?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;1️⃣ &lt;strong&gt;Using the UI&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Navigate to the following URL in your web browser to see all broken migrations:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;http://localhost:3000/rails/broken_versions
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You can delete them &lt;strong&gt;individually&lt;/strong&gt; or use the &lt;strong&gt;Delete All&lt;/strong&gt; button to clean up everything at once.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/broken_versions_page.png&quot; alt=&quot;Broken versions page&quot; /&gt;&lt;/p&gt;

&lt;p&gt;2️⃣ &lt;strong&gt;Using a Rake Task&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Run the following command to remove all broken migrations:&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;rake actual_db_schema:delete_broken_versions
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;To delete specific migrations, specify their versions:&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;rake actual_db_schema:delete_broken_versions[&lt;span class=&quot;s2&quot;&gt;&quot;20250224103352 20250224103358&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;For multi-database applications, you can also specify the database:&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;rake actual_db_schema:delete_broken_versions[&lt;span class=&quot;s2&quot;&gt;&quot;20250224103352 20250224103358&quot;&lt;/span&gt;, &lt;span class=&quot;s2&quot;&gt;&quot;primary&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;filter-migrations-in-the-ui&quot;&gt;Filter Migrations in the UI&lt;/h2&gt;

&lt;p&gt;Tired of scrolling through hundreds of migrations? We’ve added a search bar to the migrations page, making it easy to find migrations by &lt;strong&gt;name&lt;/strong&gt; or &lt;strong&gt;content&lt;/strong&gt;. Whether you’re tracking down a specific change or reviewing past updates, finding what you need is now faster and more intuitive.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/migrations_page_with_filter.png&quot; alt=&quot;Migrations page with filter&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;customize-your-migrated-folder-location&quot;&gt;Customize Your Migrated Folder Location&lt;/h2&gt;

&lt;p&gt;By default, ActualDbSchema stores migration records in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tmp/migrated&lt;/code&gt;. Now you can customize this path to match your project structure:&lt;/p&gt;

&lt;p&gt;1️⃣ &lt;strong&gt;Using Environment Variable&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Set the environment variable &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ACTUAL_DB_SCHEMA_MIGRATED_FOLDER&lt;/code&gt; to your desired folder path:&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;ACTUAL_DB_SCHEMA_MIGRATED_FOLDER&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;custom/migrated&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;2️⃣ &lt;strong&gt;Using Initializer&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Add the following line to your initializer file (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;config/initializers/actual_db_schema.rb&lt;/code&gt;):&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;migrated_folder&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Rails&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;root&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;custom&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;migrated&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;With ActualDbSchema v0.8.3, managing your database schema is easier than ever. From visualizing schema changes to cleaning up broken migrations and filtering migrations more efficiently, this release provides new tools for your Rails projects.&lt;/p&gt;

&lt;p&gt;We hope these enhancements improve your development workflow. Give them a try in your next project and let us know what you think!&lt;/p&gt;

&lt;p&gt;Happy coding, and stay tuned for more updates! 🌟&lt;/p&gt;

  
  &lt;p&gt;&lt;a href=&quot;http://blog.widefix.com/new-ui-features-for-schema-tracking-and-migration-management-in-actualdbschema/&quot;&gt;New UI Features for Schema Tracking and Migration Management in ActualDbSchema &lt;/a&gt; was originally published by Andrei Kaleshka at &lt;a href=&quot;http://blog.widefix.com&quot;&gt;Take your Rails development to the next level&lt;/a&gt; on February 28, 2025.&lt;/p&gt;</content>
</entry>


<entry>
  <title type="html"><![CDATA[Trace Schema Changes &amp; Run Migrations in Rails Console with ActualDbSchema]]></title>
  <link rel="alternate" type="text/html" href="http://blog.widefix.com/trace-schema-changes-and-run-migrations-in-rails-console-with-actualdbschema/"/>
  <id>http://blog.widefix.com/trace-schema-changes-and-run-migrations-in-rails-console-with-actualdbschema</id>
  <updated>2025-02-06 12:37:00 +0400T00:00:00-00:00</updated>
  <published>2025-02-06T00:00:00-05:00</published>
  
  <author>
    <name>Andrei Kaleshka</name>
    <uri>http://blog.widefix.com</uri>
    <email>ka8725@gmail.com</email>
  </author>
  <category scheme="http://blog.widefix.com/tags/#actual_db_schema" term="actual_db_schema" /><category scheme="http://blog.widefix.com/tags/#rails" term="rails" /><category scheme="http://blog.widefix.com/tags/#rails-development" term="rails-development" /><category scheme="http://blog.widefix.com/tags/#migrations" term="migrations" />
  <content type="html">
  
    &lt;p&gt;Hello, everyone! We’re happy to announce the release of ActualDbSchema v0.8.2! 🎉 This release introduces two features that will make your Rails development experience even smoother. Let’s dive into the details!&lt;/p&gt;

&lt;h2 id=&quot;schema-diff-with-migration-annotations&quot;&gt;Schema Diff with Migration Annotations&lt;/h2&gt;

&lt;p&gt;Ever stared at the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;schema.rb&lt;/code&gt; diff wondering &lt;em&gt;“Which migration caused this?”&lt;/em&gt; Tracking down the origin of schema changes across multiple migrations can be time-consuming, especially in collaborative environments.&lt;/p&gt;

&lt;p&gt;The new &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;diff_schema_with_migrations&lt;/code&gt; Rake task generates a diff of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;schema.rb&lt;/code&gt; file, annotated with the migrations responsible for each change. This makes it easier to identify which migration introduced a specific schema modification, helping you decide whether to resolve the diff on your own or discuss it with your teammates to determine the next steps.&lt;/p&gt;

&lt;p&gt;Here’s an example of what the schema diff with migration annotations looks like in action:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/schema_diff.png&quot; alt=&quot;schema_diff&quot; /&gt;&lt;/p&gt;

&lt;p&gt;By default, the task uses &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;db/schema.rb&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;db/migrate&lt;/code&gt; as the schema and migrations paths. You can run it with the following command:&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;rake actual_db_schema:diff_schema_with_migrations
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If your project uses custom paths for the schema or migrations, you can provide them as arguments:&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;rake actual_db_schema:diff_schema_with_migrations[path/to/custom_schema.rb, path/to/custom_migrations]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;This task also checks for phantom migrations. If phantom migrations were not properly rolled back (due to failure or pending migration), the schema might contain related changes that are missing from the original migrations. These changes are properly detected and included in the schema diff, giving you a more complete and accurate picture of your database history.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;console-migrations&quot;&gt;Console Migrations&lt;/h2&gt;

&lt;p&gt;Sometimes, you need to make quick adjustments to your database schema without creating migration files - whether you’re fixing a corrupted schema, experimenting with indexes, or making quick changes in development. This feature allows you to run the same commands used in migrations directly in the Rails console.&lt;/p&gt;

&lt;p&gt;By default, Console Migrations is disabled. You can enable it in two ways:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Using Environment Variable&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Set the environment variable &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ACTUAL_DB_SCHEMA_CONSOLE_MIGRATIONS_ENABLED&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;true&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;ACTUAL_DB_SCHEMA_CONSOLE_MIGRATIONS_ENABLED&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;2. Using Initializer&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Add the following line to your initializer file (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;config/initializers/actual_db_schema.rb&lt;/code&gt;):&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;console_migrations_enabled&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Once enabled, you can run migration commands directly in the Rails console. Here are a few examples:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Create a new table&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;create_table&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:posts&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:title&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Add a column&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;add_column&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:users&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:age&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:integer&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Remove an index&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;remove_index&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:users&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:email&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Rename a column&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;rename_column&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:users&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:username&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:handle&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;We hope these features help you keep your database schema clean, consistent, and easy to work with. We’re committed to making Rails development more efficient and enjoyable, and your feedback continues to drive these improvements. Give these new features a try and let us know what you think!&lt;/p&gt;

&lt;p&gt;Stay tuned for more updates in the next release. Happy coding! 🚀&lt;/p&gt;

  
  &lt;p&gt;&lt;a href=&quot;http://blog.widefix.com/trace-schema-changes-and-run-migrations-in-rails-console-with-actualdbschema/&quot;&gt;Trace Schema Changes &amp; Run Migrations in Rails Console with ActualDbSchema&lt;/a&gt; was originally published by Andrei Kaleshka at &lt;a href=&quot;http://blog.widefix.com&quot;&gt;Take your Rails development to the next level&lt;/a&gt; on February 06, 2025.&lt;/p&gt;</content>
</entry>


<entry>
  <title type="html"><![CDATA[Multiple schemas support added to ActualDbSchema]]></title>
  <link rel="alternate" type="text/html" href="http://blog.widefix.com/multiple-schemas-support-added-to-actualdbschema/"/>
  <id>http://blog.widefix.com/multiple-schemas-support-added-to-actualdbschema</id>
  <updated>2025-01-15 19:21:16 +0100T00:00:00-00:00</updated>
  <published>2025-01-15T00:00:00-05:00</published>
  
  <author>
    <name>Andrei Kaleshka</name>
    <uri>http://blog.widefix.com</uri>
    <email>ka8725@gmail.com</email>
  </author>
  <category scheme="http://blog.widefix.com/tags/#actual_db_schema" term="actual_db_schema" /><category scheme="http://blog.widefix.com/tags/#rails" term="rails" /><category scheme="http://blog.widefix.com/tags/#rails-development" term="rails-development" /><category scheme="http://blog.widefix.com/tags/#migrations" term="migrations" />
  <content type="html">
  
    &lt;p&gt;Good day, everyone! I’m excited to announce the &lt;a href=&quot;https://github.com/widefix/actual_db_schema/releases/tag/v0.8.1&quot;&gt;release of actual_db_schema v0.8.1&lt;/a&gt;! 🎉 This milestone is particularly special as it focuses entirely on addressing feedback from our valued users. Check out the updates and improvements introduced in this release.&lt;/p&gt;

&lt;h2 id=&quot;rake-task-added-to-initialize-the-gem&quot;&gt;Rake task added to initialize the gem&lt;/h2&gt;

&lt;p&gt;Run the following command to generate the default initializer (an optional post-installation step):&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;rake actual_db_schema:install
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;This step is optional and only necessary if you wish to modify the default configuration. If you are already using the gem and have manually created the initializer, it is recommended to back up your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;config/initializers/actual_db_schema.rb&lt;/code&gt; file (if it exists) before proceeding.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;dsl-for-configuring-the-gem&quot;&gt;DSL for configuring the gem&lt;/h2&gt;

&lt;p&gt;To make the gem configuration more flexible and reliable, we have introduced a DSL.&lt;/p&gt;

&lt;p&gt;Use the following DSL to configure the gem. This will also be generated as a content of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;config/initializers/actual_db_schema.rb&lt;/code&gt; by the init command above.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;no&quot;&gt;ActualDbSchema&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;configure&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# Enable the gem.&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;enabled&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Rails&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;development?&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# Disable automatic rollback of phantom migrations.&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# config.auto_rollback_disabled = true&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;auto_rollback_disabled&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ENV&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;ACTUAL_DB_SCHEMA_AUTO_ROLLBACK_DISABLED&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;present?&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# Enable the UI for managing migrations.&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ui_enabled&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Rails&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;development?&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ENV&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;ACTUAL_DB_SCHEMA_UI_ENABLED&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;present?&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# Enable automatic phantom migration rollback on branch switch,&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# config.git_hooks_enabled = true&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;git_hooks_enabled&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ENV&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;ACTUAL_DB_SCHEMA_GIT_HOOKS_ENABLED&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;present?&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# If your application leverages multiple schemas for multi-tenancy, define the active schemas.&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# config.multi_tenant_schemas = -&amp;gt; { [&quot;public&quot;, &quot;tenant1&quot;, &quot;tenant2&quot;] }&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;support-for-multiple-database-schemas&quot;&gt;Support for multiple database schemas&lt;/h2&gt;

&lt;p&gt;From now on, the gem is compatible with multi-tenant applications that are using the &lt;a href=&quot;https://github.com/influitive/apartment&quot;&gt;apartment gem&lt;/a&gt; or similar solutions.&lt;/p&gt;

&lt;p&gt;To add support for multiple schemas, define the active schemas in the configuration:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;no&quot;&gt;ActualDbSchema&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;configure&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;multi_tenant_schemas&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;public&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;tenant1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;tenant2&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;By multi-tenant schemas, we refer to schemas that are designated for different tenants within an application. For instance, in a SaaS application, each tenant is assigned its own schema. Although the set of migrations and schema structure remains consistent across all tenants, the data within each schema is isolated, ensuring tenant-specific separation. Check out the &lt;a href=&quot;https://github.com/influitive/apartment&quot;&gt;apartment gem&lt;/a&gt; for more details.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;the-link-to-changelog-fixed&quot;&gt;The link to Changelog fixed&lt;/h2&gt;

&lt;p&gt;The &lt;a href=&quot;https://rubygems.org/gems/actual_db_schema&quot;&gt;Rubygems.org&lt;/a&gt; page now correctly displays the changelog link, which was previously broken.&lt;/p&gt;

&lt;h2 id=&quot;run-the-git-hook-on-switch-branch-only&quot;&gt;Run the git hook on switch branch only&lt;/h2&gt;

&lt;p&gt;The gem now runs the git hook only when switching branches, ensuring that the rollback process is triggered only when necessary.&lt;/p&gt;

&lt;p&gt;This ensures that when you reset a file using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git checkout&lt;/code&gt; in the current branch, the gem hook will no longer run as it previously would. This prevents unnecessary rollbacks and streamlines the development process, making it more efficient and convenient.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Thanks to the valuable feedback and suggestions from our users, we’ve made significant improvements to the gem. We believe these updates will enhance your experience with ActualDbSchema, making it an even more powerful tool for your Rails application development.&lt;/p&gt;

&lt;p&gt;The next release is already in progress, so stay tuned for more exciting updates!&lt;/p&gt;

  
  &lt;p&gt;&lt;a href=&quot;http://blog.widefix.com/multiple-schemas-support-added-to-actualdbschema/&quot;&gt;Multiple schemas support added to ActualDbSchema&lt;/a&gt; was originally published by Andrei Kaleshka at &lt;a href=&quot;http://blog.widefix.com&quot;&gt;Take your Rails development to the next level&lt;/a&gt; on January 15, 2025.&lt;/p&gt;</content>
</entry>


<entry>
  <title type="html"><![CDATA[The Importance of Understanding the What and Why in Software Development]]></title>
  <link rel="alternate" type="text/html" href="http://blog.widefix.com/the-importance-of-understanding-the-what-and-why-in-software-development/"/>
  <id>http://blog.widefix.com/the-importance-of-understanding-the-what-and-why-in-software-development</id>
  <updated>2024-11-28 18:24:48 +0100T00:00:00-00:00</updated>
  <published>2024-11-28T00:00:00-05:00</published>
  
  <author>
    <name>Andrei Kaleshka</name>
    <uri>http://blog.widefix.com</uri>
    <email>ka8725@gmail.com</email>
  </author>
  <category scheme="http://blog.widefix.com/tags/#growing" term="growing" /><category scheme="http://blog.widefix.com/tags/#software%20development" term="software development" />
  <content type="html">
  
    &lt;h1 id=&quot;understanding-the-what-and-why-in-software-development-a-mentorship-story&quot;&gt;Understanding the “What” and “Why” in Software Development: A Mentorship Story&lt;/h1&gt;

&lt;p&gt;In software development, it’s crucial to understand not just &lt;em&gt;what&lt;/em&gt; you’re doing, but also &lt;em&gt;why&lt;/em&gt; you’re doing it. This simple but essential concept is often overlooked by junior developers, which can lead to incomplete or incorrect implementations. Let me share a story from my experience mentoring a junior developer, where this principle proved pivotal.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/accounting.jpg&quot; alt=&quot;Accounting&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;the-task-implementing-a-refund-operation&quot;&gt;The Task: Implementing a Refund Operation&lt;/h2&gt;

&lt;p&gt;A junior developer I was mentoring had to implement a refund operation in Sage, an accounting software. The task seemed straightforward: reverse the related payment transaction when a refund is issued. However, instead of questioning the broader context, the developer simply reverted the payment transaction in the code.&lt;/p&gt;

&lt;h3 id=&quot;what-went-wrong&quot;&gt;What Went Wrong?&lt;/h3&gt;

&lt;p&gt;While the payment was reversed, the outcome didn’t align with the requirements. Specifically:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;The invoice remained in a “paid” status&lt;/strong&gt;, which was incorrect after a refund.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;The payment wasn’t marked as refunded&lt;/strong&gt;, leaving the system in an inconsistent state.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The developer was satisfied with the outcome, claiming that the job was complete. This led to a key realization: they had focused solely on the &lt;em&gt;what&lt;/em&gt;—reverting the transaction—without understanding the &lt;em&gt;why&lt;/em&gt; behind the operation.&lt;/p&gt;

&lt;h3 id=&quot;why-it-happened&quot;&gt;Why It Happened&lt;/h3&gt;

&lt;p&gt;This issue stemmed from the developer’s failure to fully grasp the business logic behind the refund operation. In addition to reversing the payment, the invoice needed to reflect its refunded status, and the payment itself should be marked as refunded for accurate reporting.&lt;/p&gt;

&lt;p&gt;The key to solving this problem wasn’t just about fixing the code; it was about understanding the purpose behind the refund operation. That meant clarifying the inputs (e.g., refund amount, invoice ID) and expected outputs (e.g., updated invoice and payment statuses).&lt;/p&gt;

&lt;h2 id=&quot;the-lesson-understanding-what-and-why&quot;&gt;The Lesson: Understanding &lt;em&gt;What&lt;/em&gt; and &lt;em&gt;Why&lt;/em&gt;&lt;/h2&gt;

&lt;p&gt;This situation highlighted the importance of understanding both the &lt;em&gt;what&lt;/em&gt; and &lt;em&gt;why&lt;/em&gt; when developing software. It’s not enough to implement a feature or operation based on surface-level requirements. Developers must dive deeper into:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;What the operation actually does&lt;/strong&gt;: Reversing a payment is only part of the solution.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Why it needs to be done this way&lt;/strong&gt;: The operation impacts multiple entities (invoice, payment, accounting) and must maintain system consistency.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By emphasizing these two aspects, developers can ensure their work aligns with business goals, prevents errors, and creates more reliable software.&lt;/p&gt;

&lt;h2 id=&quot;mentoring-junior-developers&quot;&gt;Mentoring Junior Developers&lt;/h2&gt;

&lt;p&gt;As a mentor, this was a perfect opportunity to reinforce the importance of understanding the business logic behind every operation. I walked the developer through the full process, from understanding the input/output flow to ensuring that all related statuses and records were updated correctly.&lt;/p&gt;

&lt;p&gt;The lesson here was clear: developers should never assume that completing the technical task means the job is done. They need to take the time to understand the broader implications of their work.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;In conclusion, understanding both the &lt;em&gt;what&lt;/em&gt; and the &lt;em&gt;why&lt;/em&gt; is fundamental to effective software development. This approach prevents incomplete implementations and ensures that code contributes to the broader goals of the system. By asking the right questions and considering the full scope of a task, developers can create more robust, efficient, and maintainable software.&lt;/p&gt;

  
  &lt;p&gt;&lt;a href=&quot;http://blog.widefix.com/the-importance-of-understanding-the-what-and-why-in-software-development/&quot;&gt;The Importance of Understanding the What and Why in Software Development&lt;/a&gt; was originally published by Andrei Kaleshka at &lt;a href=&quot;http://blog.widefix.com&quot;&gt;Take your Rails development to the next level&lt;/a&gt; on November 28, 2024.&lt;/p&gt;</content>
</entry>


<entry>
  <title type="html"><![CDATA[Personal Gemfile for development]]></title>
  <link rel="alternate" type="text/html" href="http://blog.widefix.com/personal-gemfile-for-development/"/>
  <id>http://blog.widefix.com/personal-gemfile-for-development</id>
  <updated>2024-03-26 16:04:28 +0100T00:00:00-00:00</updated>
  <published>2024-03-26T00:00:00-04:00</published>
  
  <author>
    <name>Andrei Kaleshka</name>
    <uri>http://blog.widefix.com</uri>
    <email>ka8725@gmail.com</email>
  </author>
  <category scheme="http://blog.widefix.com/tags/#bundler" term="bundler" /><category scheme="http://blog.widefix.com/tags/#ruby" term="ruby" />
  <content type="html">
  
    &lt;p&gt;I want to use &lt;a href=&quot;https://github.com/widefix/actual_db_schema&quot;&gt;actual_db_schema&lt;/a&gt; in my development process. My colleagues are against it. They say that solving the same problem this gem solves can be done by other means. But I don’t want to use their solution as it doesn’t improve my development process. I got used to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;actual_db_schema&lt;/code&gt;. It makes my workflow so comfortable that I don’t accept their solutions. I also don’t want to force them using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;actual_db_schema&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;What should I do if installing this gem requires changes in the Gemfile? Is it a dead-end? It turned out that it’s not. Keep reading to learn how to overcome this obstacle.&lt;/p&gt;

&lt;h2 id=&quot;configure-the-gem-not-to-run&quot;&gt;Configure the gem not to run&lt;/h2&gt;

&lt;p&gt;If you persuaded your colleagues and they agreed to install the gem, then accept my congratulations! You can install the gem by modifying Gemfile and then configuring it so that it doesn’t run for anyone by default. But for you, it will run.&lt;/p&gt;

&lt;p&gt;Here is how you can do it. Add the gem to Gemfile in development group and install it with bundle. Then add the following initializer:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# config/initializers/actual_db_schema.rb&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;ActualDbSchema&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:enabled&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ENV&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'ACTUAL_DB_SCHEMA'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'true'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now, if the environment variable &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ACTUAL_DB_SCHEMA&lt;/code&gt; is set to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;true&lt;/code&gt; the gem will do its job. By default it’s not defined for anyone. So the gem won’t work for them. But you can define this variable in your shell profile or in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.env&lt;/code&gt; file.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;While the solution is related to only one gem, you can use it for any other gem. The only requirement for other gems is that they have similar facilities - a configuration option to enable/disable the gem.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;While this solution is good, your colleagues may not like that the changes to Gemfile were committed. This is usually the case, and this is okay. Don’t worry! There is a solution for this scenario. Keep reading.&lt;/p&gt;

&lt;h2 id=&quot;local-gemfile&quot;&gt;Local Gemfile&lt;/h2&gt;

&lt;p&gt;I turned out that you can have a local Gemfile. This file is not committed to the repository. It’s used only by you. You can use it to install gems that you want to use in your development process but you don’t want to force others to use them. This solution is possible because Bundler allows you to specify the path to the Gemfile.&lt;/p&gt;

&lt;p&gt;Here is how you can do it. Create a file named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Gemfile.local&lt;/code&gt; in the root of your project. Add the following content to it:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;eval_gemfile&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'Gemfile'&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;gem&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'actual_db_schema'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now, install the gem with the following command:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;BUNDLE_GEMFILE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;Gemfile.local bundle &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This command will install the gem only for you. The changes in the Gemfile are not committed. The gem is not installed for anyone else. You can use this approach for any other gem you want to use in your development process. Don’t forget to add the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Gemfile.local&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Gemfile.local.lock&lt;/code&gt; to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.gitignore&lt;/code&gt; file.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;The solution is not limited to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;actual_db_schema&lt;/code&gt; gem. You can use it for any other gem you want to use in your development process but you don’t want to force others to use them.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is the full checklist of the steps you need to follow to install a gem for your development process without forcing others to use it:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Add these lines to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.gitignore&lt;/code&gt;:&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Gemfile.local
Gemfile.local.lock
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;Create the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Gemfile.local&lt;/code&gt; file with the following content:&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;eval_gemfile&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'Gemfile'&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# add your local gems here&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;Define the environment variable in your shell profile or in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.env&lt;/code&gt; file:&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;BUNDLE_GEMFILE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;Gemfile.local
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;Install the gem with the following command:&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;bundle &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;Enjoy the gem in your development process.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;If you want to commit changes into the original Gemfile, you should unset the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BUNDLE_GEMFILE&lt;/code&gt; environment variable or temprorary set it to the original Gemfile:&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;unset &lt;/span&gt;BUNDLE_GEMFILE
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;or&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;BUNDLE_GEMFILE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;Gemfile
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;or without modifying the shell profile:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;BUNDLE_GEMFILE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;Gemfile bundle &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Don’t be afraid to use the gems that improve your development process. You can use them without forcing others to use them. The solutions I described in this article will help you to achieve this goal. Use them and enjoy the gems that make your workflow more comfortable. Share this solution with others who may need it. They will appreciate it.&lt;/p&gt;

  
  &lt;p&gt;&lt;a href=&quot;http://blog.widefix.com/personal-gemfile-for-development/&quot;&gt;Personal Gemfile for development&lt;/a&gt; was originally published by Andrei Kaleshka at &lt;a href=&quot;http://blog.widefix.com&quot;&gt;Take your Rails development to the next level&lt;/a&gt; on March 26, 2024.&lt;/p&gt;</content>
</entry>


<entry>
  <title type="html"><![CDATA[Prevent account sharing with MFA]]></title>
  <link rel="alternate" type="text/html" href="http://blog.widefix.com/prevent-account-sharing-with-mfa/"/>
  <id>http://blog.widefix.com/prevent-account-sharing-with-mfa</id>
  <updated>2024-03-14 15:57:54 +0100T00:00:00-00:00</updated>
  <published>2024-03-14T00:00:00-04:00</published>
  
  <author>
    <name>Andrei Kaleshka</name>
    <uri>http://blog.widefix.com</uri>
    <email>ka8725@gmail.com</email>
  </author>
  <category scheme="http://blog.widefix.com/tags/#rails-development" term="rails-development" /><category scheme="http://blog.widefix.com/tags/#data-analysis" term="data-analysis" />
  <content type="html">
  
    &lt;p&gt;TL;DR: MFA increased our app new signups by 30% daily. It can help you but measure the impact of the changes.&lt;/p&gt;

&lt;p&gt;Preventing account sharing in your app is vital. Various methods tackle this. Recently, we’ve been focusing on the issue. We discovered a simple, low-risk solution. It protects revenue and boosts security. It assists in generating dynamic reports. This solution allows us track data. It helps to manage violation cases. Look how we have achieved this.&lt;/p&gt;

&lt;h2 id=&quot;why-to-prevent-sharing-account&quot;&gt;Why to prevent sharing account&lt;/h2&gt;

&lt;p&gt;Sharing an account is one of the backdoors that leads to financial losses. Instead of having paid two users who are friends in your app, you have only one. They buy one account and share credentials, resulting in underpaying the business.&lt;/p&gt;

&lt;p&gt;Another aspect of preventing sharing accounts is security. Someone might steal a user’s credentials. The thief will use the account, and nobody will know about it.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/security.jpg&quot; alt=&quot;MFA is for security&quot; /&gt;&lt;/p&gt;

&lt;p&gt;All of that is very important indeed. However, our primary goal was to boost app revenue.&lt;/p&gt;

&lt;h2 id=&quot;prevent-account-sharing-with-a-third-party-service&quot;&gt;Prevent account sharing with a third-party service&lt;/h2&gt;

&lt;p&gt;Some services promise to solve the issue with account sharing in your product. That option might sound like a good choice. Relying on their implementation, you sign up. Then, you configure your app by following their instructions and enjoying it. Sounds easy and fantastic. Yet, some disadvantages can prevent you from following this path.&lt;/p&gt;

&lt;p&gt;To start, consider how these services tackle the problem. They track user actions. These include clicks, mouse movements, IP addresses, device types, and locations. If the user’s behavior deviates from the norm, the service flags it as suspicious. While effective, there are limitations to this approach. It hinges on a vast amount of user data. The more data and users they have, the more accurate the service becomes. The law of large numbers must function. But you never know if they have enough data. They also need to know your app data and users. Hence, the conclusions about sharing an account might be wrong. In the end, remember that there will always be false positives. It’s essential to have a plan to address them.&lt;/p&gt;

&lt;p&gt;This solution takes time to set up and integrate. Dealing with false positives is challenging. You need custom logic to link this service to your app. You lose control over valuable data to improve your product. Also, there’s a fee that may not suit long-term needs. Comparing it to your solution is necessary.&lt;/p&gt;

&lt;p&gt;We aimed for user session control, limiting per-user sessions. Account sharing was still possible. The system permitted a fixed number of simultaneous sessions. A user accessing the app on various devices led us here. Working with a third-party service brought obstacles here. Thus, we chose to drop this method.&lt;/p&gt;

&lt;h2 id=&quot;our-path-to-a-solution-for-preventing-account-sharing&quot;&gt;Our path to a solution for preventing account sharing&lt;/h2&gt;

&lt;p&gt;As the first step, many recommend using multi-factor authentication (MFA) and stopping there. Indeed, it’s an excellent option to consider. We definitely should implement it. Yet, how can we be sure it helps? How can we prevent user loss after enabling MFA? Each app community is different. Their expectations may vary. Enabling MFA for all for no reason could alienate many users. They may leave the app, hurting the business.&lt;/p&gt;

&lt;p&gt;Hence, we need indicators to track the impact of our changes on user behavior. Yet, we must decide who gets MFA enabled and assess whether account-sharing prevention works. Thus, an imaginary “violations per user” is a crucial indicator to track. Ultimately, it aligns with login sessions per user—the fewer active login sessions a user has, the fewer violations.
If all goes well, the “violations per user” indicator should drop after enabling MFA. Additionally, we expect more users to sign up. Some users using the shared account should eventually sign up for the app. Moreover, the number of active users should not drop. If all that happens, that would mean the feature works well and improves things rather than hurting the business.&lt;/p&gt;

&lt;p&gt;We can easily track the number of new signups and the number of active users without changing the app. Having direct access to the database, we can do that with relatively easy SQL queries.&lt;/p&gt;

&lt;p&gt;However, we do not have data for the “violations per user” indicator. Thus, we should implement a solution for that.&lt;/p&gt;

&lt;p&gt;Thinking this way, we come up with the following initial ideas:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Analyze the user agent’s IP and device type in the web server logs. Create a report of suspicious user actions such as often IP change or device type.&lt;/li&gt;
  &lt;li&gt;Then, if every subsequent request differs from what’s inside the cookies, it’s a violation.&lt;/li&gt;
  &lt;li&gt;After analyzing these reports, come up with an idea of who should have MFA enabled. Eventually, implement MFA for them.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;/images/idea-bulb.jpg&quot; alt=&quot;Bright idea&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;solution-1-logs-analysis&quot;&gt;Solution #1: logs analysis&lt;/h2&gt;

&lt;p&gt;Looking into logs and checking how many unique IPs and devices one user uses is a natural solution that comes to mind. We have implemented it. However, this solution did not derive a “sharing account violations” indicator. The story behind this deserves a separate post. In short, it was a challenging task. Based on this data, we’ve had many false positive reports.
This is a challenge for us and giants like Google, LastPass, and even your local banks. For that reason, you get a lot of irritating emails every time you log in to these services. I do, and I have it. Our users could hate it as well. A more straightforward solution should correctly identify the fact of sharing accounts.&lt;/p&gt;

&lt;h2 id=&quot;solution-2-unique-token-per-device-written-in-cookies&quot;&gt;Solution #2: unique token per device written in cookies&lt;/h2&gt;

&lt;p&gt;If we have to limit the users’ login sessions, we could restrict them by device type. We can issue a unique token per user device and store it in the DB along with other important information such as IP and the user agent (device info). The idea was to allow one user to use only one device of a specific type at a time. For example, one user can use one web login on a desktop and one on a mobile app. If someone else uses the same device type at the time, it’s a violation. We successfully implemented this solution, and in fact, it worked well. There were some false positives. But fewer than in the previous solution. We could even stick with this solution. However, the business changed its mind and allowed a limited number of login sessions, independent of the device type.&lt;/p&gt;

&lt;p&gt;Nevertheless, even though it’s been rejected, this solution can still be used to implement the “sharing account violations” indicator. We use it to collect and analyze reports.&lt;/p&gt;

&lt;h2 id=&quot;solution-3-login-session-concept&quot;&gt;Solution #3: login session concept&lt;/h2&gt;

&lt;p&gt;Using the experience of the previous solution, we implemented a modernized version that elaborated into a login session concept. The idea is the following. One user can have many login sessions. A login session is an entity with a unique token. It’s created right after the user’s login and deactivated after the user logs out. This unique token is injected into cookies. We search for an active login session in every subsequent request and create if it still needs to be added. A user with many such login sessions shares an account. And for them, we should enable MFA.&lt;/p&gt;

&lt;h2 id=&quot;solution-4-device-limit-limit-of-login-sessions&quot;&gt;Solution #4: device limit, limit of login sessions&lt;/h2&gt;

&lt;p&gt;Having the previous solution ready, we can limit the login sessions in the following way: Every time a user logs in, check if the number of login sessions is too big, say 4. If that’s the case, prevent the user from going further and ask them to log in from some of the current login sessions. This is what we are implementing next.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/active-sessions.png&quot; alt=&quot;Active login sessions&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;our-results-analysis-and-reports&quot;&gt;Our results, analysis, and reports&lt;/h2&gt;

&lt;p&gt;Now, we have the indicator that shows the dynamic of account sharing violations. We gradually enable MFA for those users with the most violations and check the following dynamics.&lt;/p&gt;

&lt;p&gt;New signups and active users on a paid plan. Note that it includes users on a trial that lasts for 15 days. That’s why there is this peak on the right side:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/prevent-account-sharing/active-users-report.png&quot; alt=&quot;Active login sessions&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Sharing token reports dynamics:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/prevent-account-sharing/token-violations-report.png&quot; alt=&quot;Token violations report&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The report dynamics are based on log analysis. It turned out to be useless, and for that reason, we will drop it:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/prevent-account-sharing/log-analyzed-reports.png&quot; alt=&quot;Log analyzed reports&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Active login sessions per user with MFA enabled:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/prevent-account-sharing/otp-enabled-reports.png&quot; alt=&quot;MFA enabled reports&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Active login sessions per user with MFA disabled:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/prevent-account-sharing/otp-disabled-reports.png&quot; alt=&quot;MFA disabled reports&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Active login sessions for all users regardless of the MFA toggle. This one has the data combined with the 2 previous charts:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/prevent-account-sharing/average-per-user-reports.png&quot; alt=&quot;Regardless MFA toggle reports&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Looking into these charts, we conclude that:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;The number of new paying signups increases. Roughly 30% more new signups daily.&lt;/li&gt;
  &lt;li&gt;The number of sharing account reports based on the token solution decreases. The regression line predicts a drop from 170 points to 120, almost a 30% drop when IP and device differ.&lt;/li&gt;
  &lt;li&gt;The number of login sessions per user decreases for users with MFA enabled. This improvement is significant compared to users with MFA disabled.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;

&lt;p&gt;In this post, you learned how the positive impact of MFA enabling in your project can be measured. Now, you know which metrics inside your app you should watch for to understand if your solution works and helps the business survive.&lt;/p&gt;

&lt;p&gt;Finally, you got a beneficial insight — MFA positively impacts user behavior. It indeed prevents users from sharing accounts. The improvement is significant — roughly 30% more new signups. Additionally, it’s a security improvement for your app. It is worth considering for your project. But keep in mind that your auditory can be different, so measure the impact.&lt;/p&gt;

&lt;p&gt;In the next step, we will implement the login session limits. We expect an even more positive impact and will measure the results. Stay tuned!&lt;/p&gt;

&lt;p&gt;Follow me on social networks not to miss the next post:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.linkedin.com/in/ka8725&quot;&gt;LinkedIn&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://twitter.com/ka8725&quot;&gt;Twitter&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/ka8725&quot;&gt;GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

  
  &lt;p&gt;&lt;a href=&quot;http://blog.widefix.com/prevent-account-sharing-with-mfa/&quot;&gt;Prevent account sharing with MFA&lt;/a&gt; was originally published by Andrei Kaleshka at &lt;a href=&quot;http://blog.widefix.com&quot;&gt;Take your Rails development to the next level&lt;/a&gt; on March 14, 2024.&lt;/p&gt;</content>
</entry>


<entry>
  <title type="html"><![CDATA[Reconcile app users vs Stripe and prevent financial losses]]></title>
  <link rel="alternate" type="text/html" href="http://blog.widefix.com/reconcile-app-users-against-stripe-and-prevent-financial-losses/"/>
  <id>http://blog.widefix.com/reconcile-app-users-against-stripe-and-prevent-financial-losses</id>
  <updated>2023-01-30 18:23:20 +0100T00:00:00-00:00</updated>
  <published>2024-01-30T00:00:00-05:00</published>
  
  <author>
    <name>Andrei Kaleshka</name>
    <uri>http://blog.widefix.com</uri>
    <email>ka8725@gmail.com</email>
  </author>
  <category scheme="http://blog.widefix.com/tags/#stripe" term="stripe" /><category scheme="http://blog.widefix.com/tags/#sql" term="sql" /><category scheme="http://blog.widefix.com/tags/#ruby" term="ruby" /><category scheme="http://blog.widefix.com/tags/#data-analysis" term="data-analysis" />
  <content type="html">
  
    &lt;h2 id=&quot;possible-discrepancies-in-stripe-integrations&quot;&gt;Possible discrepancies in Stripe integrations&lt;/h2&gt;

&lt;p&gt;Stripe integrations often experience data consistency issues, such as users being on different subscription plans in the app compared to Stripe. This misalignment can lead to lost revenue or jeopardize customer success stories.&lt;/p&gt;

&lt;p&gt;For example, a customer may have a premium status within the app, while Stripe shows no active subscription. This type of discrepancy can lead to lost revenue for the business. Another potential issue occurs when a customer overpays. In this case, the customer is on the free plan within the app but has an active subscription to the premium plan in Stripe, jeopardizing the customer success story.&lt;/p&gt;

&lt;h2 id=&quot;stripe-integration-state-of-our-application&quot;&gt;Stripe integration state of our application&lt;/h2&gt;

&lt;p&gt;This blog post outlines the approach we took to resolve such issues in our app, which had around 80k users, with 10% on the premium plan. We identified instances of both overpayment and underpayment, which could be attributed to manual data manipulation in Stripe, missing webhooks, or bugs in our system.&lt;/p&gt;

&lt;h2 id=&quot;the-plan-to-reconcile-data-with-stripe&quot;&gt;The plan to reconcile data with Stripe&lt;/h2&gt;

&lt;p&gt;While we used Ruby for scripting, this solution is universal and can be applied to applications written in other languages like Python, Node.js, Java, Rust, and Go.&lt;/p&gt;

&lt;p&gt;Our plan is the following:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Fetch all relevant data from Stripe via Stripe REST API. In our case that included all customer entities and their subscriptions.&lt;/li&gt;
  &lt;li&gt;Put the fetched data into our database. To avoid polluting the main database schema, use a separate namespace for these tables. In fact, PostgreSQL refers to this namespace concept as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;schema&lt;/code&gt;. The default schema is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;public&lt;/code&gt;. We create the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;stripe&lt;/code&gt; schema and necessary tables inside it.&lt;/li&gt;
  &lt;li&gt;Analyze the discrepancies using SQL queries. SQL is not only faster and less prone to bugs for this kind of task in terms of data processing but also in development. This means we will obtain results faster with fewer expenses for the business.&lt;/li&gt;
  &lt;li&gt;Configure Metabase for these reports. Metabase is the system we use to write SQL and build reports. Set up notifications for data discrepancies so that we receive alerts about new cases and can react to them as soon as possible. Luckily, it was already set up for this project. The good news is that its setup takes very little time.&lt;/li&gt;
  &lt;li&gt;Schedule the data scraper from Step 1 to run automatically every week.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;img src=&quot;/images/shining-plan.jpg&quot; alt=&quot;Shining plan&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;stripe-data-store-preparation&quot;&gt;Stripe data store preparation&lt;/h2&gt;

&lt;p&gt;First, we need to create the DB schema along with the Stripe tables. This is the SQL script we used for that:&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;create&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;schema&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stripe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;create&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;table&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stripe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;customers&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;serial&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;stripe_id&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;250&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;email&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;250&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;created_in_stripe&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;timestamp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;description&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;250&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;metadata&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;jsonb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;deleted&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;boolean&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;created_at&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;timestamp&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;current_timestamp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;updated_at&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;timestamp&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;current_timestamp&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;create&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;table&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stripe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;subscriptions&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;serial&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;stripe_id&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;250&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;stripe_customer_id&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;250&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;plan_id&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;250&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;status&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;250&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;created_at&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;timestamp&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;current_timestamp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;updated_at&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;timestamp&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;current_timestamp&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;Inside the Rails app, data migrations could be created for this purpose. However, as we are unsure whether these tables are permanent at the moment, we use SQL and execute it manually on the server.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Our app uses Rails, so it has &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ActiveRecord&lt;/code&gt;, and we can point our models to these tables. These are the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ActiveRecord&lt;/code&gt; models we created for our convenience while working with these tables in the Ruby script:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;StripeSchema&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActiveRecord&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Base&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;abstract_class&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;establish_connection&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:stripe&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;StripeCustomer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;StripeSchema&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;table_name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:customers&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;primary_key&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:id&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;StripeSubscription&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;StripeSchema&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;table_name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:subscriptions&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;primary_key&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:id&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;belongs_to&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:stripe_customer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;foreign_key: :stripe_id&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We have a special folder in the app, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;app/scripts&lt;/code&gt;, where we put this kind of ad-hoc code. Later, we use Rails runner to execute them on the production server against the real data. They can also be run locally for testing purposes during script development. This script code was put into &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;app/scripts/stripe_classes.rb&lt;/code&gt; file.&lt;/p&gt;

&lt;h2 id=&quot;the-stripe-data-scrapper&quot;&gt;The Stripe data scrapper&lt;/h2&gt;

&lt;p&gt;And this is the script we’ve come up with:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;starting_after&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt;

&lt;span class=&quot;kp&quot;&gt;loop&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;params&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;expand: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'data.subscriptions'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;limit: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:starting_after&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;starting_after&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;starting_after&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;customers&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Stripe&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Customer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;customers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cus&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;ApplicationRecord&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;transaction&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
      &lt;span class=&quot;no&quot;&gt;StripeCustomer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;ss&quot;&gt;email: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cus&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;email&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
        &lt;span class=&quot;ss&quot;&gt;stripe_id: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cus&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
        &lt;span class=&quot;ss&quot;&gt;description: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cus&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;description&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
        &lt;span class=&quot;ss&quot;&gt;created_in_stripe: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cus&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;created&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;zone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;at&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cus&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;created&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;ss&quot;&gt;metadata: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cus&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;metadata&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
        &lt;span class=&quot;ss&quot;&gt;subscriptions: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;customer_subscriptions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;ss&quot;&gt;deleted: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cus&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;deleted&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

      &lt;span class=&quot;n&quot;&gt;cus&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;subscriptions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sub&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
        &lt;span class=&quot;no&quot;&gt;StripeSubscription&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
          &lt;span class=&quot;ss&quot;&gt;stripe_customer_id: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cus&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
          &lt;span class=&quot;ss&quot;&gt;stripe_id: &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sub&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
          &lt;span class=&quot;ss&quot;&gt;plan_id: &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sub&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;plan&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
          &lt;span class=&quot;ss&quot;&gt;status: &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sub&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;status&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;starting_after&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cus&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;break&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;customers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This script fetches all Stripe customers along with their subscriptions. Later, using this data, we can compare it against our app’s database and identify any deviations.&lt;/p&gt;

&lt;p&gt;We run this script on the server with the &lt;a href=&quot;https://guides.rubyonrails.org/command_line.html#bin-rails-runner&quot;&gt;rails runner&lt;/a&gt;. Its run can take a while. Not to get the process killed with a closed SSH connection to the server, we use &lt;a href=&quot;https://www.gnu.org/software/screen/&quot;&gt;screen&lt;/a&gt; utility. At least for the first time, we have to run it manually and monitor for any failures. If there are any issues, we fix them. This way, we verify that the script is valid and reliable. Later, we can automate it to run on a schedule. We’ve collected data on 85k customers and 8.5k subscriptions, and it took around 1 hour. Not bad for this amount of data.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/data-stripe.jpg&quot; alt=&quot;Collected data from Stripe&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;run-the-script-automatically&quot;&gt;Run the script automatically&lt;/h2&gt;

&lt;p&gt;After some preliminary data analysis and verification of its correctness, we can set up this script to run automatically on the server. We use sidekiq and its extension sidekiq-scheduler for this, as it’s already in place. The app also has Rollbar configured to monitor exceptions and errors, so if something goes wrong, we can be notified.&lt;/p&gt;

&lt;p&gt;This is the job:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;UpdateStripeData&lt;/span&gt;
  &lt;span class=&quot;kp&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Sidekiq&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Worker&lt;/span&gt;

  &lt;span class=&quot;nb&quot;&gt;require_relative&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'../../scripts/stripe_classes'&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;perform&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;drop_old_data&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;record_stripe_data&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

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

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;record_stripe_data&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;starting_after&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt;

    &lt;span class=&quot;kp&quot;&gt;loop&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;params&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;expand: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'data.subscriptions'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;limit: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:starting_after&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;starting_after&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;starting_after&lt;/span&gt;

      &lt;span class=&quot;n&quot;&gt;customers&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Stripe&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Customer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

      &lt;span class=&quot;n&quot;&gt;customers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cus&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
        &lt;span class=&quot;no&quot;&gt;ApplicationRecord&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;transaction&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
          &lt;span class=&quot;no&quot;&gt;StripeCustomer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;ss&quot;&gt;email: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cus&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;email&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
            &lt;span class=&quot;ss&quot;&gt;stripe_id: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cus&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
            &lt;span class=&quot;ss&quot;&gt;description: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cus&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;description&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
            &lt;span class=&quot;ss&quot;&gt;created_in_stripe: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cus&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;created&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;zone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;at&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cus&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;created&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;ss&quot;&gt;metadata: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cus&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;metadata&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
            &lt;span class=&quot;ss&quot;&gt;subscriptions: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;customer_subscriptions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;ss&quot;&gt;deleted: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cus&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;deleted&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
          &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

          &lt;span class=&quot;n&quot;&gt;cus&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;subscriptions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sub&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
            &lt;span class=&quot;no&quot;&gt;StripeSubscription&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
              &lt;span class=&quot;ss&quot;&gt;stripe_customer_id: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cus&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
              &lt;span class=&quot;ss&quot;&gt;stripe_id: &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sub&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
              &lt;span class=&quot;ss&quot;&gt;plan_id: &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sub&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;plan&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
              &lt;span class=&quot;ss&quot;&gt;status: &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sub&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;status&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

        &lt;span class=&quot;n&quot;&gt;starting_after&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cus&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;break&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;customers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;drop_old_data&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;ActiveRecord&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Base&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;connection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;~&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;SQL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;
      delete from stripe.customers;
      delete from stripe.subscriptions;
&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;    SQL&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And this is the configuration defined in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;config/sidekiq_scheduler.yml&lt;/code&gt; to run it every Saturday at 12:00PM in UTC time zone:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;production&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;update_stripe_data&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;UpdateStripeData&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;cron&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;12&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;6'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We chose that time as the servers are less loaded during those hours.&lt;/p&gt;

&lt;h2 id=&quot;analyze-stripe-data-discrepancies-with-sql&quot;&gt;Analyze Stripe data discrepancies with SQL&lt;/h2&gt;

&lt;p&gt;We are all set. With the data in place, we can use SQL to identify all deviations. This will involve using common table expression (CTE), views, joins, filters, and aggregation functions.&lt;/p&gt;

&lt;p&gt;Note that the script fetches the Stripe data once a week, and it takes around 1 hour to run. Consequently, the app data will always be ahead of the Stripe data. This is crucial to understand because we cannot join the current app data against the cached Stripe data due to this time lag. Therefore, we need the app data snapshot at the moment the script was run.&lt;/p&gt;

&lt;p&gt;Creating a real data snapshot is an option, but it would significantly complicate our architectural story. Fortunately, we have Papertrail configured in the app, a gem that stores all changes to users. This means we can reinstall actual data at the moment the script fetched Stripe data was run. The only field of interest for us is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;plan_id&lt;/code&gt;. Therefore, we reinstall only this field. To reuse the reinstalled data, we utilize views. These are kind of virtual tables inside SQL but don’t store the collected data:&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;create&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;view&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stripe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;users_with_actual_plan&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stripe_update&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;select&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;created_at&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ts&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stripe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;subscriptions&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;limit&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;recent_versions&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;select&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;versions&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;where&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;created_at&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;select&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ts&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;interval&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'1 day'&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stripe_update&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;select&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;u&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;coalesce&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;coalesce&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
                    &lt;span class=&quot;n&quot;&gt;coalesce&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
                        &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;substring&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;object_changes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'plan_id'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;', (&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\d&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;+)'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))::&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                        &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;substring&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;object&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;plan_id: (&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\d&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;+)'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))::&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;int&lt;/span&gt;
                    &lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
                    &lt;span class=&quot;n&quot;&gt;coalesce&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
                        &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;substring&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;object_changes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'plan_id'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'(&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\d&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;+),'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))::&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                        &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;substring&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;object&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;plan_id: (&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\d&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;+)'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))::&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;int&lt;/span&gt;
                    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;u&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;plan_id&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;actual_plan_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;rank&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;over&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;partition&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;by&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;u&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;order&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;by&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;vp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;created_at&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;desc&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nulls&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;last&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;vf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;created_at&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;asc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;version_rank&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;users&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;u&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;left&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;join&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stripe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;customers&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;c&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;on&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;stripe_id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;u&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;stripe_customer_id&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;left&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;join&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;recent_versions&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;vp&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;on&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;vp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;item_id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;u&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;vp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;created_at&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;coalesce&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;created_at&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;select&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ts&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stripe_update&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;left&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;join&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;recent_versions&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;vf&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;on&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;vf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;item_id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;u&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;vf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;created_at&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;coalesce&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;created_at&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;select&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ts&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stripe_update&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;select&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;where&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;version_rank&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This SQL query might look intimidating, and that’s okay. It’s not essential for understanding everything here. All you need to know is what it does, and you already have that knowledge. It creates a virtual table of users with the actual &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;plan_id&lt;/code&gt; that was set just before the script fetched Stripe data.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;One might ask, “How do we end up creating SQL queries that appear so complicated and intimidating?” The answer is simple: through small steps, selecting one field at a time, joining step by step, and eventually combining them into a single query using the CTE construction.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And now, we are ready to join these users with the actual plan data from Stripe.&lt;/p&gt;

&lt;p&gt;This time, the SQL looks much easier:&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stripe_update&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;select&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;created_at&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ts&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stripe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;subscriptions&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;limit&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;select&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;u&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;stripe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;users_with_actual_plan&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;u&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;left&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;join&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stripe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;subscriptions&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;on&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;stripe_customer_id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;u&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;stripe_customer_id&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;where&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;u&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;actual_plan_id&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;u&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;actual_plan_id&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;6&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;121&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;u&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;created_at&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;select&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ts&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stripe_update&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The plan with ID = 6 is the free plan, and 121 is a technical one used only for internal purposes. Therefore, the entire expression &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;u.actual_plan_id is not null and u.actual_plan_id not in (6, 121)&lt;/code&gt; indicates that the user is on a paid plan.&lt;/p&gt;

&lt;p&gt;We inputted this SQL into Metabase, and this is what it looks like:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/reported-users.png&quot; alt=&quot;SQL inside Metabase&quot; /&gt;&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Optionally, we can configure notifications to be sent even when a new record is added to these results. See the bell icon at the bottom right; it’s intended for this purpose.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Metabase comes in handy for exporting data into CSV and Excel with just one click. We do that often. That simplifies communication with the business a lot. It’s also possible to share the reports right away with the team via a direct link.&lt;/p&gt;

&lt;p&gt;As we can see, there were 983 instances of user data discrepancies found. It’s a significant amount of data among all active subscriptions that match the app user data, which is 8,488. That represents roughly a 10% margin of error! Too much!&lt;/p&gt;

&lt;p&gt;We found the number of subscriptions that match user data using this SQL:&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stripe_update&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;select&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;created_at&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ts&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stripe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;subscriptions&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;limit&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;select&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;u&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stripe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;users_with_actual_plan&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;u&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;join&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stripe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;subscriptions&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;on&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;stripe_customer_id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;u&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;stripe_customer_id&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;join&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;plans&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;on&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;stripe_price_id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;plan_id&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;where&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;u&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;created_at&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;select&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ts&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stripe_update&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;See how the created view “users_with_actual_plan” becomes handy here as well. It’s a very powerful tool that can save a ton of code and coding time!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Further data analysis shows that 514 of these 983 discrepancies are related to deleted users. It turns out our app employs a so-called soft delete feature, meaning they are not actually deleted but marked as such, preventing them from using the app anymore. Well, now the situation looks much better. It’s just a 5% margin of error, not the 10% we initially thought. It’s almost within 3% of the standard deviation. Not so bad.&lt;/p&gt;

&lt;h2 id=&quot;fixing-the-data-deviations-between-the-app-and-stripe&quot;&gt;Fixing the data deviations between the app and Stripe&lt;/h2&gt;

&lt;p&gt;All actions related to fixing data require a thorough understanding of the app at a good level. We can either examine the instances of discrepancies one by one, allowing us to understand what happened to each of them. When we analyze each instance of discrepancy, we check the app database, logs, Papertrail records, Stripe logs, Rollbar, and any other data sources that could help us determine what happened to a certain user.&lt;/p&gt;

&lt;p&gt;By simply looking into the app database, we noticed those 514 softly deleted users. This can be easily done by examining the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;deleted_at&lt;/code&gt; column in the report. Any Excel-like tool can allow us to filter this information, or we can accomplish this in Metabase.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/metabase-filters.png&quot; alt=&quot;Filter by non-null deleted_at column in Metabase&quot; /&gt;&lt;/p&gt;

&lt;p&gt;We can move these users to free plan right away by writing a Ruby script that was successfully done.&lt;/p&gt;

&lt;p&gt;Now, there are only 469 records with data discrepancies remaining. We go through some of them. That shows that all of them indeed had data discrepancies. Delving into the codebase with some hypotheses about what happened, we spot a serious breach inside the app. Each Stripe webhook has its own handler with specific code manipulating the user subscription data. All processed by Sidekiq. But Sidekiq doesn’t guarantee the order of incoming webhook processing.&lt;/p&gt;

&lt;p&gt;At this point, we decide to address this issue by making slight adjustments to the architecture. From now on, all webhooks will be processed by one handler that consistently checks the actual Stripe subscription state before changing the user’s plan within the app. Hence, the multiple webhooks that used to process subscription-related actions will now utilize only one blocking handler, ensuring it cannot run in parallel for several requests for one user.&lt;/p&gt;

&lt;p&gt;This is the simplicied version of the code we used to fix the bug:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;sync_app_subscription&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;PlanChangeSchedule&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;transaction&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# Prevent concurrent plan updates; if another job attempts to acquire the lock simultaneously, it will wait until this one is completed&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;PlanChangeSchedule&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;user: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;lock&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;actual_subscription&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stripe_plan&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;plan&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;synchronize!&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;elsif&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;actual_subscription&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;cancel_account&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;ensure&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;PlanChangeSchedule&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;user: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;delete_all&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PlanChangeSchedule&lt;/code&gt; is a special table that stores the user’s plan change requests. It’s used to prevent concurrent plan updates. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lock&lt;/code&gt; method is used to prevent concurrent plan updates. If another job attempts to acquire the lock simultaneously, it will wait until this one is completed. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;synchronize!&lt;/code&gt; method is used to synchronize the user’s plan with the actual Stripe subscription. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cancel_account&lt;/code&gt; method is used to cancel the user’s account if there is no actual subscription.&lt;/p&gt;

&lt;p&gt;After fixing the bug, we transferred all users who had not used the app in the last 90 days to the free plan. There were 391 such accounts. The remaining users got moved to the paid subscription. In the end, the result looked like that:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;30 users got moved to the free plan;&lt;/li&gt;
  &lt;li&gt;For the rest 48, we tried to reactivate the subscription. 19 of which got successfully reactivated. 5 of them, could not have created the subscription due to a missing payment method. 24 of them created subscriptions but with the payment failing, they will be moved to the free plan after 3 failed attempts, or remain active if paid.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That resulted in roughly a $525 immediate increase in Monthly Recurring Revenue (MRR) and 600$ in potential MRR increase if all the users with failed payments will be reactivated.&lt;/p&gt;

&lt;h2 id=&quot;preventing-future-discrepancies&quot;&gt;Preventing future discrepancies&lt;/h2&gt;

&lt;p&gt;To prevent future discrepancies, we set up a monitoring system that checks for new discrepancies every week. We use Metabase for this purpose. We set up a dashboard that shows the number of discrepancies. We also set up alerts that notify us when the number of discrepancies exceeds a certain threshold. All of that was done using Metabase and SQL.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Asynchronous communication between services is a painful point. It can lead to customer dissatisfaction, losses to the business, and increased workload for the customer support team. This post shows how we deal with these kinds of issues. Efficiently solving them requires deep expertise in SQL, the business, the tech stack, data analysis, and programming.&lt;/p&gt;

&lt;p&gt;Happy coding and bug-free apps to you, our reader!&lt;/p&gt;

  
  &lt;p&gt;&lt;a href=&quot;http://blog.widefix.com/reconcile-app-users-against-stripe-and-prevent-financial-losses/&quot;&gt;Reconcile app users vs Stripe and prevent financial losses&lt;/a&gt; was originally published by Andrei Kaleshka at &lt;a href=&quot;http://blog.widefix.com&quot;&gt;Take your Rails development to the next level&lt;/a&gt; on January 30, 2024.&lt;/p&gt;</content>
</entry>


<entry>
  <title type="html"><![CDATA[Smart route aliases in Rails]]></title>
  <link rel="alternate" type="text/html" href="http://blog.widefix.com/smart-route-aliases-in-rails/"/>
  <id>http://blog.widefix.com/smart-route-aliases-in-rails</id>
  <updated>2023-12-19 18:00:50 +0100T00:00:00-00:00</updated>
  <published>2023-12-19T00:00:00-05:00</published>
  
  <author>
    <name>Andrei Kaleshka</name>
    <uri>http://blog.widefix.com</uri>
    <email>ka8725@gmail.com</email>
  </author>
  <category scheme="http://blog.widefix.com/tags/#rails" term="rails" /><category scheme="http://blog.widefix.com/tags/#rails-development" term="rails-development" />
  <content type="html">
  
    &lt;p&gt;Sometimes, for SEO and marketing reasons, you might need to create an alias route in your Rails project. This can get tricky if the original route relies on dynamic elements that change based on the user’s session.&lt;/p&gt;

&lt;p&gt;In our project, we wanted to create a quick and easy shortcut, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/edit_my_plans&lt;/code&gt;, for the user-specific route &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/users/:id/edit?tab=plans&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For example, Rails offers a way to set up this type of redirect using the following code (taken from their official documentation):&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;get&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'/stories/:name'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;to: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;redirect&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path_params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;/articles/&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path_params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;pluralize&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;With this knowledge in hand, we developed a clear and straightforward method for defining these types of routes, allowing us to embed business logic of any level of complexity directly into the router:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# in config/routes.rb:&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;get&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;/edit_my_plans&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;to: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;redirect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;RoutesAlias&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;edit_my_plans&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This is our special class, called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RoutesAlias&lt;/code&gt;, that handles all the complex stuff. It’s better to keep this stuff separate from the router, so we put it in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;app/lib&lt;/code&gt; folder. You can put it somewhere else in your project if you want.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;RoutesAlias&lt;/span&gt;
  &lt;span class=&quot;kp&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Rails&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;application&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;routes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;url_helpers&lt;/span&gt;

  &lt;span class=&quot;nb&quot;&gt;attr_reader&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:session&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;edit_my_plans&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;proc&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;session&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;edit_my_plans&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;initialize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;session&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;vi&quot;&gt;@session&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;session&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;edit_my_plans&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;current_user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Authentication&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;user_from_session&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;session&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;current_user&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;user_type&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;vendor&quot;&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;edit_user_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;current_user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;tab: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;plans&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;user&quot;&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;projects_path&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt;
      &lt;span class=&quot;s2&quot;&gt;&quot;/auth/auth0?origin=/edit_my_plans&quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now, marketing sites, like landing pages or other marketing pages, can easily use the generic URL &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/edit_my_plans&lt;/code&gt;. Rails will handle everything, taking you to the right place, whether it’s a login form, a specific homepage for a particular user type, or something else.&lt;/p&gt;

&lt;p&gt;Hope this helps with your Rails project! Happy coding!&lt;/p&gt;

  
  &lt;p&gt;&lt;a href=&quot;http://blog.widefix.com/smart-route-aliases-in-rails/&quot;&gt;Smart route aliases in Rails&lt;/a&gt; was originally published by Andrei Kaleshka at &lt;a href=&quot;http://blog.widefix.com&quot;&gt;Take your Rails development to the next level&lt;/a&gt; on December 19, 2023.&lt;/p&gt;</content>
</entry>


<entry>
  <title type="html"><![CDATA[Cheaper and Risk-Free Ruby on Rails App Redesign]]></title>
  <link rel="alternate" type="text/html" href="http://blog.widefix.com/risk-free-redesign-ruby-on-rails-app/"/>
  <id>http://blog.widefix.com/risk-free-redesign-ruby-on-rails-app</id>
  <updated>2023-08-30 15:06:43 +0200T00:00:00-00:00</updated>
  <published>2023-08-30T00:00:00-04:00</published>
  
  <author>
    <name>Andrei Kaleshka</name>
    <uri>http://blog.widefix.com</uri>
    <email>ka8725@gmail.com</email>
  </author>
  <category scheme="http://blog.widefix.com/tags/#rails-development" term="rails-development" />
  <content type="html">
  
    &lt;h2 id=&quot;ruby-on-rails-app-redesigning-challenges&quot;&gt;Ruby on Rails app redesigning challenges&lt;/h2&gt;

&lt;p&gt;Redesigning a Ruby on Rails application is a well-known challenge for many projects. Any project UI gets outdated. It needs to get a fresh look that’s more appealing to the users. It may not be an issue for web apps without live users. But it’s a tricky task for already launched businesses serving thousands of users per day.&lt;/p&gt;

&lt;p&gt;While a development team is redesigning, the application should continue functioning. At the development time, the new design gets validated and tested against the back-end. If the back-end is incompatible, it must be adapted to the new realm. These changes should not break the old functionality. Overcoming these issues is essential in a Ruby on Rails application redesign.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/redesign-decision.jpg&quot; alt=&quot;Ruby On Rails redesign decision&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This article shares the approach we took to apply the new design in one of our projects. The changes we made increased the project revenue by 30%. As a bonus, the implemented changes unleashed many other opportunities, including:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Building a modern mobile application.&lt;/li&gt;
  &lt;li&gt;Making the code more fault-tolerant, performant, and stable.&lt;/li&gt;
  &lt;li&gt;Adding new features, such as preventing account sharing, that can increase the app revenue.&lt;/li&gt;
  &lt;li&gt;Making the tech stack upgrade easier.&lt;/li&gt;
  &lt;li&gt;Implementing traffic control and advanced caching.&lt;/li&gt;
  &lt;li&gt;Making SEO optimization easier.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;why-redesigning-a-ruby-on-rails-app&quot;&gt;Why redesigning a Ruby on Rails app&lt;/h2&gt;

&lt;p&gt;There are several reasons why an app may consider implementing a new UI/UX design. Regardless of the motive, the ultimate goal is always the same - &lt;strong&gt;to increase revenue&lt;/strong&gt;. Note, that keeping users using the app and not going away is the same goal.  Due to an old and unhandy design, an app can make the customers leave. In this case, redesign also can help. If it’s clear that a redesign won’t have a positive impact on revenue, it may be an unnecessary expense. Redesigning is usually an expensive change. So learn from the users if the design is a problem before making this decision.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/redesign-weigh.jpg&quot; alt=&quot;Ruby On Rails redesign weigh&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;what-is-the-new-design&quot;&gt;What is the new design&lt;/h2&gt;

&lt;p&gt;A new design consists of screens (mockups) created by a designer using software like Figma or Adobe Photoshop.&lt;/p&gt;

&lt;p&gt;In our case, the client provided the mockups, and our task was to turn them into code and integrate them with the existing back-end. We aimed to minimize downtime during the transition and reduce development efforts.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/redesign-new-design.jpg&quot; alt=&quot;What is the new design&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;different-technical-approaches-of-a-ruby-on-rails-app-redesign&quot;&gt;Different technical approaches of a Ruby on Rails app redesign&lt;/h2&gt;

&lt;p&gt;Nowadays, web projects must be responsive and have a mobile application. Modern web app design features a rich UI with many elements on one page. The old-fashioned way of generating HTML on the back-end is going away. Instead, a separate web front-end or mobile app handles the front-end, while the back-end serves data via API.&lt;/p&gt;

&lt;p&gt;In pure Ruby On Rails applications, the front-end code lives alongside the back-end. That can fulfill modern web app requirements and is cheap in the beginning. However, it becomes hard to maintain due to the mix of different technologies in one place. It makes it difficult to find developers who can understand and maintain the system. We prefer separate back-end and front-end. The specialists can do their job quickly with high quality on their end.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/redesign-business.jpg&quot; alt=&quot;Redesign Ruby On Rails business&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;risk-free-approach-of-a-ruby-on-rails-app-redesign&quot;&gt;Risk-free approach of a Ruby on Rails app redesign&lt;/h2&gt;

&lt;p&gt;The project we received was a Ruby On Rails application with a Rest API implemented on Grape. An iOS app used this API. The web version used ERB and Slim templates with a UI from the previous decade. Some dynamic features on the web used Knockout.js, which is no longer maintained.&lt;/p&gt;

&lt;p&gt;To update the UI, we used the Next.js framework with TypeScript and implemented GraphQL for the API. To avoid errors, we created a separate repository for the new UI and extracted functionality that could be reused in both the old app and the new UI.&lt;/p&gt;

&lt;p&gt;The project took almost a year with 2 developers and 1 project manager working on it. To deploy the changes, we used CloudFront as a proxy on top of the old app and the new front-end app. We gradually switched web requests to the new UI using a feature flag to reduce the risk of failures or outages. The transition went smoothly without major issues.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/rails-app-redesign.png&quot; alt=&quot;Risk free Rails App Redesign&quot; /&gt;&lt;/p&gt;

&lt;p&gt;As a bonus, we got all requests geolocated since they pass through the AWS CloudFront. That allowed us to control the users’ network traffic and fight against sharing accounts.&lt;/p&gt;

&lt;h2 id=&quot;our-results-of-the-ruby-on-rails-app-redesign&quot;&gt;Our results of the Ruby on Rails app redesign&lt;/h2&gt;

&lt;p&gt;Switching to the new design made the project more attractive to users, resulting in more signups and increased satisfaction among old users. This allowed us to increase charges by 20% and revenue by 30%. The redesign expenses were paid off within the first 3 months after release. See below for the paid user dynamics analysis.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/users-increase.png&quot; alt=&quot;User signups dynamics&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The release date was on the 1st of February 2021.&lt;/p&gt;

&lt;h2 id=&quot;why-our-approach-of-ruby-on-rails-app-redesign-is-cheaper-and-less-risky&quot;&gt;Why our approach of Ruby on Rails app redesign is cheaper and less risky&lt;/h2&gt;

&lt;p&gt;Redesigning apps can be painful and distracting. The Rails assets pipeline changes often, making sticking with it turn the code into legacy quickly. Finding specialists to maintain this code without sacrificing quality on both front-end and back-end is challenging. Separating the front-end and leaving only the Rails back-end for the API makes redesigning and maintenance smoother, allowing for a diversified team with good specialists on both ends.&lt;/p&gt;

&lt;p&gt;Next.js was a wise choice for the front-end, improving SEO with image optimization and cache facilities out of the box.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/redesign-seo.png&quot; alt=&quot;Redesign Rails App SEO impact&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;acknowledges&quot;&gt;Acknowledges&lt;/h2&gt;

&lt;p&gt;I’m grateful and proud to have worked with the following people on this project at different times:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Daniel Dauwe&lt;/li&gt;
  &lt;li&gt;Vadzim Jakushau&lt;/li&gt;
  &lt;li&gt;Illia Pruskyi&lt;/li&gt;
  &lt;li&gt;Soltan Yangibayev&lt;/li&gt;
  &lt;li&gt;Svetlana Zhuravitskaya&lt;/li&gt;
  &lt;li&gt;Alexey Mikitsik&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;small class=&quot;side-note&quot;&gt;
When seeking proficient expertise in Ruby on Rails development, explore the option of using a reputable talent marketplace. Toptal provides access to dependable &lt;a href=&quot;https://www.toptal.com/ruby-on-rails&quot;&gt;Ruby on Rails developers&lt;/a&gt; for hire, assisting companies with their critical web application requirements.
&lt;/small&gt;&lt;/p&gt;

  
  &lt;p&gt;&lt;a href=&quot;http://blog.widefix.com/risk-free-redesign-ruby-on-rails-app/&quot;&gt;Cheaper and Risk-Free Ruby on Rails App Redesign&lt;/a&gt; was originally published by Andrei Kaleshka at &lt;a href=&quot;http://blog.widefix.com&quot;&gt;Take your Rails development to the next level&lt;/a&gt; on August 30, 2023.&lt;/p&gt;</content>
</entry>


<entry>
  <title type="html"><![CDATA[Select unique latest grouped records from DB]]></title>
  <link rel="alternate" type="text/html" href="http://blog.widefix.com/select-unique-latest-grouped-records-from-db/"/>
  <id>http://blog.widefix.com/select-unique-latest-grouped-records-from-db</id>
  <updated>2023-07-13 17:26:54 +0200T00:00:00-00:00</updated>
  <published>2023-07-13T00:00:00-04:00</published>
  
  <author>
    <name>Andrei Kaleshka</name>
    <uri>http://blog.widefix.com</uri>
    <email>ka8725@gmail.com</email>
  </author>
  <category scheme="http://blog.widefix.com/tags/#sql" term="sql" /><category scheme="http://blog.widefix.com/tags/#active_record" term="active_record" />
  <content type="html">
  
    &lt;p&gt;Nowadays, almost every Ruby on Rails application has a so-called recent records block.
This block usually shows statistics or a list of recent things within the project during the last few days searched by some criteria.
It can be something like “top 10 products”, “the most popular projects”, or “the most relevant apartments”. Read this blog post the learn how to efficiently build data for these blocks using SQL and window functions in Ruby on Rails app.&lt;/p&gt;

&lt;h2 id=&quot;recent-records---the-task-overview&quot;&gt;Recent records - the task overview&lt;/h2&gt;

&lt;p&gt;Assume that you’ve got an app that has &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Project&lt;/code&gt; model. It has many &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ratings&lt;/code&gt;. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Rating&lt;/code&gt; model has just a value from 1 to 5 assigned by users to some projects.&lt;/p&gt;

&lt;p&gt;For the task understanding it’s enough to have a look into the table definition:&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;db&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-#&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;\&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;d&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ratings&lt;/span&gt;
                                          &lt;span class=&quot;k&quot;&gt;Table&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;&quot;public.ratings&quot;&lt;/span&gt;
   &lt;span class=&quot;k&quot;&gt;Column&lt;/span&gt;    &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;              &lt;span class=&quot;k&quot;&gt;Type&lt;/span&gt;              &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;Collation&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;Nullable&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;               &lt;span class=&quot;k&quot;&gt;Default&lt;/span&gt;
&lt;span class=&quot;err&quot;&gt;═════════════╪════════════════════════════════╪═══════════╪══════════╪═════════════════════════════════════&lt;/span&gt;
 &lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;          &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;bigint&lt;/span&gt;                         &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;           &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nextval&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'ratings_id_seq'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;regclass&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
 &lt;span class=&quot;n&quot;&gt;reviewer_id&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;bigint&lt;/span&gt;                         &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;           &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;          &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;
 &lt;span class=&quot;n&quot;&gt;reviewee_id&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;bigint&lt;/span&gt;                         &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;           &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;          &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;
 &lt;span class=&quot;n&quot;&gt;rating&lt;/span&gt;      &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;integer&lt;/span&gt;                        &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;           &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;          &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;
 &lt;span class=&quot;n&quot;&gt;review&lt;/span&gt;      &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;text&lt;/span&gt;                           &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;           &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;          &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;
 &lt;span class=&quot;n&quot;&gt;project_id&lt;/span&gt;  &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;bigint&lt;/span&gt;                         &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;           &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;          &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;
 &lt;span class=&quot;n&quot;&gt;created_at&lt;/span&gt;  &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;timestamp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;6&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;without&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;time&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;zone&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;           &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;
 &lt;span class=&quot;n&quot;&gt;updated_at&lt;/span&gt;  &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;timestamp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;6&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;without&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;time&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;zone&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;           &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;Indexes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;&quot;ratings_pkey&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;PRIMARY&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;KEY&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;btree&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This table has the following data:&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;db&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-#&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;select&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;reviewer_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;reviewee_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rating&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;project_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;created_at&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ratings&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
 &lt;span class=&quot;n&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;reviewer_id&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;reviewee_id&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rating&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;project_id&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;         &lt;span class=&quot;n&quot;&gt;created_at&lt;/span&gt;
&lt;span class=&quot;err&quot;&gt;════╪═════════════╪═════════════╪════════╪════════════╪════════════════════════════&lt;/span&gt;
  &lt;span class=&quot;mi&quot;&gt;8&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;           &lt;span class=&quot;mi&quot;&gt;9&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;          &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;      &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;         &lt;span class=&quot;mi&quot;&gt;27&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2022&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;12&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;05&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;21&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;46&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;01&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;583185&lt;/span&gt;
 &lt;span class=&quot;mi&quot;&gt;12&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;           &lt;span class=&quot;mi&quot;&gt;7&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;           &lt;span class=&quot;mi&quot;&gt;6&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;      &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;         &lt;span class=&quot;mi&quot;&gt;26&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2022&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;12&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;23&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;14&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;35&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;11&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;047002&lt;/span&gt;
 &lt;span class=&quot;mi&quot;&gt;13&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;           &lt;span class=&quot;mi&quot;&gt;6&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;           &lt;span class=&quot;mi&quot;&gt;7&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;      &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;         &lt;span class=&quot;mi&quot;&gt;26&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2022&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;12&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;23&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;14&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;36&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;48&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;366411&lt;/span&gt;
 &lt;span class=&quot;mi&quot;&gt;18&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;           &lt;span class=&quot;mi&quot;&gt;9&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;          &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;      &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;         &lt;span class=&quot;mi&quot;&gt;39&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2023&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;03&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;01&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;23&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;27&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;52&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;68548&lt;/span&gt;
 &lt;span class=&quot;mi&quot;&gt;19&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;          &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;           &lt;span class=&quot;mi&quot;&gt;9&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;      &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;         &lt;span class=&quot;mi&quot;&gt;39&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2023&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;03&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;01&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;23&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;28&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;880234&lt;/span&gt;
 &lt;span class=&quot;mi&quot;&gt;20&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;           &lt;span class=&quot;mi&quot;&gt;9&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;          &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;      &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;         &lt;span class=&quot;mi&quot;&gt;86&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2023&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;03&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;01&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;23&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;35&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;15&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;564763&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;6&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;rows&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Our task is to return the latest reviews per project. So the resulting records should be these:&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; &lt;span class=&quot;n&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;reviewer_id&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;reviewee_id&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rating&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;project_id&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;         &lt;span class=&quot;n&quot;&gt;created_at&lt;/span&gt;
&lt;span class=&quot;err&quot;&gt;════╪═════════════╪═════════════╪════════╪════════════╪════════════════════════════&lt;/span&gt;
  &lt;span class=&quot;mi&quot;&gt;8&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;           &lt;span class=&quot;mi&quot;&gt;9&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;          &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;      &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;         &lt;span class=&quot;mi&quot;&gt;27&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2022&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;12&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;05&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;21&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;46&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;01&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;583185&lt;/span&gt;
 &lt;span class=&quot;mi&quot;&gt;13&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;           &lt;span class=&quot;mi&quot;&gt;6&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;           &lt;span class=&quot;mi&quot;&gt;7&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;      &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;         &lt;span class=&quot;mi&quot;&gt;26&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2022&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;12&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;23&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;14&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;36&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;48&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;366411&lt;/span&gt;
 &lt;span class=&quot;mi&quot;&gt;19&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;          &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;           &lt;span class=&quot;mi&quot;&gt;9&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;      &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;         &lt;span class=&quot;mi&quot;&gt;39&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2023&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;03&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;01&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;23&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;28&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;880234&lt;/span&gt;
 &lt;span class=&quot;mi&quot;&gt;20&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;           &lt;span class=&quot;mi&quot;&gt;9&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;          &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;      &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;         &lt;span class=&quot;mi&quot;&gt;86&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2023&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;03&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;01&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;23&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;35&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;15&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;564763&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;rows&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Note the project_id is distinct compared to the all records. And the timestamps are the most recent for those duplicated projects (their id = 26, 39).&lt;/p&gt;

&lt;p&gt;There is no way to solve this task efficiently using only ActiveRecord functionality and pure Ruby. But SQL can solve this with the &lt;a href=&quot;https://www.postgresql.org/docs/current/tutorial-window.html&quot; ref=&quot;nofollow&quot; target=&quot;_blank&quot;&gt;window function&lt;/a&gt; technique.&lt;/p&gt;

&lt;h2 id=&quot;how-window-function-with-row-number-partition-works&quot;&gt;How window function with row number partition works&lt;/h2&gt;

&lt;p&gt;The idea is the following - we rank all records inside the table from 1 no N for the duplicated records of our search criteria. The most recent record gets 1, older one gets higher rank. The distinct rows will have 1.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; &lt;span class=&quot;n&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;project_id&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;reviewee_id&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;         &lt;span class=&quot;n&quot;&gt;created_at&lt;/span&gt;         &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;row_number&lt;/span&gt;
&lt;span class=&quot;err&quot;&gt;════╪════════════╪═════════════╪════════════════════════════╪════════════&lt;/span&gt;
  &lt;span class=&quot;mi&quot;&gt;8&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;         &lt;span class=&quot;mi&quot;&gt;27&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;          &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2022&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;12&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;05&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;21&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;46&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;01&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;583185&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;          &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
 &lt;span class=&quot;mi&quot;&gt;12&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;         &lt;span class=&quot;mi&quot;&gt;26&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;           &lt;span class=&quot;mi&quot;&gt;6&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2022&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;12&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;23&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;14&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;35&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;11&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;047002&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;          &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;
 &lt;span class=&quot;mi&quot;&gt;13&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;         &lt;span class=&quot;mi&quot;&gt;26&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;           &lt;span class=&quot;mi&quot;&gt;7&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2022&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;12&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;23&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;14&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;36&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;48&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;366411&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;          &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
 &lt;span class=&quot;mi&quot;&gt;18&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;         &lt;span class=&quot;mi&quot;&gt;39&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;          &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2023&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;03&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;01&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;23&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;27&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;52&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;68548&lt;/span&gt;  &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;          &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;
 &lt;span class=&quot;mi&quot;&gt;19&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;         &lt;span class=&quot;mi&quot;&gt;39&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;           &lt;span class=&quot;mi&quot;&gt;9&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2023&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;03&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;01&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;23&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;28&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;880234&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;          &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
 &lt;span class=&quot;mi&quot;&gt;20&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;         &lt;span class=&quot;mi&quot;&gt;86&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;          &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2023&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;03&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;01&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;23&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;35&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;15&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;564763&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;          &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;6&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;rows&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The ratings with id = 8, 20 receive row number 1 because these projects are distinct (27, 86). But projects with id = 26, 39 have several ratings that’s why the rows with this project id have row_number 1 and 2. The most recent ratings per project receive 1, and the older ones receive row number 2.&lt;/p&gt;

&lt;h2 id=&quot;use-subselect-to-filter-correct-results&quot;&gt;Use subselect to filter correct results&lt;/h2&gt;

&lt;p&gt;If we filter out those row numbers greater 1 we get the required result. If that would be a table we could use the SQL’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;where&lt;/code&gt; clause. For example, a view (virtual table) could be created for that. But we will keep it simple. We will use subselect: initially we prepare select to return the data as above and immediately use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;select&lt;/code&gt; statement to filter out the correct result.&lt;/p&gt;

&lt;p&gt;But first, let’s see how to write SQL statement to assign the row number using the already noticed &lt;strong&gt;window function&lt;/strong&gt;:&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;db&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-#&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;select&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;project_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;reviewee_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;created_at&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;row_number&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;over&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;partition&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;by&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;project_id&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;order&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;by&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;created_at&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;desc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ratings&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;order&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;by&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;created_at&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

 &lt;span class=&quot;n&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;project_id&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;reviewee_id&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;         &lt;span class=&quot;n&quot;&gt;created_at&lt;/span&gt;         &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;row_number&lt;/span&gt;
&lt;span class=&quot;err&quot;&gt;════╪════════════╪═════════════╪════════════════════════════╪════════════&lt;/span&gt;
  &lt;span class=&quot;mi&quot;&gt;8&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;         &lt;span class=&quot;mi&quot;&gt;27&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;          &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2022&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;12&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;05&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;21&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;46&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;01&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;583185&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;          &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
 &lt;span class=&quot;mi&quot;&gt;12&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;         &lt;span class=&quot;mi&quot;&gt;26&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;           &lt;span class=&quot;mi&quot;&gt;6&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2022&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;12&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;23&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;14&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;35&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;11&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;047002&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;          &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;
 &lt;span class=&quot;mi&quot;&gt;13&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;         &lt;span class=&quot;mi&quot;&gt;26&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;           &lt;span class=&quot;mi&quot;&gt;7&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2022&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;12&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;23&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;14&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;36&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;48&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;366411&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;          &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
 &lt;span class=&quot;mi&quot;&gt;18&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;         &lt;span class=&quot;mi&quot;&gt;39&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;          &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2023&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;03&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;01&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;23&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;27&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;52&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;68548&lt;/span&gt;  &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;          &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;
 &lt;span class=&quot;mi&quot;&gt;19&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;         &lt;span class=&quot;mi&quot;&gt;39&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;           &lt;span class=&quot;mi&quot;&gt;9&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2023&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;03&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;01&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;23&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;28&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;880234&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;          &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
 &lt;span class=&quot;mi&quot;&gt;20&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;         &lt;span class=&quot;mi&quot;&gt;86&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;          &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2023&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;03&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;01&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;23&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;35&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;15&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;564763&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;          &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;6&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;rows&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;row_number() over (partition by project_id order by created_at desc)&lt;/code&gt; is a window function that assigns row number from 1 to N for the records duplicated by some criteria. In this case the criteria is distinct project_id sorted by created_at desc.&lt;/p&gt;

&lt;p&gt;Running this query inside DB console will produce the result above.&lt;/p&gt;

&lt;p&gt;Wrap this &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;select&lt;/code&gt; with another &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;select&lt;/code&gt; and filter only rows with number = 1:&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;db&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-#&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;select&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;reviewer_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;reviewee_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;rating&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;project_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;created_at&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;select&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;row_number&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;over&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;partition&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;by&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;project_id&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;order&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;by&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;created_at&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;desc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ratings&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;order&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;by&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;created_at&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ratings&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;where&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;row_number&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

 &lt;span class=&quot;n&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;reviewer_id&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;reviewee_id&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rating&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;project_id&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;         &lt;span class=&quot;n&quot;&gt;created_at&lt;/span&gt;
&lt;span class=&quot;err&quot;&gt;════╪═════════════╪═════════════╪════════╪════════════╪════════════════════════════&lt;/span&gt;
  &lt;span class=&quot;mi&quot;&gt;8&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;           &lt;span class=&quot;mi&quot;&gt;9&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;          &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;      &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;         &lt;span class=&quot;mi&quot;&gt;27&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2022&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;12&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;05&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;21&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;46&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;01&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;583185&lt;/span&gt;
 &lt;span class=&quot;mi&quot;&gt;13&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;           &lt;span class=&quot;mi&quot;&gt;6&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;           &lt;span class=&quot;mi&quot;&gt;7&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;      &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;         &lt;span class=&quot;mi&quot;&gt;26&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2022&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;12&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;23&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;14&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;36&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;48&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;366411&lt;/span&gt;
 &lt;span class=&quot;mi&quot;&gt;19&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;          &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;           &lt;span class=&quot;mi&quot;&gt;9&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;      &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;         &lt;span class=&quot;mi&quot;&gt;39&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2023&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;03&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;01&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;23&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;28&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;880234&lt;/span&gt;
 &lt;span class=&quot;mi&quot;&gt;20&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;           &lt;span class=&quot;mi&quot;&gt;9&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;          &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;      &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt;         &lt;span class=&quot;mi&quot;&gt;86&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;│&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2023&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;03&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;01&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;23&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;35&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;15&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;564763&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;rows&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Voila, we’ve got what we want!&lt;/p&gt;

&lt;h2 id=&quot;use-activerecordfrom-to-return-the-results-as-ruby-objects&quot;&gt;Use ActiveRecord.from to return the results as Ruby objects&lt;/h2&gt;

&lt;p&gt;Since we’ve got the SQL query it’s easy to port it into ActiveRecord and get eventually the list of Ruby objects. We will use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ActiveRecord.from&lt;/code&gt; to write the subselect:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Rating&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;select&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;id, reviewer_id, reviewee_id, rating, project_id, created_at&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;(select *, row_number() over (partition by project_id order by created_at desc) from ratings group by project_id, reviewee_id, created_at, id order by created_at) as ratings&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;no&quot;&gt;Rating&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Load&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;41.9&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ms&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;no&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;reviewer_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;reviewee_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rating&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;project_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;created_at&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;select&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;row_number&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;over&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;partition&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;by&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;project_id&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;order&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;by&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;created_at&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;desc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ratings&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;group&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;by&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;project_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;reviewee_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;created_at&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;order&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;by&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;created_at&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ratings&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;ratings&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;row_number&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;vg&quot;&gt;$1&lt;/span&gt;  &lt;span class=&quot;p&quot;&gt;[[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;row_number&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]]&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;#&amp;lt;Rating:0x000000011190d410 id: 8, reviewer_id: 9, reviewee_id: 10, rating: 5, project_id: 27, created_at: Mon, 05 Dec 2022 21:46:01.583185000 UTC +00:00&amp;gt;,&lt;/span&gt;
 &lt;span class=&quot;c1&quot;&gt;#&amp;lt;Rating:0x000000011190d348 id: 13, reviewer_id: 6, reviewee_id: 7, rating: 5, project_id: 26, created_at: Fri, 23 Dec 2022 14:36:48.366411000 UTC +00:00&amp;gt;,&lt;/span&gt;
 &lt;span class=&quot;c1&quot;&gt;#&amp;lt;Rating:0x000000011190d280 id: 19, reviewer_id: 10, reviewee_id: 9, rating: 5, project_id: 39, created_at: Wed, 01 Mar 2023 23:28:32.880234000 UTC +00:00&amp;gt;,&lt;/span&gt;
 &lt;span class=&quot;c1&quot;&gt;#&amp;lt;Rating:0x000000011190d1b8 id: 20, reviewer_id: 9, reviewee_id: 10, rating: 5, project_id: 86, created_at: Wed, 01 Mar 2023 23:35:15.564763000 UTC +00:00&amp;gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You can run this experiment yourself on this &lt;a href=&quot;https://github.com/widefix/demo-fast-sql&quot; ref=&quot;nofollow&quot; target=&quot;_blank&quot;&gt;demo app&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Advanced SQL understanding allows you to write performant advanced functionality in a Ruby on Rails application efficiently.&lt;/p&gt;

&lt;p&gt;If you like this article and would like to see more examples of how SQL can improve your software development life read these articles:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https:http://blog.widefix.com/importance-sql-for-rails-experts/&quot; ref=&quot;nofollow&quot; target=&quot;_blank&quot;&gt;Make your Ruby on Rails app 80x faster with SQL&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https:http://blog.widefix.com/financial-plan-on-postgresql/&quot; ref=&quot;nofollow&quot; target=&quot;_blank&quot;&gt;Financial plan on PostgreSQL&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https:http://blog.widefix.com/financial-plan-on-rails/&quot; ref=&quot;nofollow&quot; target=&quot;_blank&quot;&gt;Financial plan on Rails&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https:http://blog.widefix.com/from-single-dd-to-multiple-checkboxes/&quot; ref=&quot;nofollow&quot; target=&quot;_blank&quot;&gt;From Single drop-down to Multiple check-boxes&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https:http://blog.widefix.com/date-ranges-overlap/&quot; ref=&quot;nofollow&quot; target=&quot;_blank&quot;&gt;Efficient algorithm to check dates overlap&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Have a good day ahead and happy coding!&lt;/p&gt;

  
  &lt;p&gt;&lt;a href=&quot;http://blog.widefix.com/select-unique-latest-grouped-records-from-db/&quot;&gt;Select unique latest grouped records from DB&lt;/a&gt; was originally published by Andrei Kaleshka at &lt;a href=&quot;http://blog.widefix.com&quot;&gt;Take your Rails development to the next level&lt;/a&gt; on July 13, 2023.&lt;/p&gt;</content>
</entry>


<entry>
  <title type="html"><![CDATA[Use timestamp for predicates in Rails]]></title>
  <link rel="alternate" type="text/html" href="http://blog.widefix.com/use-timestamp-attributes-as-predicates-in-rails/"/>
  <id>http://blog.widefix.com/use-timestamp-attributes-as-predicates-in-rails</id>
  <updated>2023-06-15 15:19:16 +0200T00:00:00-00:00</updated>
  <published>2023-06-15T00:00:00-04:00</published>
  
  <author>
    <name>Andrei Kaleshka</name>
    <uri>http://blog.widefix.com</uri>
    <email>ka8725@gmail.com</email>
  </author>
  <category scheme="http://blog.widefix.com/tags/#rails" term="rails" /><category scheme="http://blog.widefix.com/tags/#active_record" term="active_record" /><category scheme="http://blog.widefix.com/tags/#DSL" term="DSL" />
  <content type="html">
  
    &lt;h2 id=&quot;why-use-timestamp-field-instead-of-boolean-for-checkbox&quot;&gt;Why use timestamp field instead of boolean for checkbox&lt;/h2&gt;

&lt;p&gt;You are about to add a new boolean attribute into your model inside a Ruby on Rails application. It’s simple as is - add a boolean column in DB and call it a day. But stop and think - consider a timestamp type for the corresponding columns in the database.&lt;/p&gt;

&lt;p&gt;This approach allows for tracking the time when the value change. That can be helpful in debugging, especially when dealing with production issues.&lt;/p&gt;

&lt;p&gt;Ok, it’s preferable to use the timestamp field type on the back-end. But it is still convenient to have a checkbox on the user interface. Thus, the timestamp column type does not match the UI field type (checkbox). That means the value (such as true/false, yes/no, or any other) from the front-end should be transformed into a timestamp or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nil&lt;/code&gt; on the back-end. That’s needed, to ensure compatibility with the mass-assignment methods (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.new&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#update&lt;/code&gt;).&lt;/p&gt;

&lt;h2 id=&quot;avoid-code-duplicating-with-a-universal-solution&quot;&gt;Avoid code duplicating with a universal solution&lt;/h2&gt;

&lt;p&gt;One could write this type of transformation logic ad-hoc inside controllers. But you can create a universal solution. One way to achieve this is by defining a helper method in the base model class. In Rails, the base model class is typically &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ApplicationRecord&lt;/code&gt;. By doing this, you can ensure its availability across all models in the application. See the following example how to do this:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ApplicationRecord&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActiveRecord&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Base&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;primary_abstract_class&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# Defines a predicate method and a setter method for the specified attributes.&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;#&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# To use this code in the User model, call the following:&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;#   timestamp_as_boolean :muted&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;#&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# In this scenario, it assumes that a &quot;muted_at&quot; column is defined in the table.&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# After calling the code, the following methods are defined on the User model:&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;#   muted? - checks if a user is muted&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;#   muted=(value) - sets the muted_at timestamp if the value is equivalent to boolean &quot;true&quot;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;timestamp_as_boolean&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fields&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;fields&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;field&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;timestamp_field&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;field&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;_at&quot;&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;predicate&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;field&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;?&quot;&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;define_method&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;predicate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;public_send&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;timestamp_field&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;present?&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;define_method&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;field&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;=&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;new_value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActiveModel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Type&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Boolean&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;cast&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;current&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;public_send&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;timestamp_field&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;=&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;new_value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;new_value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;nil?&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;public_send&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;predicate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We have now implemented a universal solution using some meta-programming techniques in Ruby. Here’s how you can use it. Let’s take the example of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;User&lt;/code&gt; model and convert the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;muted_at&lt;/code&gt; column into a boolean attribute using the following approach:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;User&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;timestamp_as_boolean&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:muted&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;To utilize this solution in an ERB template on the user interface, follow these steps:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;sx&quot;&gt;%= form.check_box :muted, { checked: @user.muted? } %&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Let’s verify its functionality:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;gt; user = User.last
&amp;gt; user.muted? # =&amp;gt; true
&amp;gt; user.update!(muted: false)
&amp;gt; user.muted? # =&amp;gt; false
&amp;gt; user.update!(muted: true) # =&amp;gt; true
&amp;gt; user.muted? # =&amp;gt; true
&amp;gt; user.muted_at # =&amp;gt; Thu, 15 Jun 2023 15:16:54.582848000 UTC +00:00
&amp;gt; user.update!(muted: true) # =&amp;gt; true
&amp;gt; user.muted_at # =&amp;gt; Thu, 15 Jun 2023 15:16:54.582848000 UTC +00:00
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It is important to note that after the second assignment of muted = true, the timestamp did not change. This behavior is expected and logical because the value itself was not modified. The purpose of tracking the timestamp is to capture the moment when a change in the value occurs. In this case, since the value remains the same, there is no need for the timestamp to be updated. This behavior aligns with the intended functionality.  It ensures that the timestamp accurately reflects when a change in the value of the attribute occurs.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Use a timestamp type for columns in the database. Create a universal solution to transform boolean values. As a result, enhance the functionality and debugging capabilities of our models. The timestamp type allows us to track the values change time. That can aid in identifying and troubleshooting issues, particularly in production environments. Avoid code duplication and ensure consistent behavior across models with a universal solution. Metaprogramming can help with that. These improvements contribute to a more robust and efficient application.&lt;/p&gt;

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

  
  &lt;p&gt;&lt;a href=&quot;http://blog.widefix.com/use-timestamp-attributes-as-predicates-in-rails/&quot;&gt;Use timestamp for predicates in Rails&lt;/a&gt; was originally published by Andrei Kaleshka at &lt;a href=&quot;http://blog.widefix.com&quot;&gt;Take your Rails development to the next level&lt;/a&gt; on June 15, 2023.&lt;/p&gt;</content>
</entry>


<entry>
  <title type="html"><![CDATA[Optimize Rails app performance with ChatGPT]]></title>
  <link rel="alternate" type="text/html" href="http://blog.widefix.com/optimize-performance-of-rails-app-with-chatgpt/"/>
  <id>http://blog.widefix.com/optimize-performance-of-rails-app-with-chatgpt</id>
  <updated>2023-06-07 18:30:34 +0200T00:00:00-00:00</updated>
  <published>2023-06-07T00:00:00-04:00</published>
  
  <author>
    <name>Andrei Kaleshka</name>
    <uri>http://blog.widefix.com</uri>
    <email>ka8725@gmail.com</email>
  </author>
  <category scheme="http://blog.widefix.com/tags/#chatgpt" term="chatgpt" /><category scheme="http://blog.widefix.com/tags/#sql" term="sql" /><category scheme="http://blog.widefix.com/tags/#postgresql" term="postgresql" /><category scheme="http://blog.widefix.com/tags/#performance" term="performance" /><category scheme="http://blog.widefix.com/tags/#active_record" term="active_record" />
  <content type="html">
  
    &lt;p&gt;The previous article &lt;a href=&quot;https:http://blog.widefix.com/importance-sql-for-rails-experts/&quot; ref=&quot;nofollow&quot; target=&quot;_blank&quot;&gt;Make your Ruby on Rails app 80x faster with SQL&lt;/a&gt; shows how SQL knowledge can help to optimize your Rails application performance. We discussed it within &lt;a href=&quot;https://www.brug.by/&quot; ref=&quot;nofollow&quot; target=&quot;_blank&quot;&gt;Belarus User Group&lt;/a&gt; community. Not everyone agreed with the point and didn’t find SQL knowledge a good asset for investment. That’s something expected. There is no revelation here. But that meeting had something that everyone was impressed with. We experimented and found out how ChatGPT is good with code optimization. We saw how it transforms Ruby code into performant SQL. The results were great. Check that out within this article.&lt;/p&gt;

&lt;h2 id=&quot;chatgpt-request-to-transform-ruby-code-into-sql&quot;&gt;ChatGPT request to transform Ruby code into SQL&lt;/h2&gt;

&lt;p&gt;As before, we are going to use this &lt;a href=&quot;https://github.com/widefix/demo-fast-sql&quot; ref=&quot;nofollow&quot; target=&quot;_blank&quot;&gt;experimental repository&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We took &lt;a href=&quot;https://github.com/widefix/demo-fast-sql/blob/4ea63b68d4404403e543b1d09978e5cdd5742f36/app/queries/services_stats_query.rb#L31-L64&quot; ref=&quot;nofollow&quot; target=&quot;_blank&quot;&gt;the following code snippet&lt;/a&gt;, the slowest and original version, and asked to improve its performance by rewriting it into SQL:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# the original code&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;projects_full&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;projects_empty&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;Service&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;user: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;status: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;approved&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;active: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;category_id: :asc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;ratings_average&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;ratings_count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;ratings_total&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;Rating&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;reviewee: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rating&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;project&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Project&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rating&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;project_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;project&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;category_id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;category_id&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;ratings_count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;ratings_total&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rating&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;rating&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;ratings_average&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ratings_total&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ratings_count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;round&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_s&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ratings_count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ratings_total&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;completed_projects_count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Project&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;vendor: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;status: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Complete&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;category_id: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;category_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;size&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;service_hash&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;category_id: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;category_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;category_name: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Category&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Category&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;category_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;parent_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;subcategory_name: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Category&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;category_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;completed_projects_count: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;completed_projects_count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;ratings_count: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ratings_count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;ratings_average: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ratings_average&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;resolved_hash&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;service_hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;transform_keys&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;k&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;k&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;camelize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:lower&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;completed_projects_count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;projects_full&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;resolved_hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;projects_empty&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;resolved_hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;projects_full&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sort_by!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;k&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;k&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;completedProjectsCount&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;projects_full&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;projects_empty&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;projects_full&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And we got the following response from ChatGPT:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;no&quot;&gt;SELECT&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;c1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;AS&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;category_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;c2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;AS&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;category_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;c1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;AS&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;subcategory_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;COUNT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;DISTINCT&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;FILTER&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;status&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'Complete'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;AS&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;completed_projects_count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;COUNT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;AS&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ratings_count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;COALESCE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;AVG&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;rating&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;AS&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ratings_average&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;FROM&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;services&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;JOIN&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;categories&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c1&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ON&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;category_id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;id&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;JOIN&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;categories&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c2&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ON&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;parent_id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;id&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;LEFT&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;JOIN&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;projects&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;p&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ON&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;user_id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;vendor_id&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;AND&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;category_id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;id&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;LEFT&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;JOIN&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ratings&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ON&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;user_id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;reviewee_id&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;AND&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;project_id&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;WHERE&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;user_id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:user_id&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;AND&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;status&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'approved'&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;AND&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;active&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;GROUP&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;BY&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;c1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;name&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;ORDER&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;BY&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;completed_projects_count&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;DESC&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ASC&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And these are the performance measurement results:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Warming up --------------------------------------
                ruby     1.000  i/100ms
                 sql    54.000  i/100ms
             sql_gpt    59.000  i/100ms
Calculating -------------------------------------
                ruby      6.846  (±14.6%) i/s -     34.000  in   5.000338s
                 sql    540.320  (± 8.5%) i/s -      2.700k in   5.038711s
             sql_gpt    609.335  (± 6.9%) i/s -      3.068k in   5.059364s

Comparison:
             sql_gpt:      609.3 i/s
                 sql:      540.3 i/s - same-ish: difference falls within error
                ruby:        6.8 i/s - 89.01x  slower
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We also checked that the result of the query service is the same for both calls. That means there are no bugs in the produced SQL by the AI.&lt;/p&gt;

&lt;p&gt;The ChatGPT result is the fastest one. At the same time, no business logic was defective. That’s a very good result!&lt;/p&gt;

&lt;h2 id=&quot;should-one-use-chatgpt-to-optimize-code-performance-or-not&quot;&gt;Should one use ChatGPT to optimize code performance or not&lt;/h2&gt;

&lt;p&gt;While the AI results are good, it’s still doubtful who and how to use the tool. Not seasoned developers might don’t understand where the bottleneck is. They would struggle with finding a correct question for ChatGPT. They would not understand how to fix the generated code if it has bugs. Some time later I tried to repeat the experiment and know the machine produced a very different SQL that has bugs inside:&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;-- Get average ratings and count for each service&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;WITH&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rating_summary&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;AS&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;category_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;COUNT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;AS&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ratings_count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;COALESCE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;AVG&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rating&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;AS&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ratings_average&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ratings&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;INNER&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;JOIN&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;projects&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;ON&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;project_id&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;reviewee&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user_id&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;GROUP&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;BY&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;category_id&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;-- Get completed projects count for each service&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;completed_projects&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;AS&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;category_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;COUNT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;AS&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;completed_projects_count&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;projects&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;vendor&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user_id&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;AND&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;status&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'Complete'&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;GROUP&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;BY&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;category_id&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;-- Combine the results&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;category_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;AS&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;category_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;AS&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;subcategory_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;COALESCE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;completed_projects_count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;AS&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;completed_projects_count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;COALESCE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ratings_count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;AS&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ratings_count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;ROUND&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;COALESCE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ratings_average&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;AS&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ratings_average&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;services&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;JOIN&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;categories&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;c&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;ON&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;category_id&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;JOIN&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;categories&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;ON&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;parent_id&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;LEFT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;JOIN&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rating_summary&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rs&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;ON&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;category_id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;category_id&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;LEFT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;JOIN&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;completed_projects&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cp&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;ON&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;category_id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;category_id&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user_id&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;AND&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;status&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'approved'&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;AND&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;active&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;true&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;ORDER&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;BY&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;completed_projects_count&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;DESC&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;While the AI results are good, it’s still doubtful who and how to use the tool. Not seasoned developers might don’t understand where the bottleneck is. They would struggle with finding a correct question for ChatGPT. They would not understand how to fix the generated code if it has bugs. Later, I tried to repeat the experiment. This time the machine produced a very different SQL:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Warming up --------------------------------------
                ruby     1.000  i/100ms
                 sql    52.000  i/100ms
             sql_gpt    40.000  i/100ms
         sql_gpt_new    38.000  i/100ms
Calculating -------------------------------------
                ruby      5.754  (±17.4%) i/s -     28.000  in   5.039290s
                 sql    433.067  (±20.3%) i/s -      2.080k in   5.029710s
             sql_gpt    561.018  (±14.4%) i/s -      2.760k in   5.044415s
         sql_gpt_new    514.242  (±19.4%) i/s -      2.432k in   5.013023s

Comparison:
             sql_gpt:      561.0 i/s
         sql_gpt_new:      514.2 i/s - same-ish: difference falls within error
                 sql:      433.1 i/s - same-ish: difference falls within error
                ruby:        5.8 i/s - 97.50x  slower
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;However, it’s still faster than the original rewrite to SQL.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;ChatGPT is very good at code refactoring and transforming Ruby code into SQL. Even though the results are impressive it still needs an expert communicating with the tool to form a correct question, check the produced results, and fix minor issues. The results can be a good start in code refactoring and optimization.&lt;/p&gt;

&lt;p&gt;That was a great meeting. We had a memorable time together. Thanks to everyone who participated. I am looking forward to our weekly calls and I invite everyone to join the community.&lt;/p&gt;

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

  
  &lt;p&gt;&lt;a href=&quot;http://blog.widefix.com/optimize-performance-of-rails-app-with-chatgpt/&quot;&gt;Optimize Rails app performance with ChatGPT&lt;/a&gt; was originally published by Andrei Kaleshka at &lt;a href=&quot;http://blog.widefix.com&quot;&gt;Take your Rails development to the next level&lt;/a&gt; on June 07, 2023.&lt;/p&gt;</content>
</entry>


<entry>
  <title type="html"><![CDATA[Boost Your Business with Expert Ruby on Rails Development Consultancy]]></title>
  <link rel="alternate" type="text/html" href="http://blog.widefix.com/boost-your-business-with-expert-ruby-on-rails-development-consultancy/"/>
  <id>http://blog.widefix.com/boost-your-business-with-expert-ruby-on-rails-development-consultancy</id>
  <updated>2023-05-31 13:43:33 +0200T00:00:00-00:00</updated>
  <published>2023-05-31T00:00:00-04:00</published>
  
  <author>
    <name>Andrei Kaleshka</name>
    <uri>http://blog.widefix.com</uri>
    <email>ka8725@gmail.com</email>
  </author>
  
  <content type="html">
  
    &lt;p&gt;Nowadays, a robust and efficient web application is crucial for businesses. Ruby on Rails (RoR), is a popular and powerful web development framework. It can help transform your ideas into scalable and fast applications. Yet, RoR development can be challenging without the right expertise. A specialized Ruby on Rails development consultancy can help you with that. It provides you with guidance and support. This article explores the benefits and aspects of outsourcing Ruby on Rails development.&lt;/p&gt;

&lt;h2 id=&quot;unleash-the-power-of-ruby-on-rails&quot;&gt;Unleash the Power of Ruby on Rails&lt;/h2&gt;

&lt;p&gt;Ruby on Rails is made for productivity and streamlining the web development process. A skilled consultancy leverages the full potential of RoR. It utilizes its robust features, conventions, and best practices. It ensures the development is scalable and maintainable. It adds feature-rich web applications that cater to your business requirements.&lt;/p&gt;

&lt;h2 id=&quot;custom-tailored-solutions&quot;&gt;Custom-Tailored Solutions&lt;/h2&gt;

&lt;p&gt;A Ruby on Rails development consultancy understands that business is unique. It does the following things:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Works closely with you to analyze your requirements.&lt;/li&gt;
  &lt;li&gt;Creates a comprehensive development roadmap.&lt;/li&gt;
  &lt;li&gt;Designs a customized solution that aligns with your business objectives.&lt;/li&gt;
  &lt;li&gt;Brainstorms ideas to craft a user-friendly interface.&lt;/li&gt;
  &lt;li&gt;Ensures your web application stands out.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;expertise-and-experience&quot;&gt;Expertise and Experience&lt;/h2&gt;

&lt;p&gt;A Ruby on Rails development consultancy supplies you with an experienced professional team. Their extensive knowledge and expertise allow them to solve complex challenges. They can optimize performance and deliver high-quality results. They stay up-to-date with the latest trends and tools. It ensures your application remains cutting-edge and future-proof.&lt;/p&gt;

&lt;h2 id=&quot;faster-time-to-market&quot;&gt;Faster Time to Market&lt;/h2&gt;

&lt;p&gt;In today’s evolving market, speed is essential. With a Ruby on Rails consultancy, you can speed up your development process and get your web application to market faster. Their agile development approach enables rapid prototyping and iterative development. Integration of new features becomes seamless. It allows you to capitalize on opportunities.&lt;/p&gt;

&lt;h2 id=&quot;ongoing-support-and-maintenance&quot;&gt;Ongoing Support and Maintenance&lt;/h2&gt;

&lt;p&gt;Building a web application is the beginning. A reputable Ruby on Rails development consultancy provides post-development support and maintenance services. They ensure your application remains secure, stable, and optimized for performance. From fixing bugs to implementing updates and enhancements. Their dedicated support team ensures to deliver a seamless user experience.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Partnering with a specialized Ruby on Rails development consultancy offers many advantages. From harnessing the power of RoR to receiving custom-tailored solutions and ongoing support. Their expertise and experience will elevate your development process. You speed up your time to market. You boost your business and stay ahead of the competition in the digital landscape.&lt;/p&gt;

  
  &lt;p&gt;&lt;a href=&quot;http://blog.widefix.com/boost-your-business-with-expert-ruby-on-rails-development-consultancy/&quot;&gt;Boost Your Business with Expert Ruby on Rails Development Consultancy&lt;/a&gt; was originally published by Andrei Kaleshka at &lt;a href=&quot;http://blog.widefix.com&quot;&gt;Take your Rails development to the next level&lt;/a&gt; on May 31, 2023.&lt;/p&gt;</content>
</entry>


<entry>
  <title type="html"><![CDATA[How to write SQL query in Ruby On Rails]]></title>
  <link rel="alternate" type="text/html" href="http://blog.widefix.com/how-to-write-sql-query-in-ruby-on-rails/"/>
  <id>http://blog.widefix.com/how-to-write-sql-query-in-ruby-on-rails</id>
  <updated>2023-05-25 11:49:18 +0200T00:00:00-00:00</updated>
  <published>2023-05-25T00:00:00-04:00</published>
  
  <author>
    <name>Andrei Kaleshka</name>
    <uri>http://blog.widefix.com</uri>
    <email>ka8725@gmail.com</email>
  </author>
  <category scheme="http://blog.widefix.com/tags/#sql" term="sql" /><category scheme="http://blog.widefix.com/tags/#postgresql" term="postgresql" /><category scheme="http://blog.widefix.com/tags/#rails" term="rails" /><category scheme="http://blog.widefix.com/tags/#rails-development" term="rails-development" /><category scheme="http://blog.widefix.com/tags/#performance" term="performance" />
  <content type="html">
  
    &lt;p&gt;ActiveRecord is a central component of a Rails application. It’s a wrapper for a database used behind the scene. And the database is another essential thing in almost any web application. Understanding how these two things go along is crucial to develop a fast, performant, and correct project. Missing knowledge here leads to overengineered solutions, performance issues, and bad user experience.&lt;/p&gt;

&lt;p&gt;In this article, you will see how to write SQL and use that knowledge in a Rails application.&lt;/p&gt;

&lt;h2 id=&quot;how-to-see-which-sql-generated-by-activerecord&quot;&gt;How to see which SQL generated by ActiveRecord&lt;/h2&gt;

&lt;blockquote&gt;
  &lt;p&gt;The post uses this &lt;a href=&quot;https://github.com/widefix/demo-fast-sql&quot; ref=&quot;nofollow&quot; target=&quot;_blank&quot;&gt;app&lt;/a&gt; to play with examples.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To see which SQL is generated by queries to models in a common Rails app, use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.to_sql&lt;/code&gt; method. We can check that in the Rails console (run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rails console&lt;/code&gt; and then write your experimental Ruby code there):&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Project&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;all&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_sql&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;SELECT &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;projects&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;.* FROM &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;projects&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Note, it works for any query, for example, when it uses conditions or joins:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Project&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;joins&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:ratings&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_sql&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;SELECT &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;projects&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;.* FROM &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;projects&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; INNER JOIN &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;ratings&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; ON &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;ratings&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;project_id&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; = &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;projects&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Project&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;joins&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:ratings&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;ratings: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;rating: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_sql&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;SELECT &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;projects&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;.* FROM &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;projects&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; INNER JOIN &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;ratings&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; ON &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;ratings&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;project_id&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; = &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;projects&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; WHERE &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;ratings&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;rating&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; = 5&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The query can be very complex. It can be constructed from different parts of the app using scopes, and separate classes implementing some patterns like Query Object, etc. In the end, the generated SQL query defines a related feature correctness. Hence, use this trick while debugging code to find a bug in the generated query.&lt;/p&gt;

&lt;h2 id=&quot;how-to-write-raw-sql-in-a-rails-application&quot;&gt;How to write raw SQL in a Rails application&lt;/h2&gt;

&lt;p&gt;ActiveRecord comes with a handy interface and abstraction to write raw SQL queries. Why should one do that? Well, there can be many reasons for that:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Improving query performance.&lt;/li&gt;
  &lt;li&gt;Lack of knowledge in ActiveRecord.&lt;/li&gt;
  &lt;li&gt;Lack of functionalities in ActiveRecord.&lt;/li&gt;
  &lt;li&gt;Avoiding Arel queries (this thing is used behind the scene of ActiveRecord to generate SQL queries out of the Ruby constructions).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Whatever the reason, it’s reduced to just one desire - &lt;strong&gt;demand of controlling SQL queries&lt;/strong&gt;. If you are one of this kind of person having this desire, use the following approach:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActiveRecord&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Base&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;connection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;select * from projects&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_a&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;s2&quot;&gt;&quot;description&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;it's a test project&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;s2&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;s2&quot;&gt;&quot;created_at&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2022&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;09&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;29&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;46.751835&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;UTC&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;s2&quot;&gt;&quot;updated_at&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2023&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mo&quot;&gt;01&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;17&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;23&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;22&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;42.275697&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;UTC&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;s2&quot;&gt;&quot;status&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Open&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;s2&quot;&gt;&quot;category&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Website and landing page design&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;s2&quot;&gt;&quot;experience&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;New to Widefix — under one year&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;s2&quot;&gt;&quot;existing_website&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Yes, I already have a website set up and live for customers&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;s2&quot;&gt;&quot;existing_website_platform&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Widefix&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;s2&quot;&gt;&quot;category_tasks&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;s2&quot;&gt;&quot;delivery_timeline&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;11-21 days&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Also, this API allows to write SQL queries inside migrations. It’s even easier in migrations as the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;execute&lt;/code&gt; method is available there.&lt;/p&gt;

&lt;p&gt;To construct correct SQL you would propaply need a db console. It’s the fastest way to write pure and correct SQL related to your Rails app DB. Use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rails db&lt;/code&gt; command to open DB console and do your experiments with SQL.&lt;/p&gt;

&lt;h2 id=&quot;how-to-measure-sql-query-performance-in-rails&quot;&gt;How to measure SQL query performance in Rails&lt;/h2&gt;

&lt;p&gt;To understand why SQL query is slow you can use explain analyze built-in functionality that every SQL DB has:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;no&quot;&gt;Project&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;joins&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:ratings&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;ratings: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;rating: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;explain&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;Project&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Load&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;2.1&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ms&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;no&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;projects&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;projects&quot;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;INNER&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;JOIN&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;ratings&quot;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ON&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;ratings&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;project_id&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;projects&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;id&quot;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;ratings&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;rating&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;vg&quot;&gt;$1&lt;/span&gt;  &lt;span class=&quot;p&quot;&gt;[[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;rating&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]]&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;EXPLAIN&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;for: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;projects&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;projects&quot;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;INNER&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;JOIN&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;ratings&quot;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ON&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;ratings&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;project_id&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;projects&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;id&quot;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;ratings&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;rating&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;vg&quot;&gt;$1&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;rating&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]]&lt;/span&gt;
                                      &lt;span class=&quot;no&quot;&gt;QUERY&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;PLAN&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;---------------------------------------------------------------------------------------&lt;/span&gt;
 &lt;span class=&quot;no&quot;&gt;Nested&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Loop&lt;/span&gt;  &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.14&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;9.63&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rows&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1694&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
   &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;  &lt;span class=&quot;no&quot;&gt;Seq&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Scan&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;on&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ratings&lt;/span&gt;  &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.00&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;1.29&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rows&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
         &lt;span class=&quot;no&quot;&gt;Filter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rating&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
   &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;  &lt;span class=&quot;no&quot;&gt;Index&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Scan&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;projects_pkey&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;on&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;projects&lt;/span&gt;  &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.14&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;8.16&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rows&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1694&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
         &lt;span class=&quot;no&quot;&gt;Index&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Cond&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ratings&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;project_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rows&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If the query was slow, we had to pay attention to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Seq Scan on ratings&lt;/code&gt;. That means the it didn’t use index to filter ratings by the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rating = 5&lt;/code&gt; condition. We can add a corresponding index to fix the issue.&lt;/p&gt;

&lt;p&gt;Another problem that is frequently related to performance penalties is N+1 queries. Why many solutions can help with that, it’s pretty easy to detect the issues by looking into logs. Many repeated lines with the following format indicate that issue:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  &lt;span class=&quot;no&quot;&gt;Category&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Load&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.9&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ms&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;no&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;categories&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;categories&quot;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;categories&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;id&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;vg&quot;&gt;$1&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;LIMIT&lt;/span&gt; &lt;span class=&quot;vg&quot;&gt;$2&lt;/span&gt;  &lt;span class=&quot;p&quot;&gt;[[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;LIMIT&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]]&lt;/span&gt;
  &lt;span class=&quot;err&quot;&gt;↳&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;queries&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;services_stats_query&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;rb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;48&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:in&lt;/span&gt; &lt;span class=&quot;sb&quot;&gt;`block in call_ruby'
  Category Load (0.9ms)  SELECT &quot;categories&quot;.* FROM &quot;categories&quot; WHERE &quot;categories&quot;.&quot;id&quot; = $1 LIMIT $2  [[&quot;id&quot;, 1], [&quot;LIMIT&quot;, 1]]
  ↳ app/queries/services_stats_query.rb:48:in `&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;block&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;call_ruby&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'
  Category Load (0.8ms)  SELECT &quot;categories&quot;.* FROM &quot;categories&quot; WHERE &quot;categories&quot;.&quot;id&quot; = $1 LIMIT $2  [[&quot;id&quot;, 3], [&quot;LIMIT&quot;, 1]]
  ↳ app/queries/services_stats_query.rb:49:in `block in call_ruby'&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;Rating&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Load&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.9&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ms&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;no&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;ratings&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;ratings&quot;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;ratings&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;reviewee_id&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;vg&quot;&gt;$1&lt;/span&gt;  &lt;span class=&quot;p&quot;&gt;[[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;reviewee_id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]]&lt;/span&gt;
  &lt;span class=&quot;err&quot;&gt;↳&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;queries&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;services_stats_query&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;rb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;37&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:in&lt;/span&gt; &lt;span class=&quot;sb&quot;&gt;`block in call_ruby'
  Project Load (1.0ms)  SELECT &quot;projects&quot;.* FROM &quot;projects&quot; WHERE &quot;projects&quot;.&quot;id&quot; = $1 LIMIT $2  [[&quot;id&quot;, 27], [&quot;LIMIT&quot;, 1]]
  ↳ app/queries/services_stats_query.rb:38:in `&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;block&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;levels&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;call_ruby&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'
  Project Load (0.9ms)  SELECT &quot;projects&quot;.* FROM &quot;projects&quot; WHERE &quot;projects&quot;.&quot;id&quot; = $1 LIMIT $2  [[&quot;id&quot;, 39], [&quot;LIMIT&quot;, 1]]
  ↳ app/queries/services_stats_query.rb:38:in `block (2 levels) in call_ruby'&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;Project&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Load&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.9&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ms&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;no&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;projects&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;projects&quot;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;projects&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;id&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;vg&quot;&gt;$1&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;LIMIT&lt;/span&gt; &lt;span class=&quot;vg&quot;&gt;$2&lt;/span&gt;  &lt;span class=&quot;p&quot;&gt;[[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;86&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;LIMIT&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Usually, eager loading techniques fix this issue. But that’s a topic for a separate article.&lt;/p&gt;

&lt;h2 id=&quot;play-with-sql-in-rails-apps-with-confidence&quot;&gt;Play with SQL in Rails apps with confidence&lt;/h2&gt;

&lt;p&gt;Now you know all the essential tricks to work with SQL in a Rails application.&lt;/p&gt;

&lt;p&gt;Moving further, you can read the following articles that expand the related knowledge:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https:http://blog.widefix.com/importance-sql-for-rails-experts/&quot; ref=&quot;nofollow&quot; target=&quot;_blank&quot;&gt;Make your Ruby on Rails app 80x faster with SQL&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https:http://blog.widefix.com/financial-plan-on-postgresql/&quot; ref=&quot;nofollow&quot; target=&quot;_blank&quot;&gt;Financial plan on PostgreSQL&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https:http://blog.widefix.com/financial-plan-on-rails/&quot; ref=&quot;nofollow&quot; target=&quot;_blank&quot;&gt;Financial plan on Rails&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https:http://blog.widefix.com/from-single-dd-to-multiple-checkboxes/&quot; ref=&quot;nofollow&quot; target=&quot;_blank&quot;&gt;From Single drop-down to Multiple check-boxes&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https:http://blog.widefix.com/date-ranges-overlap/&quot; ref=&quot;nofollow&quot; target=&quot;_blank&quot;&gt;Efficient algorithm to check dates overlap&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These articles show how to use SQL within Rails applications efficiently on practical examples. They also explain some useful but not very popular in the Rails community SQL tricks.&lt;/p&gt;

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

  
  &lt;p&gt;&lt;a href=&quot;http://blog.widefix.com/how-to-write-sql-query-in-ruby-on-rails/&quot;&gt;How to write SQL query in Ruby On Rails&lt;/a&gt; was originally published by Andrei Kaleshka at &lt;a href=&quot;http://blog.widefix.com&quot;&gt;Take your Rails development to the next level&lt;/a&gt; on May 25, 2023.&lt;/p&gt;</content>
</entry>


<entry>
  <title type="html"><![CDATA[Improve NextJS application performance]]></title>
  <link rel="alternate" type="text/html" href="http://blog.widefix.com/improve-nextjs-application-performance/"/>
  <id>http://blog.widefix.com/improve-nextjs-application-performance</id>
  <updated>2023-05-24 18:23:56 +0200T00:00:00-00:00</updated>
  <published>2023-05-24T00:00:00-04:00</published>
  
  <author>
    <name>Andrei Kaleshka</name>
    <uri>http://blog.widefix.com</uri>
    <email>ka8725@gmail.com</email>
  </author>
  <category scheme="http://blog.widefix.com/tags/#performance" term="performance" />
  <content type="html">
  
    &lt;p&gt;To improve the performance of your app built on React and Next.js using TypeScript, you can follow several strategies.&lt;/p&gt;

&lt;h2 id=&quot;code-optimization&quot;&gt;Code Optimization&lt;/h2&gt;

&lt;p&gt;Minimize unnecessary re-renders by using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;React.memo&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;useMemo&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;useCallback&lt;/code&gt; hooks to memoize components and functions that don’t depend on changing data.
Avoid unnecessary state updates and re-renders by optimizing the usage of useState and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;useEffect&lt;/code&gt; hooks.&lt;/p&gt;

&lt;p&gt;Using TypeScript’s type checking can catch potential errors and improve code quality.&lt;/p&gt;

&lt;h2 id=&quot;bundle-size-optimization&quot;&gt;Bundle Size Optimization&lt;/h2&gt;

&lt;p&gt;Split your code into smaller chunks using dynamic imports and code splitting. This allows you to load only the necessary code for each page or component, reducing the initial bundle size and improving load times.
Analyze your bundle using tools like Webpack Bundle Analyzer to identify and eliminate any unnecessary dependencies or large libraries.
Compress and optimize your assets (images, CSS, etc.) to reduce their size and improve load times. Next.js has built-in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Image&lt;/code&gt; component that allows to compress images on the fly. It also converts then to modern image formats that are lightweight.&lt;/p&gt;

&lt;h2 id=&quot;server-side-rendering-ssr-and-static-site-generation-ssg&quot;&gt;Server-Side Rendering (SSR) and Static Site Generation (SSG)&lt;/h2&gt;

&lt;p&gt;Utilize Next.js’s built-in features for server-side rendering and static site generation to pre-render pages and improve initial load times. This reduces the amount of work required by the client’s browser.
Identify pages that don’t require real-time data and generate them statically using Next.js’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;getStaticProps&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;getStaticPaths&lt;/code&gt; functions. This eliminates the need for client-side rendering and improves performance.&lt;/p&gt;

&lt;h2 id=&quot;performance-monitoring-and-optimization&quot;&gt;Performance Monitoring and Optimization&lt;/h2&gt;

&lt;p&gt;Use performance monitoring tools like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Lighthouse&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WebPageTest&lt;/code&gt;, or Chrome DevTools to identify performance bottlenecks, such as slow-loading components, large files, or long JavaScript execution times.
Optimize critical rendering paths by prioritizing the loading of essential content and deferring non-critical scripts or styles.
Implement lazy loading for images and other non-essential assets to load them only when they become visible in the viewport.
Implement caching mechanisms for API requests or frequently accessed data to reduce server load and improve response times.&lt;/p&gt;

&lt;h2 id=&quot;ssr-caching-and-incremental-static-regeneration&quot;&gt;SSR Caching and Incremental Static Regeneration&lt;/h2&gt;

&lt;p&gt;Utilize Next.js’s caching capabilities to cache rendered pages on the server-side and serve them directly for subsequent requests, reducing the need for re-rendering.
Implement incremental static regeneration for pages that require dynamic data. This allows you to re-generate and update specific pages at predefined intervals, ensuring the content stays fresh while reducing the load on the server.&lt;/p&gt;

&lt;h2 id=&quot;performance-testing-and-optimization-iteration&quot;&gt;Performance Testing and Optimization Iteration&lt;/h2&gt;

&lt;p&gt;Regularly test your app’s performance using tools like Lighthouse or Chrome DevTools to measure metrics like First Contentful Paint (FCP), Time to Interactive (TTI), and Total Blocking Time (TBT). Set performance budgets and strive to stay within them.
Continuously analyze and optimize critical paths, reducing the time required for JavaScript execution, network requests, and rendering.
Remember to always profile and benchmark your optimizations to ensure they have a positive impact on your app’s performance.&lt;/p&gt;

  
  &lt;p&gt;&lt;a href=&quot;http://blog.widefix.com/improve-nextjs-application-performance/&quot;&gt;Improve NextJS application performance&lt;/a&gt; was originally published by Andrei Kaleshka at &lt;a href=&quot;http://blog.widefix.com&quot;&gt;Take your Rails development to the next level&lt;/a&gt; on May 24, 2023.&lt;/p&gt;</content>
</entry>


<entry>
  <title type="html"><![CDATA[Choosing Top-Tier Ruby on Rails Consulting]]></title>
  <link rel="alternate" type="text/html" href="http://blog.widefix.com/choosing-top-tier-ruby-on-rails-consulting/"/>
  <id>http://blog.widefix.com/choosing-top-tier-ruby-on-rails-consulting</id>
  <updated>2023-05-23 13:47:19 +0200T00:00:00-00:00</updated>
  <published>2023-05-23T00:00:00-04:00</published>
  
  <author>
    <name>Andrei Kaleshka</name>
    <uri>http://blog.widefix.com</uri>
    <email>ka8725@gmail.com</email>
  </author>
  <category scheme="http://blog.widefix.com/tags/#rails-development" term="rails-development" />
  <content type="html">
  
    &lt;p&gt;Ruby on Rails has long been recognized as a powerful web development framework, enabling developers to build robust and scalable applications with ease. However, navigating the vast landscape of Ruby on Rails development can be a daunting task, especially when faced with complex projects and tight deadlines. This is where top-tier Ruby on Rails consulting services come into play, offering invaluable expertise, guidance, and support. In this article, we explore the benefits of engaging with renowned Ruby on Rails consulting firms, shedding light on why partnering with the best can make all the difference.&lt;/p&gt;

&lt;h2 id=&quot;leveraging-deep-domain-expertise&quot;&gt;Leveraging Deep Domain Expertise&lt;/h2&gt;

&lt;p&gt;Top-tier Ruby on Rails consulting firms boast teams of skilled professionals. They have honed their expertise through years of hands-on experience. These experts have an in-depth understanding of Ruby on Rails and its ecosystem. That allows them to navigate complex challenges. By leveraging their domain expertise, these consultants can provide invaluable insights. They can recommend best practices and suggest innovative solutions to your project specifics. Their guidance ensures that your application is efficient, scalable, and maintainable.&lt;/p&gt;

&lt;h2 id=&quot;accelerating-development-time&quot;&gt;Accelerating Development Time&lt;/h2&gt;

&lt;p&gt;Time is of the essence in today’s fast-paced business environment. Partner with a Ruby on Rails consulting firm to hurry your project’s timeline. But make sure you choose a reputable consulting firm. These firms are adept at optimizing development workflows. They leverage reusable code libraries to ensure rapid development cycles. With their expertise, you can deliver high-quality applications within tight deadlines. With that, you gain a competitive edge in the market.&lt;/p&gt;

&lt;h2 id=&quot;ensuring-scalability-and-performance&quot;&gt;Ensuring Scalability and Performance&lt;/h2&gt;

&lt;p&gt;Building a scalable and high-performing application is crucial for long-term success. Ruby on Rails consulting firms understand the intricacies of scaling applications. That handles growing user bases and increasing traffic. They use proven techniques and architectural patterns. That ensures your application can handle heavy loads. They incorporate strategies such as caching, load balancing, and database optimization. They ensure your application can scale as your business grows. They provide a smooth user experience and avoid potential bottlenecks.&lt;/p&gt;

&lt;h2 id=&quot;security-and-risk-mitigation&quot;&gt;Security and Risk Mitigation&lt;/h2&gt;

&lt;p&gt;Data security and risk mitigation are crucial concerns for any business operating. Renowned Ruby on Rails consulting firms focus on robust security practices. They have extensive experience in implementing industry-standard security measures. They can conduct comprehensive security audits. They can identify vulnerabilities and put in place stringent security protocols. All that is to safeguard your application and sensitive data. By mitigating risks, these experts help you build trust with your users. That protects your brand reputation.&lt;/p&gt;

&lt;h2 id=&quot;continued-support-and-maintenance&quot;&gt;Continued Support and Maintenance&lt;/h2&gt;

&lt;p&gt;Launching your Ruby on Rails application is the beginning. Ongoing support and maintenance are crucial. A good consulting firm ensures smooth operation and addresses any emerging issues. Top-tier consulting firms provide reliable post-launch support. They offer timely bug fixes, performance optimizations, and feature enhancements. At the same time, you can rest. You know that your application will remain up-to-date, secure, and functional. That allows you to focus on your core business activities.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Nowadays, choosing the right Ruby on Rails consulting partner is vital for success. By engaging with top-tier consulting firms, you can leverage their deep domain expertise. You speed up development time. Your project gets scalable and performant. You have risks mitigated and receive continued support. The benefits —higher-quality applications, reduced time-to-market, enhanced security, and peace of mind. Be sure to partner with a reputable consulting firm that can unlock the full potential of your application.&lt;/p&gt;

  
  &lt;p&gt;&lt;a href=&quot;http://blog.widefix.com/choosing-top-tier-ruby-on-rails-consulting/&quot;&gt;Choosing Top-Tier Ruby on Rails Consulting&lt;/a&gt; was originally published by Andrei Kaleshka at &lt;a href=&quot;http://blog.widefix.com&quot;&gt;Take your Rails development to the next level&lt;/a&gt; on May 23, 2023.&lt;/p&gt;</content>
</entry>


<entry>
  <title type="html"><![CDATA[Make your Ruby on Rails app 80x faster with SQL]]></title>
  <link rel="alternate" type="text/html" href="http://blog.widefix.com/importance-sql-for-rails-experts/"/>
  <id>http://blog.widefix.com/importance-sql-for-rails-experts</id>
  <updated>2023-03-30 11:59:17 +0200T00:00:00-00:00</updated>
  <published>2023-03-30T00:00:00-04:00</published>
  
  <author>
    <name>Andrei Kaleshka</name>
    <uri>http://blog.widefix.com</uri>
    <email>ka8725@gmail.com</email>
  </author>
  <category scheme="http://blog.widefix.com/tags/#sql" term="sql" /><category scheme="http://blog.widefix.com/tags/#active_record" term="active_record" /><category scheme="http://blog.widefix.com/tags/#rails" term="rails" />
  <content type="html">
  
    &lt;p&gt;SQL can improve the performance and efficiency of your Ruby on Rails application. No need for heavy technologies. No need for switching to another programming language or framework.&lt;/p&gt;

&lt;p&gt;SQL is a powerful tool that can take your Ruby on Rails expertise to the next level. Nowadays many technologies claim to replace SQL or deem Ruby on Rails as “too slow”. But it remains a popular and efficient choice for many web projects. This article will explore the importance of SQL for Ruby on Rails experts. It will show why it’s a must-have skill in today’s development landscape.&lt;/p&gt;

&lt;h2 id=&quot;why-ruby-on-rails-can-be-slow&quot;&gt;Why Ruby on Rails can be slow&lt;/h2&gt;

&lt;p&gt;Imagine the following class that’s used in a Rails controller to feed data to a page in your application. This class may look familiar to you. I’ve encountered similar classes in real-world applications:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ServicesStatsQuery&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;attr_accessor&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:user&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;initialize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;projects_full&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;projects_empty&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;Service&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;user: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;status: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;approved&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;active: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;category_id: :asc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;ratings_average&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;ratings_count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;ratings_total&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
      &lt;span class=&quot;no&quot;&gt;Rating&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;reviewee: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rating&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;project&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Project&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rating&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;project_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;project&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;category_id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;category_id&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;ratings_count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;ratings_total&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rating&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;rating&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;ratings_average&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ratings_total&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ratings_count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;round&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_s&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ratings_count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ratings_total&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;completed_projects_count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;
        &lt;span class=&quot;no&quot;&gt;Project&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;vendor: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;status: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Complete&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;category_id: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;category_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;size&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;service_hash&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;ss&quot;&gt;category_id: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;category_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;ss&quot;&gt;category_name: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Category&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Category&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;category_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;parent_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;ss&quot;&gt;subcategory_name: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Category&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;category_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;ss&quot;&gt;completed_projects_count: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;completed_projects_count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;ss&quot;&gt;ratings_count: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ratings_count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;ss&quot;&gt;ratings_average: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ratings_average&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;resolved_hash&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;service_hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;transform_keys&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;k&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;k&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;camelize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:lower&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;completed_projects_count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;projects_full&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;resolved_hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;projects_empty&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;resolved_hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;projects_full&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sort_by!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;k&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;k&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;completedProjectsCount&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;projects_full&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;projects_empty&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;projects_full&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Your application is running in production and serving the needs of hundreds of users. You’ve noticed that the related page is becoming slower and slower with each passing day. This can be a frustrating problem to solve. It’s natural to feel tempted to consider drastic measures. You can think of switching to a faster programming language. Or you might plan to use heavy technology.&lt;/p&gt;

&lt;p&gt;Before taking such drastic steps take a step back and understand the root cause of the issue.&lt;/p&gt;

&lt;p&gt;There are a few things that could be contributing to the slowness of this code:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;N+1 queries: The code is making a separate database query for each rating. Then it gets the associated project. That can become slow if there are a large number of ratings.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Nested &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;each&lt;/code&gt; loops: The code is using this method to iterate over the Service. For each service in the nested loop, it iterates through all related Rating records. That can also be slow if there are many records. Instead, it would be better to use SQL joins to fetch all the data in a single query.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Redundant queries: The code is calling &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Category.find&lt;/code&gt; many times. Instead, better to fetch all the category data using a single query. And then use it to populate the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;service_hash&lt;/code&gt; object.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Optimizing this code will need a combination of SQL query optimization. That should cut unnecessary database queries and improve the performance of the code.&lt;/p&gt;

&lt;h2 id=&quot;how-to-analyze-slow-ruby-on-rails-code&quot;&gt;How to analyze slow Ruby On Rails code&lt;/h2&gt;

&lt;p&gt;There are plenty of tools out there, like &lt;a href=&quot;https://github.com/flyerhzm/bullet&quot; ref=&quot;nofollow&quot; target=&quot;_blank&quot;&gt;bullet&lt;/a&gt;, &lt;a href=&quot;https://github.com/evanphx/benchmark-ips&quot; ref=&quot;nofollow&quot; target=&quot;_blank&quot;&gt;benchmark-ips&lt;/a&gt;, or any others.&lt;/p&gt;

&lt;p&gt;But before taking these radical steps use what you already have.  Open logs  (use command &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tail -f log/development.log&lt;/code&gt; for local).  Observe what happens there while you open the slow page in your browser. If you spot many repeating lines like this one you have an N+1 problem:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;mo&quot;&gt;00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mo&quot;&gt;03&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;22&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;web&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Rating&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Load&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;1.6&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ms&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;no&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;ratings&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;ratings&quot;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;ratings&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;user_id&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;vg&quot;&gt;$1&lt;/span&gt;  &lt;span class=&quot;p&quot;&gt;[[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;15&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Pay attention to the phrase “Rating Load” as it serves as an indicator. If you notice “CACHE Rating Load”, there’s no need to worry. The object is already in memory, retrieved from there without accessing the database. This suggests that eager loading is already working.&lt;/p&gt;

&lt;p&gt;Another tool is to visually inspect the code. You can feel discomfort and uncertainty when trying to understand how the code pieces relate to each other. Yet, this is not related to performance. The most crucial aspect is that the code contains a nested loop, which indicates that its complexity is squared. As more objects are iterated, a squared function grows rapidly:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/plot.png&quot; alt=&quot;Growth rate for different functions&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Sorry for getting into the math, but it’s important to understand that your code should ideally grow linearly or at least logarithmically. This is the best way to ensure that your code’s performance remains optimal for many years to come.&lt;/p&gt;

&lt;p&gt;If you are unsure about what all of that means, now is a good time to start learning about the theory of algorithms and data structures.&lt;/p&gt;

&lt;h2 id=&quot;use-sql-to-improve-rails-application-performance&quot;&gt;Use SQL to improve Rails application performance&lt;/h2&gt;

&lt;p&gt;To fetch all the required data in a single query while performing the necessary aggregation, sorting, and grouping, consider using this SQL query:&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;select&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;category_id&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;&quot;categoryId&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;pc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;&quot;categoryName&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;&quot;subcategoryName&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;&quot;completedProjectsCount&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;&quot;ratingsCount&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;avg&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;&quot;ratingsAverage&quot;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;services&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;join&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;categories&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;c&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;on&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;category_id&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;join&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;categories&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pc&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;on&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;parent_id&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;left&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;join&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;lateral&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;select&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;cast&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;round&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;coalesce&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;avg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rating&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;avg&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ratings&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;join&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;projects&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rp&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;on&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;project_id&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;category_id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;category_id&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;where&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;reviewee_id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user_id&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;on&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;true&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;left&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;join&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;projects&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;on&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vendor_id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user_id&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;status&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'Complete'&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;category_id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;category_id&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;where&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;status&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'approved'&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;active&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user_id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user_id&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;group&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;by&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;category_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;avg&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;order&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;by&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;desc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;category_id&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;asc&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;One way to test this query is to replace the placeholder &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:user_id&lt;/code&gt; with an actual user id. Then, we can open the DB console and paste the query to see if it produces the expected results.&lt;/p&gt;

&lt;p&gt;Make sure the query is successful and returns the desired results. Then we can replace the slow Ruby code with this efficient SQL query:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ServicesStatsQuery&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;SQL&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;~&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;SQL&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;
    select
      s.category_id as &quot;categoryId&quot;,
      pc.name as &quot;categoryName&quot;,
      c.name as &quot;subcategoryName&quot;,
      count(p.id) as &quot;completedProjectsCount&quot;,
      r.count as &quot;ratingsCount&quot;,
      r.avg as &quot;ratingsAverage&quot;
      from services s
      join categories c on c.id = s.category_id
      join categories pc on pc.id = c.parent_id
      left join lateral (
        select count(r.id), cast(round(coalesce(avg(r.rating), 0), 1) as text) as avg from ratings r
        join projects rp on rp.id = r.project_id and s.category_id = rp.category_id
        where r.reviewee_id = :user_id
      ) r on true
      left join projects p on p.vendor_id = :user_id and p.status = 'Complete' and p.category_id = s.category_id
      where s.status = 'approved' and s.active and s.user_id = :user_id
      group by s.category_id, pc.name, c.name, r.count, r.avg
      order by count(p.id) desc, s.category_id asc
&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;  SQL&lt;/span&gt;

  &lt;span class=&quot;nb&quot;&gt;attr_accessor&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:user&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;initialize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;sql&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActiveRecord&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Base&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sanitize_sql_array&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;SQL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;user_id: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;ActiveRecord&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Base&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;connection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sql&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_a&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This SQL query can provide fast results for most Ruby On Rails applications. Although, it assumes that the DB has appropriate indexes defined.&lt;/p&gt;

&lt;h2 id=&quot;native-sql-is-much-faster-than-the-pure-ruby-on-rails-code&quot;&gt;Native SQL is much faster than the pure Ruby on Rails code&lt;/h2&gt;

&lt;p&gt;It’s time to perform measurements. First, install the &lt;a href=&quot;https://github.com/evanphx/benchmark-ips&quot; ref=&quot;nofollow&quot; target=&quot;_blank&quot;&gt;benchmark-ips&lt;/a&gt; gem. Then you can prepare the following code and save it in a file. For example, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;t.rb&lt;/code&gt;, in the root directory of your Rails application:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'benchmark/ips'&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;service&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ServicesStatsQuery&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;no&quot;&gt;Benchmark&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ips&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;report&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;ruby&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;call_ruby&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;report&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;sql&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;call_sql&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;compare!&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We change the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ServicesStatsQuery&lt;/code&gt; a bit to have the two methods defined &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;call_ruby&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;call_sql&lt;/code&gt; instead of one method &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;call&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ServicesStatsQuery&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;SQL&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;~&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;SQL&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;
    select
      s.category_id as &quot;categoryId&quot;,
      pc.name as &quot;categoryName&quot;,
      c.name as &quot;subcategoryName&quot;,
      count(p.id) as &quot;completedProjectsCount&quot;,
      r.count as &quot;ratingsCount&quot;,
      r.avg as &quot;ratingsAverage&quot;
      from services s
      join categories c on c.id = s.category_id
      join categories pc on pc.id = c.parent_id
      left join lateral (
        select count(r.id), cast(round(coalesce(avg(r.rating), 0), 1) as text) as avg from ratings r
        join projects rp on rp.id = r.project_id and s.category_id = rp.category_id
        where r.reviewee_id = :user_id
      ) r on true
      left join projects p on p.vendor_id = :user_id and p.status = 'Complete' and p.category_id = s.category_id
      where s.status = 'approved' and s.active and s.user_id = :user_id
      group by s.category_id, pc.name, c.name, r.count, r.avg
      order by count(p.id) desc, s.category_id asc
&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;  SQL&lt;/span&gt;

  &lt;span class=&quot;nb&quot;&gt;attr_accessor&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:user&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;initialize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;call_ruby&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;projects_full&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;projects_empty&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;Service&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;user: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;status: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;approved&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;active: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;category_id: :asc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;ratings_average&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;ratings_count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;ratings_total&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
      &lt;span class=&quot;no&quot;&gt;Rating&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;reviewee: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rating&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;project&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Project&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rating&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;project_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;project&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;category_id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;category_id&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;ratings_count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;ratings_total&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rating&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;rating&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;ratings_average&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ratings_total&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ratings_count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;round&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_s&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ratings_count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ratings_total&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;completed_projects_count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Project&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;vendor: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;status: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Complete&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;category_id: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;category_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;size&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;service_hash&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;ss&quot;&gt;category_id: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;category_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;ss&quot;&gt;category_name: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Category&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Category&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;category_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;parent_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;ss&quot;&gt;subcategory_name: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Category&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;category_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;ss&quot;&gt;completed_projects_count: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;completed_projects_count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;ss&quot;&gt;ratings_count: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ratings_count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;ss&quot;&gt;ratings_average: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ratings_average&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;resolved_hash&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;service_hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;transform_keys&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;k&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;k&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;camelize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:lower&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;completed_projects_count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;projects_full&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;resolved_hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;projects_empty&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;resolved_hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;projects_full&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sort_by!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;k&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;k&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;completedProjectsCount&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;projects_full&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;projects_empty&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;projects_full&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;call_sql&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;sql&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActiveRecord&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Base&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sanitize_sql_array&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;SQL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;user_id: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;ActiveRecord&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Base&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;connection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sql&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_a&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Run it with the command: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rails runner t.rb&lt;/code&gt; and see the results:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;no&quot;&gt;Warming&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;up&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;--------------------------------------&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;ruby&lt;/span&gt;     &lt;span class=&quot;mf&quot;&gt;1.000&lt;/span&gt;  &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ms&lt;/span&gt;
                 &lt;span class=&quot;n&quot;&gt;sql&lt;/span&gt;    &lt;span class=&quot;mf&quot;&gt;48.000&lt;/span&gt;  &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ms&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;Calculating&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-------------------------------------&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;ruby&lt;/span&gt;      &lt;span class=&quot;mf&quot;&gt;5.658&lt;/span&gt;  &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;±&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;s&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;     &lt;span class=&quot;mf&quot;&gt;29.000&lt;/span&gt;  &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt;   &lt;span class=&quot;mf&quot;&gt;5.147460&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;
                 &lt;span class=&quot;n&quot;&gt;sql&lt;/span&gt;    &lt;span class=&quot;mf&quot;&gt;451.144&lt;/span&gt;  &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;±&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;8.6&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;s&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;      &lt;span class=&quot;mf&quot;&gt;2.256&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;k&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt;   &lt;span class=&quot;mf&quot;&gt;5.042193&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;

&lt;span class=&quot;no&quot;&gt;Comparison&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                 &lt;span class=&quot;ss&quot;&gt;sql:      &lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;451.1&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;
                &lt;span class=&quot;ss&quot;&gt;ruby:        &lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;5.7&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;s&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;79.74&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;  &lt;span class=&quot;n&quot;&gt;slower&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Performance boost of almost 80 times! The slow-loading page that took over 30 seconds now loads within 300ms. This significant improvement is not the programming language or framework merit.&lt;/p&gt;

&lt;h2 id=&quot;demo-faster-sql-in-ruby-on-rails-app&quot;&gt;Demo: Faster SQL in Ruby on Rails App&lt;/h2&gt;

&lt;p&gt;I have developed a &lt;a href=&quot;https://github.com/widefix/demo-fast-sql&quot; ref=&quot;nofollow&quot; target=&quot;_blank&quot;&gt;demo application using both Ruby and SQL versions&lt;/a&gt; and made it available on GitHub as an open source project. This means that you have the opportunity to experiment with the code and run benchmarks on your own.&lt;/p&gt;

&lt;h2 id=&quot;determine-whether-to-use-the-measurement-tools-or-not&quot;&gt;Determine whether to use the measurement tools or not&lt;/h2&gt;

&lt;p&gt;You can analyze performance issues and measure optimization results without fancy tools. &lt;a href=&quot;https://github.com/evanphx/benchmark-ips&quot; ref=&quot;nofollow&quot; target=&quot;_blank&quot;&gt;benchmark-ips&lt;/a&gt; is for demonstration purposes. The best tools are reading the code, and understanding it. Then analyze its complexity. Finally, use SQL knowledge. These tools are enough. Nothing can replace them. Well, except in the distant future, ChatGPT may offer more help.&lt;/p&gt;

&lt;p&gt;There are always plenty of built-in tools by your hand. Learn how to use them. For example, moving further, the written SQL can be slow. You will have to check why. Use &lt;a href=&quot;https://www.postgresql.org/docs/current/sql-explain.html&quot; ref=&quot;nofollow&quot; target=&quot;_blank&quot;&gt;explain analyze&lt;/a&gt; for that. This is a built-in tool in all modern SQL databases.&lt;/p&gt;

&lt;h2 id=&quot;why-ruby-on-rails-expert-should-know-sql&quot;&gt;Why Ruby on Rails expert should know SQL&lt;/h2&gt;

&lt;p&gt;Ruby on Rails is a powerful web application framework. It provides developers with high-level abstractions and conventions. That allows to build web applications quickly and efficiently. But, despite its powerful abstractions, Ruby on Rails experts need to know SQL.&lt;/p&gt;

&lt;p&gt;SQL allows Ruby on Rails experts to write efficient and optimized database queries. That can improve the performance of a web application. By understanding SQL, a Ruby on Rails expert can design the database schema and data access layer.  If that’s done well, the application maximizes performance, scalability, and maintainability.&lt;/p&gt;

&lt;p&gt;SQL is a widely-used and powerful language. People use it beyond interacting with the database. Ruby on Rails experts use SQL to perform complex calculations and data transformations. They can generate reports that are challenging to achieve using Ruby code alone.&lt;/p&gt;

&lt;p&gt;SQL allows Ruby on Rails experts to have more control over their web applications. They write more efficient and optimized code. They make better data-driven decisions.&lt;/p&gt;

&lt;h2 id=&quot;how-can-a-ruby-on-rails-expert-learn-sql&quot;&gt;How can a Ruby on Rails expert learn SQL&lt;/h2&gt;

&lt;p&gt;Nowadays, there are plenty of courses and books available on the subject. So it’s hard to recommend specific resources. You can choose whatever you like and suits your learning style.&lt;/p&gt;

&lt;p&gt;But, if you are like me and prefer hands-on learning, you might find the following resource helpful -  &lt;a href=&quot;https://pgexercises.com/&quot; ref=&quot;nofollow&quot; target=&quot;_blank&quot;&gt;https://pgexercises.com/&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You might also find this article I wrote previously interesting - &lt;a href=&quot;https:http://blog.widefix.com/financial-plan-on-postgresql/&quot;&gt;Financial plan on PostgreSQL&lt;/a&gt;.&lt;/p&gt;

  
  &lt;p&gt;&lt;a href=&quot;http://blog.widefix.com/importance-sql-for-rails-experts/&quot;&gt;Make your Ruby on Rails app 80x faster with SQL&lt;/a&gt; was originally published by Andrei Kaleshka at &lt;a href=&quot;http://blog.widefix.com&quot;&gt;Take your Rails development to the next level&lt;/a&gt; on March 30, 2023.&lt;/p&gt;</content>
</entry>


<entry>
  <title type="html"><![CDATA[Send Emails with SendGrid: A Step-by-Step Guide for Ruby on Rails Applications]]></title>
  <link rel="alternate" type="text/html" href="http://blog.widefix.com/send-emails-with-sendgrid-a-step-by-step-guide-for-ruby-on-rails-applications/"/>
  <id>http://blog.widefix.com/send-emails-with-sendgrid-a-step-by-step-guide-for-ruby-on-rails-applications</id>
  <updated>2023-03-15 15:29:47 +0100T00:00:00-00:00</updated>
  <published>2023-03-15T00:00:00-04:00</published>
  
  <author>
    <name>Andrei Kaleshka</name>
    <uri>http://blog.widefix.com</uri>
    <email>ka8725@gmail.com</email>
  </author>
  <category scheme="http://blog.widefix.com/tags/#rails" term="rails" /><category scheme="http://blog.widefix.com/tags/#mailer" term="mailer" />
  <content type="html">
  
    &lt;p&gt;Sending emails from a Ruby on Rails application can be a critical aspect of many web applications. Whether it’s for sending password resets, notifications, newsletters or transactional emails, you want your emails to be reliable and fast. That’s where SendGrid comes in. This article guides you through the process of sending emails from your Ruby on Rails application using SendGrid.&lt;/p&gt;

&lt;p&gt;Before you start, it’s crucial to understand that username and password authentication for email delivery is no longer sufficient. Nowadays, email provider services like SendGrid require more rigorous verification processes to ensure secure and reliable email delivery. As such, the first step in this journey is setting up your SendGrid account properly.&lt;/p&gt;

&lt;h3 id=&quot;sendgrid-account-setup&quot;&gt;SendGrid Account Setup&lt;/h3&gt;

&lt;p&gt;To get started with &lt;a href=&quot;https://sendgrid.com/&quot; ref=&quot;nofollow&quot; target=&quot;_blank&quot;&gt;SendGrid&lt;/a&gt; in your Ruby on Rails application, you first need to sign up for a SendGrid account and &lt;a href=&quot;https://docs.sendgrid.com/ui/account-and-settings/api-keys#creating-an-api-key&quot; ref=&quot;nofollow&quot; target=&quot;_blank&quot;&gt;obtain an API key&lt;/a&gt;. Once you have created it, make sure to copy it somewhere. Otherwise, you won’t be able to read it later.&lt;/p&gt;

&lt;p&gt;It’s recommended to have &lt;a href=&quot;https://docs.sendgrid.com/ui/account-and-settings/how-to-set-up-domain-authentication&quot; ref=&quot;nofollow&quot; target=&quot;_blank&quot;&gt;completed the Domain Authentication process&lt;/a&gt; to ensure the deliverability of your emails. This step requires registering your domain, either your own or one provided by other services such as Heroku that generate it automatically. By authenticating your domain, you can verify that you are a legitimate sender, improve email deliverability, and reduce the risk of your emails being flagged as spam.&lt;/p&gt;

&lt;p&gt;When using SendGrid to send emails from your Ruby on Rails application, it’s essential to &lt;a href=&quot;https://docs.sendgrid.com/ui/sending-email/sender-verification&quot; ref=&quot;nofollow&quot; target=&quot;_blank&quot;&gt;verify the email address from which the emails will be sent&lt;/a&gt;. This verification process involves adding the email address to your SendGrid account and proving that you have access to it. Failure to verify the email address can result in errors when attempting to send emails, such as authentication failures or email delivery issues. Therefore, it’s crucial to verify any email addresses you plan to use for sending emails to ensure reliable and error-free email delivery.&lt;/p&gt;

&lt;p&gt;After these steps completed, you should have:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;The API key (a long abrakadabra string) like this: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SG.ve9OolaiLeenoh0iatoon0.hei8geiz1Shoorohtai6choopah8cahjae9vako8uBa&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Verified domain, let it be &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mydomain.com&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Verified sender email address, let it be &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hello@mydomain.com&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;configure-server&quot;&gt;Configure server&lt;/h3&gt;

&lt;p&gt;The Rails application will be configured via environment variables, which should be defined on your server (or on your local machine if you want to send emails from it):&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;SMTP_DEFAULT_FROM:        &amp;lt;your sender email&amp;gt;
SMTP_DOMAIN:              &amp;lt;your domain&amp;gt;
SMTP_PASSWORD:            &amp;lt;your api key&amp;gt;
SMTP_LOGIN:               apikey
SMTP_PORT:                587
SMTP_SERVER:              smtp.sendgrid.net
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;Note that some of the values are static, while others are placeholders that you should replace with your own values. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SMTP_LOGIN&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SMTP_PORT&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SMTP_SERVER&lt;/code&gt; are static and will always have the same values. It is important to note that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SMTP_LOGIN&lt;/code&gt; will always equal apikey, and you will not have your own value for that.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here is an example of how to do that using the Heroku command line:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;heroku config:set &lt;span class=&quot;nv&quot;&gt;SMTP_DEFAULT_FROM&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Hello &amp;lt;hello@mydomain.com&amp;gt;&quot;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;SMTP_DOMAIN&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;mydomain.com &lt;span class=&quot;nv&quot;&gt;SMTP_LOGIN&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;apikey &lt;span class=&quot;nv&quot;&gt;SMTP_PASSWORD&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;SG.ve9OolaiLeenoh0iatoon0.hei8geiz1Shoorohtai6choopah8cahjae9vako8uBa &lt;span class=&quot;nv&quot;&gt;SMTP_PORT&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;587
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;configure-ruby-on-rails&quot;&gt;Configure Ruby On Rails&lt;/h3&gt;

&lt;p&gt;Emails are typically sent from a production server. Therefore, you need to add the necessary configuration to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;config/environments/production.rb&lt;/code&gt; file as follows:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;action_mailer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;smtp_settings&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;port: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;ENV&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'SMTP_PORT'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;address: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;ENV&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'SMTP_SERVER'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;user_name: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;ENV&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'SMTP_LOGIN'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;password: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;ENV&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'SMTP_PASSWORD'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;domain: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;ENV&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'SMTP_DOMAIN'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;authentication: :plain&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;enable_starttls_auto: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;action_mailer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;delivery_method&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:smtp&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Make sure that these settings are not overridden elsewhere in this file.&lt;/p&gt;

&lt;p&gt;If you need to send emails from development or staging mode, add these lines to the respective files &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;config/environments/development.rb&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;config/environments/staging.rb&lt;/code&gt;. Deliver these changes to your server.&lt;/p&gt;

&lt;h3 id=&quot;testing&quot;&gt;Testing&lt;/h3&gt;

&lt;p&gt;That’s all the steps needed to get it done. But before moving on to your next task, let’s make sure everything is working correctly. Open the Rails console and define a variable with the email address you want to send a verification email to (for this example, we’ll use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;my@example.com&lt;/code&gt;; replace it with your own):&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;to_email&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'my@example.com'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And then paste there the following code snippet:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;mailer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActionMailer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Base&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;mailer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;mail&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;from: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;ENV&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'SMTP_DEFAULT_FROM'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;to: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;to_email&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;subject: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'Test from WideFix guide'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;body: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Hello, you've got mail!&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;deliver&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Wait a bit and you should receive the email in your inbox.&lt;/p&gt;

&lt;p&gt;If you follow these steps correctly, all mailers defined in your Rails application will start using SendGrid for sending emails, and no additional configuration or checks are required.&lt;/p&gt;

  
  &lt;p&gt;&lt;a href=&quot;http://blog.widefix.com/send-emails-with-sendgrid-a-step-by-step-guide-for-ruby-on-rails-applications/&quot;&gt;Send Emails with SendGrid: A Step-by-Step Guide for Ruby on Rails Applications&lt;/a&gt; was originally published by Andrei Kaleshka at &lt;a href=&quot;http://blog.widefix.com&quot;&gt;Take your Rails development to the next level&lt;/a&gt; on March 15, 2023.&lt;/p&gt;</content>
</entry>


<entry>
  <title type="html"><![CDATA[Unlocking the Power of ChatGPT with Ruby]]></title>
  <link rel="alternate" type="text/html" href="http://blog.widefix.com/unlocking-the-power-of-chatgpt-with-ruby-a-beginners-guide/"/>
  <id>http://blog.widefix.com/unlocking-the-power-of-chatgpt-with-ruby-a-beginners-guide</id>
  <updated>2023-02-22 22:31:11 +0100T00:00:00-00:00</updated>
  <published>2023-02-22T00:00:00-05:00</published>
  
  <author>
    <name>Andrei Kaleshka</name>
    <uri>http://blog.widefix.com</uri>
    <email>ka8725@gmail.com</email>
  </author>
  <category scheme="http://blog.widefix.com/tags/#ruby" term="ruby" /><category scheme="http://blog.widefix.com/tags/#chatgpt" term="chatgpt" />
  <content type="html">
  
    &lt;p&gt;Are you looking to harness the power of artificial intelligence and machine learning to streamline your coding workflow? ChatGPT and Ruby are two powerful tools that can help you do just that. However, if you’re new to these tools, getting started can be daunting. That’s where our beginner-friendly guide comes in. In this article, we’ll take you through everything you need to know to use ChatGPT with Ruby, from the basics of integration to more advanced features. Whether you’re a seasoned developer or just starting out, this guide will help you unlock the full potential of these powerful tools and take your coding skills to the next level.&lt;/p&gt;

&lt;p&gt;Before you start integrating the ChatGPT API, you need to register for an API key and obtain your credentials from the ChatGPT website. To do that, follow this &lt;a href=&quot;https://platform.openai.com/account/api-keys&quot; ref=&quot;nofollow&quot; target=&quot;_blank&quot;&gt;page&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Create the following class in your Ruby On Rails or any other Ruby-based project:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;OpenaiPrompt&lt;/span&gt;
  &lt;span class=&quot;kp&quot;&gt;extend&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Dry&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Initializer&lt;/span&gt;

  &lt;span class=&quot;no&quot;&gt;URL&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;https://api.openai.com/v1/completions&quot;&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;param&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:prompt&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;option&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;default: &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;proc&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;text-davinci-003&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;option&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:max_tokens&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;default: &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;proc&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1000&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;option&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:temperature&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;default: &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;proc&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;connection&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;
      &lt;span class=&quot;no&quot;&gt;Faraday&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;faraday&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;faraday&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ssl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:verify&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;faraday&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;headers&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;connection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;URL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;json&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;choices&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;text&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

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

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;body&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;ss&quot;&gt;model: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;ss&quot;&gt;prompt: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;prompt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;ss&quot;&gt;max_tokens: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;max_tokens&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;ss&quot;&gt;temperature: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;temperature&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_json&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;headers&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;s2&quot;&gt;&quot;Content-Type&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;application/json&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;s2&quot;&gt;&quot;Authorization&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Bearer &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;ENV&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'OPENAI_ACCESS_TOKEN'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;s2&quot;&gt;&quot;OpenAI-Organization&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ENV&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'OPENAI_ORGANIZATION_ID'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You can define your ChatGPT class within an existing file or create a new one specifically for it. However, it is generally recommended to create a separate file for your ChatGPT class for better organization and modularity of your code. This makes it easier to find and modify your ChatGPT code in the future without affecting other parts of your project. If it’s a Ruby On Rails application, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;app/services/openai_prompt.rb&lt;/code&gt; is a good file location for this piece of code.&lt;/p&gt;

&lt;p&gt;This class has the following dependencies (gems) that should be added to your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Gemfile&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;faraday&lt;/code&gt; - This is a handy library that provides an abstract interface for making HTTP requests. It simplifies the process of making requests to external APIs and handling responses.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dry-initializer&lt;/code&gt; - This library allows you to define the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;new&lt;/code&gt; method implicitly, reducing the amount of repetitive code required. This makes it easier to create new instances of your class without having to manually define instance variables and their default values.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;OpenaiPrompt&lt;/code&gt; class, you first need to define the environment variable &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;OPENAI_ACCESS_TOKEN&lt;/code&gt;. If you have multiple organizations registered within ChatGPT, you can also define the optional &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;OPENAI_ORGANIZATION_ID&lt;/code&gt; environment variable.&lt;/p&gt;

&lt;p&gt;Once you have set these environment variables, you can use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;OpenaiPrompt&lt;/code&gt; class from any line of your project by simply calling &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;OpenaiPrompt.new('Why is Ruby an awesome programming language?').call&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here is an example of how you can use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;OpenaiPrompt&lt;/code&gt; class in a Rails console:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;OpenaiPrompt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'Why is Ruby awesome programming language?'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# =&amp;gt; &quot;\n\nRuby is an awesome programming language because it is easy to learn and use, has a large and supportive community, and is highly flexible and powerful. It is also object-oriented, meaning that it allows developers to create complex applications quickly and easily. Additionally, Ruby is open source, meaning that it is free to use and modify. Finally, Ruby is known for its readability, which makes it easier for developers to understand and debug code.&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;ChatGPT offers a wide range of functionalities and models that can be utilized in various ways, not just limited to answering common questions. For instance, you can leverage GPT-3 to generate text for creative writing, content generation, or chatbot responses.&lt;/p&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;OpenaiPrompt&lt;/code&gt; class serves as an example of how you can use ChatGPT to answer common questions programmatically. However, the same principles can be applied to build other classes that utilize ChatGPT’s other capabilities.&lt;/p&gt;

&lt;p&gt;Furthermore, integrating ChatGPT with a user interface, such as a web form or a chatbot, can allow users to ask questions and get responses in a more intuitive manner. This can be particularly useful for applications that require natural language processing and understanding, such as customer service chatbots or virtual assistants. By integrating ChatGPT with a UI, you can make it more accessible and user-friendly for non-technical users.&lt;/p&gt;

&lt;p&gt;If you have questions or need to integrate ChatGPT into your Ruby On Rails or any other Ruby-based project, please &lt;a href=&quot;https://widefix.com/&quot;&gt;contact&lt;/a&gt;.&lt;/p&gt;

  
  &lt;p&gt;&lt;a href=&quot;http://blog.widefix.com/unlocking-the-power-of-chatgpt-with-ruby-a-beginners-guide/&quot;&gt;Unlocking the Power of ChatGPT with Ruby&lt;/a&gt; was originally published by Andrei Kaleshka at &lt;a href=&quot;http://blog.widefix.com&quot;&gt;Take your Rails development to the next level&lt;/a&gt; on February 22, 2023.&lt;/p&gt;</content>
</entry>


<entry>
  <title type="html"><![CDATA[Fake Time.now in production]]></title>
  <link rel="alternate" type="text/html" href="http://blog.widefix.com/fake-time-dot-now-in-production/"/>
  <id>http://blog.widefix.com/fake-time-dot-now-in-production</id>
  <updated>2023-02-08 13:59:03 +0100T00:00:00-00:00</updated>
  <published>2023-02-08T00:00:00-05:00</published>
  
  <author>
    <name>Andrei Kaleshka</name>
    <uri>http://blog.widefix.com</uri>
    <email>ka8725@gmail.com</email>
  </author>
  <category scheme="http://blog.widefix.com/tags/#rails" term="rails" /><category scheme="http://blog.widefix.com/tags/#debug" term="debug" />
  <content type="html">
  
    &lt;p&gt;TL;DR: see this &lt;a href=&quot;https://gist.github.com/ka8725/95c21119b8fd4883925132ac0514f966&quot; ref=&quot;nofollow&quot; target=&quot;_blank&quot;&gt;gist&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;As a Ruby on Rails expert, you may find yourself wondering how to debug a Rails application in production. In some cases, you may need to mimic the current time to check a time-dependent function result.&lt;/p&gt;

&lt;p&gt;While there are third-party gems that offer the ability to fake &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Time.now&lt;/code&gt; functionality, Rails has this built-in functionality as part of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ActiveSupport::Testing::TimeHelpers&lt;/code&gt; module. However, this module is only intended for use in testing and not in production environments.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;no&quot;&gt;Time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;now&lt;/span&gt;          &lt;span class=&quot;c1&quot;&gt;# =&amp;gt; Wed, 08 Feb 2023 14:01:33 UTC +00:00&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;travel&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;day&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ago&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;Time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;now&lt;/span&gt;          &lt;span class=&quot;c1&quot;&gt;# =&amp;gt; Tue, 07 Feb 2023 14:01:33 UTC +00:00&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;Date&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;current&lt;/span&gt;      &lt;span class=&quot;c1&quot;&gt;# =&amp;gt; Tue, 07 Feb 2023&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Check in production console:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;travel&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;day&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ago&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# =&amp;gt; Traceback (most recent call last):&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;#    NoMethodError (undefined method `travel' for main:Object)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If you want to use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;travel&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;freeze_time&lt;/code&gt; method in production, you can load the module source code and include it into the console runtime:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;mod&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;https://raw.githubusercontent.com/rails/rails/2a2a6ab6219b12e9e77931a60fe83c658db44ac7/activesupport/lib/active_support/testing/time_helpers.rb&quot;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;eval&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mod&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;read&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;kp&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActiveSupport&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Testing&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;TimeHelpers&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;travel_to&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;day&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ago&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;now&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# =&amp;gt; Tue, 07 Feb 2023 14:01:33 UTC +00:00&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;Tip: Use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Time.current&lt;/code&gt; instead of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Time.now&lt;/code&gt;. This blog post uses &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Time.now&lt;/code&gt; only for demonstration purposes due to its widespread usage over &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Time.current&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here’s an example of how I recently implemented this in a production environment. In our project, we have a function called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Plan#stripe_id&lt;/code&gt; that links to a product pricing object. The pricing is dependent on the current time because on February 15th, the project’s prices will be increased. Before February 15th, the system uses the old prices, but after that date, it uses the new prices.&lt;/p&gt;

&lt;p&gt;Here’s how the method is defined:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Plan&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;NEW_PRICES_TIME&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'15 Feb 21:00 UTC'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_datetime&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;stripe_id&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;NEW_PRICES_TIME&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;past?&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;stripe_price_id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;name&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
      &lt;span class=&quot;nb&quot;&gt;name&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Please note that name is a deprecated column referring to an outdated Stripe product price.&lt;/p&gt;

&lt;p&gt;Once this feature is deployed to production, we will verify its functionality across all plans by accessing the production Rails console and reviewing the pre-implementation values.&lt;/p&gt;

&lt;p&gt;Checking how it works now:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;no&quot;&gt;Plan&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;all&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# =&amp;gt; [&quot;a&quot;, &quot;b&quot;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And then we do the same at the traveled time after Feb 15:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;mod&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;https://raw.githubusercontent.com/rails/rails/2a2a6ab6219b12e9e77931a60fe83c658db44ac7/activesupport/lib/active_support/testing/time_helpers.rb&quot;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;eval&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mod&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;read&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;kp&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActiveSupport&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Testing&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;TimeHelpers&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;travel_to&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;16 Feb, 2024&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_datetime&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Plan&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;all&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# =&amp;gt; [&quot;a_2023&quot;, &quot;b_2023&quot;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;With this verification process, we can now confidently say that the production environment is functioning correctly and will be utilizing the correct prices after Feb 15, thereby avoiding any unexpected issues in the final stages.&lt;/p&gt;

&lt;h3 id=&quot;be-aware-of-the-following-security-concerns-before-proceeding-with-these-steps&quot;&gt;Be aware of the following security concerns before proceeding with these steps&lt;/h3&gt;

&lt;p&gt;Loading code from an external source via an HTTP link can pose a security risk as it may contain malicious code. A recommended solution is to use unit tests. However, unit tests cannot guarantee the behavior of code in a production environment. In emergency situations where time is of the essence, a faster and more creative approach may be necessary to ensure the code’s integrity.&lt;/p&gt;

&lt;p&gt;The security concern can be addressed by creating the link yourself. To do so, visit the GitHub Rails repository, search for the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;time_helpers.rb&lt;/code&gt; file, and click the “Raw” button. This will generate the link. However, it is important to note that even this manual process does not guarantee the absence of malicious code, as the resource may have been tampered with by hackers. Always review the contents before evaluating them:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;url&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&amp;lt;HTTP LINK&amp;gt;&quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;code&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;read&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;code&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It’s important to carefully review the code before evaluating it, to ensure it is the original, intended code and not something malicious. Only after confirming the authenticity of the code should you include it as a module:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;eval&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;code&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;kp&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActiveSupport&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Testing&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;TimeHelpers&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If there is limited time to review the code, a simple check can help prevent dangerous actions. This can be done by raising an error if a specific line of code is not present. This will serve as a safeguard and ensure that any malicious code is not executed:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;raise&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'dangerous code'&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;unless&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;code&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;include?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'module ActiveSupport'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Manipulating production systems can be risky and should only be done with a high level of confidence in what you are doing. Although in some scenarios, the console is the only solution to fix a production issue, it should be approached with caution. Before making any changes, consider the potential impact and weigh the decision carefully, as even a small mistake can have significant consequences. In some cases, the cost of fixing the issue through other means may be less than the cost of fixing the mistake made in the production console.&lt;/p&gt;

&lt;p&gt;At &lt;a href=&quot;https://widefix.com&quot; ref=&quot;nofollow&quot; target=&quot;_blank&quot;&gt;WideFix&lt;/a&gt;, we take pride in our expertise and experience. You can trust that our solutions will not harm your business and will always keep your site running smoothly. You can rely on us to provide confident and effective solutions that meet your needs.&lt;/p&gt;

&lt;div style=&quot;display: flex;align-items:center;justify-content: center;margin-top: 20px;&quot;&gt;
  &lt;a class=&quot;btn&quot; style=&quot;background-color: #f04338; cursor: pointer;font-size: 24px;&quot; target=&quot;_blank&quot; rel=&quot;nofollow&quot; href=&quot;https://widefix.com&quot;&gt;Hire Ruby On Rails expert now&lt;/a&gt;
&lt;/div&gt;

  
  &lt;p&gt;&lt;a href=&quot;http://blog.widefix.com/fake-time-dot-now-in-production/&quot;&gt;Fake Time.now in production&lt;/a&gt; was originally published by Andrei Kaleshka at &lt;a href=&quot;http://blog.widefix.com&quot;&gt;Take your Rails development to the next level&lt;/a&gt; on February 08, 2023.&lt;/p&gt;</content>
</entry>

</feed>
