<?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: Phil Smy</title>
    <description>The latest articles on Forem by Phil Smy (@philsmy).</description>
    <link>https://forem.com/philsmy</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%2F291038%2F77ba16d3-6cda-4dbc-9d4c-7087665809d0.jpeg</url>
      <title>Forem: Phil Smy</title>
      <link>https://forem.com/philsmy</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/philsmy"/>
    <language>en</language>
    <item>
      <title>Why I Deploy Rails Apps with Capistrano in Docker (Yes, Still)</title>
      <dc:creator>Phil Smy</dc:creator>
      <pubDate>Thu, 01 May 2025 07:58:21 +0000</pubDate>
      <link>https://forem.com/philsmy/why-i-deploy-rails-apps-with-capistrano-in-docker-yes-still-2b89</link>
      <guid>https://forem.com/philsmy/why-i-deploy-rails-apps-with-capistrano-in-docker-yes-still-2b89</guid>
      <description>&lt;p&gt;Most Rails devs have moved on to CI pipelines, fancy cloud hooks, or even zero-deploy tools.&lt;br&gt;&lt;br&gt;
So when I tell people I still use &lt;strong&gt;Capistrano&lt;/strong&gt; to deploy all my Rails apps, I get a look—like I just admitted to still using FTP.&lt;/p&gt;

&lt;p&gt;But here’s the thing:&lt;br&gt;&lt;br&gt;
I’ve deployed hundreds of times using Capistrano.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;And now I do it all inside Docker.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not only does it work flawlessly, it avoids a whole category of headaches:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No SSH agent weirdness
&lt;/li&gt;
&lt;li&gt;No local Ruby version dependencies
&lt;/li&gt;
&lt;li&gt;No flaky CI step failures
&lt;/li&gt;
&lt;li&gt;No provisioning a new box every time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you’re a solo developer or running a small team, this setup might save your sanity.&lt;/p&gt;


&lt;h2&gt;
  
  
  🧠 Why Not Just Use CI or GitHub Actions?
&lt;/h2&gt;

&lt;p&gt;Because I don’t need to.&lt;/p&gt;

&lt;p&gt;My Docker-based Capistrano workflow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Works &lt;strong&gt;consistently&lt;/strong&gt; on every project&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Doesn’t rely on CI/CD services&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Deploys &lt;strong&gt;over existing installations&lt;/strong&gt;, with &lt;strong&gt;zero downtime&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Avoids &lt;strong&gt;manual SSH key forwarding&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Is &lt;strong&gt;self-contained&lt;/strong&gt;, fast, and shareable with collaborators&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No CI pipeline. No secrets storage. No cloud runners.&lt;br&gt;&lt;br&gt;
Just a predictable image, and a deploy that &lt;em&gt;just works&lt;/em&gt;.&lt;/p&gt;


&lt;h2&gt;
  
  
  🧱 How It Works
&lt;/h2&gt;
&lt;h3&gt;
  
  
  🐳 The Dockerfile (Summary)
&lt;/h3&gt;

&lt;p&gt;This Dockerfile installs all the system dependencies needed to run Capistrano inside a container, bundles your app, and sets up a deploy script as the container's entrypoint.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; ruby:3.2.2-bullseye&lt;/span&gt;

&lt;span class="c"&gt;# Installs system packages like libpq-dev, openssh-client, yarn, etc.&lt;/span&gt;
&lt;span class="c"&gt;# Creates the working directory&lt;/span&gt;
&lt;span class="c"&gt;# Installs bundler and installs gems&lt;/span&gt;
&lt;span class="c"&gt;# Adds a deploy.sh script and runs it&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can build it like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker build &lt;span class="nt"&gt;-t&lt;/span&gt; myapp-deploy &lt;span class="nt"&gt;-f&lt;/span&gt; Dockerfile.deploy.fixed &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The full Dockerfile is available in the free starter kit (link below).&lt;/p&gt;




&lt;h3&gt;
  
  
  🚀 Running the Deploy
&lt;/h3&gt;

&lt;p&gt;Once built, you can deploy like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;/server_key:/app/server_key &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;/github_key:/app/github_key &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;:/app &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;DEPLOY_STAGE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;production &lt;span class="se"&gt;\&lt;/span&gt;
  myapp-deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This mounts your app directory and private SSH keys into the container so that Capistrano can connect to both the server and your Git repo.&lt;/p&gt;




&lt;h3&gt;
  
  
  🧰 The Deploy Script (deploy.sh) — Summary
&lt;/h3&gt;

&lt;p&gt;This script configures the SSH environment inside the container, adds GitHub to known hosts, and kicks off the Capistrano deployment.&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;# Sets up /root/.ssh&lt;/span&gt;
&lt;span class="c"&gt;# Copies and sets permissions for mounted SSH keys&lt;/span&gt;
&lt;span class="c"&gt;# Adds github.com to known_hosts&lt;/span&gt;
&lt;span class="c"&gt;# Writes SSH config for your target server&lt;/span&gt;
&lt;span class="c"&gt;# Runs: bundle exec cap production deploy&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The full script is included in the download below.&lt;/p&gt;




&lt;h2&gt;
  
  
  📦 Free Starter Kit
&lt;/h2&gt;

&lt;p&gt;I've packaged everything (Dockerfile, deploy script, Capistrano config) into a free downloadable starter kit.&lt;/p&gt;

&lt;p&gt;Grab it here → &lt;a href="https://realliferails.com/dockerized-deploy-kit/" rel="noopener noreferrer"&gt;https://realliferails.com/dockerized-deploy-kit/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Dockerfile.deploy.fixed&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;deploy.sh&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Example &lt;code&gt;deploy.rb&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;README instructions&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🤝 Tradeoffs
&lt;/h2&gt;

&lt;p&gt;Let’s be honest:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This doesn’t use CI. If that’s a dealbreaker for you, no problem.&lt;/li&gt;
&lt;li&gt;Some people dislike the Capistrano DSL. I don’t. It works.&lt;/li&gt;
&lt;li&gt;This method expects you to manage SSH keys carefully—but &lt;em&gt;you&lt;/em&gt; are in control, not a black box.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🔧 Why I Stick With It
&lt;/h2&gt;

&lt;p&gt;I use this for &lt;strong&gt;every Rails project&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
It’s portable, reproducible, and doesn’t get in my way.&lt;br&gt;&lt;br&gt;
Even contractors can deploy safely without polluting their machines or setting up CI pipelines.&lt;/p&gt;




&lt;h2&gt;
  
  
  🛠 Need Help?
&lt;/h2&gt;

&lt;p&gt;If you’re dealing with flaky deploys, broken staging environments, or SSH key nightmares, I run &lt;strong&gt;Rails Rescue Sprints&lt;/strong&gt; that cover exactly this kind of infrastructure cleanup.&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://realliferails.com/ruby-on-rails-rescue-sprint" rel="noopener noreferrer"&gt;https://realliferails.com/ruby-on-rails-rescue-sprint&lt;/a&gt;&lt;/p&gt;




</description>
      <category>rails</category>
      <category>capistrano</category>
      <category>devops</category>
      <category>docker</category>
    </item>
    <item>
      <title>5 Signs It’s Time to Rescue Your Rails App (Not Rebuild It)</title>
      <dc:creator>Phil Smy</dc:creator>
      <pubDate>Tue, 29 Apr 2025 05:05:33 +0000</pubDate>
      <link>https://forem.com/philsmy/5-signs-its-time-to-rescue-your-rails-app-not-rebuild-it-5akd</link>
      <guid>https://forem.com/philsmy/5-signs-its-time-to-rescue-your-rails-app-not-rebuild-it-5akd</guid>
      <description>&lt;p&gt;Keeping a Rails app healthy gets harder the longer it runs.&lt;br&gt;
If you've ever thought, "Maybe we should just rebuild," you're not alone.&lt;br&gt;
But a rebuild isn't always the best path.&lt;/p&gt;

&lt;p&gt;It’s a tempting thought - wipe the slate clean, start fresh.&lt;/p&gt;

&lt;p&gt;But in my experience fixing legacy Rails apps, a full rebuild is often unnecessary, risky, and expensive.&lt;/p&gt;

&lt;p&gt;Most apps don't need a total rebuild.&lt;/p&gt;

&lt;p&gt;They need a focused, strategic &lt;strong&gt;rescue&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Here are five clear signs it's time to stabilize your Rails app — without blowing it up.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. You Fear Deploying (Not New Features)
&lt;/h2&gt;

&lt;p&gt;If hitting “Deploy” feels like spinning a roulette wheel, you have a problem.&lt;/p&gt;

&lt;p&gt;Stable apps make shipping new features easy and low-stress.&lt;/p&gt;

&lt;p&gt;If your team hesitates to push even small updates, the issue isn’t the app's architecture — it’s hidden fragility.&lt;/p&gt;

&lt;p&gt;A rescue sprint focuses on restoring deploy confidence without rewriting every controller.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Outdated Gems Everywhere (But Core Logic Still Works)
&lt;/h2&gt;

&lt;p&gt;Seeing dozens of outdated gems can be overwhelming.&lt;/p&gt;

&lt;p&gt;But if the core business logic — your critical workflows — still runs smoothly, there’s no need to throw everything away.&lt;/p&gt;

&lt;p&gt;Systematic upgrades and careful patching often extend the life of a Rails app by years without the chaos of a ground-up rewrite.&lt;/p&gt;

&lt;p&gt;I’m not gonna lie - some gems are non-trivial to update, especially if they jump major versions. But ‘newer’ is not always better. We are not dealing in theory, we are dealing with &lt;strong&gt;your&lt;/strong&gt; app. So it might be fine to fork an old version of a gem and make sure it works with newer Ruby and leave it at that. You don’t have to be on the latest and ‘greatest’ of every gem.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Background Jobs Are Failing, But Patchable
&lt;/h2&gt;

&lt;p&gt;Jobs timing out, retries clogging Sidekiq, or random failures happening overnight?&lt;/p&gt;

&lt;p&gt;These issues are serious but usually fixable with targeted debugging and better monitoring.&lt;/p&gt;

&lt;p&gt;You don’t need a new app.&lt;/p&gt;

&lt;p&gt;You need an experienced eye on your queues and retries.&lt;/p&gt;

&lt;p&gt;Make sure you’re logging the errors verbosely enough and just pick them off one at a time. I’ve dealt with issues that caused thousands of errors an hour and it turned out to mostly be the same error. Slow and steady wins the race.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Performance Has Degraded Slowly Over Time
&lt;/h2&gt;

