<?xml version="1.0" encoding="UTF-8" ?>
<rss xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:sy="http://purl.org/rss/1.0/modules/syndication/" version="2.0"><channel><title>CrunchyData Blog</title>
<atom:link href="https://www.crunchydata.com/blog/topic/ruby-on-rails/rss.xml" rel="self" type="application/rss+xml" />
<link>https://www.crunchydata.com/blog/topic/ruby-on-rails</link>
<image><url>https://www.crunchydata.com/card.png</url>
<title>CrunchyData Blog</title>
<link>https://www.crunchydata.com/blog/topic/ruby-on-rails</link>
<width>800</width>
<height>419</height></image>
<description>PostgreSQL experts from Crunchy Data share advice, performance tips, and guides on successfully running PostgreSQL and Kubernetes solutions</description>
<language>en-us</language>
<pubDate>Wed, 20 Dec 2023 08:00:00 EST</pubDate>
<dc:date>2023-12-20T13:00:00.000Z</dc:date>
<dc:language>en-us</dc:language>
<sy:updatePeriod>hourly</sy:updatePeriod>
<sy:updateFrequency>1</sy:updateFrequency>
<item><title><![CDATA[ Using acts_as_tenant for Multi-tenant Postgres with Rails ]]></title>
<link>https://www.crunchydata.com/blog/using-acts_as_tenant-for-multi-tenant-postgres-with-rails</link>
<description><![CDATA[ Chris walks through using the acts_as_tenant gem. He shows some example code to get started with this gem, how to migrate, and other tips for working with B2B or multi-tenant applications. ]]></description>
<content:encoded><![CDATA[ <p>Since its launch, Ruby on Rails has been a preferred open source framework for small-team B2B SaaS companies. Ruby on Rails uses a conventions-over-configuration mantra. This approach reduces common technical choices, thus elevating decisions. With this approach, the developers get an ORM (ActiveRecord), templating engine (ERB), helper methods (like <code>number_to_currency</code>), controller (ActiveController), directory setup defaults (<code>app/{models,controllers,views}</code>), authentication methods (<code>has_secure_password</code>), and more.<p><a href=https://www.crunchydata.com/blog/designing-your-postgres-database-for-multi-tenancy>Multi-tenant</a> is the backbone of B2B SaaS products, yet core-Rails remains un-opinionated on multi-tenant implementations. Through the years, there has been many different Ruby gem implementations for multi-tenant. Many of these gems were built for complicated situations — either adapting to scaling needs or regulated industries that require physical separation of data. Many of these gems required deep integration with your Rails application code.<h2 id=enter-acts_as_tenant><a href=#enter-acts_as_tenant>Enter acts_as_tenant</a></h2><p>With all that as context, the <code>acts_as_tenant</code> gem is super simple. <a href=https://github.com/ErwinM/acts_as_tenant><code>acts_as_tenant</code></a> has recently released version 1.0 after 12 years of development — so it’s not new. The gem implements multi-tenant best-practices by augmenting Rails’ ActiveRecord ORM:<ul><li>protects developers from building queries that return other tenant’s records<li>requires a <code>tenant_id</code> on the tables for models specific to a tenant<li>adds the <code>tenant_id</code> scope to the query<li>includes ActionController, ActiveRecord, ActiveJob helpers to insert new records with the scoped tenant</ul><p>Acts_as_tenant is built for row-level multi-tenancy, and that is it. So, no need to manage multiple databases or schemas for data structures — it keeps it simple. One of the best things I can say about <code>acts_as_tenant</code> is that it can be implemented by an existing application code-base. Too many times, with the older multi-tenant gems, the implementation was invasive, and thus required complex refactoring.<p><strong>What it’s not:</strong> acts_as_tenant is not for account-based sharding — either schema-based or multi-cluster based sharding. It’s purely for multi-tenant safety.<h2 id=for-the-paranoid><a href=#for-the-paranoid>For the paranoid</a></h2><p>I have built a few multi-tenant apps in industries with data regulation (think finance and education). I am overly cautious when building multi-tenant apps — so this guardrail is my favorite.<p>To enforce the <code>tenant_id</code> on every ActiveRecord query within an application, add the following to a initializer file in <code>config/initializers/acts_as_tenant.rb</code>:<pre><code class=language-ruby>ActsAsTenant.configure do |config|
	config.require_tenant = true
end
</code></pre><p>Having worked in a few multi-tenant apps where showing data to another customer is consequential, I wish <code>acts_as_tenant</code> had an enforcing requirement of a <code>tenant_id</code> for queries. One of the apps I wrote required high-performance, large-scale data loads. We had an intermittent bug where people would be assigned to the incorrect tenant. After tracking down the bug, we found the incident in the implementation of multiple <code>external_ids</code>:<pre><code class=language-sql>-- bug code
SELECT
  *
FROM people
WHERE tenant_id = %1 AND external_id = $2 OR other_external_id = $2;

-- correct code
SELECT
  *
FROM people
WHERE tenant_id = %1 AND (external_id = $2 OR other_external_id = $2);
</code></pre><p>The lesson: wrap your <code>OR</code> statements in parenthesis. The bug code interpreted as:<pre><code class=language-sql>(tenant_id = %1 AND external_id = $2) OR other_external_id = $2;
</code></pre><p>When using acts_as_tenant, you can avoid this bug when using ActiveRecord models. Below, you’ll see that ActiveRecord encapsulates the following:<p><img alt="active record output"loading=lazy src=https://imagedelivery.net/lPM0ntuwQfh8VQgJRu0mFg/7e4070b3-c6bc-48e2-09fb-bd4b55f1ff00/public><p>Remember, if you choose to use raw SQL, you’ll need to keep your guard up.<h2 id=testing-from-rails-new-app><a href=#testing-from-rails-new-app>Testing from <code>rails new app</code></a></h2><p>To install from a new rails application, do the following:<ol><li>Run <code>rails new multi-tenant-app</code><li>Decide on your application’s tenant model: typically <code>Organization</code> or <code>Account</code> or <code>Team</code> or <code>School</code>. Use the underscore version of the name with <code>_id</code> appended as your tenant id for all columns, such as <code>organization_id</code> or <code>account_id</code> or <code>team_id</code> or <code>school_id</code>. Below, we will use the tenant name <code>Account</code>.<li>Add <code>gem "acts_as_tenant"</code> to <code>Gemfile</code>, and run <code>bundle install</code>.<li>Create some models:</ol><pre><code class=language-bash>rails g model Account name:string
rails g model User email:string account_id:integer
rails g model Post content:string user_id:integer account_id:integer

rails db:create &#38&#38 rails db:migrate
</code></pre><ol start=5><li>Add the following to <code>app/models/account.rb</code></ol><pre><code class=language-ruby>class Account &#60 ApplicationRecord

  has_many :users
  has_many :posts

end
</code></pre><ol start=6><li>Add the following to <code>app/models/post.rb</code>:</ol><pre><code class=language-ruby>class Post &#60 ApplicationRecord

  belongs_to :user
  acts_as_tenant :account

end
</code></pre><ol start=7><li>Add the following to <code>app/models/user.rb</code>:</ol><pre><code class=language-ruby>class User &#60 ApplicationRecord
  acts_as_tenant :account
  validates_uniqueness_to_tenant :email
end
</code></pre><ol start=8><li>Now, let’s experiment with the Rails REPL:</ol><pre><code class=language-bash>rails console
</code></pre><p>Then, you can run the following commands:<pre><code class=language-ruby>first_account = Account.create!(name: "First Account")
last_account = Account.create!(name: "Last Account")

ActsAsTenant.with_tenant(first_account) do
  user = User.create!(email: "test@example.com")
  post = Post.create!(user: user, content: "Lorem Ipsum")
end

ActsAsTenant.with_tenant(first_account) do
  Post.first.content # -> "Lorem Ipsum"
end

ActsAsTenant.with_tenant(last_account) do
  Post.first.nil? # -> true because we did not create a tenant
end

Post.first.content # -> "Lorem Ipsum"

ActsAsTenant.configure do |config|
  config.require_tenant = true
end

Post.first.content # -> ActsAsTenant::Errors::NoTenantSet (ActsAsTenant::Errors::NoTenantSet)
</code></pre><p>When looking at the queries that are run by ActiveRecord, you’ll see it automatically appends the <code>account_id</code> to the User and Post that are created. Later, after we set <code>require_tenant</code>, you’ll see that the next command fails with an error.<ol start=9><li>From the terminal, we explicitly used <code>with_tenant</code>. acts_as_tenant has helpers for the controller as well. Depending on how your authentication systems and tenancy work, you can use domains, subdomains, or implicit tenancy based on the authenticated user. From here, you’ll need to implement something like:</ol><pre><code class=language-ruby>class ApplicationController &#60 ActionController::Base
  set_current_tenant_through_filter
  before_action :require_authentication
  before_action :set_tenant

  def require_authentication
    current_user || redirect_to(new_session_path)
  end

  def current_user
    @current_user ||= if session[:user_id].present?
			User.find(session[:user_id])
    end
  end

  def current_acount
    @current_account ||= current_user.try(:account)
  end

  def set_tenant
    set_current_tenant(current_account)
  end
end
</code></pre><p>Implementation of proper authentications are complex, so this is simply for example. The code specific to acts_as_tenant are <code>set_current_tenant_through_filter</code> and <code>before_action :set_tenant</code> and <code>def set_tenant</code>.<h2 id=migrating-to-acts_as_tenant><a href=#migrating-to-acts_as_tenant>Migrating to acts_as_tenant</a></h2><p>If you have an existing codebase that would benefit from acts_as_tenant, the migration is a process and can be broken into multiple steps:<ol><li><strong>Add a tenant_id column to each affected model</strong> - this step can be quite complicated. It requires data migrations and data updates. The method of updating columns will be dependent on the size of your database.<li><strong>Add the acts_as_tenant gem, but do not set require_tenant yet</strong><li><strong>Define the tenancy for your ApplicationController using either domains, subdomains, or filter</strong><li><strong>Define the tenancy for your Action Job</strong><li><strong>Define tenancy for your models</strong></ol><p>Taking a measured approach to migrating, you can deploy each of the steps above independently. And, you can deploy each model change independently of the entire change.<h2 id=removing-acts_as_tenant><a href=#removing-acts_as_tenant>Removing acts_as_tenant</a></h2><p>The best thing I can say about a library is: you can migrate away from it if it does not work for you. Because acts_as_tenant is not a deep integration as past multi-tenant libraries, it is possible to move away from acts_as_tenant.<h2 id=summary><a href=#summary>Summary</a></h2><p>Back in the 2009-ish era, Ruby on Rails and “The Cloud” grew up together when cloud-SaaS and social networks took off. Back then, the maximum performance of network attached storage was 100 IOPs and size maxed out at 1TB. The IOPs strangled database performance, and 1TB was an unbreakable limitation (if you did not RAID early). I started my career in that era. Due to infrastructure limitations, multi-tenant databases would start to see issues when an application hit as little as 50 requests per second. In this era, RAM was expensive and disk performance was not available. Because of this, “sharding” was talked about at all the conferences.<p><em>Side note: also, data was suddenly available everywhere, and there were business models that stored massive amounts of data hoping to figure out a business model later.</em><p>Now, in 2023, RAM is plentiful and IOPs are available. Scaling the database can be punted to 10s of thousands of requests per second.<p>Why do I say all this? Because now, we can approach multi-tenant apps and scaling more practically. Multi-tenant can focus on data-security and coding-practically instead of scaling. You may not ever get to the point of needing distributed data stores, but a solid multi-tenant implementation creates foundational success for your application.<p>The old multi-tenant Ruby Gems were for scalability. acts_as_tenant is built for practicality. ]]></content:encoded>
<category><![CDATA[ Ruby on Rails ]]></category>
<author><![CDATA[ Christopher.Winslett@crunchydata.com (Christopher Winslett) ]]></author>
<dc:creator><![CDATA[ Christopher Winslett ]]></dc:creator>
<guid isPermalink="false">154b989b0bceca71d07af0a50e0c670a7cca03cc23a3023377d73f25e9904bee</guid>
<pubDate>Wed, 20 Dec 2023 08:00:00 EST</pubDate>
<dc:date>2023-12-20T13:00:00.000Z</dc:date>
<atom:updated>2023-12-20T13:00:00.000Z</atom:updated></item>
<item><title><![CDATA[ Use Github Actions on Pull Requests to Automate Postgres on Crunchy Bridge ]]></title>
<link>https://www.crunchydata.com/blog/use-github-action-on-pull-requests-to-automate-postgres-on-crunchy-bridge</link>
<description><![CDATA[ Automate Postgres with your review apps! Chris offers up some sample code for GitHub actions and getting a test Postgres database created, getting the connection string to your review app, and closing it down. ]]></description>
<content:encoded><![CDATA[ <p>Automating pull requests to deploy staging applications is a game changer for large teams performing shipping quality products. Using <a href=https://www.crunchydata.com/products/crunchy-bridge>Crunchy Bridge</a>’s CLI or API, you can easily automate the entire process for these staging deployments. The simplest workflow would look something like the following:<p><img alt="Crunchy Bridge review apps diagram"loading=lazy src=https://imagedelivery.net/lPM0ntuwQfh8VQgJRu0mFg/455c396d-98fb-4c22-bb27-84140b0a9400/public><p>In this example, during the “Create Postgres Cluster”, we’ll create a hobby-0 cluster for Postgres. Then, when the PR is closed the cluster will be torn down. We keep it simple for this example, but depending on your use case you can expand the capabilities.<p>For teams that like to have an anonymized dataset for staging, they use the Crunchy Bridge CLI to <a href=https://docs.crunchybridge.com/api/cluster#create-cluster-fork>fork the production cluster</a>, then run an anonymization process on the forked cluster. For teams that are running PRs often, they could have an anonymized cluster available to be forked as well. You can also create an empty database and add an automated process to load a seed file.<h2 id=naming-convention><a href=#naming-convention>Naming convention</a></h2><p>Since naming is among the two hardest things in computer science, let’s tackle it early. I like to keep my cloud tidy, so I give my Postgres clusters predictable names. Names can be added to the cluster at the time of creation. I like to name the automation clusters with something like:<pre><code>{github repository name}-merge-{pull request id}
</code></pre><p>If you have a repository with name <code>Wayne-Enterprises/Batmans_Code</code> and a PR with id <code>7</code>, it will end up with a cluster like this:<pre><code>batmans-code-merge-7
</code></pre><p>Later, you’ll see this in the code we use to generate the name. Of course, you can change this to whatever you like.<h2 id=prepping-your-crunchy-bridge-account><a href=#prepping-your-crunchy-bridge-account>Prepping your Crunchy Bridge account</a></h2><p>Next, log into your Crunchy Bridge account, and go to My Account → API Keys. Add an API key for this account:<p><img alt="crunchy api keys"loading=lazy src=https://imagedelivery.net/lPM0ntuwQfh8VQgJRu0mFg/75f8afbb-177c-4942-d34e-e67de50dc100/public><p>Since I like to be tidy, I also use production teams and developer teams on my cloud services. So, I create a developer team:<p><img alt="crunchy team creation"loading=lazy src=https://imagedelivery.net/lPM0ntuwQfh8VQgJRu0mFg/98776f6a-78c9-4c2a-7444-bbf2d4c9e600/public><p>After creating this team, grab the team’s id from the URL, as you’ll need it in a moment. In the URL, you’ll see something that looks like <code>https://crunchybridge.com/teams/gqa4owetwbdfvfacpdxhgf2qmu/dashboard</code>. From it, grab the string that is between “teams/” and “/dashboard”. For the above URL, it would be <a href=https://crunchybridge.com/teams/gqa4owetwbdfvfacpdxhgf2qmu/dashboard><code>gqa4owetwbdfvfacpdxhgf2qmu</code></a> — but yours will be something different. This is your team’s id that we will use in a GitHub Secret in a moment.<h2 id=adding-github-actions-secrets><a href=#adding-github-actions-secrets>Adding GitHub Actions Secrets</a></h2><p>GitHub secrets allow you to add sensitive values to your GitHub Actions without revealing them to the world. When the GitHub Action runs, you can use the syntax <code>${{ secrets.CRUNCHY_BRIDGE_API_KEY }}</code> to request the sensitive value at that time. Note: it’s important that your sections never print the secrets, else it can be viewed by anyone with access to the repository.<p>To add GitHub Secret, go to the repository, then click Settings → Secrets and Variables → Actions. From there, add the secrets for <code>CRUNCHY_BRIDGE_API_KEY</code> and <code>CRUNCHY_BRIDGE_STAGING_TEAM_ID</code> that were created in the previous section.<p>Once complete, you should see the following:<p><img alt="github secrets add"loading=lazy src=https://imagedelivery.net/lPM0ntuwQfh8VQgJRu0mFg/f604dfdd-d1d0-4ad0-0bf6-39e2cff54f00/public><h2 id=adding-the-workflow-file><a href=#adding-the-workflow-file>Adding the workflow file</a></h2><p>Now, add a file to your repository at <code>.github/workflows/crunchy-bridge-review-cluster.yml</code>:<pre><code class=language-yaml>name: Crunchy Bridge Review Cluster

on:
  pull_request:
    types: [opened, reopened, closed]

permissions:
  contents: read

jobs:
  launch:
    if: ${{ github.event.pull_request.state == 'open' }}
    runs-on: ubuntu-latest
    name: Launch Crunchy Bridge Review Cluster

    steps:
      - name: Create Crunchy Bridge Review Cluster
        run: |
          export CRUNCHY_BRIDGE_CLUSTER_NAME=$(echo "$GITHUB_REPOSITORY-$GITHUB_REF_NAME" | sed 's/^[^\/]\+\///' | sed 's/[^0-9A-z\-]\+/-/g' )
          export CB_API_KEY=${{ secrets.CRUNCHY_BRIDGE_API_KEY }}

          wget https://github.com/CrunchyData/bridge-cli/releases/download/v3.4.0/cb-v3.4.0_linux_amd64.zip
          unzip cb-v3.4.0_linux_amd64.zip

          (./cb list | grep $CRUNCHY_BRIDGE_CLUSTER_NAME) || ./cb create --platform aws --region us-east-1 --plan hobby-0 --team ${{ secrets.CRUNCHY_BRIDGE_STAGING_TEAM_ID }} --storage 10 --name $CRUNCHY_BRIDGE_CLUSTER_NAME --version 16

          ./cb uri $CRUNCHY_BRIDGE_CLUSTER_NAME

          for i in $(seq 1 120)
          do
            (./cb info $CRUNCHY_BRIDGE_CLUSTER_NAME | grep 'state: ready') &#38&#38 exit 0
            echo -n '.'
            sleep 5
          done

          exit 1
  teardown:
    if: ${{ github.event.pull_request.state == 'closed' }}
    runs-on: ubuntu-latest
    name: Delete Crunchy Bridge Review Cluster

    steps:
      - name: Delete Crunchy Bridge Test Cluster
        run: |
          export CRUNCHY_BRIDGE_CLUSTER_NAME=$(echo "$GITHUB_REPOSITORY-$GITHUB_REF_NAME" | sed 's/^[^\/]\+\///' | sed 's/[^0-9A-z\-]\+/-/g' )
          export CB_API_KEY=${{ secrets.CRUNCHY_BRIDGE_API_KEY }}

          wget https://github.com/CrunchyData/bridge-cli/releases/download/v3.4.0/cb-v3.4.0_linux_amd64.zip
          unzip cb-v3.4.0_linux_amd64.zip

          (./cb list | grep $CRUNCHY_BRIDGE_CLUSTER_NAME) &#38&#38 ./cb destroy $CRUNCHY_BRIDGE_CLUSTER_NAME --confirm || exit 0
</code></pre><p>To trigger the workflow, commit the file, push to GitHub, and create a pull request. The file is fairly simple. It sets environmental variables for the name of the cluster to be created and the Crunchy Bridge API key. Then, it downloads the <a href=https://docs.crunchybridge.com/quickstart/cli>Crunchy Bridge CLI</a> called <code>cb</code>. Then, it either creates the cluster or deletes the cluster depending on the state of the pull request.<h2 id=sending-the-connection-uri-to-the-application><a href=#sending-the-connection-uri-to-the-application>Sending the connection URI to the application</a></h2><p>To get the Postgres URI , do the following and it will return the full URI string for the cluster:<pre><code class=language-bash>export CB_API_KEY=${{ secrets.CRUNCHY_BRIDGE_API_KEY }}
wget https://github.com/CrunchyData/bridge-cli/releases/download/v3.4.0/cb-v3.4.0_linux_amd64.zip
unzip cb-v3.4.0_linux_amd64.zip
./cb uri $CRUNCHY_BRIDGE_CLUSTER_NAME
</code></pre><p>If you were deploying to Heroku, the final line would look like:<pre><code class=language-bash>heroku config:set DATABASE_URL=$(./cb uri $CRUNCHY_BRIDGE_CLUSTER_NAME)
</code></pre><p>If you were deploying with to your own stack, you could write to the <code>database/config.yml</code> to a file:<pre><code class=language-bash>cat &#60&#60 EOF | tee config/database.yml
default: &#38default
  adapter: postgresql
  encoding: unicode
  pool: 5

staging:
  url: $(./cb uri $CRUNCHY_BRIDGE_CLUSTER_NAME)
EOF
</code></pre><p>Generally, use the <code>$(./cb uri $CRUNCHY_BRIDGE_CLUSTER_NAME)</code> command to retrieve the value to write to your environments connection of choice.<h2 id=cli-v-api><a href=#cli-v-api>CLI v. API</a></h2><p>In this tutorial, we chose to keep it simple with the CLI. Previously, when configured for another Ruby on Rails application, I had written this interaction with a Rake file. In that scenario, I had built a process highly specific to that application and Ruby on Rails. The CLI allowed me to use the tools at hand, and I did not have to mess with language libraries. Crunchy Bridge has an amazingly <a href=https://docs.crunchybridge.com/api-concepts/getting-started>powerful API</a>. If you need custom functionality and would like to orchestrate it with your language of choice, check out the API.<h2 id=protecting-production><a href=#protecting-production>Protecting production</a></h2><p>When automating review applications, I’m always watching to make sure I’m automating the correct database. For that reason, Crunchy Bridge has “<a href=https://docs.crunchybridge.com/concepts/cluster-settings#protected-setting>Cluster Protection</a>” to keep your production clusters from being a victim of an automation failure. To turn on, go to your Cluster, then go to Settings → General → Danger Zone → Cluster Protection.<p><img alt="cluster protection"loading=lazy src=https://imagedelivery.net/lPM0ntuwQfh8VQgJRu0mFg/f3c174ed-5073-40a0-b949-f76a9c2a6400/public><h2 id=quick-summary><a href=#quick-summary>Quick summary</a></h2><ul><li>Automate your database creation with your GitHub actions and review apps! Seriously, they're amazing.<li>Name your provisions predictably and save your API key in GitHub secrets.<li>Create a custom workflow file that runs upon pull request to have a github action create your new database and wait until its ready. Also within that workflow file specify that upon removal of the pull request, the database will automatically be removed.<li>Send the connection string to your review application and start testing.</ul> ]]></content:encoded>
<category><![CDATA[ Crunchy Bridge ]]></category>
<category><![CDATA[ Ruby on Rails ]]></category>
<author><![CDATA[ Christopher.Winslett@crunchydata.com (Christopher Winslett) ]]></author>
<dc:creator><![CDATA[ Christopher Winslett ]]></dc:creator>
<guid isPermalink="false">98635e90a3c03bef9e0a659043b7fb43a60c559ae14afa8329fde047d4991e5b</guid>
<pubDate>Thu, 07 Dec 2023 08:00:00 EST</pubDate>
<dc:date>2023-12-07T13:00:00.000Z</dc:date>
<atom:updated>2023-12-07T13:00:00.000Z</atom:updated></item>
<item><title><![CDATA[ Ruby on Rails Neighbor Gem for AI Embeddings ]]></title>
<link>https://www.crunchydata.com/blog/ruby-on-rails-neighbor-gem-for-ai-embeddings</link>
<description><![CDATA[ Thinking about using pgvector to power some AI data in your Rails app? Chris walks through the very handy Neighbor gem and how it helps for vector data types and ActiveRecord. ]]></description>
<content:encoded><![CDATA[ <p>Over the past 12 months, AI has taken over budgets and initiatives. Postgres is a popular store for AI embedding data because it can store, calculate, optimize, and scale using the <a href=https://www.crunchydata.com/blog/whats-postgres-got-to-do-with-ai>pgvector extension</a>. A recently introduced gem to the Ruby on Rails ecosystem, the neighbor gem, makes working with pgvector and Rails even better.<h4 id=background-on-ai-in-postgres><a href=#background-on-ai-in-postgres>Background on AI in Postgres</a></h4><p>An “embedding” is a set of floating point values that represent the characteristics of a thing (nothing new, we’ve had these since the 70s). Using the OpenAI API or any of their competitors, you can send over blocks of text, images, and pdfs, and OpenAI will return an embedding with 1536 values representing the characteristics. With the <code>pgvector</code> extension, you can store that embedding in a vector column type on Postgres. Then, using nearest neighbor calculations, you can then find the most-similar objects. For a deeper review of <a href=https://www.crunchydata.com/blog/topic/ai>AI with Postgres</a>, see my previous posts in this series.<h2 id=the-neighbor-gem><a href=#the-neighbor-gem>The neighbor gem</a></h2><p>By default, Ruby on Rails does not know about the "vector" data type. If you've used Ruby on Rails + Postgres + pgvector, you've probably written SQL queries in your migrations, and implemented some other janky-code. The <a href=https://github.com/ankane/neighbor>neighbor gem</a> will remove the janky-code, and take you back to a native ActiveRecord experience.<p>At a minimum, all you have to do is add the following to you <code>Gemfile</code>:<pre><code class=language-ruby>gem 'neighbor'
</code></pre><p>Side note: I can't overstate the impact <a href=https://github.com/ankane>Andrew Kane</a> has had on embedding data in Postgres. He's also making it easy for developers to use those vector data types with Ruby on Rails and Node.<h2 id=fixed-schema-dump><a href=#fixed-schema-dump>Fixed schema dump</a></h2><p>The biggest risk of not using Neighbor is that ActiveRecord will create a failing <code>db/schema.rb</code> file. Because ActiveRecord does not understand the <code>vector</code> data type, instead of failing, running <code>rails db:schema:dump</code> will omit any table with that data type. It will show this error in your <code>db/schema.rb</code>:<pre><code class=language-ruby># Could not dump table "recipe_embeddings" because of following StandardError
#   Unknown type 'vector(1536)' for column 'embedding'
</code></pre><p>With Neighbor, you'll get a fully-functional schema like the following:<pre><code class=language-ruby>create_table "recipe_embeddings", primary_key: "recipe_id", id: :bigint, default: nil, force: :cascade do |t|
    t.vector "embedding", limit: 1536, null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["embedding"], name: "recipe_embeddings_embedding", opclass: :vector_l2_ops, using: :hnsw
    t.index ["recipe_id"], name: "index_recipe_embeddings_on_recipe_id"
end
</code></pre><p>Notice that Neighbor also understands the []<code>hnsw</code> index type](<a href=https://www.crunchydata.com/blog/hnsw-indexes-with-postgres-and-pgvector>https://www.crunchydata.com/blog/hnsw-indexes-with-postgres-and-pgvector</a>) released with pgvector 0.5.<p><strong>Side note</strong>: for projects that go all-in on Postgres, I opt to use the following to dump to a <code>db/structure.sql</code>:<pre><code>SCHEMA_FORMAT=sql rails db:schema:dump
</code></pre><h2 id=easier-migrations--data-type-handling><a href=#easier-migrations--data-type-handling>Easier migrations + data type handling</a></h2><p>Without Neighbor, ActiveRecord is not informed of vector. Just as your <code>config/schema.rb</code> file is important for your typical migration would look something like the following:<pre><code class=language-ruby>create_table :recipe_embeddings, primary_key: [:recipe_id] do |t|
  t.references :recipe, null: false, foreign_key: true
  t.vector :embedding, limit: 1536, null: false

  t.timestamps
end
</code></pre><p>Additionally, you get improved handling of the vector data type. Without Neighbor, working with embedding data required <code>to_s</code> to manipulate the values when inserting into Postgres. But, with Postgres, it's simplifies to a native process:<pre><code class=language-ruby>RecipeEmbedding.create!(recipe_id: Recipe.last.id, embedding: [-0.078427136, 0.0014401458, ...])
</code></pre><p>But, wait! There's more …<h2 id=the-nearest_neighbor-method><a href=#the-nearest_neighbor-method>The <code>nearest_neighbor</code> method</a></h2><p>After you add the <code>embedding</code> column to a table, you can use <code>has_neighbors</code> to define your nearest neighbor queries:<pre><code class=language-ruby>class RecipeEmbedding &#60 ApplicationRecord
  has_neighbors :embedding
end
</code></pre><p>Then, you can find the nearest neighbors like so:<pre><code class=language-ruby>recipe_embedding.nearest_neighbors(:embedding, distance: "euclidean").first
</code></pre><p>The distance calcuations include <code>euclidean</code> and <code>cosine</code>.<h2 id=conclusion><a href=#conclusion>Conclusion</a></h2><p>Launching a project to use embeddings with Ruby on Rails?<p>Step 1: use the neighbor gem<p>Step 2: provision your database on <a href=https://www.crunchydata.com/products/crunchy-bridge>Crunchy Bridge</a> with pgvector<p>Step 3: profit ]]></content:encoded>
<category><![CDATA[ AI ]]></category>
<category><![CDATA[ Ruby on Rails ]]></category>
<author><![CDATA[ Christopher.Winslett@crunchydata.com (Christopher Winslett) ]]></author>
<dc:creator><![CDATA[ Christopher Winslett ]]></dc:creator>
<guid isPermalink="false">aa4e8c25d1d0a137f5d8f6dfd0e3d8bda9165c7e81aa6b2a31bb44bbb24980b1</guid>
<pubDate>Fri, 03 Nov 2023 09:00:00 EDT</pubDate>
<dc:date>2023-11-03T13:00:00.000Z</dc:date>
<atom:updated>2023-11-03T13:00:00.000Z</atom:updated></item>
<item><title><![CDATA[ Postgres Goodies in Ruby on Rails 7.1 ]]></title>
<link>https://www.crunchydata.com/blog/postgres-goodies-in-ruby-on-rails-7-1</link>
<description><![CDATA[ We are excited about some of the Active Record updates with Rails 7.1! Chris reviews some of the notable new features for working with Postgres including async queries, composite primary keys, native support for CTEs, unlogged tables, and syntax normalization. ]]></description>
<content:encoded><![CDATA[ <p>I just spent last week at Rails World in Amsterdam and had a blast digging back into the Rails and Active Record world. In conversations with developers over the week, I had some notable takeaways from the newest version of Ruby on Rails that I just had to get written up.<p>A quick summary before we dig in:<ul><li><p><strong>async queries</strong>: send long-running queries the background while the code runs along, great for pages with multiple long-running queries that can be run in parallel<li><p><strong>composite primary keys</strong>: native support for using two or more columns as a primary key<li><p><strong><dfn>common table expression</dfn></strong> (<abbr>CTEs</abbr>): native integration for a subquery for use later in the statement<li><p><strong>unlogged tables</strong>: native support for disabling Postgres’ WAL logs on a table (mostly for test environments), so that you get better performance on your tests that use databases<li><p><strong>value normalization</strong>: a native, universal syntax for normalization of values (like downcase of a email column) instead of using <code>before_validation</code></ul><h2 id=expansion-of-async-queries><a href=#expansion-of-async-queries>Expansion of Async queries</a></h2><p>A feature (not a bug IMO) of Ruby is that it has traditionally been used as a blocking (i.e. not-asynchronous) language. While it does have asynchronous capabilities, in the typical use-case people do not have to grok asynchronous workflows to use it effectively.<p>In the 7.0 release, Active Record added <code>load_async</code> for loading whole objects. In 7.1, asynchronous queries have been enabled for aggregations and in full SQL queries using <code>async_find_by_sql</code>.<p>First, you'll need to define the <code>async_query_executor</code> in your environment files (<code>config/environments/{development, production}.rb</code>).<p>To use a global setting, use something like the following:<pre><code class=language-ruby>config.active_record.async_query_executor = :global_thread_pool
config.active_record.global_executor_concurrency = 5

</code></pre><p>To use a per-database setting, use something like the following and <code>min_threads</code> + <code>max_threads</code> the <code>database.yml</code>:<p><code>config/environments/{development, production}.rb</code><pre><code class=language-ruby>config.active_record.async_query_executor = :multi_thread_pool

</code></pre><p><code>config/database.yml</code><pre><code class=language-yaml>development:
  adapter: postgresql
  pool: 5
  max_threads: 5
  min_threads: 5
</code></pre><p>After setting one of those configurations, we can see how the queries work asynchronously:<pre><code class=language-ruby>irb> u = User.async_find_by_sql("SELECT *, pg_sleep(3) FROM users") # this query will sleep for 3 seconds for each record in your database
=> #&#60ActiveRecord::Promise status=pending>

</code></pre><p>Then, sometime later you can use the <code>value</code> syntax to retrieve the value:<pre><code class=language-ruby># … sometime later …
irb> u.value
=> {returned results}

</code></pre><p>Once you call the <code>.value</code> method, if the query has returned, it will return instantly. If the query has not returned, processing is blocked until the query is complete. This is great for dashboards, charts, and reports that generate more complex queries. Send the query to the background, and let it process while you complete other elements of the request.<h2 id=composite-primary-keys><a href=#composite-primary-keys>Composite primary keys</a></h2><p>Composite primary keys have been noticeably absent from Ruby on Rails for a while -- unless you chose the <a href=https://github.com/composite-primary-keys/composite_primary_keys>CPK gem</a>. Rails 7.1 added two native methods for implementing Primary Keys: database level and application level.<p><strong>Database Level Composite Primary Keys:</strong> When defining the table in the database migration, you can pass an array of column names to the <code>primary_key</code> attribute. For databases capable of composite primary keys (like Postgres), Active Record will infer from the schema:<pre><code class=language-ruby>    create_table :user_accounts, primary_key: [:user_id, :account_id] do |t|
      t.belongs_to(:user, foreign_key: true)
      t.belongs_to(:account, foreign_key: true)

      t.string :role

      t.timestamps
    end

</code></pre><p>After running this migration, if you run <code>\d user_accounts</code> on your Postgres database, you’ll see the following line for the composite key:<pre><code class=language-ruby>"user_accounts_pkey" PRIMARY KEY, btree (user_id, account_id)
</code></pre><p>Then, when querying with the composite keys, you do the following:<pre><code class=language-ruby>UserAccounts.find([1, 2]) # where user_id = 1 and account_id = 2
</code></pre><p>In the hypothetical use-case above, we use the composite primary keys for a join table between users and accounts. Typically, in the past I would use an <code>id</code> column with a unique constraint on the <code>user_id</code> and <code>account_id</code> values. Now, with Postgres, we can use the composite primary key for the row<p><strong>Application Level Composite Primary Keys:</strong> Rails documentation calls this a "virtual primary key". And, you’ll want to know that it’s a bit more restrictive than the composite primary keys above because it enforces an explicit foreign key definition on relationships:<pre><code class=language-ruby>class UserAccounts &#60 ActiveRecord::Base
  query_constraints :user_id, :account_id

  belongs_to :user, foreign_key: :user_id
  belongs_to :account, foreign_key: :account_id
end

</code></pre><p>You have to be explicit on the foreign_key definitions of the relationship, else it tries to find <code>user_id</code> and <code>account_id</code> on every related model.<p>My recommendation is to use Postgres and the native composite primary keys in a database.<h2 id=native-support-for-ctes><a href=#native-support-for-ctes>Native Support for CTEs</a></h2><p>A "<abbr>CTE</abbr>" is a "<dfn>Common Table Expression</dfn>". A <a href=https://www.crunchydata.com/blog/postgres-subquery-powertools-subqueries-ctes-materialized-views-window-functions-and-lateral>CTE</a> is a type of a nested SQL statement that is defined before the SQL. Below is an example using native-SQL that would find the latest event for each user on an account:<pre><code class=language-pgsql>WITH latest_event_per_user AS (
	SELECT
		user_id,
		MAX(event_logs.created_at) AS last_created_at
	FROM event_logs
	WHERE
		event_logs.account_id = 1
	GROUP BY 1
)

SELECT
	event_logs.user_id,
	event_logs.name,
	event_logs.created_at
FROM event_logs
	INNER JOIN latest_event_per_user ON
		event_logs.user_id = latest_event_per_user.user_id AND
		event_logs.created_at = latest_event_per_user.last_created_at;

</code></pre><p>When would you use something like this? Above is a query that returns the latest events for each user on an account. Another common usage of CTEs is when generating the data for charts and reports. Most of the time, generating this data at the SQL level is much faster than generating it using application level logic. Application logic would require some type of N + 1 query, which can be avoided with a more expressive SQL query.<p>To support CTEs, Active Record added <code>.with()</code> method for chaining queries. The <code>with()</code> accepts an Object, which is quite nice when using a large block. Writing the same query from above in Active Record would look like the following:<pre><code class=language-ruby>latest_event_per_user = EventLog.
	where(account_id: 1).
	group(:user_id).
	select(:user_id, "max(event_logs.created_at) AS last_created_at")

el = EventLog.
	with(my_cte: latest_event_per_user).
	joins("JOIN my_cte ON event_logs.user_id = my_cte.user_id AND event_logs.created_at = my_cte.last_created_at")

</code></pre><p>My recommendation: if you are SQL-nerd enough to use CTEs, consider using <code>ActiveRecord::Base.connection.execute()</code> to execute the raw sql. The one time I could see using this Active Record CTE syntax is if you need the chaining capabilities due to conditional query creation.<h2 id=support-for-unlogged-tables-test-env-only><a href=#support-for-unlogged-tables-test-env-only>Support for <a href=https://www.crunchydata.com/blog/postgresl-unlogged-tables>unlogged tables</a> (test env only!)</a></h2><p>Postgres in test environments do not need the persistence that Postgres needs in production. Enter <code>UNLOGGED TABLE</code>, which does not apply Postgres durability of a table, but improves performance:<pre><code class=language-ruby># config/environments/test.rb

ActiveSupport.on_load(:active_record_postgresqladapter) do
  self.create_unlogged_tables = true
end

</code></pre><h2 id=activerecordbasenormalizes><a href=#activerecordbasenormalizes>ActiveRecord::Base.normalizes</a></h2><p>Most modern databases (including Postgres) are case-sensitive by default. And, users have been known to randomly capitalize values. So, it's best to sanitize values. If you've written a Rails application, you've probably written something like the following:<pre><code class=language-ruby>class User &#60 ApplicationRecord
	before_validation do
	  self.email = self.email.strip.downcase
	end
end

</code></pre><p>Now, we have an native way to do this with <code>normalizes</code>:<pre><code class=language-ruby>class User &#60 ApplicationRecord
	normalizes :email, with: -> given_value { given_value.strip.downcase }
end

</code></pre><p>For those who don't know about the <code>-></code> syntax, this is a function with a single argument called <code>given_value</code>.<h2 id=summary><a href=#summary>Summary</a></h2><p>The PostgreSQL, Active Record, and Ruby on Rails communities continue to show investment in features to make this a strong stack for data heavy production applications. ]]></content:encoded>
<category><![CDATA[ Ruby on Rails ]]></category>
<author><![CDATA[ Christopher.Winslett@crunchydata.com (Christopher Winslett) ]]></author>
<dc:creator><![CDATA[ Christopher Winslett ]]></dc:creator>
<guid isPermalink="false">56676b90cb4a47aa568a33d598d7acbc764b10170116f592740727e91583562b</guid>
<pubDate>Mon, 16 Oct 2023 09:00:00 EDT</pubDate>
<dc:date>2023-10-16T13:00:00.000Z</dc:date>
<atom:updated>2023-10-16T13:00:00.000Z</atom:updated></item>
<item><title><![CDATA[ Solving N+1 Postgres queries for Ruby on Rails apps ]]></title>
<link>https://www.crunchydata.com/blog/postgresql-for-solving-n+1-queries-in-ruby-on-rails</link>
<description><![CDATA[ Chris has some tips for working with Ruby on Rails and ActiveRecord and using better SQL to improve performance and avoid N+1 queries. ]]></description>
<content:encoded><![CDATA[ <p>Crunchy Data is getting ready to be at RailsConf 2023 in Atlanta next week and we’ve been thinking about our Rails and ActiveRecord users and customers. One of the easiest ways to improve query performance using an ORM is to lean on as much SQL as you can. I’m going to walk through some of the ActiveRecord basics and how to use some smart SQL to work around N+1 query problems.<h2 id=the-easy-crud-basics-with-activerecord><a href=#the-easy-crud-basics-with-activerecord>The easy CRUD Basics with ActiveRecord</a></h2><p>What do I mean by "<abbr>CRUD</abbr>"? It's short-hand for <dfn>create-read-update-delete</dfn>. For instance, ORMs make it so nice to do any of the following.<p>Insert a record:<pre><code class=language-ruby>batman = user.create(name: "Batman", email: "batman@wayne-enterprises.com")
</code></pre><p>Find a record:<pre><code class=language-ruby>user = User.find(batman.id)
</code></pre><p>Update a record:<pre><code class=language-ruby>user.update(email: "batman@retired.com")
</code></pre><p>Destroy a record:<pre><code class=language-ruby>user.destroy
</code></pre><p>ORMs can even manage relationships and joins:<pre><code class=language-ruby>batman.sidekicks = robin
User.find(batman.id).joins(sidekick: :user)
</code></pre><p>The above would obviously return Robin.<p>Sometime in the 1970s, superheroes switched from one-to-one hero-to-sidekick ratio to having multiple side-kicks, or functioning as a group. Then, Marvel Universe started introducing groupings of superheroes. The Marvel Universe of superheroes is like teenager group chats -- not every superhero likes every other superhero.<p>Hulk and Iron Man -- you don't want them in the same room together, unless you have to.<p>But, I digress.<h2 id=sql-superpowers-on-vs-where><a href=#sql-superpowers-on-vs-where>SQL Superpowers: ON vs. WHERE</a></h2><p>This type of grouping relationship necessary for managing superheroes is what ties ORMs into knots. Anytime you want to append a <em>conditional join</em>, they get quite messy.<p>Below is what I mean when I say <em>conditional join</em>, it is a join, but it conditions with the <code>ON</code> statement:<pre><code class=language-pgsql>SELECT
	*
FROM table
LEFT JOIN other_table ON conditional_1 AND conditional_2
</code></pre><p>This query will return all rows of table, but exclude any <code>other_table</code> rows where <code>conditional_1</code> or <code>conditional_2</code> are false. So, results look something like this:<pre><code class=language-text> table.id | other_table.conditional_1 | other_table.conditional_2 |
----------|---------------------------|---------------------------|--
        1 |                      true |                      true |
        2 |                           |                           |
</code></pre><p>If we put the conditional in <code>WHERE</code> instead of the <code>ON</code>, and ran this query:<pre><code class=language-pgsql>SELECT
	*
FROM table
LEFT JOIN other_table ON conditional_1
WHERE conditional_2
</code></pre><p>Then it only returns results where all conditions are met:<pre><code class=language-text> table.id | other_table.conditional_1 | other_table.conditional_2 |
----------|---------------------------|---------------------------|--
        1 |                      true |                      true |
</code></pre><p>If you notice, there is only a single row returned. The usage of the <code>WHERE</code> conditional filters out the entire second row.<p>So, sometimes, filters need to be in the join’s <code>ON</code> clause, instead of being in the <code>WHERE</code> clause.<h2 id=an-orm-in-knots><a href=#an-orm-in-knots>An ORM in knots</a></h2><p>Using Rails’ ActiveRecord ORM, let's return a list of superheroes, then if they are in a chat group owned by Hulk, return those groups as well. We would probably start with something like this:<pre><code class=language-ruby>Users
	.left_joins(:group_users => groups)
	.where(group_users: {groups: {owner_id: hulk.id}})
</code></pre><p>This would generate a query that looks something like this:<pre><code class=language-pgsql>SELECT
	*
FROM users
LEFT OUTER JOIN group_users ON group_users.user_id = users.id
LEFT OUTER JOIN groups ON group_users.group_id = groups.group_id
WHERE
	groups.owner_id = [[hulk_user_id]]
</code></pre><p>This has the problem we defined before: it filters out all rows that do not return true for the conditional. So, it's not returning all users, it's only returning users who belong to a group that is owned by Hulk.<p>Iron Man would be mad. He'd probably even threaten to take his toys and go home, until someone told him it was just a bug in the software.<h3 id=a-false-positive-unless-><a href=#a-false-positive-unless->A false positive, unless …</a></h3><p>With ActiveRecord, there appears to be a way to do this, but it's a false positive. Using SQL fragment runs the query that we want:<pre><code class=language-ruby>users = Users
	.joins(:group_users)
	.joins(ActiveRecord::Base.sanitize_sql_array("LEFT JOIN groups ON group_users.group_id = groups.id AND groups.owner_id = ?", hulk.id]))
</code></pre><p>But, when accessing the object's relationships, we get all related rows, not the ones you want (i.e. the conditional join did not stick):<pre><code class=language-ruby>users.first.group_users.groups => all groups, unfiltered
</code></pre><p>In Rails 6.1, the <code>strict_loading</code> functionality was added that makes this join behave properly. Run the same ruby code above, and append <code>strict_loading</code>, and this will prevent additional lazy loading.<pre><code class=language-ruby>users.strict_loading.first.group_users => filtered groups
</code></pre><h2 id=should-we-settle-with-n--1><a href=#should-we-settle-with-n--1>Should we settle with N + 1?</a></h2><p>The typical alternative is to just settle with N + 1 from the controller or the template. It's an attempt to solve data retrieval shortcomings using application level code:<pre><code class=language-ruby>&#60% users.each do |user| %>
	&#60%= user.name %>
  &#60% user.group_users.includes(:groups).where(group_users: {group_id: params[:group_id]}).each do |group_user|
    &#60%= group_user.group.name %>
  &#60% end %>
&#60% end %>
</code></pre><p>Of course, this works … but, it does not scale. It will be fast in development, and it will run fast with small data sets. But, it runs a query for each user record. If the application grows, the loop above will run an additional query for each user displayed.<p>There is a better way.<h2 id=lets-just-use-sql-instead><a href=#lets-just-use-sql-instead>Let's just use SQL instead</a></h2><p>First, we'll use the quick-and-dirty method. It will call some of the code internals for ActiveRecord.<p>Let's use <code>ActiveRecord::Base.connection.execute</code> to run the SQL. We'll also use <code>ActiveRecord::Base.sanitize_sql_array</code> to securely inject values to safely build the SQL query.<pre><code class=language-pgsql>results = ActiveRecord::Base.connection.execute(ActiveRecord::Base.sanitize_sql_array([&#60&#60SQL, hulk_user_id]))
SELECT
	users.id AS id,
	users.name AS name,
	groups.name AS group_name
FROM users
LEFT OUTER JOIN group_users ON group_users.user_id = users.id
LEFT OUTER JOIN groups ON group_users.group_id = groups.group_id
	AND	groups.owner_id = ?
ORDER BY users.name
SQL
</code></pre><p>Then, in the view, the following code can be used to iterate over the returned values:<pre><code class=language-ruby>&#60% results.each do |row| %>
  &#60%= row["id"] %>
  &#60%= row["name"] %>
  &#60%= row["group_name"] || "--" %>
&#60% end %>
</code></pre><h3 id=clean-it-up-to-make-it-a-little-nicer><a href=#clean-it-up-to-make-it-a-little-nicer>Clean it up to make it a little nicer</a></h3><p>To clean up the code a bit when running multiple SQL queries, I typically do something like this. I searched for a modern Ruby Gem to handle this type of issue, but none were immediately obvious as being stable and maintained.<ol><li><p>Store one-file per query in the <code>app/models/sql</code> directory. So the query above would be stored in a file called <code>app/models/sql/all_users_and_groups_with_specific_owner.sql</code> so the above query would look like this:<pre><code class=language-pgsql>SELECT
	users.id AS id,
	users.name AS name,
	groups.name AS group_name
FROM users
LEFT OUTER JOIN group_users ON group_users.user_id = users.id
LEFT OUTER JOIN groups ON group_users.group_id = groups.group_id
	AND	groups.owner_id = ?
ORDER BY users.name
</code></pre><li><p>Then, we can have a model that handles these queries for us. Save the following to <code>app/models/sql.rb</code><pre><code class=language-ruby>class Sql
	def self.run(sql_name, *arguments)
		sql = File.read(File.join(Rails.root, 'app', 'models', 'sql', sql_name + '.sql'))
		sanitized_sql = ActiveRecord::Base.sanitize_sql_array(sql, *arguments)
		ActiveRecord::Base.connection.execute(sanitized_sql)
	end
end
</code></pre><li><p>Then, when running a SQL command, just do the following:<pre><code class=language-ruby>result = Sql.run("all_users_and_groups_with_specific_owner", hulk_user_id)
</code></pre></ol><p>Using this method, it puts the SQL query into a space away from the rest of our code. Then, in that SQL file, we can include comments to help our future-selves read the SQL and know why we are using it.<h3 id=what-about-database-lock-in><a href=#what-about-database-lock-in>What about database lock-in?</a></h3><p>By querying with raw SQL, you will be locked into a database. However, once a raw SQL becomes necessary for performance, it is best to favor database lock-in -- the alternative being slower, generic database interactions.<p>Once you decide on the database for the long-haul, there is no better database than open-source 100% native Postgres.<h2 id=summary><a href=#summary>Summary</a></h2><ul><li>ActiveRecord is awesome for getting started with databases in your Rails application but performance wise, there can be some limits.<li>N+1 queries are a common issue with ActiveRecord or an ORM, and can become more of hindrance as the application scales.<li>Writing SQL and embedding that as a model is an easy way to add sql to your application. You’ll be locked into PostgreSQL for the long haul, but that’s ok, there’s no better database for a Rails production application.</ul><p>See you next week at RailsConf! ]]></content:encoded>
<category><![CDATA[ Ruby on Rails ]]></category>
<author><![CDATA[ Christopher.Winslett@crunchydata.com (Christopher Winslett) ]]></author>
<dc:creator><![CDATA[ Christopher Winslett ]]></dc:creator>
<guid isPermalink="false">7334071a04be2936e49a48a70c51d203a4d015837f31255707dc008a2c16b266</guid>
<pubDate>Fri, 21 Apr 2023 09:00:00 EDT</pubDate>
<dc:date>2023-04-21T13:00:00.000Z</dc:date>
<atom:updated>2023-04-21T13:00:00.000Z</atom:updated></item></channel></rss>