<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Stephen Margheim</title>
    <description>The latest articles on Forem by Stephen Margheim (@fractaledmind).</description>
    <link>https://forem.com/fractaledmind</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F18371%2F908bf0a1-2b97-4e70-8898-bcab03ed0607.jpeg</url>
      <title>Forem: Stephen Margheim</title>
      <link>https://forem.com/fractaledmind</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/fractaledmind"/>
    <language>en</language>
    <item>
      <title>ActiveRecord adapter improvements</title>
      <dc:creator>Stephen Margheim</dc:creator>
      <pubDate>Fri, 22 Sep 2023 15:53:31 +0000</pubDate>
      <link>https://forem.com/fractaledmind/activerecord-adapter-improvements-45np</link>
      <guid>https://forem.com/fractaledmind/activerecord-adapter-improvements-45np</guid>
      <description>&lt;p&gt;&lt;a href="https://rubyonrails.org"&gt;Ruby on Rails&lt;/a&gt; continues to be a lively and thriving framework. Unfortunately, when it comes to the database adapters, a vast majority of the attention and effort has been focused on the MySQL and PosgreSQL adapters. &lt;a href="https://www.sqlite.org/index.html"&gt;SQLite&lt;/a&gt; supports a large percentage of the database features that Rails has added support for in the recent past. So, today I am starting to do my part to make the developer experience of using Rails with SQLite as seamless and powerful as possible. Maybe you'll join me?&lt;/p&gt;




&lt;p&gt;Today I opened my first few pull requests to begin improving Rails' &lt;code&gt;SQLite3Adapter&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/rails/rails/pull/49287"&gt;support &lt;code&gt;||&lt;/code&gt; concatenation in default functions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/rails/rails/pull/49290"&gt;support &lt;code&gt;RETURNING&lt;/code&gt; values on &lt;code&gt;INSERT&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/rails/rails/pull/49346"&gt;support generated columns&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/rails/rails/pull/49349"&gt;performance tune connection configuration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/rails/rails/pull/49352"&gt;add &lt;code&gt;retries&lt;/code&gt; option as alternative to &lt;code&gt;timeout&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And this blog post is my personal declaration of intent—I am going to do my part in bringing as many of the newer ActiveRecord features to the SQLite adapter. From &lt;a href="https://www.sqlite.org/foreignkeys.html#fk_composite"&gt;composite foreign keys&lt;/a&gt; to &lt;a href="https://www.sqlite.org/gencol.html"&gt;virtual columns&lt;/a&gt;, with &lt;a href="https://github.com/rails/rails/pull/48241"&gt;auto-populated columns&lt;/a&gt; thrown in, SQLite will no longer lag behind PostgreSQL and MySQL.&lt;/p&gt;

&lt;p&gt;But, this is no simple task, and I'm certain there are many more features beyond the ones I have bumped into. So, this post is also a call to action. If you are a SQLite and Rails enthusiast, join me! Let's start leveling up the &lt;code&gt;SQLite3Adapter&lt;/code&gt; together. Because one step at a time, we can help surge the tide of SQLite in production usage for Rails applications.&lt;/p&gt;




&lt;p&gt;That's it for today. But be on the lookout for a post on how supporting Rails' &lt;code&gt;RETURNING&lt;/code&gt; feature opens up the possibility for &lt;a href="https://en.wikipedia.org/wiki/Universally_unique_identifier"&gt;UUIDs&lt;/a&gt; or &lt;a href="https://github.com/ulid/spec"&gt;ULIDs&lt;/a&gt; as primary keys.&lt;/p&gt;

</description>
      <category>code</category>
      <category>ruby</category>
      <category>rails</category>
      <category>sqlite</category>
    </item>
    <item>
      <title>Local snapshots</title>
      <dc:creator>Stephen Margheim</dc:creator>
      <pubDate>Fri, 22 Sep 2023 15:49:45 +0000</pubDate>
      <link>https://forem.com/fractaledmind/local-snapshots-19aa</link>
      <guid>https://forem.com/fractaledmind/local-snapshots-19aa</guid>
      <description>&lt;p&gt;Today we consider how &lt;a href="https://www.sqlite.org/index.html"&gt;SQLite&lt;/a&gt; can enhance working with our database in our &lt;a href="https://rubyonrails.org"&gt;Ruby on Rails&lt;/a&gt; applications. The the database is simply a file, snapshots and clones are both simple and powerful.&lt;/p&gt;




&lt;p&gt;When working on a web application, there are various tasks you will have &lt;em&gt;at some point&lt;/em&gt; that involve your database. You will want to take a snapshot of your database. You will want to restore your database to a previously saved snapshot. You will want to clone your production database locally. You will want to merge your production data into your existing local database. You will want to merge data from &lt;a href="//%20link%20_posts/2023-09-06-enhancing-rails-sqlite-branch-databases.md%20"&gt;another, branch-specific databases&lt;/a&gt; into your current branch's database. You get the idea. When working with database engines that run in a separate process, often on a separate computer, these tasks can be somewhat cumbersome, if not practically impossible. Sure &lt;a href="https://www.postgresql.org"&gt;PostgreSQL&lt;/a&gt; has &lt;a href="https://www.postgresql.org/docs/current/app-pgdump.html"&gt;&lt;code&gt;pg_dump&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://www.postgresql.org/docs/15/app-pgrestore.html"&gt;&lt;code&gt;pg_restore&lt;/code&gt;&lt;/a&gt;, but I wouldn't call this straight-forward:&lt;sup id="fnref1"&gt;1&lt;/sup&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pg_dump &lt;span class="nt"&gt;-U&lt;/span&gt; postgres &lt;span class="nt"&gt;-h&lt;/span&gt; localhost &lt;span class="nt"&gt;-p&lt;/span&gt; 5432 &lt;span class="nt"&gt;-F&lt;/span&gt; c &lt;span class="nt"&gt;-b&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; /dev/null DB_NAME 2&amp;gt;&amp;amp;1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With SQLite, each of these actions is, in my opinion, quite straight-forward. So, let's walk through them one by one and see for ourselves.&lt;/p&gt;

&lt;p&gt;{:.notice}&lt;br&gt;
&lt;strong&gt;Note:&lt;/strong&gt; All code will be in a Rake namespace, as we will eventually be preparing a task file to put in our Rails application's &lt;code&gt;/lib/tasks&lt;/code&gt; directory so that we can quickly and easily accomplish these tasks on a day-to-day basis within our apps.&lt;/p&gt;
&lt;h2&gt;
  
  
  Snapshotting your database
&lt;/h2&gt;

&lt;p&gt;A database snapshot is just a copy of your data at a particular moment in time. When your entire database is simply a file on your filesystem, taking a snapshot is as simple as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;namespace&lt;/span&gt; &lt;span class="ss"&gt;:snap&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="ss"&gt;:create&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="vi"&gt;@snapshot_dir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'storage/snapshots'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@db_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connection_db_config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;database&lt;/span&gt;
    &lt;span class="vi"&gt;@db_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vi"&gt;@db_path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rpartition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;last&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'.sqlite3'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;timestamp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_formatted_s&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;snap_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vi"&gt;@db_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.backup"&lt;/span&gt;
    &lt;span class="n"&gt;snap_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Pathname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@snapshot_dir&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;snap_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="no"&gt;FileUtils&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;copy_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@db_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;snap_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is some boilerplate here, but the core is simply a &lt;code&gt;cp&lt;/code&gt; call to copy the database file. We get the current ActiveRecord database path, prepare a timestamped snapshot file name, and just copy the database file over.&lt;/p&gt;

&lt;p&gt;Snapshots are useful as they give you the ability to create save points with your schema and data that are easy to jump back to later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Restoring a snapshot
&lt;/h2&gt;

&lt;p&gt;Once you have a snapshot, you may need to restore your database to that point in time. Typically, you will take a snapshot before you begin an experiment that will require altering your database schema or data or both. You want to be able to revert your changes if needed, so you take a snapshot first and revert later. With simple SQLite files, you can probably guess how snapshot restoring is going to go:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;namespace&lt;/span&gt; &lt;span class="ss"&gt;:snap&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="ss"&gt;:restore&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="vi"&gt;@snapshot_dir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'storage/snapshots'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@db_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connection_db_config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;database&lt;/span&gt;
    &lt;span class="vi"&gt;@db_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vi"&gt;@db_path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rpartition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;last&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'.sqlite3'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@snaps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Pathname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@snapshot_dir&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;children&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
        &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extname&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;".backup"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
        &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;basename&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;include?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@db_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reverse&lt;/span&gt;

    &lt;span class="n"&gt;latest_snapshot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vi"&gt;@snaps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;

    &lt;span class="no"&gt;FileUtils&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@db_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="no"&gt;FileUtils&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;copy_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;latest_snapshot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="vi"&gt;@db_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Restoring a snapshot itself is straight-forward. We take the most recent snapshot of our current database. Then, we delete the current database file and copy the snapshot file into the current database file's place. Again, because we are working with simple files, we are fundamentally just putting some nice boilerplate around &lt;code&gt;cp&lt;/code&gt; copy commands.&lt;/p&gt;




&lt;p&gt;Because our database is just a file on the file system, working with our production data can also be simplified. I will write about that in a future post. For now, I think that this exploration of how we can snapshot and restore local databases is sufficient for one post. With a bit of cleanup and polish, we can create a &lt;code&gt;/lib/tasks/dbspan.rake&lt;/code&gt; file that provides the following usage:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bin/rails db:snap:list
bin/rails db:snap:create
bin/rails db:snap:restore
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will set the foundation that will then allow us to add on the ability to work with our production database as well.&lt;/p&gt;

&lt;p&gt;{:.notice}&lt;br&gt;
You can find the full code for the model concern detailed in &lt;a href="https://gist.github.com/fractaledmind/4fe00d226715e8ce7209a525f3d9d98e"&gt;this Gist&lt;/a&gt;.&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;This command is taken from the &lt;a href="https://github.com/iseth/pg-snap/"&gt;&lt;code&gt;pg-snap&lt;/code&gt;&lt;/a&gt; repository, which provides a simpler CLI utility for working with Postgres snapshots. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>code</category>
      <category>ruby</category>
      <category>rails</category>
      <category>sqlite</category>
    </item>
    <item>
      <title>Array columns in SQLite</title>
      <dc:creator>Stephen Margheim</dc:creator>
      <pubDate>Tue, 12 Sep 2023 14:25:20 +0000</pubDate>
      <link>https://forem.com/fractaledmind/array-columns-in-sqlite-15i0</link>
      <guid>https://forem.com/fractaledmind/array-columns-in-sqlite-15i0</guid>
      <description>&lt;p&gt;One of the reasons people hesitate to use &lt;a href="https://www.sqlite.org/index.html"&gt;SQLite&lt;/a&gt; in their &lt;a href="https://rubyonrails.org"&gt;Ruby on Rails&lt;/a&gt; applications, in my opinion, is a fear that they will miss certain features they are accustomed to from &lt;a href="https://www.postgresql.org"&gt;PostgeSQL&lt;/a&gt; or &lt;a href="https://www.mysql.com"&gt;MySQL&lt;/a&gt;. As discussed in an &lt;a href="//%20link%20_posts/2023-09-08-enhancing-rails-sqlite-loading-extensions.md%20"&gt;earlier post&lt;/a&gt;, we can load SQLite extensions into our Rails applications to enhance the functionality of SQLite. Moreover, today I want to show you that it is possible to build on top of SQLite's primitives to provide matching behavior for one of my favorite features of Postgres—&lt;a href="https://www.postgresql.org/docs/current/arrays.html"&gt;array columns&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;When working with relational data, you often come across one particular dilemma: what do I do with this small amount of associated data? Do I build a whole new table with a foreign key, which keeps my schema highly &lt;a href="https://en.wikipedia.org/wiki/Database_normalization"&gt;normalized&lt;/a&gt; but also means I have to accept a &lt;code&gt;JOIN&lt;/code&gt; everytime I need to access this data. Do I simply stuff the data into a &lt;code&gt;JSON&lt;/code&gt; column on my primary table, which removes the need for the &lt;code&gt;JOIN&lt;/code&gt; but also bloats my primary table and opens up a possibility for stuffing unstructured data into that column.&lt;/p&gt;

&lt;p&gt;Postgres offers a nice compromise here with their implementation of &lt;a href="https://www.postgresql.org/docs/current/arrays.html"&gt;array columns&lt;/a&gt;. Instead of an amorphous JSON blob, your column is and will always simply be an array of values. This matches the effective behavior of a simple two-column associated table (foreign key plus value column), without the need for the &lt;code&gt;JOIN&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For my favorite example of the utility of this tool, consider &lt;a href="https://twitter.com/hopsoft?ref=fractaledmind.github.io"&gt;Nate Hopkin's&lt;/a&gt; implementation of &lt;a href="https://github.com/hopsoft/tag_columns"&gt;a tagging system&lt;/a&gt;, built on top of Postgres' array columns. To save you a click and demonstrate just how elegant this solution is, I will provide the code examples from the &lt;code&gt;README&lt;/code&gt; here:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# db/migrate/TIMESTAMP_add_groups_to_user.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AddGroupsToUser&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;5.0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;change&lt;/span&gt;
    &lt;span class="n"&gt;add_column&lt;/span&gt; &lt;span class="ss"&gt;:users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:groups&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;array: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;default: &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="ss"&gt;null: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;
    &lt;span class="n"&gt;add_index&lt;/span&gt; &lt;span class="ss"&gt;:users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:groups&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;using: &lt;/span&gt;&lt;span class="s2"&gt;"gin"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/models/user.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;TagColumns&lt;/span&gt;
  &lt;span class="n"&gt;tag_columns&lt;/span&gt; &lt;span class="ss"&gt;:groups&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# assigning tags&lt;/span&gt;
&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groups&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="ss"&gt;:reader&lt;/span&gt;
&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groups&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="ss"&gt;:writer&lt;/span&gt;
&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;

&lt;span class="c1"&gt;# checking tags&lt;/span&gt;
&lt;span class="n"&gt;is_writer&lt;/span&gt;            &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has_group?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:writer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;is_reader_or_writer&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has_any_groups?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:reader&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:writer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;is_reader_and_writer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has_all_groups?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:reader&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:writer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# finding tagged records&lt;/span&gt;
&lt;span class="n"&gt;assigned&lt;/span&gt;                &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;with_groups&lt;/span&gt;
&lt;span class="n"&gt;unassigned&lt;/span&gt;              &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;without_groups&lt;/span&gt;
&lt;span class="n"&gt;writers&lt;/span&gt;                 &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;with_any_groups&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:writer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;non_writers&lt;/span&gt;             &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;without_any_groups&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:writer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;readers_or_writers&lt;/span&gt;      &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;with_any_groups&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:reader&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:writer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;readers_and_writers&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;with_all_groups&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:reader&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:writer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;non_readers_and_writers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;without_all_groups&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:reader&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:writer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# find unique tags across all users&lt;/span&gt;
&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unique_groups&lt;/span&gt;

&lt;span class="c1"&gt;# find unique tags for users with the last name 'Smith'&lt;/span&gt;
&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unique_groups&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;last_name: &lt;/span&gt;&lt;span class="s2"&gt;"Smith"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With one array column on your model, you get a full suite of the core "tagging-style" functionality. I love solutions like this. The entire gem is no more than one file that defines the &lt;code&gt;TagColumns&lt;/code&gt; concern, and that file is only 105 lines (89 lines of code). Elegance and simplicity are what SQLite is all about, so, how can we accomplish the same result without an array column primitive?&lt;/p&gt;