&lt;p&gt;If your app has gotten slower year by year — not because of traffic spikes, but because of database growth, index decay, or outdated queries — that’s normal.&lt;/p&gt;

&lt;p&gt;Most performance degradation can be solved with indexing, caching, and refactoring key queries.&lt;/p&gt;

&lt;p&gt;It doesn’t require rewriting the entire app from scratch.&lt;/p&gt;

&lt;p&gt;Sometimes you can benchmark, sometimes you can’t. Not in the real world. Try to find slow calls. Write your own logging timers if you need to. Then focus on the worst offenders. Also - bonus tip! - see if you need those calls at all! So much redundancy in legacy apps because over time people write the same thing many different ways.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. New Developers Can Onboard (With Some Help)
&lt;/h2&gt;

&lt;p&gt;If a new developer can still figure out how the app works after a few days — even if it’s messy — your app is salvageable.&lt;/p&gt;

&lt;p&gt;Complete rewrites usually happen when &lt;em&gt;nobody&lt;/em&gt; can understand what’s going on anymore.&lt;/p&gt;

&lt;p&gt;If onboarding is painful but possible, a rescue is a smarter investment.&lt;/p&gt;




&lt;h2&gt;
  
  
  When Rescue Beats Rebuild
&lt;/h2&gt;

&lt;p&gt;Rebuilding always sounds exciting — until the costs, delays, and lost stability start adding up.&lt;/p&gt;

&lt;p&gt;If you’re seeing these signs in your Rails app, a focused rescue sprint might be the right move.&lt;/p&gt;

&lt;p&gt;I offer short, targeted Rails Rescue Sprints designed exactly for this situation.&lt;/p&gt;

&lt;p&gt;You can find out more here → &lt;a href="https://realliferails.com/ruby-on-rails-rescue-sprint/" rel="noopener noreferrer"&gt;https://realliferails.com/ruby-on-rails-rescue-sprint/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>rails</category>
      <category>ruby</category>
      <category>webdev</category>
      <category>legacycode</category>
    </item>
    <item>
      <title>🧨 The Hotel California of Managed Services: How DigitalOcean Trapped My 800GB Database</title>
      <dc:creator>Phil Smy</dc:creator>
      <pubDate>Wed, 09 Apr 2025 14:00:00 +0000</pubDate>
      <link>https://forem.com/philsmy/the-hotel-california-of-managed-services-how-digitalocean-trapped-my-800gb-database-3ae5</link>
      <guid>https://forem.com/philsmy/the-hotel-california-of-managed-services-how-digitalocean-trapped-my-800gb-database-3ae5</guid>
      <description>&lt;p&gt;We’ve been running production on DigitalOcean since 2015. I used to be a fan. I was even one of those folks who said “DO is the new Heroku” with pride.&lt;/p&gt;

&lt;p&gt;Then I tried to leave their Managed MySQL service.&lt;/p&gt;

&lt;p&gt;Now? I say this with full chest:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;DigitalOcean’s Managed MySQL is the Hotel California of hosting. You can check in any time you like, but you can never leave.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  🔥 TL;DR (for Devs in a Hurry)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;We moved an 800GB production MySQL DB to &lt;strong&gt;DigitalOcean Managed MySQL&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Wanted to move out? Surprise: &lt;strong&gt;only way is &lt;code&gt;mysqldump&lt;/code&gt;&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Our dump+restore took &lt;strong&gt;5 days&lt;/strong&gt; — unusable for real apps&lt;/li&gt;
&lt;li&gt;No support for &lt;strong&gt;Percona XtraBackup&lt;/strong&gt; (&lt;code&gt;BACKUP_ADMIN&lt;/code&gt; not allowed)&lt;/li&gt;
&lt;li&gt;No support for &lt;strong&gt;replication&lt;/strong&gt; (no binlog access)&lt;/li&gt;
&lt;li&gt;DO confirmed: no workaround, no help, no options
&lt;/li&gt;
&lt;li&gt;Our business is now &lt;strong&gt;stuck&lt;/strong&gt; — and &lt;strong&gt;potential sale is blocked&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Moral: &lt;strong&gt;Never use DO Managed MySQL&lt;/strong&gt; if you care about portability&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  🔁 The Move &lt;em&gt;In&lt;/em&gt; Was Easy
&lt;/h3&gt;

&lt;p&gt;Two years ago, we moved our production MySQL database — 800GB and growing — from a self-hosted droplet to DigitalOcean’s &lt;strong&gt;Managed MySQL&lt;/strong&gt; offering.&lt;/p&gt;

&lt;p&gt;They pitched us the usual: high availability, backups, offloaded ops work, improved security. And hey, it’s just a few clicks, right?&lt;/p&gt;

&lt;p&gt;Moving in was simple.&lt;/p&gt;

&lt;p&gt;It’s moving out that’s impossible.&lt;/p&gt;




&lt;h3&gt;
  
  
  🚫 The Only Way Out: &lt;code&gt;mysqldump&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Let me save you some time: &lt;strong&gt;the only way&lt;/strong&gt; DigitalOcean lets you get your data &lt;em&gt;out&lt;/em&gt; of their managed MySQL is &lt;a href="https://dev.mysql.com/doc/refman/8.0/en/mysqldump.html" rel="noopener noreferrer"&gt;&lt;code&gt;mysqldump&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;No joke. That's it.&lt;/p&gt;

&lt;p&gt;If your database is a few gigs, fine. If it's hundreds of gigs with write traffic? Welcome to the abyss.&lt;/p&gt;

&lt;p&gt;We ran a full test dump and restore on a production-grade server. Not theoretical. &lt;strong&gt;It took 5 days.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That’s five full days of read-only or downtime &lt;em&gt;if&lt;/em&gt; you want consistency. If your app writes during that window? You lose data or break integrity.&lt;/p&gt;

&lt;p&gt;How do I know this? We tried it.&lt;/p&gt;




&lt;h3&gt;
  
  
  🧰 Better Tools Exist — But DO Won’t Let You Use Them
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.percona.com/software/mysql-database/percona-xtrabackup" rel="noopener noreferrer"&gt;Percona XtraBackup&lt;/a&gt; is a standard tool for large, hot MySQL backups. It works beautifully. Fast, reliable, supports compression, and lets you keep things running.&lt;/p&gt;

&lt;p&gt;But it requires the &lt;code&gt;BACKUP_ADMIN&lt;/code&gt; privilege (or, in older MySQL, &lt;code&gt;RELOAD&lt;/code&gt; and &lt;code&gt;LOCK TABLES&lt;/code&gt;, depending on config).&lt;/p&gt;

&lt;p&gt;DigitalOcean doesn’t let you assign that permission.&lt;/p&gt;

&lt;p&gt;Their words, not mine:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“It is not possible to add BACKUP_ADMIN or use XtraBackup with managed MySQL.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Cool. So... replication then?&lt;/p&gt;




&lt;h3&gt;
  
  
  ❌ No Replication Either
&lt;/h3&gt;

&lt;p&gt;Replication would be the obvious move: spin up your own MySQL server, set up as a replica, sync, and cut over when ready.&lt;/p&gt;

&lt;p&gt;Nope. Denied.&lt;/p&gt;

&lt;p&gt;DO support, again:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“It is not possible to set up replication on a managed database.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Why? Because you don’t get access to the &lt;strong&gt;binary logs&lt;/strong&gt; (&lt;code&gt;binlog-do-db&lt;/code&gt;, &lt;code&gt;binlog_format&lt;/code&gt;, etc). You can’t even inspect or request the files needed to set it up.&lt;/p&gt;

&lt;p&gt;So here’s the scoreboard:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Exit Strategy&lt;/th&gt;
&lt;th&gt;DO Managed MySQL Support&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mysqldump&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✅ (but incredibly slow)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Percona XtraBackup&lt;/td&gt;
&lt;td&gt;❌ Not supported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Replication&lt;/td&gt;
&lt;td&gt;❌ Not supported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Raw file access&lt;/td&gt;
&lt;td&gt;❌ Nope&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h3&gt;
  
  
  🧯 The Support Experience
&lt;/h3&gt;

&lt;p&gt;To their credit, support was polite.&lt;/p&gt;

&lt;p&gt;But "polite" doesn’t fix the problem. Their tone was mostly robotic and mildly argumentative.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“SnapShooter utilizes mysqldump for MySQL backups, which can be challenging for large databases due to extended backup and restore times.”&lt;/p&gt;

&lt;p&gt;“We do not offer any database migration services.”&lt;/p&gt;

&lt;p&gt;“The only available option is to take a dump.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;"Take a dump." Yes. We did. Into production. It wasn’t pretty.&lt;/p&gt;




&lt;h3&gt;
  
  
  💀 The Real Cost of Lock-In
&lt;/h3&gt;

&lt;p&gt;This isn’t a blog rant for rant’s sake.&lt;/p&gt;

&lt;p&gt;We were preparing to sell the company. That database is our heart, our brain, and our product. And &lt;strong&gt;we cannot move it&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You cannot sell a product that’s locked into a vendor you can’t control.&lt;/p&gt;

&lt;p&gt;We’re now in a position where &lt;strong&gt;our entire infrastructure has to be rebuilt&lt;/strong&gt;, &lt;em&gt;except&lt;/em&gt; for the one service we can’t escape.&lt;/p&gt;

&lt;p&gt;That’s lock-in. Not in the abstract, but in the most real, money-losing, career-draining way.&lt;/p&gt;




&lt;h3&gt;
  
  
  🚨 Devs, Founders, Ops People: Be Warned
&lt;/h3&gt;

&lt;p&gt;If you're considering DigitalOcean's managed services — specifically MySQL — &lt;strong&gt;do not do it&lt;/strong&gt; unless you're prepared to stay forever.&lt;/p&gt;

&lt;p&gt;There's no replication.&lt;br&gt;
No fast backup.&lt;br&gt;
No self-serve migration path.&lt;br&gt;
No raw data access.&lt;br&gt;
No xtrabackup support.&lt;/p&gt;

&lt;p&gt;Only &lt;code&gt;mysqldump&lt;/code&gt;. Slow, brittle, ancient, downtime-heavy &lt;code&gt;mysqldump&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;And yes — they confirmed it all.&lt;/p&gt;




