ruby 51 lines · 4 tabs

Strict Loading to Catch N+1 in Development

Shared by codesnips Jan 2026
4 tabs
class ArticlesController < ApplicationController
  def index
    # Bounded queries: without preload, article.author in the view raises.
    @articles = Article
      .published
      .preload(:author, :tags)
      .order(published_at: :desc)
      .limit(50)
  end

  def feed
    # Legit escape hatch: this relation is intentionally lazy-loaded.
    @articles = current_user
      .followed_articles
      .strict_loading(false)
      .recent
  end
end
4 files · ruby Explain with highlit

This snippet shows how Rails' strict_loading feature turns silent N+1 queries into loud, catchable failures during development instead of shipping them to production as slow endpoints. The N+1 problem happens when code iterates a collection and lazily triggers one query per record for an association it forgot to preload; strict_loading flips lazy loading off so any un-preloaded association access raises ActiveRecord::StrictLoadingViolationError.

In application_record.rb, strict loading is enabled globally for every model when the environment configures it, and the mode is set to :all so that even associations reachable through already-loaded records are guarded. The strict_loading_by_default flag is the model-level switch; setting it on ApplicationRecord cascades to all subclasses. Because forcing this on in production would be dangerous, it is gated behind a config flag.

In environments/development.rb, that flag is turned on together with action_controller.raise_on_strict_loading_violation. The key trade-off is choosing between raising and logging: raising via strict_loading_violation = :raise fails fast and forces developers to add includes or preload, while :log merely records the violation. Development uses raise so mistakes cannot be ignored, but tests could use log to avoid brittle failures on legacy code paths.

ArticlesController demonstrates the fix in practice. The naive index would blow up the moment the view calls article.author.name, so the action uses preload(:author, :tags) to load everything up front in a bounded number of queries. The feed action shows a scoped escape hatch: strict_loading(false) re-enables lazy loading for a specific relation where the eager load genuinely cannot be expressed cleanly.

Finally, StrictLoadingLogger subscribes to the strict_loading_violation.active_record notification so violations are surfaced with the owner class and association name even when running in :log mode. This instrumentation is useful in CI or staging where raising is too aggressive but visibility is still required. Together these files make N+1 regressions a build-time concern rather than a runtime surprise, with per-query escape valves for the rare legitimate case.


Related snips

ruby
class CommentsController < ApplicationController
  before_action :set_post

  def create
    @comment = @post.comments.build(comment_params)

System test: asserting Turbo Stream responses

rails hotwire turbo
by codesnips 4 tabs
ruby
class Post < ApplicationRecord
  belongs_to :author, class_name: 'User'
  has_many :comments, dependent: :destroy

  scope :published, -> { where.not(published_at: nil).where('published_at <= ?', Time.current) }
  scope :draft, -> { where(published_at: nil) }

ActiveRecord scopes for reusable query logic

rails activerecord patterns
by Alex Kumar 1 tab
ruby
module Api
  module V1
    class UsersController < BaseController
      def show
        user = User.includes(:profile).find(params[:id])

ETags for conditional requests and caching

rails caching http-caching
by Alex Kumar 1 tab
ruby
class PostsController < ApplicationController
  def index
    @posts = Post.includes(:author)
                 .order(created_at: :desc)
                 .page(params[:page])
                 .per(10)

Turbo Frames: infinite scroll with lazy-loading frame

rails turbo hotwire
by codesnips 4 tabs
ruby
require "csv"

class PeopleCsvStream
  include Enumerable

  HEADERS = %w[id full_name email signed_up_at plan].freeze

Resilient CSV Export as a Streamed Response

rails performance streaming
by codesnips 3 tabs
erb
<form data-controller="query-sync" data-action="change->query-sync#apply">
  <select name="status" class="rounded border p-2">
    <option value="">Any</option>
    <option value="open">Open</option>
    <option value="closed">Closed</option>
  </select>

Filter UI that syncs query params via Stimulus (no front-end router)

rails hotwire stimulus
by Henry Kim 2 tabs

Share this code

Here's the card — post it anywhere.

Strict Loading to Catch N+1 in Development — share card
Link copied