&lt;p&gt;Let's start with how we can add a column to a table that can only be an array. SQLite does support a &lt;a href="https://www.sqlite.org/json1.html"&gt;wide variety&lt;/a&gt; of JSON functionality. It also supports standard column &lt;a href="https://www.sqlite.org/lang_createtable.html#check_constraints"&gt;check constraints&lt;/a&gt;. This gives us everything we need. We will define a &lt;code&gt;JSON&lt;/code&gt; column and then add a constraint to ensure that the column is only an &lt;code&gt;array&lt;/code&gt; JSON type. With Rails' migration DSL, if you are creating the column as you create the table it looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;create_table&lt;/span&gt; &lt;span class="ss"&gt;:posts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;force: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt; &lt;span class="ss"&gt;:tags&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;null: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;default: &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;check_constraint&lt;/span&gt; &lt;span class="s2"&gt;"JSON_TYPE(tags) = 'array'"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s1"&gt;'post_tags_is_array'&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you are simply adding the column to an existing table, the migration would look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;add_column&lt;/span&gt; &lt;span class="ss"&gt;:posts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:tags&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;default: &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="ss"&gt;null: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;
&lt;span class="n"&gt;add_check_constraint&lt;/span&gt; &lt;span class="s2"&gt;"JSON_TYPE(tags) = 'array'"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s1"&gt;'post_tags_is_array'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; SQLite does not support &lt;code&gt;GIN&lt;/code&gt; indexes. In order to provide an index for a &lt;code&gt;JSON&lt;/code&gt; column, the recommended pattern in SQLite is to define a &lt;a href="https://www.sqlite.org/gencol.html"&gt;generated column&lt;/a&gt; and then index that column. This &lt;a href="https://antonz.org/json-virtual-columns/"&gt;blog post&lt;/a&gt; provides a good overview of the approach. Unfortunately for us Rails developers, the &lt;code&gt;ActiveRecord&lt;/code&gt; adapter for SQLite doesn't yet support generated attributes, so we would have to drop down to running raw SQL. Support for Postgres generated columns was &lt;a href="https://github.com/rails/rails/pull/41856"&gt;recently added&lt;/a&gt;, and I plan to open a similar pull request for the SQLite adapter in the near future. For the time being, therefore, I will not dig into indexing an "array" column in our SQLite database. Since SQLite doesn't need to eat the network latency cost of a query, even unindexed queries can be sufficiently fast. However, of course, we would prefer to be able to ensure our SQLite implementation of "array columns" can be indexed. Once I have improved Rails' support, I will write a new post detailing how to work with SQLite generated columns and indexing them.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This gives us a &lt;code&gt;JSON&lt;/code&gt; column that will only ever be an array of values. Without a schema setup, let's turn to the "taggable" functionality that we want to support. &lt;code&gt;tag_columns&lt;/code&gt; supports 11 methods:&lt;sup id="fnref1"&gt;1&lt;/sup&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unique_column_name&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="no"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;column_name_cloud&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="no"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;with_column_name&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="no"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;without_column_name&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="no"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;with_any_column_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="no"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;with_all_column_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="no"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;without_any_column_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="no"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;without_all_column_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has_any_column_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has_all_column_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has_column_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We need SQL queries to back each one, and then the ActiveRecord method calls to generate those queries. As I don't want to derail this post with the process of coming up with each query and ActiveRecord method, I will summarize. At the heart of our implementation is the use of the &lt;a href="https://www.sqlite.org/json1.html#jeach"&gt;&lt;code&gt;JSON_EACH&lt;/code&gt;&lt;/a&gt; function that SQLite provides, which will treat each value in our array as if it were a row in a table. Each virtual row will have a &lt;code&gt;value&lt;/code&gt; column that you can select from. So, to get the unique set of values for the &lt;code&gt;tags&lt;/code&gt; column in our example &lt;code&gt;posts&lt;/code&gt; table, we simply need this SQL query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;"posts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="n"&gt;JSON_EACH&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;"posts"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"tags"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Readable and succinct. Similarly, in order to find those &lt;code&gt;posts&lt;/code&gt; that are tagged with &lt;code&gt;draft&lt;/code&gt;, we could use this query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="nv"&gt;"posts"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;"posts"&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;JSON_EACH&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;"posts"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"tags"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'draft'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This query gets more complicated. In order to find all of the &lt;code&gt;posts&lt;/code&gt; with the tag, we need to isolate our query for selecting the posts and all of their attributes from the query to check for whether the tag is present or not. This is a perfect use-case for a nested query. Our inner query does a check for whether or not the specific tag is present.&lt;sup id="fnref2"&gt;2&lt;/sup&gt; We use &lt;code&gt;SELECT 1&lt;/code&gt; because we only need to return a boolean for the &lt;code&gt;WHERE EXISTS&lt;/code&gt; check in the outer query; we use &lt;code&gt;LIMIT 1&lt;/code&gt; to optimize the inner query a bit, as we only need to know if &lt;code&gt;draft&lt;/code&gt; is present in the &lt;code&gt;tags&lt;/code&gt; array &lt;em&gt;at least once&lt;/em&gt;, we don't care about duplicates. This shape of a query will drive all of the &lt;code&gt;any_*&lt;/code&gt; methods.&lt;/p&gt;

&lt;p&gt;In order to support the &lt;code&gt;all_*&lt;/code&gt; methods, we need a query that ensures that returned &lt;code&gt;posts&lt;/code&gt; included each of the provided values. For example, &lt;code&gt;Post.with_all_tags('draft', 'sqlite')&lt;/code&gt; must only return those &lt;code&gt;posts&lt;/code&gt; who are tagged with both the &lt;code&gt;draft&lt;/code&gt; tag and the &lt;code&gt;sqlite&lt;/code&gt; tag; any post only tagged with &lt;code&gt;draft&lt;/code&gt; is ignored. Here is the shape for that query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="nv"&gt;"posts"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;"posts"&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;JSON_EACH&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;"posts"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"tags"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'draft'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'sqlite'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of a basic &lt;code&gt;WHERE EXISTS&lt;/code&gt; check, our outer query is now checking whether the number of matching tags for the post matches the number of queried tags. Remember that &lt;code&gt;JSON_EACH&lt;/code&gt; effectively converts our array column into a virtual table with rows; so, &lt;code&gt;SELECT COUNT(*) FROM JSON_EACH() WHERE ...&lt;/code&gt; will count the number of entries in our array column that match the where condition, returning that as an integer. We can use that integer returned from the inner query to ensure that the outer query only returns &lt;code&gt;posts&lt;/code&gt; with the total number of tags provided. In order to handle the possibilities of duplicate &lt;code&gt;tags&lt;/code&gt;, we ensure that we &lt;code&gt;COUNT&lt;/code&gt; only &lt;code&gt;DISTINCT value&lt;/code&gt;s. Were we to use &lt;code&gt;SELECT COUNT(*)&lt;/code&gt; or &lt;code&gt;SELECT COUNT(value)&lt;/code&gt;, our integer returned from the inner query &lt;em&gt;could be&lt;/em&gt; &lt;strong&gt;larger&lt;/strong&gt; than 2 (the size of the array of tags we are querying against). To ensure that the inner query only ever returns an integer as large or smaller than the array of tags, we need to count only distinct values.&lt;/p&gt;

&lt;p&gt;However, those three basic queries form the foundation of our entire implementation. &lt;code&gt;with_*&lt;/code&gt; scopes use &lt;code&gt;=&lt;/code&gt;, while &lt;code&gt;without_*&lt;/code&gt; use &lt;code&gt;!=&lt;/code&gt;, but the shapes are all the same. So, the final piece to our puzzle is generating these queries in ActiveRecord.&lt;/p&gt;




&lt;p&gt;Again, I'm not going to get bogged down in process. We don't want to use raw SQL strings if we can avoid it. Raw SQL strings are brittle in ActiveRecord usage. And we want to provide a model concern, so robustness is of particular value. This means we need to dip down and use &lt;a href="https://github.com/rails/rails/tree/main/activerecord/lib/arel"&gt;&lt;code&gt;Arel&lt;/code&gt;&lt;/a&gt;. This is the relational algebra library that sits at the foundation of ActiveRecord, with &lt;a href="https://guides.rubyonrails.org/active_record_querying.html"&gt;ActiveRecord's query interface&lt;/a&gt; built on top of Arel. For a good intro on working with &lt;code&gt;Arel&lt;/code&gt; directly, check out &lt;a href="https://jpospisil.com/2014/06/16/the-definitive-guide-to-arel-the-sql-manager-for-ruby"&gt;this post&lt;/a&gt;. I won't review those details in this post.&lt;/p&gt;

&lt;p&gt;As we stated earlier, every single query we need uses &lt;code&gt;JSON_EACH&lt;/code&gt; at its heart. So, we need to be able to generate this function in Ruby. &lt;code&gt;Arel&lt;/code&gt; provides an interface for functions that we can use like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# JSON_EACH("{table}"."{column}")&lt;/span&gt;
&lt;span class="n"&gt;json_each&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Arel&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Nodes&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;NamedFunction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"JSON_EACH"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;arel_table&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;column_name&lt;/span&gt;&lt;span class="p"&gt;]])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; &lt;code&gt;arel_table&lt;/code&gt; is available to us as we will be executing this code in the context of an ActiveRecord model concern.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;With our &lt;code&gt;json_each&lt;/code&gt; expression object ready, we could built the &lt;code&gt;.unique_tags&lt;/code&gt; method like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# SELECT DISTINCT value FROM "{table}", JSON_EACH("{table}"."{column}")&lt;/span&gt;
&lt;span class="n"&gt;define_singleton_method&lt;/span&gt; &lt;span class="ss"&gt;:"unique_&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;method_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="ss"&gt;"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;conditions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"true"&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="nb"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'value'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;arel_table&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json_each&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;distinct&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pluck&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'value'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In order to setup our &lt;code&gt;.with_any_tags&lt;/code&gt; scope, we simply need a method builder like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# SELECT "{table}".* FROM "{table}" WHERE EXISTS (SELECT 1 FROM JSON_EACH("{table}"."{column}") WHERE value IN ({values}) LIMIT 1)&lt;/span&gt;
&lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="ss"&gt;:"with_any_&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;method_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="ss"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;values&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;array_columns_sanitize_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;overlap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Arel&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;SelectManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json_each&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;project&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Arel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'value'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;in&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;take&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exists&lt;/span&gt;

  &lt;span class="n"&gt;where&lt;/span&gt; &lt;span class="n"&gt;overlap&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the corresponding &lt;code&gt;.with_all_tags&lt;/code&gt; scope looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# SELECT "{table}".* FROM "{table}" WHERE (SELECT COUNT(DISTINCT value) FROM JSON_EACH("{table}"."{column}") WHERE value IN ({values})) = {values.size};&lt;/span&gt;
&lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="ss"&gt;:"with_all_&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;method_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="ss"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;values&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;array_columns_sanitize_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Arel&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;SelectManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json_each&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;project&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Arel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'value'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;distinct&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Arel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'value'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;in&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="n"&gt;contains&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Arel&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Nodes&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Equality&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;where&lt;/span&gt; &lt;span class="n"&gt;contains&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I won't paste each method here. You can find them in &lt;a href="https://gist.github.com/fractaledmind/af105bc2f102bfba50b3f83adef5283e"&gt;the Gist&lt;/a&gt; I have provided to accompany this post. The idea is to demonstrate how we can map the SQL queries we need to &lt;code&gt;Arel&lt;/code&gt;-based Ruby code.&lt;/p&gt;

&lt;p&gt;We wrap all of this in an &lt;code&gt;ArrayColumns&lt;/code&gt; model concern and we are good to go. With a well-written schema migration and a single model concern, we have the ability to define "array column" types in our SQLite database, as well as query them as if they were an associated table, without the cost of a &lt;code&gt;JOIN&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Hopefully, this demonstrates the power and flexibility available in SQLite. Even without all of the native features and data types provided by Postgres, a little bit of ingenuity can provide equivalent functionality.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You can find the full code for the model concern detailed in &lt;a href="https://gist.github.com/fractaledmind/af105bc2f102bfba50b3f83adef5283e"&gt;this Gist&lt;/a&gt;. Check out the full script to see the full set of test cases as well.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;To understand what each method does more precisely, consider the &lt;a href="https://gist.github.com/fractaledmind/af105bc2f102bfba50b3f83adef5283e#file-array_columns_test-rb"&gt;test suite&lt;/a&gt; that I wrote. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;We use &lt;code&gt;IN&lt;/code&gt;, even with a single value, to allow the query to accommodate both singular and plural values easily. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>code</category>
      <category>ruby</category>
      <category>rails</category>
      <category>sqlite</category>
    </item>
    <item>
      <title>Optimizing SQLite compilation</title>
      <dc:creator>Stephen Margheim</dc:creator>
      <pubDate>Sun, 10 Sep 2023 10:00:40 +0000</pubDate>
      <link>https://forem.com/fractaledmind/optimizing-compilation-1k6n</link>
      <guid>https://forem.com/fractaledmind/optimizing-compilation-1k6n</guid>
      <description>&lt;p&gt;This is the next in a collection of posts on how to enhance &lt;a href="https://www.sqlite.org/index.html"&gt;SQLite&lt;/a&gt; in order to power up our &lt;a href="https://rubyonrails.org"&gt;Ruby on Rails&lt;/a&gt; applications. In this post, we dig into how to tune SQLite &lt;em&gt;at compile-time&lt;/em&gt; to better support production usage in a web application. This is a close companion to &lt;a href="//%20link%20_posts/2023-09-07-enhancing-rails-sqlite-fine-tuning.md%20"&gt;a previous post&lt;/a&gt; on optimizing the &lt;em&gt;run-time&lt;/em&gt; configuration of a SQLite database.&lt;/p&gt;




&lt;p&gt;But, before we get to the heart of the issue, a quick story. The fact that I am writing this post is a testament to the power of &lt;a href="https://www.youtube.com/watch?v=2YaEtaXYVtI"&gt;publishing your work&lt;/a&gt; and the amazingness of the Ruby open-source community. When I shared the post on optimizing SQLite configuration, &lt;a href="https://twitter.com/hopsoft?ref=fractaledmind.github.io"&gt;Nate Hopkins&lt;/a&gt; &lt;a href="https://twitter.com/hopsoft/status/1699795147050061839?s=20"&gt;replied&lt;/a&gt; sharing how he optimizes the compilation of SQLite via &lt;a href="https://gist.github.com/hopsoft/9a0bf00be2816cbe036fae5aa3d85b73"&gt;a Dockerfile&lt;/a&gt;. To be honest, I hadn't even yet considered whether it was even possible to optimize your SQLite database by optimizing your installation of SQLite. I personally don't use Docker for my Rails projects, so I couldn't use Nate's Dockerfile.&lt;/p&gt;