&lt;h3&gt;
  
  
  🛑 Final Thoughts
&lt;/h3&gt;

&lt;p&gt;After nearly 10 years and tens of thousands of dollars spent with DO, we’re moving &lt;strong&gt;everything&lt;/strong&gt; off the platform.&lt;/p&gt;

&lt;p&gt;Everything we &lt;em&gt;can&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The managed database? It's still there. Because there's &lt;strong&gt;literally no safe way to get it out&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So yeah. DigitalOcean’s Managed MySQL isn’t managed. It’s &lt;em&gt;confined&lt;/em&gt;. Be smarter than I was. Choose infra that respects your exit strategy.&lt;/p&gt;

&lt;p&gt;If you’re running anything bigger than a toy app, think long and hard before giving up control of your database.&lt;/p&gt;




&lt;h3&gt;
  
  
  Bonus
&lt;/h3&gt;

&lt;p&gt;If you're building a business and want to avoid messes like this, I wrote a book called &lt;a href="https://frommonkeytomogul.com" rel="noopener noreferrer"&gt;&lt;em&gt;From Monkey to Mogul&lt;/em&gt;&lt;/a&gt; that’s all about these hard-learned lessons.&lt;/p&gt;

&lt;p&gt;I also cry into my keyboard on LinkedIn sometimes. Say hi there too.&lt;/p&gt;

</description>
      <category>mysql</category>
      <category>devops</category>
      <category>cloud</category>
      <category>sass</category>
    </item>
    <item>
      <title>Parallel Testing in Rails 7: Benefits and Pitfalls</title>
      <dc:creator>Phil Smy</dc:creator>
      <pubDate>Mon, 28 Aug 2023 02:00:00 +0000</pubDate>
      <link>https://forem.com/philsmy/parallel-testing-in-rails-7-benefits-and-pitfalls-15hf</link>
      <guid>https://forem.com/philsmy/parallel-testing-in-rails-7-benefits-and-pitfalls-15hf</guid>
      <description>&lt;p&gt;Parallel testing has emerged as a popular strategy for speeding up test suites, and with Rails 7, it's more efficient and effective than ever. However, as with any technology, understanding its benefits and challenges is essential for optimal utilization.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;1. Introduction to Parallel Testing in Rails 7&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Parallel testing lets you run your test suite in a concurrent manner. By default, Rails 7 forks processes using Ruby's DRb system. The number of processes forked corresponds to the machine's core count but can be adjusted.&lt;/p&gt;

&lt;p&gt;To activate parallel testing, insert this into &lt;strong&gt;&lt;code&gt;test_helper.rb&lt;/code&gt;&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ActiveSupport::TestCase&lt;/span&gt;
  &lt;span class="n"&gt;parallelize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;workers: :number_of_processors&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;For those using JRuby or desiring threaded parallelization, Rails 7 offers a threaded parallelizer, backed by Minitest's Parallel::Executor. To employ threads:&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;ActiveSupport::TestCase&lt;/span&gt;
  &lt;span class="n"&gt;parallelize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;workers: :number_of_processors&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;with: :threads&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;h3&gt;
  
  
  &lt;strong&gt;2. How Parallel Testing Works in Rails 7&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Parallelization divides the test suite based on specified worker count. By default, Active Record handles database creation and schema loading for each process, prefixing databases with corresponding worker numbers.&lt;/p&gt;

&lt;p&gt;To modify the worker count during a test run:&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;PARALLEL_WORKERS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;15 bin/rails &lt;span class="nb"&gt;test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;3. Addressing Common Pitfalls&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;While parallel testing brings promise, be aware of the potential snags:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;a. Compatibility with RSpec&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In Rails 6, RSpec didn't support built-in parallel testing. While this has been a point of discussion, if you're still using RSpec and require parallel tests, you might rely on third-party gems like &lt;strong&gt;&lt;code&gt;[grosser/parallel_tests](https://github.com/grosser/parallel_tests)&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;b. Overhead with Smaller Test Suites&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Sometimes, a minor test suite might run slower in parallel due to overheads like multiple database setups. Rails 7, however, introduces a default threshold (50 tests) to decide when to parallelize. Adjust this threshold if required:&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;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;active_support&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test_parallelization_threshold&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;c. Random Failures Due to Shared Resources&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Race conditions introduced by parallel testing can yield random failures. For instance, when two tests access a shared file, their parallel execution can interfere with each other's operations.&lt;/p&gt;

&lt;p&gt;To resolve, ensure tests don't share resources and leverage &lt;strong&gt;&lt;code&gt;Tempfiles&lt;/code&gt;&lt;/strong&gt; for files or &lt;strong&gt;&lt;code&gt;parallelize_setup&lt;/code&gt;&lt;/strong&gt; to namespace resources.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;4. Gradual Adoption Strategy&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;For projects anticipating challenges with full-blown parallel testing adoption, a staggered approach might be beneficial. This way, developers can introduce parallel testing one test class at a time:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Module Approach&lt;/strong&gt;: Design a module that, when included by a test class, makes it run tests in parallel.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Parallelize&lt;/span&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;included&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;class_eval&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;parallelize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;workers: :number_of_processors&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="c1"&gt;# ... other setups&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;class&lt;/span&gt; &lt;span class="nc"&gt;MyTest&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveSupport&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;TestCase&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Parallelize&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Inheritance Approach&lt;/strong&gt;: Create a test class that runs tests in parallel and inherit from it for tests that are ready for parallelization.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ParallelTest&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveSupport&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;TestCase&lt;/span&gt;
  &lt;span class="n"&gt;parallelize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;workers: :number_of_processors&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;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;5. Conclusion&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Parallel testing in Rails 7 is undeniably a potent tool to accelerate test suites. With proper understanding and measures, developers can fully harness its power while sidestepping potential pitfalls. Whether adopting it fully or in stages, the key is to ensure that resources are accessed safely and that intermittent test failures are addressed appropriately.&lt;/p&gt;

&lt;h2&gt;
  
  
  Talk to me!
&lt;/h2&gt;

&lt;p&gt;You can find me on &lt;a href="https://twitter.com/psmy"&gt;Twitter&lt;/a&gt; where I share insights on Ruby on Rails, discuss my journey with &lt;a href="https://public.zonmaster.com/"&gt;Zonmaster&lt;/a&gt;, and explore various aspects of life. You can also check out my &lt;a href="https://www.youtube.com/@philsmy"&gt;YouTube&lt;/a&gt; channel where I cover various topics related to web development, including Ruby on Rails.&lt;/p&gt;

&lt;p&gt;Looking for more? I’ve got an ever-growing series of Ruby on Rails guides over on my &lt;a href="https://philsmy.gumroad.com"&gt;Gumroad&lt;/a&gt; store! Everything from “&lt;a href="https://philsmy.gumroad.com/l/yfhoq"&gt;Getting Started with Ruby on Rails: A Step-by-Step Guide for Beginners&lt;/a&gt;” up to topics like integrating &lt;a href="https://philsmy.gumroad.com/l/ovsgkr"&gt;OpenAI’s ChatGPT&lt;/a&gt;. Don’t miss out on this opportunity to level up your web development skills with Rails!&lt;/p&gt;

&lt;p&gt;Drop me a note on Twitter or &lt;a href="https://linkedin.com/in/philsmy"&gt;LinkedIn&lt;/a&gt; if you have any questions or need help with your Rails project. Happy coding! 😊🎉&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>ruby</category>
      <category>testing</category>
      <category>programming</category>
    </item>
    <item>
      <title>Sharding in Rails 7: A New Horizon in Database Management</title>
      <dc:creator>Phil Smy</dc:creator>
      <pubDate>Fri, 18 Aug 2023 05:40:00 +0000</pubDate>
      <link>https://forem.com/philsmy/sharding-in-rails-7-a-new-horizon-in-database-management-56pl</link>
      <guid>https://forem.com/philsmy/sharding-in-rails-7-a-new-horizon-in-database-management-56pl</guid>
      <description>&lt;h2&gt;
  
  
  Rails 7, released in 2021, has brought forward new horizons in database management with its advanced sharding capabilities. This revolutionary addition empowers developers to scale applications efficiently, offering a seamless approach to handling large datasets and multi-tenant configurations.
&lt;/h2&gt;

&lt;p&gt;Photo of ‘The Shard’ by [Jamie Street]&lt;/p&gt;

&lt;p&gt;Since its inception, Rails has been a platform that promotes flexibility, robustness, and ease of use. With the release of Rails 7 in 2021, the framework has taken a significant leap forward in database management with the introduction of innovative sharding features. This article aims to explore these new capabilities, detailing how they empower developers to handle database scaling in a more elegant and powerful way.&lt;/p&gt;

&lt;p&gt;I looked at sharding back in 2020 with Rails 6. You can see that video on my &lt;a href="https://youtu.be/lF7T49sph84"&gt;YouTube channel&lt;/a&gt; (please subscribe for great Rails content!), but things are different and better now!&lt;/p&gt;

&lt;p&gt;Here are the critical points of the new features.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Horizontal Sharding: Splitting the Load&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The essence of sharding lies in the distribution of data across different databases to reduce the load on each server. Rails 7’s Horizontal Sharding allows developers to split their database while maintaining the same schema across shards. This method is revolutionary in that it enables applications to scale without the need for fundamental changes to the existing codebase.&lt;/p&gt;

&lt;p&gt;With Horizontal Sharding, each shard functions as a self-contained unit with a similar schema, reducing the rows per server and improving overall performance. The transition to using shards is seamless, allowing developers to tackle challenges associated with large datasets.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Vertical and Horizontal Sharding&lt;/strong&gt;: To implement vertical sharding in Rails 7, the &lt;code&gt;connects_to&lt;/code&gt; method can be utilized. For horizontal sharding, a similar pattern is used, but with the addition of a shard key. Here are some examples:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Vertical Sharding
production:
  primary:
    database: my_primary_database
    adapter: mysql2
  primary_replica:
    database: my_primary_database
    adapter: mysql2
    replica: true

# Horizontal Sharding
production:
  primary_shard_one:
    database: my_primary_shard_one
    adapter: mysql2
  primary_shard_one_replica:
    database: my_primary_shard_one
    adapter: mysql2
    replica: true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can then connect models using the &lt;code&gt;connects_to&lt;/code&gt; API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class ApplicationRecord &amp;lt; ActiveRecord::Base
  self.abstract_class = true

  connects_to shards: {
    default: { writing: :primary, reading: :primary_replica },
    shard_one: { writing: :primary_shard_one, reading: :primary_shard_one_replica }
  }
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;strong&gt;Automatic Shard Switching: Adapting to User Needs&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Automatic Shard Switching is another key feature introduced in Rails 7. Applications can now switch shards per request using the ShardSelector. This enables developers to create custom strategies or lock options for tenant-based sharding. By allowing for real-time changes to shard selection, Rails 7 has made database management more adaptable to specific use-cases.&lt;/p&gt;

&lt;p&gt;This feature is particularly valuable for applications handling multiple tenants, where each tenant might have its data stored in a separate shard. Automatic Shard Switching empowers developers to ensure that each request is handled by the appropriate shard, optimizing query performance.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Granular Connection Management: Precision at Its Best&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;In Rails 7, the ability to manage connections has reached new heights with the introduction of Granular Database Connection Switching. This feature allows developers to switch connections for one database without affecting others, thus achieving unprecedented precision in managing complex database setups.&lt;/p&gt;

&lt;p&gt;Granular Connection Management builds on the existing connection switching capabilities, adding more nuanced control and flexibility. This is especially valuable for applications that require multiple connections to different databases, allowing developers to handle specific connections with finesse.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Emphasizing Application-Level Control&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;While Rails 7 provides robust sharding features, it’s worth noting that it does not automate load balancing of replicas. Instead, it emphasizes application-level control, enabling developers to tailor load balancing to their specific needs.&lt;/p&gt;

&lt;p&gt;This approach aligns with the philosophy of Rails that puts control in the hands of developers. It empowers them to manage load balancing based on unique requirements, thus ensuring that the system’s behavior is predictable and aligned with the intended architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Conclusion&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Rails 7’s sharding features mark a significant advancement in the framework’s evolution. From Horizontal Sharding to Automatic Shard Switching to Granular Connection Management, these enhancements transform the way developers approach database management and scaling.&lt;/p&gt;

&lt;p&gt;The introduction of sharding in Rails 7 opens doors to new possibilities, especially for applications dealing with large volumes of data or multiple tenants. It allows for more efficient distribution of data across servers and grants developers greater control over their connections.&lt;/p&gt;

&lt;p&gt;As with any technology, understanding the intricacies and leveraging these features to their full potential requires time and experimentation. Yet, for those looking to harness the latest advancements in database management, Rails 7 stands as a testament to the continuous innovation that defines the Rails community.&lt;/p&gt;

&lt;p&gt;It’s more than just a new release; it’s a new horizon in database management, affirming Rails as a modern, forward-thinking platform ready to meet the ever-changing demands of web development.&lt;/p&gt;

&lt;h2&gt;
  
  
  Talk to me!
&lt;/h2&gt;

&lt;p&gt;You can find me on &lt;a href="https://twitter.com/psmy"&gt;Twitter&lt;/a&gt; where I share insights on Ruby on Rails, discuss my journey with &lt;a href="https://public.zonmaster.com/"&gt;Zonmaster&lt;/a&gt;, and explore various aspects of life. You can also check out my &lt;a href="https://www.youtube.com/@philsmy"&gt;YouTube&lt;/a&gt; channel where I cover various topics related to web development, including Ruby on Rails.&lt;/p&gt;

&lt;p&gt;And guess what? I’ve recently released my first guide, “&lt;a href="https://philsmy.gumroad.com/l/yfhoq"&gt;Getting Started with Ruby on Rails: A Step-by-Step Guide for Beginners&lt;/a&gt;” on my &lt;a href="https://philsmy.gumroad.com/"&gt;Gumroad&lt;/a&gt; store 📚🚀 It’s a ‘Fair Price’ ebook, so you can get it for free, but any payment is greatly appreciated as it helps support my work and future guides. Don’t miss out on this opportunity to level up your web development skills with Rails!&lt;/p&gt;

&lt;p&gt;Drop me a note on Twitter or &lt;a href="https://linkedin.com/in/philsmy"&gt;LinkedIn&lt;/a&gt; if you have any questions or need help with your Rails project. Happy coding! 😊🎉&lt;/p&gt;

</description>
      <category>database</category>
      <category>ruby</category>
    </item>
    <item>
      <title>Building Robust APIs with Ruby on Rails: A Glimpse into Modern Web Development</title>
      <dc:creator>Phil Smy</dc:creator>
      <pubDate>Thu, 17 Aug 2023 13:18:29 +0000</pubDate>
      <link>https://forem.com/philsmy/building-robust-apis-with-ruby-on-rails-a-glimpse-into-modern-web-development-27im</link>
      <guid>https://forem.com/philsmy/building-robust-apis-with-ruby-on-rails-a-glimpse-into-modern-web-development-27im</guid>
      <description>&lt;p&gt;In today’s digital age, APIs (Application Programming Interfaces) serve as essential connectors between varied software, enabling seamless communication and data sharing. Ruby on Rails, with its elegance, flexibility, and supportive community, has emerged as a premier choice for developers, including myself, aiming to design top-tier APIs.&lt;/p&gt;

&lt;p&gt;While tools play a vital role, it’s knowledge, technique, and best practices that elevate your creations. In my comprehensive ebook available on Gumroad, I deep-dive into the nuances of Rails API development. Before you explore the ebook, allow me to share a snapshot of some critical aspects from it.&lt;/p&gt;

&lt;h2&gt;
  
  
  CRUD Operations: The Core of APIs
&lt;/h2&gt;

&lt;p&gt;The heartbeat of many APIs rests on CRUD operations — Create, Read, Update, and Delete. These foundational functions enable user interactivity, data manipulation, and management.&lt;/p&gt;

&lt;p&gt;Rails truly shines here, especially with routing. By simply employing the resources keyword, Rails instinctively maps HTTP verbs to their respective CRUD operations, eliminating redundant work. Controllers, the central hubs, then dictate the flow of data, responding to each CRUD request appropriately.&lt;/p&gt;

&lt;h2&gt;
  
  
  Serialization: Shaping the Ideal Response
&lt;/h2&gt;

&lt;p&gt;In API development, presentation matters. Serialization, the art of transforming intricate data structures into universally digestible formats like JSON, becomes pivotal.&lt;/p&gt;

&lt;p&gt;In Rails, I’ve found immense value in the ActiveModel::Serializers gem. It provides the flexibility to design how data gets structured in the response, from selecting attributes to embedding associated objects and even adding tailored methods. The outcome? Clear and streamlined responses fit for any application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing: Your Trusty Guard
&lt;/h2&gt;

&lt;p&gt;Building is one thing; ensuring reliability is another. Here, testing becomes indispensable.&lt;/p&gt;

&lt;p&gt;By integrating RSpec with Rails, I’ve been able to construct tests that simulate API requests. These tests validate the correctness of data returns, error handling, and overall behavior, acting as an invaluable safety net throughout the development phase.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rate Limiting: Maintaining Harmony
&lt;/h2&gt;

&lt;p&gt;With growing popularity comes greater demand. An API can quickly become swamped with excessive requests. That’s where rate-limiting steps in, acting as a guardian to prevent any one client from inundating the API.&lt;/p&gt;

&lt;p&gt;Utilizing tools like Rack-Attack, it's possible to set boundaries on request frequencies, ensuring resource distribution remains balanced and the API's performance remains unhindered.&lt;/p&gt;

&lt;p&gt;These insights offer just a taste of the vast world of Rails API development. In my &lt;a href="https://philsmy.gumroad.com/l/kskwa"&gt;ebook&lt;/a&gt;, I delve extensively into each segment, furnishing a detailed, hands-on guide. Whether you’re embarking on this journey as a newcomer or seeking to enhance your established skills, the ebook is a reservoir of insights.&lt;/p&gt;

&lt;p&gt;What’s even more compelling? It’s a ‘pay what you can’ offering on Gumroad, reflecting my desire to make quality knowledge accessible to everyone.&lt;/p&gt;

&lt;p&gt;Dive into the world of Rails API development with confidence. Expand your horizons, grasp the subtleties, and master the art of building robust, efficient, and graceful APIs with Ruby on Rails.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://philsmy.gumroad.com/l/kskwa"&gt;Download the ebook now on Gumroad and elevate your Rails API development journey.&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Talk to me!
&lt;/h3&gt;

&lt;p&gt;You can find me on &lt;a href="https://twitter.com/psmy"&gt;Twitter&lt;/a&gt; where I share insights on Ruby on Rails, discuss my journey with &lt;a href="https://public.zonmaster.com/"&gt;Zonmaster&lt;/a&gt;, and explore various aspects of life. You can also check out my &lt;a href="https://www.youtube.com/@philsmy"&gt;YouTube&lt;/a&gt; channel where I cover various topics related to web development, including Ruby on Rails.&lt;/p&gt;

&lt;p&gt;And guess what? I’ve recently released my first guide, “&lt;a href="https://philsmy.gumroad.com/l/yfhoq"&gt;Getting Started with Ruby on Rails: A Step-by-Step Guide for Beginners&lt;/a&gt;” on my &lt;a href="https://philsmy.gumroad.com"&gt;Gumroad&lt;/a&gt; store 📚🚀 It’s a ‘Fair Price’ ebook, so you can get it for free, but any payment is greatly appreciated as it helps support my work and future guides. Don’t miss out on this opportunity to level up your web development skills with Rails!&lt;/p&gt;

&lt;p&gt;Drop me a note on Twitter or &lt;a href="https://linkedin.com/in/philsmy"&gt;LinkedIn&lt;/a&gt; if you have any questions or need help with your Rails project. Happy coding! 😊🎉&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Integrate ChatGPT with Rails 7: Step-by-Step Tutorial</title>
      <dc:creator>Phil Smy</dc:creator>
      <pubDate>Mon, 14 Aug 2023 06:07:00 +0000</pubDate>
      <link>https://forem.com/philsmy/integrate-chatgpt-with-rails-7-step-by-step-tutorial-5beg</link>
      <guid>https://forem.com/philsmy/integrate-chatgpt-with-rails-7-step-by-step-tutorial-5beg</guid>
      <description>&lt;h3&gt;
  
  
  Join me in a hands-on tutorial to create, manage, and continue conversations with ChatGPT, using Ruby on Rails and OpenAI’s API. Watch the video and take a step toward the future of AI-powered communication.
&lt;/h3&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/_3AsaXoLdj4"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;The rapid integration of Artificial Intelligence into daily routines and business practices is a testament to its revolutionary impact. OpenAI, one of the frontrunners in this transformation, offers powerful language models like GPT-3 and GPT-4 that many aspire to leverage. Understanding this need, I’ve created a YouTube video tutorial (available &lt;a href="https://youtu.be/_3AsaXoLdj4"&gt;here&lt;/a&gt;) that guides you through building a Ruby on Rails frontend/middle-tier that communicates with OpenAI’s API. This allows you to create an experience similar to the ChatGPT website.&lt;/p&gt;

&lt;h3&gt;
  
  
  What the Video Offers
&lt;/h3&gt;

&lt;p&gt;The tutorial I’ve provided is segmented into easily digestible sections, covering every aspect of the process. From introducing the concept to creating the app and installing components such as Devise and Docker, you’ll find guidance every step of the way.&lt;/p&gt;

&lt;p&gt;Key implementations in the video include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;User Management&lt;/strong&gt;: Using Devise, you’ll learn how to efficiently create and manage users.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Conversations Creation and Assignment&lt;/strong&gt;: The tutorial guides you through creating and assigning ChatGPT conversations to specific users.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Conversation Continuation&lt;/strong&gt;: You’ll also discover how to save and retrieve these conversations, allowing them to be continued at any time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bootstrap&lt;/strong&gt; and &lt;strong&gt;Turbo:&lt;/strong&gt; I quickly make a dynamic front end.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To keep the tutorial focused, I’ve left out error-checking and elements unrelated to the core functionality.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to Implement
&lt;/h3&gt;

&lt;p&gt;The hands-on steps begin with creating the app and installing Devise. Following that, you’ll see how to add seed data, set up Docker, update the database, and more. The tutorial also includes instructions for creating chat objects, controllers, views, and adding ruby-openai, along with explanations on enhancing account appearance, enabling chat view, and more.&lt;/p&gt;

&lt;p&gt;Towards the end of the video, you’ll find more advanced topics such as communicating with OpenAI, continuing conversations, saving raw responses, and fixing the sidebar list.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Watch this Video
&lt;/h3&gt;

&lt;p&gt;Whether you’re an experienced developer or a beginner, this video’s step-by-step guide equips you with the skills to build a conversational interface with OpenAI’s models through a Ruby on Rails application. With clear instructions and insightful tips, you’ll gain valuable insights into the world of AI.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;The age of AI is upon us, and understanding how to implement tools like OpenAI is invaluable. In this video tutorial, I’ve laid out a pathway to create a system akin to the ChatGPT website, allowing you to advance your coding skills and embrace AI-powered conversation.&lt;/p&gt;

&lt;p&gt;Embrace this opportunity to broaden your knowledge and creativity in the fascinating world of AI. &lt;a href="https://youtu.be/_3AsaXoLdj4"&gt;Click here&lt;/a&gt; to watch the video and immerse yourself in this tutorial. Don’t miss out on this chance to learn and innovate — join me in exploring the future of conversational interfaces!&lt;/p&gt;

&lt;h3&gt;
  
  
  Source code and notes 🗳
&lt;/h3&gt;

&lt;p&gt;You can download the source code and notes from my Gumroad account: &lt;a href="https://philsmy.gumroad.com/l/ovsgkr"&gt;https://philsmy.gumroad.com/l/ovsgkr&lt;/a&gt;&lt;br&gt;
This is a 'pay what you want' item, meaning you can get it for free. But any payment is really appreciated as it helps me transition to creating great content like this full-time!&lt;/p&gt;

&lt;h3&gt;
  
  
  Talk to me!
&lt;/h3&gt;

&lt;p&gt;You can find me on &lt;a href="https://twitter.com/psmy"&gt;Twitter&lt;/a&gt; where I share insights on Ruby on Rails, discuss my journey with &lt;a href="https://public.zonmaster.com/"&gt;Zonmaster&lt;/a&gt;, and explore various aspects of life. You can also check out my &lt;a href="https://www.youtube.com/@philsmy"&gt;YouTube&lt;/a&gt; channel where I cover various topics related to web development, including Ruby on Rails.&lt;/p&gt;

&lt;p&gt;And guess what? I’ve recently released my first guide, “&lt;a href="https://philsmy.gumroad.com/l/yfhoq"&gt;Getting Started with Ruby on Rails: A Step-by-Step Guide for Beginners&lt;/a&gt;” on &lt;a href="https://philsmy.gumroad.com/l/yfhoq"&gt;Gumroad&lt;/a&gt;! 📚🚀 It’s a ‘Fair Price’ ebook, so you can get it for free, but any payment is greatly appreciated as it helps support my work and future guides. Don’t miss out on this opportunity to level up your web development skills with Rails!&lt;/p&gt;

&lt;p&gt;Drop me a note on Twitter or &lt;a href="https://linkedin.com/in/philsmy"&gt;LinkedIn&lt;/a&gt; if you have any questions or need help with your Rails project. Happy coding! 😊🎉&lt;/p&gt;

</description>
      <category>video</category>
      <category>rails</category>
      <category>webdev</category>
      <category>openai</category>
    </item>
    <item>
      <title>Encrypting Sensitive Data in Rails 7 with Encrypted Attributes</title>
      <dc:creator>Phil Smy</dc:creator>
      <pubDate>Wed, 09 Aug 2023 06:20:00 +0000</pubDate>
      <link>https://forem.com/philsmy/encrypting-sensitive-data-in-rails-7-with-encrypted-attributes-3i3n</link>
      <guid>https://forem.com/philsmy/encrypting-sensitive-data-in-rails-7-with-encrypted-attributes-3i3n</guid>
      <description>&lt;p&gt;&lt;strong&gt;At Zonmaster, we manage a substantial amount of sensitive data, including Personally Identifiable Information (PII), along with other types of data. As part of our agreement with Amazon, we are committed to storing this information securely. While we previously relied on various gems to achieve this, having encryption features built directly into Rails has greatly enhanced our ability to protect this vital information.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Security is a top priority in modern web development
&lt;/h2&gt;

&lt;p&gt;Recently I posted an article about moving some data out of our MySQL database and into S3 files. In that article, I mentioned that these columns were encrypted. Because &lt;a href="https://public.zonmaster.com/"&gt;Zonmaster&lt;/a&gt; started out life in 2015 it uses a home-grown encryption solution.&lt;/p&gt;

&lt;p&gt;But now Rails 7 has introduced a powerful feature to help developers protect sensitive data: encrypted attributes. This built-in functionality provides an additional layer of security that is both easy to implement and robust.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Are Encrypted Attributes?
&lt;/h2&gt;

&lt;p&gt;Encrypted attributes allow you to encrypt specific data within your models, ensuring that sensitive information - like passwords or personal details - is stored securely in the database. This feature is included by default in Rails 7, making it accessible to all developers working with this version of the framework.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started with Encrypted Attributes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1: Initialize Encryption Keys
&lt;/h3&gt;

&lt;p&gt;Before you can begin encrypting attributes, you'll need to generate a set of keys for your Rails credentials. This can be done with the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rails db:encryption:init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will output something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;Add&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;credentials&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt; &lt;span class="nx"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; 

&lt;span class="nx"&gt;active_record_encryption&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="nx"&gt;primary_key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;71&lt;/span&gt;&lt;span class="nx"&gt;TCMk9YKXkBJQSnbdQsRW0qdOcjeNEM&lt;/span&gt;
  &lt;span class="nx"&gt;deterministic_key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;tUJgmCDRZTJKZd35qGBFma2LFdUoH4d5&lt;/span&gt;
  &lt;span class="nx"&gt;key_derivation_salt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;XI7SAcXcoGdbaZM3zvNp0Wdpm3mx3xqw&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As it says, add these to your credentials file using &lt;code&gt;rails credentials:edit&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Declare Encrypted Attributes
&lt;/h3&gt;

&lt;p&gt;Next, you'll need to specify which attributes you want to encrypt within your model. Here's an example of how to declare an encrypted attribute called &lt;code&gt;password&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class Product &amp;lt; ApplicationRecord
  encrypts :description # why you would do this I do not know!
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Encrypt and Decrypt Data
&lt;/h3&gt;

&lt;p&gt;You can create a product with an encrypted description like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;irb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="mi"&gt;001&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;A title&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;a description&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;25.1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mh"&gt;0x000000010bae6b98&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;A title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;25.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;a description&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;updated_at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;nil&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nx"&gt;irb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="mi"&gt;002&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;save&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;
  &lt;span class="nx"&gt;TRANSACTION&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="nx"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="nx"&gt;begin&lt;/span&gt; &lt;span class="nx"&gt;transaction&lt;/span&gt;
  &lt;span class="nx"&gt;Product&lt;/span&gt; &lt;span class="nx"&gt;Create&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.7&lt;/span&gt;&lt;span class="nx"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="nx"&gt;INSERT&lt;/span&gt; &lt;span class="nx"&gt;INTO&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;products&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;price&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;description&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;created_at&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;updated_at&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(?,&lt;/span&gt; &lt;span class="p"&gt;?,&lt;/span&gt; &lt;span class="p"&gt;?,&lt;/span&gt; &lt;span class="p"&gt;?,&lt;/span&gt; &lt;span class="p"&gt;?)&lt;/span&gt;  &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;A title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;price&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;25.1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;description&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;p&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;VeK6h5jQH9BS0KAgYQ==&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;h&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;iv&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;lLXAYRRDdGxDiR9o&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;at&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;Kwbu2PH9b//BoI/sVAPTVw==&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;}}&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;created_at&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2023-08-02 05:04:41.881694&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;updated_at&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2023-08-02 05:04:41.881694&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
  &lt;span class="nx"&gt;TRANSACTION&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.4&lt;/span&gt;&lt;span class="nx"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="nx"&gt;commit&lt;/span&gt; &lt;span class="nx"&gt;transaction&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you save a product with an encrypted description, the description will be encrypted using the keys you added earlier. &lt;/p&gt;

&lt;p&gt;When you load the record the decryption of the attribute is transparent.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;irb(main):003:0&amp;gt; product = Product.last
  Product Load (1.4ms)  SELECT "products".* FROM "products" ORDER BY "products"."id" DESC LIMIT ?  [["LIMIT", 1]]
=&amp;gt; 
#&amp;lt;Product:0x000000010d53b610
...
irb(main):004:0&amp;gt; product.description
=&amp;gt; "a description"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Querying
&lt;/h2&gt;

&lt;p&gt;What this does mean is that you cannot query the model.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;irb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="mi"&gt;005&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;a description&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;Product&lt;/span&gt; &lt;span class="nx"&gt;Load&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="nx"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="nx"&gt;SELECT&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;products&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;FROM&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;products&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;WHERE&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;products&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;description&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt;  &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;description&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;p&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;wdnix8h+WE2ffrk9Lg==&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;h&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;iv&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;t99H28mm/MzcsxuB&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;at&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;SHM+/SmpAVV/sBRBopKm9w==&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;}}&lt;/span&gt;&lt;span class="dl"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But, if you make the encrypted column &lt;code&gt;deterministic&lt;/code&gt; you can!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;Product&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="nx"&gt;validates&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;presence&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

  &lt;span class="nx"&gt;encrypts&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt;
  &lt;span class="nx"&gt;encrypts&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;deterministic&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt; &lt;span class="nx"&gt;encrypt&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt; &lt;span class="nx"&gt;deterministically&lt;/span&gt;