&lt;p&gt;I did recall, however, a blog post by Julia Evans on how &lt;a href="(https://jvns.ca/blog/2019/10/28/sqlite-is-really-easy-to-compile/)"&gt;easy SQLite is to compile&lt;/a&gt;, so I thought I might be able to write a Bash script to install and compile a project-specific installation of SQLite. Using Nate's Dockerfile as a guide and the &lt;a href="https://www.sqlite.org/howtocompile.html"&gt;SQLite documentation&lt;/a&gt; it actually wasn't too difficult. You can find the script I used in &lt;a href="https://gist.github.com/fractaledmind/6e70b23ecbd150751f6513e1b9839572"&gt;this Gist&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;After compiling a custom SQLite installation, I went researching how to tell the &lt;a href="https://github.com/sparklemotion/sqlite3-ruby"&gt;&lt;code&gt;sqlite3-ruby&lt;/code&gt; gem&lt;/a&gt; to use this SQLite installation over the system one. After trying a few different things, I couldn't get it to work. So, I do as we all do when we hit a wall, I opened &lt;a href="https://github.com/sparklemotion/sqlite3-ruby/discussions/400"&gt;a new discussion&lt;/a&gt; on the GitHub repo. One of the project's primary maintainers, &lt;a href="https://twitter.com/flavorjones?ref=fractaledmind.github.io"&gt;Mike Dalessio&lt;/a&gt;, responded quickly. We went back and forth, and he then offered to hop on a pair-programming call with me to debug on my machine. After a quick call and some further chatting, Mike realized that I had overly-complicated things.&lt;/p&gt;

&lt;p&gt;I was trying to bind the &lt;code&gt;sqlite3-ruby&lt;/code&gt; gem to a custom installation of SQLite, but all that I really wanted was the ability to set compile-time flags when installing SQLite. After Mike &lt;a href="https://github.com/sparklemotion/sqlite3-ruby/discussions/400#discussioncomment-6950366"&gt;realized the core issue&lt;/a&gt;, he quickly opened a new &lt;a href="https://github.com/sparklemotion/sqlite3-ruby/pull/402"&gt;pull request&lt;/a&gt; to allow users to set compile-time flags that the &lt;code&gt;sqlite3-ruby&lt;/code&gt; gem will use when installing and compiling SQLite. The result is a &lt;a href="https://github.com/sparklemotion/sqlite3-ruby/releases/tag/v1.6.5"&gt;new release&lt;/a&gt; of the &lt;code&gt;sqlite3-ruby&lt;/code&gt; gem that enables users to pass compile-time options.&lt;/p&gt;

&lt;p&gt;I wanted to tell the whole winding tale because I find the whole thing so remarkable. This is the real power of the Ruby/Rails community. From Nate sharing his Dockerfile to Mike working to understand what I was trying to do, and then doing all of the work to make it possible, we together were able to make something new together. I am genuinely giddy with excitement that we found such a clean and simple way to allow developer's to fine-tune their SQLite with compile-time options for their Rails apps.&lt;/p&gt;




&lt;p&gt;So, what does this mean? Well, it means that we now have full control to optimize our SQLite databases for our Rails apps. We can optimize both &lt;em&gt;compile-time&lt;/em&gt; and &lt;em&gt;run-time&lt;/em&gt; options to truly &lt;strong&gt;fine-tune&lt;/strong&gt; our SQLite databases. And having the ability to tune &lt;em&gt;compile-time&lt;/em&gt; options is a massive win for Rails apps, as the default configuration of SQLite is both driven by its commitment to backwards compatibility and its more common usage in embedded systems. In practice, neither is particularly useful for modern web application usage. The SQLite documentation even notes that its default compilation setup is &lt;a href="https://www.sqlite.org/compile.html#recommended_compile_time_options"&gt;unsuited for most practical usage&lt;/a&gt;. They then outline 12 compile-time flags that they recommend setting in order "to minimize the number of CPU cycles and the bytes of memory used by SQLite."&lt;/p&gt;

&lt;p&gt;As with our previous post, I will simplify things and show you the flags that I recommend setting up for your Rails application. But first, how to we take advantage of this new feature of the &lt;code&gt;sqlite3-ruby&lt;/code&gt; gem?&lt;/p&gt;

&lt;p&gt;First and foremost, you need to be using &lt;a href="https://github.com/sparklemotion/sqlite3-ruby/releases/tag/v1.6.5"&gt;version 1.6.5 or higher&lt;/a&gt;. You should put &lt;code&gt;gem "sqlite3", "~&amp;gt; 1.6.5"&lt;/code&gt; in your &lt;code&gt;Gemfile&lt;/code&gt;. Next, you need to tell Bundler to use the "ruby" platform gem so that Bundler will compile SQLite from source by adding the &lt;code&gt;force_ruby_platform: true&lt;/code&gt; option.&lt;sup id="fnref1"&gt;1&lt;/sup&gt; So, in full, your Gemfile entry for &lt;code&gt;sqlite3&lt;/code&gt; should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s2"&gt;"sqlite3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 1.6.5"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;force_ruby_platform: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ensures that you have an appropriate version of the &lt;code&gt;sqlite3-ruby&lt;/code&gt; gem and that when the gem compiles SQLite it won't use one of the prebuilt binaries.&lt;/p&gt;

&lt;p&gt;Next, you need to set the Bundler config option for the compile-time flags. If you've ever tweaked the compile-time flags for Nokogiri, things should look familiar. You can set the config using the &lt;code&gt;bundler&lt;/code&gt; CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bundle config &lt;span class="nb"&gt;set &lt;/span&gt;build.sqlite3 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="s2"&gt;"--with-sqlite-cflags='-DSQLITE_DEFAULT_CACHE_SIZE=9999 -DSQLITE_DEFAULT_PAGE_SIZE=4444'"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; These values are for demonstration purposes only. &lt;em&gt;Do not&lt;/em&gt; copy this and run this in your project. I will provide an appropriate set of &lt;code&gt;CFLAGS&lt;/code&gt; shortly.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Running this command will create or update your project's &lt;code&gt;.bundler/config&lt;/code&gt; file to include an option like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;BUNDLE_BUILD__SQLITE3&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--with-sqlite-cflags='-DSQLITE_DEFAULT_CACHE_SIZE=9999&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-DSQLITE_DEFAULT_PAGE_SIZE=4444'"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The use of single quotes within the double-quoted string to ensure the space between compiler flags is interpreted correctly.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's it! You only need those two changes. An update to your &lt;code&gt;Gemfile&lt;/code&gt; and your &lt;code&gt;.bundler/config&lt;/code&gt; file is all it takes to get a project-specific, fine-tuned SQLite installation. You can find these steps, as well as additional instructions for more advanced usage of the &lt;code&gt;sqlite3-ruby&lt;/code&gt; gem, in the &lt;a href="https://github.com/sparklemotion/sqlite3-ruby/blob/master/INSTALLATION.md"&gt;gem's installation documentation&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;Ok, let's get to the heart of the matter. What compile-time flags should we use? The short answer is: use what SQLite recommends, minus the ones that don't make sense for web application usage. The &lt;a href="https://www.sqlite.org/compile.html#recommended_compile_time_options"&gt;SQLite docs&lt;/a&gt; recommend 12 flags. I won't repeat their explanation of what each one does here; read the docs to learn more.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;SQLITE_DQS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0
&lt;span class="nv"&gt;SQLITE_THREADSAFE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0
&lt;span class="nv"&gt;SQLITE_DEFAULT_MEMSTATUS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0
&lt;span class="nv"&gt;SQLITE_DEFAULT_WAL_SYNCHRONOUS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
SQLITE_LIKE_DOESNT_MATCH_BLOBS
&lt;span class="nv"&gt;SQLITE_MAX_EXPR_DEPTH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0
SQLITE_OMIT_DECLTYPE
SQLITE_OMIT_DEPRECATED
SQLITE_OMIT_PROGRESS_CALLBACK
SQLITE_OMIT_SHARED_CACHE
SQLITE_USE_ALLOCA
SQLITE_OMIT_AUTOINIT
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two of these options won't work with the &lt;code&gt;sqlite3-ruby&lt;/code&gt; gem: &lt;code&gt;SQLITE_OMIT_DEPRECATED&lt;/code&gt; and &lt;code&gt;SQLITE_OMIT_DECLTYPE&lt;/code&gt;. The gem needs those features of SQLite in order to function, so we must remove them.&lt;/p&gt;

&lt;p&gt;Next, we should remove the &lt;code&gt;SQLITE_THREADSAFE=0&lt;/code&gt; option, as this is only usable by applications that never access SQLite from more than one thread at a time. In a web app, we are likely to access the database from multiple threads.&lt;/p&gt;

&lt;p&gt;We should also remove the &lt;code&gt;SQLITE_OMIT_AUTOINIT&lt;/code&gt; option as it requires applications to correctly call SQLite's &lt;code&gt;initialize&lt;/code&gt; method at appropriate times. We can't guarantee that level of control, and if you fail to call &lt;code&gt;initialize&lt;/code&gt; properly, you will get a segfault.&lt;/p&gt;

&lt;p&gt;You may also want to &lt;strong&gt;&lt;em&gt;add&lt;/em&gt;&lt;/strong&gt; the &lt;code&gt;SQLITE_ENABLE_FTS5&lt;/code&gt; option, which adds SQLite's &lt;a href="https://www.sqlite.org/fts5.html"&gt;full text search&lt;/a&gt; functionality to your build. This one depends on how you plan to use your database, but if you are currently using &lt;a href="https://www.elastic.co/elasticsearch/"&gt;ElasticSearch&lt;/a&gt; or &lt;a href="https://www.meilisearch.com"&gt;Meilisearch&lt;/a&gt;, you could investigate replacing those dependencies with SQLite.&lt;/p&gt;

&lt;p&gt;With our removals (and one possible addition), our set of flags now looks like this—9 flags to crank up SQLite's performance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;SQLITE_DQS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0
&lt;span class="nv"&gt;SQLITE_DEFAULT_MEMSTATUS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0
&lt;span class="nv"&gt;SQLITE_DEFAULT_WAL_SYNCHRONOUS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
SQLITE_LIKE_DOESNT_MATCH_BLOBS
&lt;span class="nv"&gt;SQLITE_MAX_EXPR_DEPTH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0
SQLITE_OMIT_PROGRESS_CALLBACK
SQLITE_OMIT_SHARED_CACHE
SQLITE_USE_ALLOCA
SQLITE_ENABLE_FTS5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can turn these into the Bundler config we need via the CLI command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bundle config &lt;span class="nb"&gt;set &lt;/span&gt;build.sqlite3 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="s2"&gt;"--with-sqlite-cflags='-DSQLITE_DQS=0 -DSQLITE_DEFAULT_MEMSTATUS=0 -DSQLITE_DEFAULT_WAL_SYNCHRONOUS=1 -DSQLITE_LIKE_DOESNT_MATCH_BLOBS -DSQLITE_MAX_EXPR_DEPTH=0 -DSQLITE_OMIT_PROGRESS_CALLBACK -DSQLITE_OMIT_SHARED_CACHE -DSQLITE_USE_ALLOCA -DSQLITE_ENABLE_FTS5'"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or just manually updating your project's &lt;code&gt;.bundler/config&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;BUNDLE_BUILD__SQLITE3&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--with-sqlite-cflags='-DSQLITE_DQS=0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-DSQLITE_DEFAULT_MEMSTATUS=0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-DSQLITE_DEFAULT_WAL_SYNCHRONOUS=1&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-DSQLITE_LIKE_DOESNT_MATCH_BLOBS&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-DSQLITE_MAX_EXPR_DEPTH=0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-DSQLITE_OMIT_PROGRESS_CALLBACK&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-DSQLITE_OMIT_SHARED_CACHE&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-DSQLITE_USE_ALLOCA&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-DSQLITE_ENABLE_FTS5'"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, just run &lt;code&gt;bundle install&lt;/code&gt;. That's it.&lt;/p&gt;

&lt;p&gt;In a later post, I will talk about how all of our fine-tuning adjustments come together and what the performance profile comparison is. For now, suffice it to say that simply be tweaking these compile-time options along with the run-time settings discussed previously, you will get a noticeably improved SQLite experience for your Rails app.&lt;/p&gt;

&lt;p&gt;So, we now have the ability to tweak each of the knobs that SQLite provides to fine-tune its behavior and performance characteristics. And all because the Ruby community is so amazing. I love it.&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;Note that you can only use the &lt;code&gt;force_ruby_platform: true&lt;/code&gt; on Bunder version 2.3.18 or higher. For Bundler version 2.1 or later (up to 2.3.18), you will need to run &lt;code&gt;bundle config set force_ruby_platform true&lt;/code&gt;, which has the unfortunate side-effect of setting this option globally for your Gemfile 😕. For version 2.0 or earlier, you'll need to run &lt;code&gt;bundle config force_ruby_platform true&lt;/code&gt;, which has the same side-effect. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>code</category>
      <category>ruby</category>
      <category>rails</category>
      <category>sqlite</category>
    </item>
    <item>
      <title>Setting up Litestream</title>
      <dc:creator>Stephen Margheim</dc:creator>
      <pubDate>Sat, 09 Sep 2023 13:34:30 +0000</pubDate>
      <link>https://forem.com/fractaledmind/setting-up-litestream-3n0i</link>
      <guid>https://forem.com/fractaledmind/setting-up-litestream-3n0i</guid>
      <description>&lt;p&gt;This is the next in a collection of posts where I want to highlight ways we can use &lt;a href="https://www.sqlite.org/index.html"&gt;SQLite&lt;/a&gt; as the database engine for our &lt;a href="https://rubyonrails.org"&gt;Rails&lt;/a&gt; applications without giving up key features or power. In this post, I want to discuss one of the most often discussed disadvantages of SQLite—disaster recovery—and how to address it.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://twitter.com/benbjohnson?ref=fractaledmind.github.io"&gt;Ben Johnson&lt;/a&gt; is one of my favorite people in the SQLite ecosystem, and he put the point well:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;So why is SQLite considered a “toy” database in the application development world and not a production database?&lt;br&gt;
The biggest problem with using SQLite in production is disaster recovery. If your server dies, so does your data. That’s… not good.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The fact that SQLite uses a file on the local filesystem is one of the great double-edged sword that makes it so divisive. On the one hand, having a local file completely remove network latency from queries, which is often the primary performance bottleneck for web applications. Additionally, having your database simply be a single file allows for unique possibilities when it comes to managing your application database, like using &lt;a href="//%20link%20_posts/2023-09-06-enhancing-rails-sqlite-branch-databases.md%20"&gt;branch-specific databases&lt;/a&gt;. Plus, this makes the operational complexity of your web application noticably simpler, as you don't need to run a separate server for you database. In future posts in this series, I will get into other benefits that come from SQLite's simplicity as a normal file on the filesystem.&lt;/p&gt;

&lt;p&gt;However, simply being a file on the filesystem is also SQLite's primary weakness. Persistent data storage needs to be persistent; otherwise, it isn't so useful. And I've personally experienced the danger here. I once, somewhat mindlessly while waiting for another application to deploy, renamed an application in a 3rd-party platform-as-a-service provider's dashboard, just to make the app names more consistent. I didn't know that this action in the web dashboard would lead to the PaaS using &lt;code&gt;rm -rf&lt;/code&gt; on the folder that contained my application on their servers to then redeploy under a new folder name. I am using SQLite as my production database for this app, and while I had the database stored in the &lt;code&gt;/storage&lt;/code&gt; directory to keep it safe across deployments, that didn't help at all when the entire parent directory was wiped. That one mindless update in a web UI completely wiped away about 1 years worth of production data. I tried a number of recovery techniques, but nothing worked. The data was lost.&lt;/p&gt;