&lt;span class="nx"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can seamlessly query the model, just as if the column was not encrypted at all (note: this does probably decrease the security of things, but probably only marginally. Unless you work for a three letter organization it is probably fine).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;irb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="mi"&gt;001&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Phil Title&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;phil description&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;99.99&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mh"&gt;0x00000001050afae0&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Phil Title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;99.99&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;phil description&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;updated_at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;nil&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nx"&gt;irb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="mi"&gt;002&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;save&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;
  &lt;span class="nx"&gt;TRANSACTION&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="nx"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="nx"&gt;begin&lt;/span&gt; &lt;span class="nx"&gt;transaction&lt;/span&gt;
  &lt;span class="nx"&gt;Product&lt;/span&gt; &lt;span class="nx"&gt;Create&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.2&lt;/span&gt;&lt;span class="nx"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="nx"&gt;INSERT&lt;/span&gt; &lt;span class="nx"&gt;INTO&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;products&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;price&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;description&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;created_at&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;updated_at&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(?,&lt;/span&gt; &lt;span class="p"&gt;?,&lt;/span&gt; &lt;span class="p"&gt;?,&lt;/span&gt; &lt;span class="p"&gt;?,&lt;/span&gt; &lt;span class="p"&gt;?)&lt;/span&gt;  &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;p&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;1RJHqWOus4fGDw==&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;h&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;iv&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;+/zLK97EHgeIUTg6&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;at&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;tzj449jTyTKIwnRXAQboQg==&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;}}&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;price&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;99.99&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;description&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;p&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;25jfOg0Z6uXc2gDxdW5O1Q==&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;h&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;iv&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;mfpYQ31+AdSpsRS9&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;at&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;ILCst1xDituOS7eiAQ5PyA==&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;}}&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;created_at&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2023-08-02 05:11:39.280148&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;updated_at&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2023-08-02 05:11:39.280148&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
  &lt;span class="nx"&gt;TRANSACTION&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.9&lt;/span&gt;&lt;span class="nx"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="nx"&gt;commit&lt;/span&gt; &lt;span class="nx"&gt;transaction&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="nx"&gt;irb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="mi"&gt;003&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Phil Title&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;Product&lt;/span&gt; &lt;span class="nx"&gt;Load&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="nx"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="nx"&gt;SELECT&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;products&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;FROM&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;products&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;WHERE&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;products&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt;  &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;p&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;1RJHqWOus4fGDw==&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;h&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;iv&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;+/zLK97EHgeIUTg6&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;at&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;tzj449jTyTKIwnRXAQboQg==&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;}}&lt;/span&gt;&lt;span class="dl"&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="err"&gt;#&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mh"&gt;0x00000001062b3160&lt;/span&gt;
  &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Phil Title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;99.99&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;phil description&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Wed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;02&lt;/span&gt; &lt;span class="nx"&gt;Aug&lt;/span&gt; &lt;span class="mi"&gt;2023&lt;/span&gt; &lt;span class="mi"&gt;05&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;39.280148000&lt;/span&gt; &lt;span class="nx"&gt;UTC&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;updated_at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Wed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;02&lt;/span&gt; &lt;span class="nx"&gt;Aug&lt;/span&gt; &lt;span class="mi"&gt;2023&lt;/span&gt; &lt;span class="mi"&gt;05&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;39.280148000&lt;/span&gt; &lt;span class="nx"&gt;UTC&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Additional Features