&lt;p&gt;I tell this story because I don't want to gloss over this point—without a clear and strong disaster recovery plan, using SQLite as your production database is &lt;strong&gt;&lt;em&gt;dangerous&lt;/em&gt;&lt;/strong&gt; and probably foolish. There are many benefits to SQLite, and I still happily use SQLite as my production database for many applications, including the one above. I ensure, however, that I &lt;strong&gt;always&lt;/strong&gt; have a disaster recovery setup.&lt;/p&gt;

&lt;p&gt;In what follows, I want to lay out the backup and recovery setup that I use, and provide you the tools to set this up for yourself.&lt;/p&gt;




&lt;p&gt;The quote from &lt;a href="https://twitter.com/benbjohnson?ref=fractaledmind.github.io"&gt;Ben Johnson&lt;/a&gt; above comes from a &lt;a href="https://litestream.io/blog/why-i-built-litestream/"&gt;blog post&lt;/a&gt; on why he created the &lt;a href="https://litestream.io"&gt;&lt;code&gt;Litestream&lt;/code&gt;&lt;/a&gt; tool. &lt;code&gt;Litestream&lt;/code&gt; is an &lt;strong&gt;&lt;em&gt;essential&lt;/em&gt;&lt;/strong&gt; tool for someone interested in unlocking the power of SQLite for their web application to know and use. So, what is &lt;code&gt;Litestream&lt;/code&gt;? In a single sentence,&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Litestream is a streaming replication tool for SQLite databases.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let's dig into what that means more concretely. From Ben's introductory blog post, we get this fuller description:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Litestream is a tool that runs in a separate process and continuously replicates a SQLite database to Amazon S3 [or another storage provider]. You can get up and running with a few lines of configuration. Then you can set-it-and-forget-it and get back to writing code.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Put simply, &lt;code&gt;Litestream&lt;/code&gt; is your SQLite disaster recovery plan, and it is simple, robust, and resilient. For the miniscule cost of a cloud storage bucket, you can get effectively point-in-time backups of your SQLite database(s).&lt;sup id="fnref1"&gt;1&lt;/sup&gt; And it is straight-forward to setup.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://litestream.io/getting-started/"&gt;documentation&lt;/a&gt; provides guides on installing &lt;code&gt;Litestream&lt;/code&gt; on &lt;a href="https://litestream.io/install/mac/"&gt;macOS&lt;/a&gt;, &lt;a href="https://litestream.io/install/debian/"&gt;Linux (Debian)&lt;/a&gt;, or &lt;a href="https://litestream.io/install/source/"&gt;building from source&lt;/a&gt;. Personally, I deploy to a Linux (Debian) server, so I will be sharing that approach.&lt;/p&gt;

&lt;p&gt;With the package installed on your production server, you then need to get it running. Again, you have multiple options, each laid out well in the documentation. You can either run the process &lt;a href="https://litestream.io/guides/docker/"&gt;in a Docker container&lt;/a&gt;, &lt;a href="https://litestream.io/guides/kubernetes/"&gt;in a Kubernetes cluster&lt;/a&gt;, &lt;a href="https://litestream.io/guides/systemd/"&gt;as a Systemd service&lt;/a&gt;, or &lt;a href="https://litestream.io/guides/windows/"&gt;as a Windows service&lt;/a&gt;. In my case, I run &lt;code&gt;Litestream&lt;/code&gt; as a Systemd service, so we will follow that path.&lt;/p&gt;

&lt;p&gt;Finally, with the package installed and running, you need to configure it to talk to your storage provider. &lt;code&gt;Litestream&lt;/code&gt; supports a wide array of providers—&lt;a href="https://litestream.io/guides/s3/"&gt;Amazon S3&lt;/a&gt;, &lt;a href="https://litestream.io/guides/azure/"&gt;Azure Blob Storage&lt;/a&gt;, &lt;a href="https://litestream.io/guides/backblaze/"&gt;Backblaze B2&lt;/a&gt;, &lt;a href="https://litestream.io/guides/digitalocean/"&gt;DigitalOcean Spaces&lt;/a&gt;, &lt;a href="https://litestream.io/guides/scaleway/"&gt;Scaleway Object Storage&lt;/a&gt;, &lt;a href="https://litestream.io/guides/gcs/"&gt;Google Cloud Storage&lt;/a&gt;, &lt;a href="https://litestream.io/guides/linode/"&gt;Linode Object Storage&lt;/a&gt;, and &lt;a href="https://litestream.io/guides/sftp/"&gt;an SFTP Server&lt;/a&gt;. In my case, I use DigitalOcean Spaces.&lt;/p&gt;

&lt;p&gt;Hopefully, you can see that &lt;code&gt;Litestream&lt;/code&gt; is quite flexible and can be used across a multitude of different deployment situations and storage providers. I, however, only have experience with my setup. So, I will share that, with as much detail as possible, to help you hopefully get everything setup yourself.&lt;/p&gt;




&lt;p&gt;I use &lt;a href="https://hatchbox.io"&gt;Hatchbox.io&lt;/a&gt; to host my Rails applications. When using SQLite as your production database, you simply &lt;a href="https://devcenter.heroku.com/articles/sqlite3"&gt;can't use Heroku&lt;/a&gt;. But, I have fallen out of love with Heroku generally, after a decade under Salesforce's stewardship. I love Hatchbox because it allows me to "deploy to servers that I own", which mitigates the cost overhead that many PaaS providers entails, plus it tailor-made for Rails applications. It is run by &lt;a href="https://twitter.com/excid3?ref=fractaledmind.github.io"&gt;Chris Oliver&lt;/a&gt; from &lt;a href="https://gorails.com/"&gt;GoRails&lt;/a&gt; and &lt;a href="https://twitter.com/BilalBudhani?ref=fractaledmind.github.io"&gt;Bilal Budhani&lt;/a&gt;, and they offer quick and useful customer support. Salespitch aside (just a joke, I have no affiliate relationship with Hatchbox), Hatchbox is a great service, but since it mostly sits on top of servers you bring it, there is nothing in the setup of &lt;code&gt;Litestream&lt;/code&gt; that is Hatchbox-specific.&lt;/p&gt;

&lt;p&gt;In my case, I have used both DigitalOcean droplets as well as Hetzner servers through Hatchbox. In either case, I am bringing Linux machines that run Ubuntu—a Debian-based OS. Since &lt;code&gt;Litestream&lt;/code&gt; provides Debian package files, it is straight-forward to install &lt;code&gt;Litestream&lt;/code&gt; using the &lt;code&gt;dpkg&lt;/code&gt; utility.&lt;sup id="fnref2"&gt;2&lt;/sup&gt; Moreover, using a Debian-based OS allows us to use &lt;code&gt;systemd&lt;/code&gt; to run the &lt;code&gt;Litestream&lt;/code&gt; process on our behalf.&lt;/p&gt;

&lt;p&gt;Here is the Bash script I use to install and run &lt;code&gt;Litestream&lt;/code&gt; on all of my servers:&lt;sup id="fnref3"&gt;3&lt;/sup&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt;

&lt;span class="c"&gt;# Load environment&lt;/span&gt;
&lt;span class="nb"&gt;source&lt;/span&gt; /home/deploy/.bashrc

&lt;span class="c"&gt;# Determine architecture of current env&lt;/span&gt;
&lt;span class="nb"&gt;arch&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;dpkg &lt;span class="nt"&gt;--print-architecture&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="c"&gt;# Manually set the Litestream version number we are using&lt;/span&gt;
&lt;span class="nv"&gt;version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"v0.3.11"&lt;/span&gt;

&lt;span class="c"&gt;# Download the latest .deb file&lt;/span&gt;
wget &lt;span class="s2"&gt;"https://github.com/benbjohnson/litestream/releases/download/&lt;/span&gt;&lt;span class="nv"&gt;$version&lt;/span&gt;&lt;span class="s2"&gt;/litestream-&lt;/span&gt;&lt;span class="nv"&gt;$version&lt;/span&gt;&lt;span class="s2"&gt;-linux-&lt;/span&gt;&lt;span class="nv"&gt;$arch&lt;/span&gt;&lt;span class="s2"&gt;.deb"&lt;/span&gt;

&lt;span class="c"&gt;# Install that .deb file using dpkg&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;dpkg &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"litestream-&lt;/span&gt;&lt;span class="nv"&gt;$version&lt;/span&gt;&lt;span class="s2"&gt;-linux-&lt;/span&gt;&lt;span class="nv"&gt;$arch&lt;/span&gt;&lt;span class="s2"&gt;.deb"&lt;/span&gt;

&lt;span class="c"&gt;# Verify it is installed&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Litestream version:"&lt;/span&gt;
litestream version

&lt;span class="c"&gt;# Enable Litestream to run continuously as a background service&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable &lt;/span&gt;litestream

&lt;span class="c"&gt;# Start Litestream running continuously as a background service&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl start litestream

&lt;span class="c"&gt;# Verify the service is running&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Litestream service logs:"&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;journalctl &lt;span class="nt"&gt;-u&lt;/span&gt; litestream
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, the setup is straight-forward. I have run this script on both DigitalOcean and Hetzner servers with no problems. It relies on pre-installed system utilities like &lt;code&gt;dpkg&lt;/code&gt;, &lt;code&gt;wget&lt;/code&gt;, and &lt;code&gt;systemd&lt;/code&gt; so no additional setup is required. Plus, it is a small script, so it is easy to share and use and understand.&lt;/p&gt;

&lt;p&gt;If you aren't using a Debian-based OS, you will need a different setup script, but Linux and Ubuntu are quite popular, so odds are this script can be useful for you.&lt;/p&gt;

&lt;p&gt;Once you have &lt;code&gt;Litestream&lt;/code&gt; installed and running, the only thing left is to configure it to start replicating your database(s). Once again, the docs have &lt;a href="https://litestream.io/reference/config/"&gt;a page&lt;/a&gt; dedicated to configuring &lt;code&gt;Litestream&lt;/code&gt;. The summary, though, is that you will need to create an &lt;code&gt;/etc/litestream.yml&lt;/code&gt; file and then enter your YAML configuration. The basic structure of the configuration is straight-forward (you may be sensing a trend here, and this is another wonderful thing about &lt;code&gt;Litestream&lt;/code&gt;—it does straight-forward things straight-forwardly). You provide an array of &lt;code&gt;dbs&lt;/code&gt;, each with an array of &lt;code&gt;replicas&lt;/code&gt;. So, you can have your server's &lt;code&gt;Litestream&lt;/code&gt; process backing up multiple SQLite database files, plus each database file can be streamed to multiple storage providers.&lt;/p&gt;

&lt;p&gt;In my case, I use a single server for a single app, which I backup to a single storage provider. In the simplest case, I use a single SQLite file for the storage backend of my Rails app's &lt;code&gt;ActiveRecord&lt;/code&gt; models. Thus, I only have the one database. In more interesting cases (which I will be writing more about in the future), I use &lt;a href="https://github.com/oldmoe/litestack"&gt;Litestack&lt;/a&gt; to actually have SQLite back &lt;code&gt;ActionCable&lt;/code&gt;, &lt;code&gt;ActiveSupport::Cache&lt;/code&gt;, and &lt;code&gt;ActiveJob&lt;/code&gt;, such that I have four or more SQLite database files per app to backup.&lt;/p&gt;

&lt;p&gt;I won't cover how to create a cloud storage bucket. The &lt;code&gt;Litestream&lt;/code&gt; docs do a good job of that on their own. Instead, let's focus on the configuration you would have on your server. Since I am using DigitalOcean Spaces as my storage provider, I followed &lt;a href="https://litestream.io/guides/digitalocean/"&gt;the instructions&lt;/a&gt; from the &lt;code&gt;Litestream&lt;/code&gt; docs. You will need an &lt;code&gt;access-key-id&lt;/code&gt; and a &lt;code&gt;secret-access-key&lt;/code&gt; to allow &lt;code&gt;Litestream&lt;/code&gt; to connect to your storage provider. The configuration file supports setting those values globally as well as on a per-replica basis. To make adding replicas easier in the future, I default to setting them on a per-replica basis. Thus, my configuration file looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# /etc/litestream.yml&lt;/span&gt;
&lt;span class="na"&gt;dbs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/home/deploy/application-name/current/storage/production.sqlite3&lt;/span&gt;
    &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;s3://bucket-name.litestream.region.digitaloceanspaces.com/production&lt;/span&gt;
        &lt;span class="na"&gt;access-key-id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;xxxxxxxxxxxxxxxxxxxx&lt;/span&gt;
        &lt;span class="na"&gt;secret-access-key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;xxxxxxxxx/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again, straight-forward. All you need to do is point &lt;code&gt;Litestream&lt;/code&gt; at your database file, then point it at your storage bucket (with access credentials). It handles everything else.&lt;/p&gt;

&lt;p&gt;Since our installation script starts the &lt;code&gt;Litestream&lt;/code&gt; process immediately after installation, we will likely be creating the configuration file next. Once you have gotten your configuration file setup properly, you will need to restart the &lt;code&gt;Litestream&lt;/code&gt; process to pick up your new config file. As we are using &lt;code&gt;systemd&lt;/code&gt;, this is as simple as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart litestream
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With &lt;code&gt;Litestream&lt;/code&gt; installed, configured, and running, you are ready to go. If you write to your database and then visit your storage bucket, you will see that &lt;code&gt;Litestream&lt;/code&gt; has already written data there. And since no backup is viable until we have verified that we can recover with it, let us simulate a disaster and run through the recovery steps.&lt;/p&gt;

&lt;p&gt;I like to simply rename my database file to mimic a deletion. If we imagine that we moved our &lt;code&gt;/home/deploy/application-name/current/storage/production.sqlite3&lt;/code&gt; database file to &lt;code&gt;/home/deploy/application-name/current/storage/-deleted.sqlite3&lt;/code&gt;, how can we recover our &lt;code&gt;production.sqlite3&lt;/code&gt; file with &lt;code&gt;Litestream&lt;/code&gt;? From our server's command line, we need to run&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;litestream restore production.sqlite3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. We don't have the provide the entire file path, we don't need any esoteric command line arguments, just &lt;code&gt;litestream restore&lt;/code&gt;. This command will find the database in the configuration file and restore the most recent copy it has from its storage replica. Confirm that the file is present then run&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sqlite3 production.sqlite3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to inspect the contents of the database and check that the most recent data from before the "deletion" is present.&lt;/p&gt;