&lt;/h2&gt;

&lt;p&gt;Encrypted attributes in Rails 7 also offer several other features, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Support for ignoring case when querying encrypted data.&lt;/li&gt;
&lt;li&gt;Support for migrating unencrypted data to encrypted data.

&lt;ul&gt;
&lt;li&gt;Especially cool because it lets you turn an unencrypted column into an encrypted one and migrate the data later.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Support for multiple encryption schemes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a comprehensive guide, refer to the &lt;a href="https://guides.rubyonrails.org/active_record_encryption.html"&gt;Rails Guides on Active Record Encryption&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Benefits of Using Encrypted Attributes
&lt;/h2&gt;

&lt;p&gt;Utilizing encrypted attributes in Rails 7 offers a trifecta of compelling advantages. First and foremost, it provides &lt;strong&gt;Enhanced Security&lt;/strong&gt;, enabling developers to protect sensitive data such as passwords and personal information from unauthorized access. This encryption ensures that even if data is breached, it remains unreadable without the proper decryption keys. Secondly, the feature boasts &lt;strong&gt;Ease of Use&lt;/strong&gt;, with a simple implementation process that doesn't compromise usability. Developers can quickly encrypt attributes without needing extensive knowledge of cryptography. Lastly, encrypted attributes are &lt;strong&gt;Fully Integrated into the Rails Framework&lt;/strong&gt;, ensuring seamless compatibility and support within the Rails ecosystem. This integration means that developers can leverage this powerful security feature without having to navigate the complexities of third-party solutions. Together, these benefits make encrypted attributes an attractive option for any Rails developer looking to enhance data security in their applications.&lt;/p&gt;

&lt;p&gt;If you're developing an application that handles sensitive data, consider using encrypted attributes in Rails 7. It's a straightforward and effective way to bolster your security measures, ensuring that your data remains safe and secure.&lt;/p&gt;

&lt;p&gt;You can find me on &lt;a href="https://twitter.com/psmy"&gt;Twitter&lt;/a&gt; where I talk about Ruby on Rails, my company &lt;a href="https://public.zonmaster.com"&gt;Zonmaster&lt;/a&gt;, and life in general. If you’re looking for help with your Rails project drop me a note on Twitter or &lt;a href="https://linkedin.com/in/philsmy"&gt;LinkedIn&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>rails</category>
      <category>encryption</category>
      <category>security</category>
    </item>
    <item>
      <title>Revolutionize Your Rails App with DynamoDB: Unleash Speed and Scalability!</title>
      <dc:creator>Phil Smy</dc:creator>
      <pubDate>Sat, 05 Aug 2023 05:02:00 +0000</pubDate>
      <link>https://forem.com/philsmy/revolutionize-your-rails-app-with-dynamodb-unleash-speed-and-scalability-2np5</link>
      <guid>https://forem.com/philsmy/revolutionize-your-rails-app-with-dynamodb-unleash-speed-and-scalability-2np5</guid>
      <description>&lt;p&gt;When developing a new service, we realized that a core model would be well-suited for storage in &lt;a href="https://aws.amazon.com/pm/dynamodb/"&gt;DynamoDB&lt;/a&gt;, Amazon’s fast and scalable NoSQL solution.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why DynamoDB?
&lt;/h2&gt;

&lt;p&gt;In my experience, when developing apps, the data primarily shown to users is often kept in a separate structure than the underlying database. Users rarely need to see raw data; they prefer summaries, denormalized representations, and charts. For our existing service, &lt;a href="https://public.zonmaster.com"&gt;Zonmaster&lt;/a&gt;, we store user-facing data in an Elasticsearch database, which has served us well. However, for a new project, we wanted to explore alternative options, and that’s where DynamoDB comes into play.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is DynamoDB?
&lt;/h2&gt;

&lt;p&gt;To quote Amazon:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Amazon DynamoDB is a fully managed NoSQL database service that provides fast and predictable performance with seamless scalability. DynamoDB lets you offload the administrative burdens of operating and scaling a distributed database so that you don’t have to worry about hardware provisioning, setup and configuration, replication, software patching, or cluster scaling. DynamoDB also offers encryption at rest, which eliminates the operational burden and complexity involved in protecting sensitive data.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In essence, DynamoDB is an optimized NoSQL database that is fully managed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;The first step, of course, is to set up DynamoDB for development.&lt;/p&gt;

&lt;p&gt;Personally, I prefer using Docker Compose to manage my database elements, such as MySQL, Redis, Elasticsearch, etc. Fortunately, there’s a Docker image available for a local version of DynamoDB, making it easy to run in a development environment. Here’s what my docker-compose.yml looks like:&lt;/p&gt;

&lt;p&gt;Here’s what my docker-compose.yml looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;version: "3.7"
services:
  dynamodb:
    image: amazon/dynamodb-local
    ports:
      - ${DYNAMODB_PORT}:8000
    command: ["-jar", "DynamoDBLocal.jar", "-sharedDb"]
  mysqldb:
    image: mysql:8.0
    container_name: zmrm_mysql
    volumes:
      - zmrm_mysqldb-store:/var/lib/mysql
      - ./log:/var/log/mysql
      - ./docker/mysql/user-setup.sql:/docker-entrypoint-initdb.d/user-setup.sql:ro
    environment:
      - MYSQL_DATABASE=${MYSQL_DBNAME}_development
      - MYSQL_USER=${MYSQL_DBUSER}
      - MYSQL_PASSWORD=${MYSQL_DBPASS}
      - MYSQL_ROOT_PASSWORD=${MYSQL_DBPASS}
      - TZ=${TZ}
    ports:
      - ${MYSQL_DBPORT}:3306
  redis:
    image: redis:7.0.12
    command: redis-server --appendonly yes
    ports:
      - target: 6379
        published: ${REDIS_PORT}
        protocol: tcp
        mode: host
    volumes:
        - redis_data:/data
    restart: always
    environment:
      - REDIS_REPLICATION_MODE=master
volumes:
  redis_data:
  zmrm_mysqldb-store:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Please focus only on the “dynamodb” section.&lt;/p&gt;