&lt;p&gt;Once you have confirmed that your recovery process is up and running, you are good to go. Using &lt;code&gt;Litestream&lt;/code&gt; we have now mitigated the primary weakness with using SQLite in production for our web application persistence layer. We can now enjoy the DX benefits that come with using SQLite without the constant worry about disaster recovery.&lt;sup id="fnref4"&gt;4&lt;/sup&gt;&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;For those of you interested in &lt;em&gt;how&lt;/em&gt; &lt;code&gt;Litestream&lt;/code&gt; works, the documentation has a very &lt;a href="https://litestream.io/how-it-works/"&gt;understandable page&lt;/a&gt;. I would heartily recommend just reading that in its entirety. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;For an introduction to &lt;code&gt;dpkg&lt;/code&gt;, I would recommend &lt;a href="https://www.digitalocean.com/community/tutorials/dpkg-command-in-linux"&gt;this article&lt;/a&gt;. Otherwise, the short description from the &lt;code&gt;man&lt;/code&gt; page hopefully provides enough of a sense: "dpkg is a tool to install, build, remove and manage Debian packages." ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn3"&gt;
&lt;p&gt;As of the time of this post (September 9&lt;sup&gt;th&lt;/sup&gt;, 2023), the latest release of &lt;code&gt;Litestream&lt;/code&gt; is version 0.3.11. If you run this script at some point in the future and there is a newer release, replace the &lt;code&gt;version&lt;/code&gt; variable with that version number. You can always find the most recent release on the project's &lt;a href="https://github.com/benbjohnson/litestream/releases"&gt;GitHub Releases page&lt;/a&gt;. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn4"&gt;
&lt;p&gt;One alternative for a disaster recovery plan is to use remote attached storage, like AWS EBS or similar. From your application's point of view, this is still a local filesystem, but if your server dies, the data doesn't. You can then "recover" the data by simply reattaching the storage to another server. The key details with this solution is to ensure that your memory-map is large enough to ensure that reads are basically as fast as with true local storage, plus make sure that you set the &lt;code&gt;synchronous&lt;/code&gt; pragma to &lt;code&gt;NORMAL&lt;/code&gt; to minimize &lt;code&gt;fsync&lt;/code&gt; calls on writes, as these will be much slower with the attached storage. Perhaps most importantly, though, don't even get tempted by the idea of using a remote filesystem like NFS. But, as one of my &lt;a href="https://twitter.com/oldmoe?ref=fractaledmind.github.io"&gt;SQLite guru's&lt;/a&gt; has &lt;a href="https://twitter.com/oldmoe/status/1699870046871343516"&gt;said&lt;/a&gt;: "&lt;em&gt;[W]ith attached storage you get durability and availability, and SQLite can be tuned so it gets very close to local storage performance wise. [Plus,] Google &amp;amp; AWS offer auto instance recovery, so if your instance goes down another one is spawned and the storage is reattached, this happens in seconds and delivers a pretty high level of availability for your SQLite powered apps.&lt;/em&gt;" ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>code</category>
      <category>ruby</category>
      <category>rails</category>
      <category>sqlite</category>
    </item>
    <item>
      <title>Loading extensions</title>
      <dc:creator>Stephen Margheim</dc:creator>
      <pubDate>Fri, 08 Sep 2023 09:11:16 +0000</pubDate>
      <link>https://forem.com/fractaledmind/loading-extensions-4nm3</link>
      <guid>https://forem.com/fractaledmind/loading-extensions-4nm3</guid>
      <description>&lt;p&gt;Once again we are enhancing our &lt;a href="https://rubyonrails.org"&gt;Ruby on Rails&lt;/a&gt; applications to power up &lt;a href="https://www.sqlite.org/index.html"&gt;SQLite&lt;/a&gt;. In this post, we dig into how to load extensions into our SQLite database.&lt;/p&gt;




&lt;p&gt;Personally, I find SQLite to be essentially feature complete, but sometimes you have specific needs for your database that SQLite doesn't support. Luckily, SQLite offers a rich extension ecosystem. There is an &lt;a href="https://sqlpkg.org"&gt;(unofficial) package manager&lt;/a&gt;—&lt;code&gt;sqlpkg&lt;/code&gt;, an &lt;a href="https://github.com/nalgeon/sqlean"&gt;(unofficial) standard library&lt;/a&gt;—&lt;code&gt;sqlean&lt;/code&gt;, and a rich collection of &lt;a href="https://github.com/asg017/sqlite-ecosystem"&gt;Alex Garcia extensions&lt;/a&gt;. For a general introduction to installing SQLite extensions, read &lt;a href="https://antonz.org/install-sqlite-extension/"&gt;this post&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We want, however, a simple way to install and load SQLite extensions in a Rails application. Unfortunately, at the moment the &lt;code&gt;sqlpkg&lt;/code&gt; and &lt;code&gt;sqlean&lt;/code&gt; extension collections do not provide Ruby gem releases. Fortunately though, Alex Garcia &lt;em&gt;does&lt;/em&gt; release each of his extensions as a Ruby gem. You can find all of his extensions under his &lt;a href="https://rubygems.org/profiles/asg017"&gt;RubyGems' profile&lt;/a&gt;. Let's focus on how to make it easy to install and load one of &lt;em&gt;these&lt;/em&gt; extensions.&lt;/p&gt;

&lt;p&gt;The installation is simple, as these are Ruby gems. We can simply use &lt;code&gt;bundle add {extension-name}&lt;/code&gt;. Loading is the tricky part.&lt;/p&gt;

&lt;p&gt;Before extensions are loaded, we have to first enable extension loading for the SQLite database. The &lt;a href="https://github.com/sparklemotion/sqlite3-ruby"&gt;&lt;code&gt;SQLite3&lt;/code&gt; Ruby adapter&lt;/a&gt; provides a &lt;code&gt;#enable_load_extension&lt;/code&gt; method for this purpose. Alex Garcia's extensions then provide a &lt;code&gt;.load&lt;/code&gt; method on the Ruby extension class that will load the extension. So, in full we would need to do the following to load an extension in Ruby:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="vi"&gt;@raw_connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enable_load_extension&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="no"&gt;SqliteExtension&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@raw_connection&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="vi"&gt;@raw_connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enable_load_extension&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;We want to enhance Rails, though, to make the developer experience clean. So, how can we expose this functionality more elegantly? Luckily, in our &lt;a href="//%20link%20_posts/2023-09-07-enhancing-rails-sqlite-fine-tuning.md%20"&gt;previous post&lt;/a&gt; we introduced an enhancement to the &lt;code&gt;SQLite3&lt;/code&gt; adapter which provides a hook for configuring the database from options set in the &lt;code&gt;/config/database.yml&lt;/code&gt; file. We can add support for an &lt;code&gt;extensions&lt;/code&gt; section, which will accept an array of extension names. We can then add to our &lt;code&gt;configure_connection&lt;/code&gt; method to iterate over these extension names and load them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;RailsExt&lt;/span&gt;
  &lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;SQLite3Adapter&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;configure_connection&lt;/span&gt;
      &lt;span class="c1"&gt;# ...&lt;/span&gt;

      &lt;span class="vi"&gt;@raw_connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enable_load_extension&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="vi"&gt;@config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:extensions&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;extension_name&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
        &lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="n"&gt;extension_name&lt;/span&gt;
        &lt;span class="n"&gt;extension_classname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;extension_name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;camelize&lt;/span&gt;
        &lt;span class="n"&gt;extension_class&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;extension_classname&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;constantize&lt;/span&gt;
        &lt;span class="n"&gt;extension_class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@raw_connection&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;LoadError&lt;/span&gt;
        &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Failed to find the SQLite extension gem: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;extension_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;. Skipping..."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;NameError&lt;/span&gt;
        &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Failed to find the SQLite extension class: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;extension_classname&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;. Skipping..."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
      &lt;span class="vi"&gt;@raw_connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enable_load_extension&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After &lt;code&gt;bundle add {extension-name}&lt;/code&gt;, we can simply add the extension to the &lt;code&gt;extensions&lt;/code&gt; section in the &lt;code&gt;/config/database.yml&lt;/code&gt; file. Our &lt;code&gt;RailsExt::SQLite3Adapter&lt;/code&gt; will then handle the rest, dealing with possible errors as well. This means we can have a &lt;code&gt;default&lt;/code&gt; section like so to load &lt;a href="https://github.com/asg017/sqlite-ulid"&gt;an extension&lt;/a&gt; for supporting &lt;a href="https://github.com/ulid/spec"&gt;ULIDs&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nl"&gt;&amp;amp;default&lt;/span&gt;
  &lt;span class="na"&gt;adapter&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sqlite3&lt;/span&gt;
  &lt;span class="na"&gt;pool&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %&amp;gt;&lt;/span&gt;
  &lt;span class="c1"&gt;# connection attempts to make immediately before throwing a BUSY exception&lt;/span&gt;
  &lt;span class="na"&gt;retries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1000&lt;/span&gt;
  &lt;span class="na"&gt;extensions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;sqlite_ulid&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What I love about this approach to loading SQLite extensions is that extensions are &lt;em&gt;explicitly&lt;/em&gt; installed (in the &lt;code&gt;Gemfile&lt;/code&gt;) and loaded (in the &lt;code&gt;database.yml&lt;/code&gt; file), plus it naturally builds on top of our existing enhancement to the SQLite adapter. In total, our enhanced adapter now supports &lt;a href="//%20link%20_posts/2023-09-07-enhancing-rails-sqlite-fine-tuning.md%20"&gt;pragma configuration&lt;/a&gt; as well as extension loading. Plus, our database configuration powers a &lt;a href="//%20link%20_posts/2023-09-06-enhancing-rails-sqlite-branch-databases.md%20"&gt;Git branch-bound database branching approach&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This provides a rich and powerful set of functionality for local development. In the next post, we will dig into how to install and setup &lt;a href="https://litestream.io"&gt;&lt;code&gt;Litestream&lt;/code&gt;&lt;/a&gt; so that our production database will have point-in-time backups and recovery. Exiting things ahead!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You can find the files we have written throughout this post in &lt;a href="https://gist.github.com/fractaledmind/3565e12db7e59ab46f839025d26b5715/266030cb6053f05f234509c39fd07ed3d59f09c0"&gt;this Gist&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>code</category>
      <category>ruby</category>
      <category>rails</category>
      <category>sqlite</category>
    </item>
    <item>
      <title>Linked headings in your BridgetownRB site</title>
      <dc:creator>Stephen Margheim</dc:creator>
      <pubDate>Thu, 07 Sep 2023 13:11:10 +0000</pubDate>
      <link>https://forem.com/fractaledmind/linked-headings-in-your-bridgetownrb-site-2o25</link>
      <guid>https://forem.com/fractaledmind/linked-headings-in-your-bridgetownrb-site-2o25</guid>
      <description>&lt;p&gt;&lt;a href="https://www.bridgetownrb.com" rel="noopener noreferrer"&gt;BridgetownRB&lt;/a&gt; is a powerful and flexible "progressive site generator" written in Ruby. I use it to publish this blog. One feature that I wanted to support with my blog is having headings that provide a quick anchor link to that section of the page. In this post, I want to walk you through the simple steps to add this feature to a Bridgetown site.&lt;/p&gt;




&lt;p&gt;Before we jump into the code, let's ensure that we are all on the same page about the feature we are building. I want headings to behave like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ffractaledmind.github.io%2Fimages%2Flinked-headers.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ffractaledmind.github.io%2Fimages%2Flinked-headers.gif" alt="A video of a heading in a website that reveals a # on hover which is an anchor link to that particular heading"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That is, I want headings that reveal a &lt;code&gt;#&lt;/code&gt; on hover which is an anchor link to that particular section of the page. This is common interaction, perhaps most commonly seen in GitHub README's.&lt;/p&gt;

&lt;p&gt;With its &lt;a href="https://www.bridgetownrb.com/docs/plugins" rel="noopener noreferrer"&gt;plugin system&lt;/a&gt;, Bridgetown is easy to extend. In our case, we want to create a plugin that will inspect and manipulate the generated HTML of a page. Bridgetown provides the &lt;a href="https://www.bridgetownrb.com/docs/plugins/inspectors" rel="noopener noreferrer"&gt;Inspector API&lt;/a&gt; for precisely this use-case.&lt;/p&gt;

&lt;p&gt;To create a local plugin, all we need to do is create a new Ruby file in the &lt;code&gt;/plugins/builders&lt;/code&gt; directory. Following the Bridgetown convention, we will name our inspector plugin class &lt;code&gt;Builders::Inspectors&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# /plugins/builders/inspectors.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Builders::Inspectors&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;SiteBuilder&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;build&lt;/span&gt;
    &lt;span class="n"&gt;inspect_html&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;document&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="c1"&gt;# ...&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bridgetown will automatically load this plugin when it runs, so this is literally all we need to do in order to get our plugin setup and used. The only thing left is to write the logic for manipulating our headings.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;inspect_html&lt;/code&gt; method that Bridgetown provides yields a &lt;a href="https://nokogiri.org/" rel="noopener noreferrer"&gt;&lt;code&gt;Nokogiri&lt;/code&gt;&lt;/a&gt; &lt;code&gt;document&lt;/code&gt; object to our block. We can use the &lt;a href="https://nokogiri.org/rdoc/Nokogiri/XML/Searchable.html#method-i-css" rel="noopener noreferrer"&gt;&lt;code&gt;#css&lt;/code&gt;&lt;/a&gt; method to find our page headings. In our case, we only want content headings, which means we only want headings under the &lt;code&gt;&amp;lt;main&amp;gt;&lt;/code&gt; tag and only non-&lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt; headings. Moreover, we can only link to a heading if it has an &lt;code&gt;id&lt;/code&gt; attribute. So, we need a CSS selector like so: &lt;code&gt;main h2[id],h3[id],h4[id],h5[id],h6[id]&lt;/code&gt;. This finds precisely the headings we are after. For each heading, we then simply want to append an &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; tag with an &lt;code&gt;href&lt;/code&gt; that points to the anchor link for that heading's &lt;code&gt;id&lt;/code&gt;. Let's write this up in Ruby:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Builders::Inspectors&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;SiteBuilder&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;build&lt;/span&gt;
    &lt;span class="n"&gt;inspect_html&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;document&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"main h2[id],h3[id],h4[id],h5[id],h6[id]"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;heading&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
        &lt;span class="n"&gt;heading&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="sx"&gt;%(
          &amp;lt;a href="#&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;heading&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sx"&gt;" class="anchor" aria-hidden="true"&amp;gt;#&amp;lt;/a&amp;gt;
        )&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We add the &lt;code&gt;.anchor&lt;/code&gt; class to allow us to style this &lt;code&gt;#&lt;/code&gt; as we desire and the &lt;code&gt;aria-hidden="true"&lt;/code&gt; attribute to remove this link from the accessibility tree, since this link provides no utility to screen readers.&lt;/p&gt;

&lt;p&gt;With our anchor links appended to our headings, we only need to style the interaction. We want the &lt;code&gt;#&lt;/code&gt; to be visually hidden by default, only shown when hovering the heading, and to not have the standard link underline text. This can be accomplished with the following CSS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;aria-hidden&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;"true"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;visibility&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;hidden&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.anchor&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;text-decoration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;h2&lt;/span&gt;&lt;span class="nd"&gt;:hover&lt;/span&gt; &lt;span class="nc"&gt;.anchor&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
&lt;span class="nt"&gt;h3&lt;/span&gt;&lt;span class="nd"&gt;:hover&lt;/span&gt; &lt;span class="nc"&gt;.anchor&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
&lt;span class="nt"&gt;h4&lt;/span&gt;&lt;span class="nd"&gt;:hover&lt;/span&gt; &lt;span class="nc"&gt;.anchor&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
&lt;span class="nt"&gt;h5&lt;/span&gt;&lt;span class="nd"&gt;:hover&lt;/span&gt; &lt;span class="nc"&gt;.anchor&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
&lt;span class="nt"&gt;h6&lt;/span&gt;&lt;span class="nd"&gt;:hover&lt;/span&gt; &lt;span class="nc"&gt;.anchor&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;visibility&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;visible&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing too fancy or complicated, but it gets the job done. Add this CSS to your &lt;code&gt;/frontend/styles/index.css&lt;/code&gt; file, or some component file imported into &lt;code&gt;index.css&lt;/code&gt; and you are good to go.&lt;/p&gt;

&lt;p&gt;These two additions are all you need to setup linked headings in your current or next Bridgetown site. If you enjoyed this tip, please do reach out on Twitter &lt;a href="http://twitter.com/fractaledmind?ref=fractaledmind.github.io" rel="noopener noreferrer"&gt;@fractaledmind&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You can find the files we have written throughout this post in &lt;a href="https://gist.github.com/fractaledmind/7b52e7e84b396780dcb99f5e0c81f4e6" rel="noopener noreferrer"&gt;this Gist&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>code</category>
      <category>ruby</category>
      <category>bridgetown</category>
    </item>
    <item>
      <title>Fine-tuning your SQLite database</title>
      <dc:creator>Stephen Margheim</dc:creator>
      <pubDate>Thu, 07 Sep 2023 13:07:16 +0000</pubDate>
      <link>https://forem.com/fractaledmind/enhancing-your-rails-app-with-sqlite-fine-tuning-your-database-52b7</link>
      <guid>https://forem.com/fractaledmind/enhancing-your-rails-app-with-sqlite-fine-tuning-your-database-52b7</guid>
      <description>&lt;p&gt;This is the next in a collection of posts where I want to highlight ways we can enhance our &lt;a href="https://rubyonrails.org"&gt;Ruby on Rails&lt;/a&gt; applications to take advantage of and empower using &lt;a href="https://www.sqlite.org/index.html"&gt;SQLite&lt;/a&gt; as the database engine for our Rails applications. In this post, we dig into how to tune the SQLite configuration to better support production usage in a web application.&lt;/p&gt;




&lt;p&gt;Before jumping into the tuned configuration, let's step back and get familiar with how SQLite is configured. SQLite uses a custom SQL statement for configuration—the &lt;code&gt;PRAGMA&lt;/code&gt; statement:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The PRAGMA statement is an SQL extension specific to SQLite and used to modify the operation of the SQLite library or to query the SQLite library for internal (non-table) data.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;As we can see from this definition, there are two basic kinds of &lt;code&gt;PRAGMA&lt;/code&gt; statements:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;those that "modify the operation of the SQLite library", and&lt;/li&gt;
&lt;li&gt;those that "query the SQLite library for internal data"&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For configuring SQLite, we are interested in the former, and not the latter. The SQLite documentation provides &lt;a href="https://www.sqlite.org/pragma.html"&gt;a page with an overview of every &lt;code&gt;PRAGMA&lt;/code&gt; statement&lt;/a&gt;. Filtering out deprecated pragmas, specialized pragmas, and internal data pragmas, we are left with this list of 40 "configuration" pragmas:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;analysis_limit
application_id
auto_vacuum
automatic_index
busy_timeout
cache_size
cache_spill
case_sensitive_like
cell_size_check
checkpoint_fullfsync
data_version
defer_foreign_keys
encoding
foreign_keys
freelist_count
fullfsync
hard_heap_limit
ignore_check_constraints
integrity_check
journal_mode
journal_size_limit
legacy_alter_table
locking_mode
max_page_count
mmap_size
page_count
page_size
query_only
quick_check
read_uncommitted
recursive_triggers
reverse_unordered_selects
secure_delete
soft_heap_limit
synchronous
temp_store
threads
trusted_schema
user_version
wal_autocheckpoint
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I created a new Rails &lt;code&gt;7.0.7.2&lt;/code&gt; application and checked the values for each of these pragmas to see how Rails and SQLite are setup by default in a new application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"analysis_limit"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s2"&gt;"application_id"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s2"&gt;"auto_vacuum"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s2"&gt;"automatic_index"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s2"&gt;"timeout"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s2"&gt;"cache_size"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;-&lt;/span&gt;&lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s2"&gt;"cache_spill"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;483&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s2"&gt;"cell_size_check"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s2"&gt;"checkpoint_fullfsync"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s2"&gt;"data_version"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s2"&gt;"defer_foreign_keys"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s2"&gt;"encoding"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"UTF-8"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s2"&gt;"foreign_keys"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s2"&gt;"freelist_count"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s2"&gt;"fullfsync"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s2"&gt;"hard_heap_limit"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s2"&gt;"ignore_check_constraints"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s2"&gt;"integrity_check"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"ok"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s2"&gt;"journal_mode"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"delete"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s2"&gt;"journal_size_limit"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s2"&gt;"legacy_alter_table"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s2"&gt;"locking_mode"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"normal"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s2"&gt;"max_page_count"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;1073741823&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s2"&gt;"mmap_size"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s2"&gt;"page_count"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s2"&gt;"page_size"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;4096&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s2"&gt;"query_only"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s2"&gt;"quick_check"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"ok"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s2"&gt;"read_uncommitted"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s2"&gt;"recursive_triggers"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s2"&gt;"reverse_unordered_selects"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s2"&gt;"secure_delete"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s2"&gt;"soft_heap_limit"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s2"&gt;"synchronous"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s2"&gt;"temp_store"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s2"&gt;"threads"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s2"&gt;"trusted_schema"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s2"&gt;"user_version"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s2"&gt;"wal_autocheckpoint"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; This output was achieved with this command:&lt;/p&gt;


&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;pragmas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;({})&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;memo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pragma&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;memo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;merge!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"PRAGMA &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;pragma&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;

&lt;p&gt;This is interesting, but of course not every pragma is equally important for tuning Rails/ActiveRecord. So, let's focus in on the most impactful pragmas. There are around &lt;em&gt;six&lt;/em&gt; pragmas that play a big role in performance, especially in the context of a web application:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;journal_mode&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;synchronous&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;journal_size_limit&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mmap_size&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cache_size&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;busy_timeout&lt;/code&gt;/&lt;code&gt;busy_handler&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is important that we understand what each of these pragmas does, and how best to tune them for our usage in Rails and ActiveRecord.&lt;/p&gt;




&lt;h3&gt;
  
  
  The &lt;code&gt;journal_mode&lt;/code&gt; pragma
&lt;/h3&gt;

&lt;p&gt;The first and most important pragma to understand and tune is the &lt;a href="https://www.sqlite.org/pragma.html#pragma_journal_mode"&gt;&lt;code&gt;journal_mode&lt;/code&gt; pragma&lt;/a&gt;. Since version 3.7.0 (2010-07-21) SQLite has offered two implementations to support the atomic transactions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the &lt;a href="https://www.sqlite.org/lockingv3.html#rollback"&gt;Rollback journal&lt;/a&gt;, and&lt;/li&gt;
&lt;li&gt;the &lt;a href="https://www.sqlite.org/wal.html"&gt;Write-Ahead log&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The rollback journal is the default and original implementation, while the write-ahead log is the newer implementation. The &lt;code&gt;journal_mode&lt;/code&gt; pragma has &lt;em&gt;five&lt;/em&gt; options to tune how the &lt;strong&gt;rollback journal&lt;/strong&gt; implementation behaves (&lt;code&gt;DELETE&lt;/code&gt;, &lt;code&gt;TRUNCATE&lt;/code&gt;, &lt;code&gt;PERSIST&lt;/code&gt;, &lt;code&gt;MEMORY&lt;/code&gt;, and &lt;code&gt;OFF&lt;/code&gt;), and &lt;em&gt;one&lt;/em&gt; option that tells SQLite to simply use the write-ahead log implementation (&lt;code&gt;WAL&lt;/code&gt;). By default, our new Rails app uses the rollback journal with the &lt;code&gt;DELETE&lt;/code&gt; journal mode. This means that the rollback journal file will be deleted from disk after each transaction commits.&lt;/p&gt;

&lt;p&gt;For web applications, the write-ahead log is the superior option. As the &lt;a href="https://www.sqlite.org/wal.html"&gt;SQLite documentation outlines&lt;/a&gt;, the write-ahead logs comes with a few advantages over the rollback journal that are especially important in the context of a web application:&lt;/p&gt;

&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;WAL is significantly faster in most scenarios.&lt;/li&gt;
&lt;li&gt;WAL provides more concurrency as readers do not block writers and a writer does not block readers. Reading and writing can proceed concurrently.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is what we want in our application. We want faster queries and increased concurrency. So, the very first configuration change that we will need to make is to set the &lt;code&gt;journal_mode&lt;/code&gt; pragma to &lt;code&gt;WAL&lt;/code&gt; (we will get into the technical details of &lt;em&gt;how&lt;/em&gt; to do this in Rails &lt;a href=""&gt;later&lt;/a&gt; in this post).&lt;/p&gt;

&lt;h3&gt;
  
  
  The &lt;code&gt;synchronous&lt;/code&gt; pragma
&lt;/h3&gt;

&lt;p&gt;SQLite supports four different modes for the &lt;a href="https://www.sqlite.org/pragma.html#pragma_synchronous"&gt;&lt;code&gt;synchronous&lt;/code&gt; pragma&lt;/a&gt;, which controls when and how SQLite flushes content to disk. The two common options are &lt;code&gt;FULL&lt;/code&gt; and &lt;code&gt;NORMAL&lt;/code&gt;, which map to "sync on every write" and "sync every 1000 written pages" respectively. Each mode has an integer value as well, so the &lt;code&gt;"synchronous"=&amp;gt;2&lt;/code&gt; default value we see for our new Rails app maps to the &lt;code&gt;FULL&lt;/code&gt; mode. This means that SQLite syncs data with the file on disk after every write. As they say in the documentation:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This ensures that an operating system crash or power failure will not corrupt the database. FULL synchronous is very safe, but it is also slower.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Slow isn't what we want. And, it isn't what we need. As the SQLite documentation says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The synchronous=NORMAL setting is a good choice for most applications running in WAL mode.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In short, when &lt;code&gt;journal_mode&lt;/code&gt; is &lt;code&gt;WAL&lt;/code&gt;, simply set &lt;code&gt;synchronous&lt;/code&gt; to &lt;code&gt;NORMAL&lt;/code&gt;. These two go together like peanut butter and jelly.&lt;/p&gt;

&lt;p&gt;But, what precisely is the &lt;code&gt;NORMAL&lt;/code&gt; sync mode? It simply means that SQLite will flush to disk less often than after &lt;em&gt;every single&lt;/em&gt; write. SQLite has its own algorithm for determining the "most critical moments" to write to disk, where it syncs every &lt;code&gt;wal_autocheckpoint&lt;/code&gt; pages (which defaults to 1000). So, if/when the &lt;code&gt;wal_autocheckpoint&lt;/code&gt; pragma is changed, &lt;code&gt;NORMAL&lt;/code&gt; mode syncs would occur after that many pages are written. So, we trade an aggressive approach to durability for speed. However, SQLite does a lot to mitigate the reduction in durability, and it is honestly an extreme edge-case. In fact, SQLite ensures that any potential data loss could only happen with OS or filesystem failure; any process crash won't affect data durability. So, we are optimizing for the 99% case and not the 1% case, which I think is appropriate for a Rails application.&lt;/p&gt;

&lt;h3&gt;
  
  
  The &lt;code&gt;journal_size_limit&lt;/code&gt; pragma
&lt;/h3&gt;

&lt;p&gt;Next up we have the &lt;a href="https://www.sqlite.org/pragma.html#pragma_journal_size_limit"&gt;&lt;code&gt;journal_size_limit&lt;/code&gt; pragma&lt;/a&gt;. This tells SQLite how much of the write-ahead log data (in our case) to keep in the on-disk file. The default of &lt;code&gt;-1&lt;/code&gt; means that there is no limit, so this disk file will grow in size indefinitely. This is not what we want. Anyone who has experienced app downtime because log files filled up your disk space no that unlimited file size is just a massive headache waiting to happen. We need to ensure that the file size is capped at an appropriate size. But, what exactly is an appropriate size?&lt;/p&gt;

&lt;p&gt;Well, we don't want it to be too small. The more data is in the journal file, the faster SQLite will be (generally). However, we also don't want it to be too big, as this can start to negatively impact read performance. Based on production usage and experimentation, I have landed on &lt;strong&gt;64 megabytes&lt;/strong&gt; as a solid default for this setting.&lt;/p&gt;

&lt;h3&gt;
  
  
  The &lt;code&gt;mmap_size&lt;/code&gt; pragma
&lt;/h3&gt;

&lt;p&gt;Next up, we have the abbreviated pragma &lt;a href="https://www.sqlite.org/pragma.html#pragma_mmap_size"&gt;&lt;code&gt;mmap_size&lt;/code&gt;&lt;/a&gt;. This setting controls the "the maximum number of bytes of the database file that will be accessed using memory-mapped I/O." This is a mouth-full, but the gist is that when we enable memory-mapped I/O, we are allowing SQLite to share data among multiple processes. The memory map plays a similar role to Postgres' buffer pool, so instead of disabling it, we should set it to the same safe as the default Postgres buffer pool—&lt;strong&gt;128MB&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The &lt;code&gt;cache_size&lt;/code&gt; pragma
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://www.sqlite.org/pragma.html#pragma_cache_size"&gt;&lt;code&gt;cache_size&lt;/code&gt; pragma&lt;/a&gt; sets the "maximum number of database disk pages that SQLite will hold in memory at once per open database file." The default value of &lt;code&gt;-2000&lt;/code&gt; is a negative number, which SQLite interprets as a byte limit. If we use a positive number, SQLite will interpret this as a page limit. The default limit is ~2MB (2,048,000 bytes) and is independent of the number of pages. We want to ensure that we have a large cache and that it doesn't split across pages, so let's use a positive number to set the cache limit to a page number. I recommend &lt;strong&gt;2,000&lt;/strong&gt; pages as the &lt;code&gt;cache_size&lt;/code&gt;, which, with the default page size of 4,096 bytes, means that the cache limit is ~8MB (8,192,000 bytes).&lt;/p&gt;

&lt;p&gt;It is worth noting, for full understanding, that the page cache is private to each connection (even those in the same process), and it gets invalidated once another connection writes to the database file. It is nonetheless quite useful within the boundaries of a statement or a transaction to maximize concurrency speed.&lt;/p&gt;

&lt;h3&gt;
  
  
  The &lt;code&gt;busy_timeout&lt;/code&gt;/&lt;code&gt;busy_handler&lt;/code&gt; pragma
&lt;/h3&gt;

&lt;p&gt;The final pragma that is important to understand and tune is the &lt;a href="https://www.sqlite.org/pragma.html#pragma_busy_timeout"&gt;&lt;code&gt;busy_timeout&lt;/code&gt; pragma&lt;/a&gt;. This tells SQLite how long to wait to successfully connect to the database when trying to establish a new connection. When you create a new Rails app with SQLite, Rails will set the &lt;code&gt;timeout&lt;/code&gt; option in the &lt;code&gt;/config/database.yml&lt;/code&gt; to &lt;code&gt;5000&lt;/code&gt; milliseconds. SQLite uses an exponential backoff algorithm to retry connection attempts for as long as you specify the timeout (the backoff waits &lt;code&gt;1, 2, 5, 10, 15, 20, 25, 25, 25, 50, 50, 100&lt;/code&gt; milliseconds between each attempt, retrying every 100 milliseconds once 12 retries have been attempted&lt;sup id="fnref1"&gt;1&lt;/sup&gt;). This is a reasonable default, but for more aggressive performance tuning we could manually set &lt;a href="https://www.sqlite.org/c3ref/busy_handler.html"&gt;a &lt;code&gt;busy_handler&lt;/code&gt;&lt;/a&gt; instead. The &lt;code&gt;busy_timeout&lt;/code&gt; provides a higher level interface for setting the &lt;code&gt;busy_handler&lt;/code&gt; that SQLite will use. It is possible, however, to set a custom &lt;code&gt;busy_handler&lt;/code&gt; function ourselves tho. A common approach is to eschew exponential backoff and simply retry the connection as quickly as possible to establish a connection as soon as possible. In order to prevent infinite retries, we can simply cap the maximum number of retry attempts. Using the &lt;a href="https://github.com/sparklemotion/sqlite3-ruby"&gt;&lt;code&gt;SQLite3&lt;/code&gt; Ruby adapter&lt;/a&gt;, we can set a &lt;code&gt;busy_handler&lt;/code&gt; by passing a proc, e.g.:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="vi"&gt;@raw_connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;busy_handler&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="vi"&gt;@config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:retries&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the implementation section, we will discuss how to enable our Rails application to set a max retries instead of a max timeout. For now, let's suffice to say that whether you use a &lt;code&gt;busy_timeout&lt;/code&gt; or a &lt;code&gt;busy_handler&lt;/code&gt; comes down to how optimistic you are about how and when you might experience &lt;code&gt;BUSY&lt;/code&gt; exceptions.&lt;sup id="fnref2"&gt;2&lt;/sup&gt; For most Rails applications, I would recommend setting the &lt;code&gt;busy_handler&lt;/code&gt; so that you can establish connections as quickly as possible.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pragmas summary
&lt;/h3&gt;