&lt;p&gt;We pull down the docker image, and fire it up using the port we want. Done!&lt;/p&gt;

&lt;h2&gt;
  
  
  The Gem
&lt;/h2&gt;

&lt;p&gt;Next, we need to install the dynamoid gem. You can do this using the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; bundle add dynamoid
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The class
&lt;/h2&gt;

&lt;p&gt;Instead of the typical Rails migration, we define the class directly in its class file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# app/models/reaction.rb
class Reaction
  include Dynamoid::Document

  field :marketplace, :string
  field :review_count, :integer
  field :rating, :float
  field :content, :string
  field :status, :string
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Initializer
&lt;/h2&gt;

&lt;p&gt;For development, we can use the following initializer file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# config/initializers/dynamoid.rb
require 'dynamoid'
Dynamoid.configure do |config|
  # [Optional]. If provided, it communicates with the DB listening at the endpoint.
  # This is useful for testing with [DynamoDB Local] (http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Tools.DynamoDBLocal.html).
  config.endpoint = ENV['DYNAMODB_URL']
  config.logger.level = :debug
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In production we will need to set the AWS access to something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;require 'dynamoid'
Dynamoid.configure do |config|
  config.access_key = 'REPLACE_WITH_ACCESS_KEY_ID'
  config.secret_key = 'REPLACE_WITH_SECRET_ACCESS_KEY'
  config.region = 'us-west-2'
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Please refer to the Dynmoid gem documentation for more configuration options.&lt;/p&gt;

&lt;h2&gt;
  
  
  Usage
&lt;/h2&gt;

&lt;p&gt;Working with DynamoDB-backed objects is not much different from working with traditional ActiveRecord models. Here’s an example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;irb(main):001:0&amp;gt; Reaction.new
=&amp;gt; #&amp;lt;Reaction id: nil, marketplace: nil, review_count: nil, rating: nil, content: nil, status: nil, created_at: nil, updated_at: nil&amp;gt;
irb(main):002:0&amp;gt; reaction = Reaction.new(marketplace: 'mystore.shopify.com', review_count: 25, rating: 3.76, content: 'Generally positive', status: 'processed')
=&amp;gt; #&amp;lt;Reaction id: nil, marketplace: "mystore.shopify.com", review_count: 25, rating: 0.376e1, content: "Generally positive", status: "processed", created_at: nil, updated...
irb(main):003:0&amp;gt; reaction.save!
[Aws::DynamoDB::Client 200 0.009863 0 retries] list_tables(exclusive_start_table_name:nil)  

(14.39 ms) LIST TABLES
(14.48 ms) CACHE TABLES
Creating dynamoid__development_reactions table. This could take a while.
[Aws::DynamoDB::Client 200 0.062638 0 retries] create_table(table_name:"dynamoid__development_reactions",key_schema:[{attribute_name:"id",key_type:"HASH"}],attribute_definitions:[{attribute_name:"id",attribute_type:"S"}],billing_mode:"PROVISIONED",provisioned_throughput:{read_capacity_units:100,write_capacity_units:20})  

(66.08 ms) CREATE TABLE
[Aws::DynamoDB::Client 200 0.016588 0 retries] put_item(table_name:"dynamoid__development_reactions",item:{"marketplace"=&amp;gt;{s:"mystore.shopify.com"},"review_count"=&amp;gt;{n:"25"},"rating"=&amp;gt;{n:"3.76"},"content"=&amp;gt;{s:"Generally positive"},"status"=&amp;gt;{s:"processed"},"id"=&amp;gt;{s:"449b77e9-6d7e-4578-bf04-a5b911ee68c2"},"created_at"=&amp;gt;{n:"1690597034.022256"},"updated_at"=&amp;gt;{n:"1690597034.022475"}},expected:{"id"=&amp;gt;{exists:false}})  

(17.26 ms) PUT ITEM - ["dynamoid__development_reactions", {:marketplace=&amp;gt;"mystore.shopify.com", :review_count=&amp;gt;25, :rating=&amp;gt;0.376e1, :content=&amp;gt;"Generally positive", :status=&amp;gt;"processed", :id=&amp;gt;"449b77e9-6d7e-4578-bf04-a5b911ee68c2", :created_at=&amp;gt;0.1690597034022256e10, :updated_at=&amp;gt;0.1690597034022475e10}, {}]
=&amp;gt; #&amp;lt;Reaction id: "449b77e9-6d7e-4578-bf04-a5b911ee68c2", marketplace: "mystore.shopify.com", review_count: 25, rating: 0.376e1, content: "Generally positive", status: "processed", created_at: Sat, 29 Jul 2023 02:17:14 +0000, updated_at: Sat, 29 Jul 2023 02:17:14 +0000&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finding the data is equally as easy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;irb(main):015:0&amp;gt; Reaction.where(marketplace: 'mystore.shopify.com')
=&amp;gt; 
#&amp;lt;Dynamoid::Criteria::Chain:0x000000010e806718
 @consistent_read=false,
 @key_fields_detector=
  #&amp;lt;Dynamoid::Criteria::KeyFieldsDetector:0x000000010e805548
   @forced_index_name=nil,
   @query=
    #&amp;lt;Dynamoid::Criteria::KeyFieldsDetector::Query:0x000000010e805408
     @fields=["marketplace"],
     @fields_with_operator=["marketplace"],
     @query_hash={:marketplace=&amp;gt;"mystore.shopify.com"}&amp;gt;,
   @result=nil,
   @source=Reaction&amp;gt;,
 @query={:marketplace=&amp;gt;"mystore.shopify.com"},
 @scan_index_forward=true,
 @source=Reaction&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This returns a set of objects. But if you use it you’ll see this warning:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;irb(main):016:0&amp;gt; reactions.each {|r| p r.rating}
Queries without an index are forced to use scan and are generally much slower than indexed queries!
You can index this query by adding index declaration to reaction.rb:
* global_secondary_index hash_key: 'some-name', range_key: 'some-another-name'
* local_secondary_index range_key: 'some-name'
Not indexed attributes: :marketplace
(0.01 ms) SCAN - ["dynamoid__development_reactions", {:marketplace=&amp;gt;{:eq=&amp;gt;"mystore.shopify.com"}}]
[Aws::DynamoDB::Client 200 0.00822 0 retries] scan(table_name:"dynamoid__development_reactions",scan_filter:{"marketplace"=&amp;gt;{comparison_operator:"EQ",attribute_value_list:[{s:"mystore.shopify.com"}]}},attributes_to_get:nil)  

0.376e1
=&amp;gt; nil
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We haven’t indexed our data! Let’s fix that&lt;/p&gt;

&lt;h2&gt;
  
  
  Indexing
&lt;/h2&gt;

&lt;p&gt;If you want to improve query performance, you can create an index for your data. In this example, we’ll add a global secondary index to the “marketplace” field:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class Reaction
  include Dynamoid::Document

  field :marketplace, :string
  field :review_count, :integer
  field :rating, :number
  field :content, :string
  field :status, :string

  global_secondary_index hash_key: :marketplace, projected_attributes: :all
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we rebuild the table. With the index in place, querying the data becomes more efficient:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;irb(main):003:0&amp;gt; reactions = Reaction.where(marketplace: 'mystore.shopify.com')
=&amp;gt; 
#&amp;lt;Dynamoid::Criteria::Chain:0x000000010e9a0178
...
irb(main):004:0&amp;gt; reactions.each {|r| p r.rating}
[Aws::DynamoDB::Client 200 0.020613 0 retries] describe_table(table_name:"dynamoid__development_reactions")  

[Aws::DynamoDB::Client 200 0.025561 0 retries] query(consistent_read:false,scan_index_forward:true,index_name:"dynamoid__development_reactions_index_marketplace",table_name:"dynamoid__development_reactions",key_conditions:{"marketplace"=&amp;gt;{comparison_operator:"EQ",attribute_value_list:[{s:"mystore.shopify.com"}]}},query_filter:{},attributes_to_get:nil)  

0.376e1
=&amp;gt; nil
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Filtering
&lt;/h2&gt;

&lt;p&gt;You can also filter records using queries similar to ActiveRecord:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;irb(main):007:0&amp;gt; Reaction.where(marketplace: 'mystore.shopify.com', 'rating.gt': 3).each {|r| p r.marketplace}
[Aws::DynamoDB::Client 200 0.011604 0 retries] query(consistent_read:false,scan_index_forward:true,index_name:"dynamoid__development_reactions_index_marketplace",table_name:"dynamoid__development_reactions",key_conditions:{"marketplace"=&amp;gt;{comparison_operator:"EQ",attribute_value_list:[{s:"mystore.shopify.com"}]}},query_filter:{"rating"=&amp;gt;{comparison_operator:"GT",attribute_value_list:[{n:"3.0"}]}},attributes_to_get:nil)  

"mystore.shopify.com"
=&amp;gt; nil
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;For us, using DynamoDB to store user-facing data, especially summary data used in chart representations, has significantly improved website speed. While this is a new site with not a large amount of data, we are optimistic about its performance. Additionally, DynamoDB is easier to manage compared to Elasticsearch under OpenSearch on Amazon.&lt;/p&gt;

&lt;p&gt;You can find me on &lt;a href="https://twitter.com/psmy"&gt;Twitter&lt;/a&gt; where I talk about Ruby on Rails, my company Zonmaster, and life in general. If you’re looking for help with your Rails project drop me a note on Twitter or &lt;a href="https://linkedin.com/in/philsmy"&gt;LinkedIn&lt;/a&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>dynamodb</category>
      <category>rails</category>
      <category>ruby</category>
    </item>
    <item>
      <title>Optimizing Data Storage in Ruby on Rails: A Seamless Migration from Database to AWS S3</title>
      <dc:creator>Phil Smy</dc:creator>
      <pubDate>Tue, 01 Aug 2023 14:01:00 +0000</pubDate>
      <link>https://forem.com/philsmy/optimizing-data-storage-in-ruby-on-rails-a-seamless-migration-from-database-to-aws-s3-c97</link>
      <guid>https://forem.com/philsmy/optimizing-data-storage-in-ruby-on-rails-a-seamless-migration-from-database-to-aws-s3-c97</guid>
      <description>&lt;p&gt;Our application has a table dedicated to storing SENT email messages. This feature enables us to present a record of sent items to users and track changes to email templates. Currently, we store all this data in our database, which accounts for the largest share of our data storage.&lt;/p&gt;