&lt;p&gt;These six pragmas can be configured in SQLite using the following SQL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;PRAGMA&lt;/span&gt; &lt;span class="n"&gt;journal_mode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;WAL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;PRAGMA&lt;/span&gt; &lt;span class="n"&gt;synchronous&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;NORMAL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;PRAGMA&lt;/span&gt; &lt;span class="n"&gt;journal_size_limit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;67108864&lt;/span&gt; &lt;span class="c1"&gt;-- 64 megabytes;&lt;/span&gt;
&lt;span class="n"&gt;PRAGMA&lt;/span&gt; &lt;span class="n"&gt;mmap_size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;134217728&lt;/span&gt; &lt;span class="c1"&gt;-- 128 megabytes;&lt;/span&gt;
&lt;span class="n"&gt;PRAGMA&lt;/span&gt; &lt;span class="n"&gt;cache_size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;PRAGMA&lt;/span&gt; &lt;span class="n"&gt;busy_timeout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This would provide a fine-tuned SQLite database for web application usage. In fact, this default configuration is precisely the default configuration used by &lt;a href="https://github.com/oldmoe/litestack"&gt;&lt;code&gt;Litestack&lt;/code&gt;&lt;/a&gt; for its &lt;a href="https://github.com/oldmoe/litestack/blob/master/lib/litestack/litedb.rb"&gt;&lt;code&gt;Litedb&lt;/code&gt; module&lt;/a&gt;. So, I need to offer a big shout out to &lt;code&gt;Litestack&lt;/code&gt; and &lt;a href="https://twitter.com/oldmoe?ref=fractaledmind.github.io"&gt;@oldmoe&lt;/a&gt; for doing the hard work of forging the path to find an ideal default setup for Rails SQLite usage.&lt;/p&gt;

&lt;p&gt;So, we have the six pragmas that we want to configure, and we have the values that we want to set. The only thing remaining is actually configuring our Rails application to consistently use these settings. As is the theme for this series, we want to &lt;strong&gt;enhance&lt;/strong&gt; Rails, not override Rails. So, we need a mechanism that builds on top of Rails and provides similar flexibility as Rails.&lt;/p&gt;




&lt;h3&gt;
  
  
  Fine-tuning your Rails application
&lt;/h3&gt;

&lt;p&gt;There does appear to be a natural hook point for configuring Rails' database adapters; unfortunately, it isn't publicly exposed for extension yet—this is the &lt;a href="https://github.com/rails/rails/blob/main/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb#L1210-L1218"&gt;&lt;code&gt;configure_connection&lt;/code&gt; method&lt;/a&gt;. As the comment explains, this method will&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;[p]erform any necessary initialization upon the newly-established &lt;code&gt;@raw_connection&lt;/code&gt; -- this is the place to modify the adapter's connection settings, run queries to configure any application-global "session" variables, etc.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This sounds like exactly what we need. We can't hook into it naturally yet, so let's responsibly monkey-patch the SQLite adapter instead. Right now, the SQLite adapter will set the &lt;code&gt;busy_timeout&lt;/code&gt; pragma if the &lt;code&gt;timeout&lt;/code&gt; option is set and turn on the &lt;code&gt;foreign_keys&lt;/code&gt; pragma in its &lt;a href="https://github.com/rails/rails/blob/main/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb#L691-L695"&gt;&lt;code&gt;configure_connection&lt;/code&gt; method&lt;/a&gt;. In order to extend this method, let's create an initializer file to extend the SQLite adapter.&lt;/p&gt;

&lt;p&gt;We can use the &lt;code&gt;ActiveSupport.on_load(:active_record_sqlite3adapter)&lt;/code&gt; hook to only extend the adapter when it is loaded. This block will be passed the &lt;code&gt;SQLite3Adapter&lt;/code&gt;, so we can simply call &lt;code&gt;prepend&lt;/code&gt; in the block. This means we can simply define a module that will extend the &lt;code&gt;configure_connection&lt;/code&gt; method and then &lt;code&gt;prepend&lt;/code&gt; that module into the adapter class. I put Rails extensions under the &lt;code&gt;RailsExt&lt;/code&gt; module namespace, so let's create a &lt;code&gt;RailsExt::SQLite3Adapter&lt;/code&gt; module:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# /config/initializers/active_record_sqlite3adapter.rb&lt;/span&gt;
&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;RailsExt&lt;/span&gt;
  &lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;SQLite3Adapter&lt;/span&gt;
    &lt;span class="c1"&gt;# Perform any necessary initialization upon the newly-established&lt;/span&gt;
    &lt;span class="c1"&gt;# @raw_connection -- this is the place to modify the adapter's&lt;/span&gt;
    &lt;span class="c1"&gt;# connection settings, run queries to configure any application-global&lt;/span&gt;
    &lt;span class="c1"&gt;# "session" variables, etc.&lt;/span&gt;
    &lt;span class="c1"&gt;#&lt;/span&gt;
    &lt;span class="c1"&gt;# Implementations may assume this method will only be called while&lt;/span&gt;
    &lt;span class="c1"&gt;# holding @lock (or from #initialize).&lt;/span&gt;
    &lt;span class="c1"&gt;#&lt;/span&gt;
    &lt;span class="c1"&gt;# extends https://github.com/rails/rails/blob/main/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb#L691&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;configure_connection&lt;/span&gt;
      &lt;span class="k"&gt;super&lt;/span&gt;

      &lt;span class="c1"&gt;# ...&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We want to provide an enhancement to the Rails database configuration setup. In my opinion, setting up a &lt;code&gt;pragmas&lt;/code&gt; section in the default portion of the database configuration provides a clear and flexible developer experience. We can then iterate over the pragmas hash and make the SQLite calls to set the pragmas in our extension module. This is simple to implement:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;configure_connection&lt;/span&gt;
  &lt;span class="k"&gt;super&lt;/span&gt;

  &lt;span class="vi"&gt;@config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:pragmas&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;raw_execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"PRAGMA &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; = &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"SCHEMA"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows us to enhance our database configuration like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nl"&gt;&amp;amp;default&lt;/span&gt;
  &lt;span class="na"&gt;adapter&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sqlite3&lt;/span&gt;
  &lt;span class="na"&gt;pool&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %&amp;gt;&lt;/span&gt;
  &lt;span class="c1"&gt;# time to wait (in milliseconds) to obtain a write lock before raising an exception&lt;/span&gt;
  &lt;span class="c1"&gt;# https://www.sqlite.org/pragma.html#pragma_busy_timeout&lt;/span&gt;
  &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5000&lt;/span&gt;
  &lt;span class="na"&gt;pragmas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# level of database durability, 2 = "FULL" (sync on every write), other values include 1 = "NORMAL" (sync every 1000 written pages) and 0 = "NONE"&lt;/span&gt;
    &lt;span class="c1"&gt;# https://www.sqlite.org/pragma.html#pragma_synchronous&lt;/span&gt;
    &lt;span class="na"&gt;synchronous&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NORMAL"&lt;/span&gt;
    &lt;span class="c1"&gt;# Journal mode WAL allows for greater concurrency (many readers + one writer)&lt;/span&gt;
    &lt;span class="c1"&gt;# https://www.sqlite.org/pragma.html#pragma_journal_mode&lt;/span&gt;
    &lt;span class="na"&gt;journal_mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;WAL"&lt;/span&gt;
    &lt;span class="c1"&gt;# impose a limit on the WAL file to prevent unlimited growth (with a negative impact on read performance as well)&lt;/span&gt;
    &lt;span class="c1"&gt;# https://www.sqlite.org/pragma.html#pragma_journal_size_limit&lt;/span&gt;
    &lt;span class="na"&gt;journal_size_limit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;%= 64.megabytes %&amp;gt;&lt;/span&gt;
    &lt;span class="c1"&gt;# set the global memory map so all processes can share data&lt;/span&gt;
    &lt;span class="c1"&gt;# https://www.sqlite.org/pragma.html#pragma_mmap_size&lt;/span&gt;
    &lt;span class="c1"&gt;# https://www.sqlite.org/mmap.html&lt;/span&gt;
    &lt;span class="na"&gt;mmap_size&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;%= 128.megabytes %&amp;gt;&lt;/span&gt;
    &lt;span class="c1"&gt;# increase the local connection cache to 2000 pages&lt;/span&gt;
    &lt;span class="c1"&gt;# https://www.sqlite.org/pragma.html#pragma_cache_size&lt;/span&gt;
    &lt;span class="na"&gt;cache_size&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And just like that, we have tuned our application's SQLite database to be better configured for web application usage, while also providing a clear and simple mechanism for setting additional SQLite pragmas as needed/desired.&lt;/p&gt;




&lt;p&gt;As a capstone, let's talk about how to support a &lt;code&gt;retries&lt;/code&gt; option as an alternative to the &lt;code&gt;timeout&lt;/code&gt; option. There are two key details here. Firstly, it is important that &lt;code&gt;retries&lt;/code&gt; and &lt;code&gt;timeout&lt;/code&gt; option cannot both be set at the same time, as the &lt;code&gt;busy_handler&lt;/code&gt; and &lt;code&gt;busy_timeout&lt;/code&gt; are mutually exclusive. Secondly, it is important that this be the very first pragma that is set so that other pragma queries respect our busy handling logic. We can update our &lt;code&gt;configure_connection&lt;/code&gt; method like so to support these features:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;configure_connection&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:timeout&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="vi"&gt;@config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:retries&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="no"&gt;ArgumentError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Cannot specify both timeout and retries arguments"&lt;/span&gt;
  &lt;span class="k"&gt;elsif&lt;/span&gt; &lt;span class="vi"&gt;@config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:retries&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="c1"&gt;# see: https://www.sqlite.org/c3ref/busy_handler.html&lt;/span&gt;
    &lt;span class="vi"&gt;@raw_connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;busy_handler&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="vi"&gt;@config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:retries&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;super&lt;/span&gt;

  &lt;span class="vi"&gt;@config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:pragmas&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;raw_execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"PRAGMA &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; = &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"SCHEMA"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, we can replace the &lt;code&gt;timeout: 5000&lt;/code&gt; setting with a &lt;code&gt;retries: 1000&lt;/code&gt; setting instead, and the appropriate &lt;code&gt;busy_handler&lt;/code&gt; will get setup.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;You can find the files we have written throughout this post in &lt;a href="https://gist.github.com/fractaledmind/3565e12db7e59ab46f839025d26b5715/645f2d2dde3a275c270eabc00ce3067583b1b530"&gt;this Gist&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;This is mentioned by a maintainer in a &lt;a href="https://sqlite.org/forum/info/3fd33f0b9be72353"&gt;forum response&lt;/a&gt; and can be seen in the &lt;code&gt;sqliteDefaultBusyCallback&lt;/code&gt; method in the &lt;a href="https://sqlite.org/src/file?name=src/main.c&amp;amp;ci=trunk"&gt;&lt;code&gt;main.c&lt;/code&gt; file&lt;/a&gt;. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;If you want to dive deeper into understanding how and when SQLite will throw a &lt;code&gt;BUSY&lt;/code&gt; exception, this is an excellent blog post: &lt;a href="https://activesphere.com/blog/2018/12/24/understanding-sqlite-busy"&gt;https://activesphere.com/blog/2018/12/24/understanding-sqlite-busy&lt;/a&gt;. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>code</category>
      <category>ruby</category>
      <category>rails</category>
      <category>sqlite</category>
    </item>
    <item>
      <title>Branch-specific databases with SQLite</title>
      <dc:creator>Stephen Margheim</dc:creator>
      <pubDate>Thu, 07 Sep 2023 13:02:58 +0000</pubDate>
      <link>https://forem.com/fractaledmind/enhancing-your-rails-app-with-sqlite-30n1</link>
      <guid>https://forem.com/fractaledmind/enhancing-your-rails-app-with-sqlite-30n1</guid>
      <description>&lt;p&gt;This is the first in a collection of posts where I want to highlight ways we can enhance our &lt;a href="https://rubyonrails.org"&gt;Ruby on Rails&lt;/a&gt; applications. Specifically, in this first series, I want to dig into the ways that we can take advantage of and empower using &lt;a href="https://www.sqlite.org/index.html"&gt;SQLite&lt;/a&gt; as the database engine for our Rails applications. In this inaugural post, let's dig into how using SQLite as our database engine opens up powerful new possibilities for our local development workflow; specifically, allowing us to have and use &lt;strong&gt;branch-specific databases&lt;/strong&gt;.&lt;/p&gt;




&lt;p&gt;There has been a &lt;a href="https://tailscale.com/blog/database-for-2022/"&gt;surge&lt;/a&gt; &lt;a href="https://pretalx.com/djangocon-europe-2023/talk/J98ZTN/#:~:text=SQLite%20is%20a%20popular%20option,running%20your%20app%20in%20production."&gt;of&lt;/a&gt; &lt;a href="https://news.ycombinator.com/item?id=20367679"&gt;interest&lt;/a&gt; &lt;a href="https://litestream.io/blog/why-i-built-litestream/#moving-to-sqlite"&gt;in&lt;/a&gt; using SQLite in production for web applications in the last few years. I readily confess that I am fully onboard as well. SQLite removes network latency, simplifies operations, and makes automated testing against your production stack easy.&lt;/p&gt;

&lt;p&gt;Over the course of this series, I want to lay out some of the key tweaks we can make to our Rails applications to make working with SQLite more powerful and pleasurable. To start, I want to focus on a developer experience (DX) improvement for local development.&lt;/p&gt;

&lt;p&gt;As anyone who has worked on a Rails application within a team of developers knows, managing your database schema can be tricky. Each developer's branches might include some migrations, which update the schema, but locally you only have the one single development database. Switching between branches becomes a pain, running migrations becomes a pain, and sometimes bugs sneak into production as schema changes are merged that shouldn't have been a part of that release.&lt;/p&gt;