&lt;p&gt;I recently realized that this data, which is seldom accessed, occupies a significant amount of storage and contributes to database slowdowns. It became clear that a more efficient solution was required.&lt;/p&gt;

&lt;p&gt;After exploring multiple options, I landed on a straightforward resolution: migrate the data from the database to an S3 storage system.&lt;/p&gt;

&lt;p&gt;Below is the execution process of this solution.&lt;/p&gt;

&lt;h2&gt;
  
  
  Our Existing Email Storage Class
&lt;/h2&gt;

&lt;p&gt;Our application initially had a class like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Table name: mail_queue_items
#
# id :integer
# body :text(16777215) &amp;lt;- this is where all the data goes!
# completed_at :datetime
# delivery_code :string(255)
# error :string(255)
# mail_to :string(255)
# msg_reference :string(255)
# run_at :datetime
# status :string(255)
# subject_line :string(255)
# tracked :boolean
# tracked_at :datetime
# created_at :datetime
# updated_at :datetime
class MailQueueItem &amp;lt; ApplicationRecord
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The data (the body column) is stored in a compressed, encoded format in our database:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sig { params(data: String).void }
  def body=(data)
    if data[/^HTML/]
      super(CompressedColumn.new.dump(data))
    else
      super(data)
    end
  end

  sig { returns(String) }
  def body
    CompressedColumn.new.load(read_attribute(:body))
  end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Storing in S3
&lt;/h2&gt;

&lt;p&gt;This process turned out to be simpler than anticipated. Since we were already using S3 (like everyone else on the planet!), the transition was pretty straightforward.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Create a new bucket&lt;br&gt;
For our needs, we didn’t require any complex setup. So we just created a new bucket using the AWS console. It’s worth noting that you might want to enable versioning for added safety in case of accidental overwrites.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Connect to it&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;s3 = Aws::S3::Resource.new(
  region:      ENV.fetch("S3_REGION", nil),
  credentials: Aws::Credentials.new(ENV.fetch("S3_ACCESS_KEY", nil), ENV.fetch("S3_SECRET_ACCESS_KEY", nil))
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Create and save the object with the data
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bucket_name = "my-great-bucket-name"
file_name = "#{tenant_id}-#{mqi_id}" # This should be a unique identifier for each document
obj = s3.bucket(bucket_name).object(file_name)

# Upload the file
obj.put(body: mqi.body)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;That’s it!
No step 4.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  In-class Implementation
&lt;/h2&gt;

&lt;p&gt;Integrating the process into our existing methods resulted in minimal changes to the calling code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sig { params(data: String).void }
def body=(data)
  if data[/^HTML/]
    obj = s3_connector.bucket(ENV.fetch("MQI_BUCKET_NAME", nil)).object(s3_file_name)
    compressed_encoded_data = CompressedColumn.new.dump(data)
    obj.put(body: compressed_encoded_data)
  else
    super(data)
  end
end

sig { returns(String) }
def body
  download = s3_connector.bucket(ENV.fetch("MQI_BUCKET_NAME", nil)).object(s3_file_name)
  compressed_encoded_data = download.get.body.read

  CompressedColumn.new.load(compressed_encoded_data)
end

sig { returns(Aws::S3::Resource) }
def s3_connector
  Aws::S3::Resource.new(
    region:      ENV.fetch("S3_REGION", nil),
    credentials: Aws::Credentials.new(ENV.fetch("S3_ACCESS_KEY", nil), ENV.fetch("S3_SECRET_ACCESS_KEY", nil))
  )
end

sig { returns(String) }
def s3_file_name
  "#{tenant_id}-#{mqi_id}" # This should be a unique identifier for each document
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Next Steps: Building a Migrator
&lt;/h2&gt;

&lt;p&gt;It should be quite straightforward to create a migrator that will transfer the data from our current structure to S3. This development task will involve retrieving the data from the existing database, moving it to S3, and reconstructing the table in the database. By doing so, we could likely recover a significant amount of the 500GB currently occupied. This approach offers an exciting potential for optimizing our application’s storage usage and efficiency.&lt;/p&gt;

&lt;p&gt;An added bonus with the reduced data size is the reduced cost in backups and much smaller and faster database dumps!&lt;/p&gt;

&lt;h2&gt;
  
  
  Reflections
&lt;/h2&gt;

&lt;p&gt;The process of migrating email data from our database to S3 was a relatively simple yet highly effective solution to combat the issues of excessive data storage and potential slowdowns. This not only improved the efficiency of our database but also underscored the value of exploring straightforward solutions to complex challenges.&lt;/p&gt;

&lt;p&gt;By implementing minor adjustments to our existing code and leveraging the capabilities of S3, we were able to establish a more streamlined and robust system for storing SENT email messages.&lt;/p&gt;

&lt;p&gt;This strategy showcases the potential for continuous improvement and optimization within any software system. It’s always worth investigating whether there’s a more efficient way of handling large data sets in your applications!&lt;/p&gt;

&lt;p&gt;As usual, I am writing this here mainly to cement it in my own brain. But I couldn’t find any examples of this online, so hopefully this benefits someone else!&lt;/p&gt;

&lt;p&gt;You can find me on &lt;a href="https://twitter.com/psmy"&gt;Twitter&lt;/a&gt; where I talk about Ruby on Rails, my company &lt;a href="https://public.zonmaster.com"&gt;Zonmaster&lt;/a&gt;, and life in general. If you’re looking for help with your Rails project drop me a note on Twitter or &lt;a href="https://linkedin.com/in/philsmy"&gt;LinkedIn&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>webdev</category>
      <category>aws</category>
    </item>
    <item>
      <title>How to Pass Data from a Rails view to Stimulus: A Simple Guide</title>
      <dc:creator>Phil Smy</dc:creator>
      <pubDate>Sat, 22 Jul 2023 03:12:35 +0000</pubDate>
      <link>https://forem.com/philsmy/how-to-pass-data-from-a-rails-view-to-stimulus-a-simple-guide-1l5g</link>
      <guid>https://forem.com/philsmy/how-to-pass-data-from-a-rails-view-to-stimulus-a-simple-guide-1l5g</guid>
      <description>&lt;p&gt;Everyone talks about the importance of pulling data from endpoints, but what if you already have your data on the page and want to pass it directly to your Stimulus controller? Here’s an efficient method of passing data to Stimulus!&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;I have a page that has a bunch of modals on it that the user can open to see more details.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh23wy121p1esgrrfkp3f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh23wy121p1esgrrfkp3f.png" alt="User can click ‘More…’ and get a modal"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But we already have the details when we render the page. We just use some of them to produce this summary block. The rest are there, but unused.&lt;/p&gt;

&lt;p&gt;In the modal, we want to present a Tabulator table. With Tabulator you have many options to get data into the view. Most people would say, fine, just load the data from an endpoint when the user opens a modal.&lt;/p&gt;

&lt;p&gt;But I already have the data, why call the server again?&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution: Passing Data to Stimulus Directly
&lt;/h2&gt;

&lt;p&gt;Here’s my Stimulus controller for my table&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { Controller } from "@hotwired/stimulus";

// Connects to data-controller="pc-period-details-table"
export default class extends Controller {
  static targets = ["table"];

  connect() {
    this.tabulatorTable = new Tabulator(this.tableTarget, {
      height: "311px",
      dataTree: true,
      dataTreeStartExpanded: false,
      columns: [
        { title: "Name", field: "lineItemName", width: 200 },
        { title: "Notes", field: "lineNotes", width: 150 },
        { title: "Value", field: "lineValue", width: 150 }
      ],
    });
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In my partial, I already have the data in a local variable called base_data.&lt;/p&gt;

&lt;p&gt;I found a very simple trick to get that data from the view into Stimulus as a JSON object.&lt;/p&gt;

&lt;p&gt;Here’s the view&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   &amp;lt;div class="small periodDetails"&amp;gt;
      &amp;lt;div id="example-table" data-controller="pc-period-details-table" 
      data-pc-period-details-table-target="table" 
      data-pc-period-details-table-data-value="&amp;lt;%=base_data.to_json%&amp;gt;"&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;We do the following:&lt;/p&gt;

&lt;p&gt;Connect the div to the Stimulus controller using data-controller&lt;br&gt;
Set ourselves as a target. Ok, I don’t technically need to do this. I could use this.element in the controller. I just prefer this way.&lt;br&gt;
Pass our data up to the controller as a value by called to_json on the data.&lt;br&gt;
We can update the Stimulus controller to look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { Controller } from "@hotwired/stimulus";

// Connects to data-controller="pc-period-details-table"
export default class extends Controller {
  static targets = ["table"];

  static values = {
    data: Object
  };

  connect() {
    this.tabulatorTable = new Tabulator(this.tableTarget, {
      height: "311px",
      data: this.dataValue,
      dataTree: true,
      dataTreeStartExpanded: false,
      columns: [
        { title: "Name", field: "lineItemName", width: 200 },
        { title: "Notes", field: "lineNotes", width: 150 },
        { title: "Value", field: "lineValue", width: 150 }
      ],
    });
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now our modal will render with the table and the same data that we had on the page.&lt;/p&gt;

&lt;p&gt;In conclusion, it’s quite clear that passing data directly to your Stimulus controller instead of pulling it from endpoints can provide an efficient alternative, especially when you already have the required data on your page. Implementing this method can eliminate unnecessary server calls, improving the performance of your application. If you’re seeking to enhance your knowledge of Stimulus and the many ways you can interact with data, mastering this method of passing data to Stimulus is a step in the right direction. Remember, there’s more than one way to approach a problem, and finding the most optimal solution often requires exploring a variety of strategies.&lt;/p&gt;

&lt;p&gt;As usual, I am writing this here mainly to cement it in my own brain. But I couldn’t find any examples of this online, so hopefully this benefits someone else!&lt;/p&gt;

&lt;p&gt;You can find me on &lt;a href="https://twitter.com/psmy" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt; where I talk about Ruby on Rails, my company &lt;a href="https://public.zonmaster.com" rel="noopener noreferrer"&gt;Zonmaster&lt;/a&gt;, and life in general. If you’re looking for help with your Rails project drop me a note on Twitter or &lt;a href="https://linkedin.com/in/philsmy" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>rails</category>
      <category>stimulus</category>
      <category>frontend</category>
    </item>
  </channel>
</rss>