&lt;p&gt;One of my favorite features from &lt;a href="https://planetscale.com"&gt;PlanetScale&lt;/a&gt; is their &lt;a href="https://planetscale.com/docs/onboarding/branching-and-deploy-requests"&gt;branching&lt;/a&gt; feature:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Branches are copies of your database schema that live in a completely separate environment from each other. Making changes in one branch does not affect other branches until you merge them, just like you manage code branches in your projects.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is lovely, and it solves the pain points laid out above. However, this isn't quite &lt;em&gt;perfect&lt;/em&gt; in my opinion. Because we now have two different kinds of "branches" for our app. We have our Git branches, which isolate our code (including migrations), and our database branches, which isolate our schema. Having two separate branches now creates syncing issues. How do Git branches and database branches relate? How do we tie git branch merging to database branch merging to production deployment? etc.&lt;/p&gt;

&lt;p&gt;This is a necessary trade-off that PlanetScale needs to make, because their serverless database platform can't be deeply integrated with every single user's codebase. However, as Rails developers using SQLite, we have unique opportunities available to us.&lt;/p&gt;




&lt;p&gt;Let's describe our ideal scenario, and then dig into how to implement it. What we want is to have a single branch (a Git branch) which isolates &lt;em&gt;both&lt;/em&gt; our code and our schema. We want switching Git branches to &lt;strong&gt;automatically&lt;/strong&gt; switch schema branches. We want production deployment driven by Git branch merging that also &lt;strong&gt;automatically&lt;/strong&gt; ensures a predictable and stable production schema. Sounds nice, doesn't it? Well, luckily for us, Rails and SQLite make such a setup remarkably easy to create.&lt;/p&gt;

&lt;p&gt;Let's start with our last feature, as this is something that Rails gives us, regardless of our database engine. This feature is precisely the value of our &lt;code&gt;/migrations&lt;/code&gt; directory and the &lt;code&gt;/db/schema.rb&lt;/code&gt; or &lt;code&gt;/db/structure.sql&lt;/code&gt; file. By integrating our database schema management into our Rails application codebase, and ensuring that all schema changes are implemented via Rails migrations, we can bind our production schema to simple Git branch merging, while also ensuring that our production schema is predictable and stable.&lt;/p&gt;

&lt;p&gt;To be honest, the other two features are also possible with any of Rails' supported database adapters, but SQLite fits most nicely with the approach. And the approach is simple. Fundamentally, all we need to do is tell Rails to use a dynamic database name, tied to the Git branch name, for local development. Rails makes this easy through the &lt;code&gt;/config/database.yml&lt;/code&gt; file, which is where we configure the core details of our database. By default, when using SQLite, Rails will generate a &lt;code&gt;/config/database.yml&lt;/code&gt; that looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# SQLite. Versions 3.8.0 and up are supported.&lt;/span&gt;
&lt;span class="c1"&gt;#   gem install sqlite3&lt;/span&gt;
&lt;span class="c1"&gt;#&lt;/span&gt;
&lt;span class="c1"&gt;#   Ensure the SQLite 3 gem is defined in your Gemfile&lt;/span&gt;
&lt;span class="c1"&gt;#   gem "sqlite3"&lt;/span&gt;
&lt;span class="c1"&gt;#&lt;/span&gt;
&lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nl"&gt;&amp;amp;default&lt;/span&gt;
  &lt;span class="na"&gt;adapter&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sqlite3&lt;/span&gt;
  &lt;span class="na"&gt;pool&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %&amp;gt;&lt;/span&gt;
  &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5000&lt;/span&gt;

&lt;span class="na"&gt;development&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;*default&lt;/span&gt;
  &lt;span class="na"&gt;database&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;storage/development.sqlite3&lt;/span&gt;

&lt;span class="c1"&gt;# Warning: The database defined as "test" will be erased and&lt;/span&gt;
&lt;span class="c1"&gt;# re-generated from your development database when you run "rake".&lt;/span&gt;
&lt;span class="c1"&gt;# Do not set this db to the same as development or production.&lt;/span&gt;
&lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;*default&lt;/span&gt;
  &lt;span class="na"&gt;database&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;storage/test.sqlite3&lt;/span&gt;

&lt;span class="na"&gt;production&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;*default&lt;/span&gt;
  &lt;span class="na"&gt;database&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;storage/production.sqlite3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Simple and reasonable. Each Rails environment gets its own database file. Every database file is stored in the &lt;code&gt;/storage&lt;/code&gt; directory (where most hosting providers ensure that contents are persisted across deployments). And each database file has a fixed file name. What we want is to have our &lt;code&gt;development&lt;/code&gt; environment use a &lt;em&gt;dynamic&lt;/em&gt; file name for the database, and we want that file name to be based on the current Git branch. A quick Google search leads to a &lt;a href="https://stackoverflow.com/a/6245587/2884386"&gt;StackOverflow answer&lt;/a&gt; that provides the &lt;code&gt;git&lt;/code&gt; command for getting the current branch name:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git branch &lt;span class="nt"&gt;--show-current&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; If you are using a Git version less than 2.22 (when the &lt;code&gt;--show-current&lt;/code&gt; option was added), you can use &lt;code&gt;git rev-parse --abbrev-ref HEAD&lt;/code&gt; instead.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So, how do we use this in our &lt;code&gt;/config/database.yml&lt;/code&gt; file? Well, Rails makes this easy as it allows for ERB. Adding a tiny bit of resilience to the code, we can replace our &lt;code&gt;development&lt;/code&gt; section with this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;development&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;*default&lt;/span&gt;
  &lt;span class="na"&gt;database&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;storage/&amp;lt;%= `git branch --show-current`.chomp || 'development' %&amp;gt;.sqlite3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, instead of always using &lt;code&gt;storage/development.sqlite3&lt;/code&gt; as our database file name, we provide a dynamic file name that will name the database file whatever our current Git branch name is. With this single line change we have implemented the first of our ideal features—we have a single branch (a Git branch) which isolates &lt;em&gt;both&lt;/em&gt; our code and our schema.&lt;/p&gt;

&lt;p&gt;Next, how can we configure Rails to &lt;strong&gt;automatically&lt;/strong&gt; switch "schema branches" when our Git branch changes? Well, here we need to articulate with a bit more clarity what exactly we need, given our solution above. The change to the &lt;code&gt;/config/database.yml&lt;/code&gt; file already ensures that when we switch Git branches, our Rails app will talk to an isolated development database. However, this change alone doesn't ensure that once we have switched Git branches that our isolated development database is ready for use. Imagine that a colleague has created a branch which adds two new database migrations. You pull down that branch to do some code review and local manual testing. When you switch your local Git repo to checkout your colleague's branch for the first time, our dynamic &lt;code&gt;/config/database.yml&lt;/code&gt; configuration will ensure that a new SQLite database file is created in our &lt;code&gt;/storage&lt;/code&gt; directory. However, this new SQLite database file doesn't yet have anything in it, and it doesn't have the schema setup either. So, how can we ensure that Rails automatically prepares this new database file whenever we switch database branches?&lt;/p&gt;

&lt;p&gt;Well, again, luckily for us Rails makes this pretty easy. Rails provides the &lt;a href="https://api.rubyonrails.org/classes/ActiveRecord/Tasks/DatabaseTasks.html"&gt;&lt;code&gt;ActiveRecord::Tasks::DatabaseTasks&lt;/code&gt; utility class&lt;/a&gt;, which "encapsulates logic behind common tasks used to manage database and migrations." For our needs, we can turn to the &lt;a href="https://api.rubyonrails.org/classes/ActiveRecord/Tasks/DatabaseTasks.html#method-i-prepare_all"&gt;&lt;code&gt;.prepare_all&lt;/code&gt; method&lt;/a&gt;, which is the programmatic equivalent to the &lt;a href="https://github.com/rails/rails/pull/35768"&gt;&lt;code&gt;db:prepare&lt;/code&gt; Rake command&lt;/a&gt; added &lt;a href="https://www.bigbinary.com/blog/rails-6-adds-rails-db-prepare-to-migrate-or-setup-a-database"&gt;in Rails 6&lt;/a&gt;. Preparing a database means, simply, running migrations if the database already exists or creating the database and loading the schema if not. All we need is to tell Rails to run this command in development every time we boot up the app. This will ensure that our dynamic database is always ready for use by our Rails app (whether running the server or jumping into a console).&lt;/p&gt;

&lt;p&gt;In order to have Rails run this command in development when we boot that app, we can simply add this to our &lt;code&gt;/config/environments/development.rb&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Ensure that our branch-specific SQLite database is prepared for our application to use&lt;/span&gt;
&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;after_initialize&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Tasks&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;DatabaseTasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prepare_all&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We simply configure our &lt;code&gt;development&lt;/code&gt; environment to run the &lt;code&gt;.prepare_all&lt;/code&gt; command after the app has been initialized.&lt;/p&gt;

&lt;p&gt;Which this simple configuration added, we now have our ideal setup. Every Git branch has its own, isolated database file. That database is &lt;strong&gt;automatically&lt;/strong&gt; prepared for usage on-demand when we boot our Rails app. And production deploys driven by Git branch merging continues to produce stable and predictable production schemas, by using migrations exclusively to alter our schema.&lt;/p&gt;




&lt;p&gt;I can say, I have been using this setup in a few different Rails applications and I &lt;em&gt;absolutely love it&lt;/em&gt;! And I love how easy Rails and SQLite make such a feature to setup. This was a grand total of &lt;strong&gt;&lt;em&gt;four&lt;/em&gt;&lt;/strong&gt; lines (and could easily be &lt;em&gt;two&lt;/em&gt; if we used an inline block for &lt;code&gt;after_initialize&lt;/code&gt;) to provide a similar (and in some key ways improved) feature to a fancy platform like PlanetScale.&lt;/p&gt;

&lt;p&gt;It is precisely these kinds of enhancements—simple, small, but powerful—that I want to explore in the coming weeks and months. So, stay tuned. And, if you enjoyed this tip, please do reach out on Twitter &lt;a href="http://twitter.com/fractaledmind?ref=fractaledmind.github.io"&gt;@fractaledmind&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>code</category>
      <category>ruby</category>
      <category>rails</category>
      <category>sqlite</category>
    </item>
    <item>
      <title>Why parameterized objects in Rails are so powerful</title>
      <dc:creator>Stephen Margheim</dc:creator>
      <pubDate>Tue, 17 Jan 2023 15:40:49 +0000</pubDate>
      <link>https://forem.com/fractaledmind/why-parameterized-objects-in-rails-are-so-powerful-5e52</link>
      <guid>https://forem.com/fractaledmind/why-parameterized-objects-in-rails-are-so-powerful-5e52</guid>
      <description>&lt;p&gt;&lt;a href="https://twitter.com/_swanson" rel="noopener noreferrer"&gt;Matt Swanson&lt;/a&gt; wrote &lt;a href="https://boringrails.com/articles/writing-better-action-mailers/" rel="noopener noreferrer"&gt;an insightful post&lt;/a&gt; today on how to level-up your usage of Mailers in your Rails application. In it, he discussed how using "parameterized mailers" unlocked the ability to build mailers that could be sent from either a custom domain or the applications default domain. As he details in his post, a parameterized mailer is initialized with &lt;code&gt;params&lt;/code&gt; &lt;em&gt;before&lt;/em&gt; calling the mailer method. So instead of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;NotificationMailer&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;comment_reply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;comment&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deliver_later&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You would write:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;NotificationMailer&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;user: &lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;comment: &lt;/span&gt;&lt;span class="n"&gt;comment&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;comment_reply&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deliver_later&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I tweeted a quick response to Matt noting that parameterizing jobs can similarly unlock some powerful and useful functionality. Matt reminded me that a fuller post would be more helpful. So, here we are.&lt;/p&gt;




&lt;p&gt;Well, what is "parameterizing" something like a mailer? While &lt;a href="https://api.rubyonrails.org/classes/ActionMailer/Parameterized/ClassMethods.html#method-i-with" rel="noopener noreferrer"&gt;the implementation&lt;/a&gt; in Rails is somewhat more complicated for mailers, in essence &lt;code&gt;with&lt;/code&gt; is simply an alias for &lt;code&gt;new&lt;/code&gt;. That is, to parameterize is to initialize with state. Instead of passing all of the necessary information into the instance method, we pass it into the initialization method. &lt;/p&gt;

&lt;p&gt;As Matt says in his post, this can feel like a difference without a point:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;My first impression was that I didn’t quite understand the point of this. I generally prefer having the explicit method arguments on the mailer method compared to a generic &lt;code&gt;params&lt;/code&gt; hash.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But, of course, there is a point. The point is that by moving the required state into a class instance, we make it possible to extend, prepend, and inject behavior that alters how the executing method behaves. In Matt's example, he used a before callback to inject behavior that switches the &lt;code&gt;from&lt;/code&gt; field for the address, depending on whether the &lt;code&gt;Account&lt;/code&gt; has a custom domain set or not. &lt;/p&gt;

&lt;p&gt;Notably, &lt;code&gt;ActiveJob&lt;/code&gt; is "parameterized" by default. As you recall, you define the executing method via &lt;code&gt;def perform&lt;/code&gt; in your job class; that is, you define an instance method named &lt;code&gt;perform&lt;/code&gt;. But, you invoke the job by using class methods—either &lt;code&gt;perform_now&lt;/code&gt; or &lt;code&gt;perform_later&lt;/code&gt;. Under the hood, ActiveJob's class methods are little more than short-hand for initializing the job class with the passed parameters and invoking the &lt;code&gt;perform&lt;/code&gt; method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform_now&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;job&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This isn't &lt;em&gt;exactly&lt;/em&gt; what the source code for &lt;code&gt;perform_now&lt;/code&gt; looks like, but the essence is the same. And what that means is that &lt;em&gt;before&lt;/em&gt; your &lt;code&gt;perform&lt;/code&gt; method is invoked, your job has all of the information it needs for this particular execution. This is what allows callbacks to be useful, for example.&lt;/p&gt;

&lt;p&gt;In my particular case, this has proven so powerful because it allows something like &lt;a href="https://github.com/fractaledmind/acidic_job" rel="noopener noreferrer"&gt;&lt;code&gt;AcidicJob&lt;/code&gt;&lt;/a&gt; to exist. &lt;code&gt;AcidicJob&lt;/code&gt; is a gem that provides a suite of features that you can use to make your jobs both more coherent and more resilient. And, at its heart, it functions by serializing and deserializing your job execution into a database record, and then leaning on the ACID guarantees provided by database engines for transactions. This is easy to do with ActiveJob, since the job instances are parameterized and thus can be serialized &lt;em&gt;before&lt;/em&gt; the job is executed with the &lt;strong&gt;full&lt;/strong&gt; set of information needed to execute that job.&lt;/p&gt;

&lt;p&gt;At present, such behavior is not possible with pure Sidekiq because Sidekiq does not initialize a worker/job instance with the parameters needed to execute the worker/job. I started &lt;a href="https://github.com/mperham/sidekiq/issues/5131" rel="noopener noreferrer"&gt;a conversation&lt;/a&gt; on why this would be valuable, but we haven't yet found an API that works well in the context of Sidekiq. This isn't to say anything negative about Sidekiq, which is fantastic software, but simply to point out what kind of flexibility is unlocked when parameterizing operations like jobs and mailers.&lt;/p&gt;

&lt;p&gt;Matt's example is only one of many, many possible ways to make your mailers or jobs more flexible, coherent, and perhaps even resilient by leaning on the power of parameterized classes.&lt;/p&gt;

</description>
      <category>rails</category>
      <category>ruby</category>
      <category>actionmailer</category>
      <category>activejob</category>
    </item>
  </channel>
</rss>
