How to enforce strict loading of records?

My understanding is that strict loading should be enabled app-wide when adding self.strict_loading_by_default = true to ApplicationRecord. However I silently still get N+1’s with the example below. Would someone have insight into how to enforce strict loading?

class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true
  self.strict_loading_by_default = true
end

class ModuleSession < ApplicationRecord
  has_many :viewerships, as: :record, dependent: :destroy

  scope :with_viewerships, ->(user) {
    # BTW is there any cleaner way to load the user's viewerships?
    joins("LEFT JOIN viewerships "\
          "ON viewerships.record_type = 'ModuleSession' "\
          "AND viewerships.record_id = module_sessions.id AND "\
          "viewerships.user_id = #{user.id}")
      .includes(:viewerships)
  }
end

class Viewership < ApplicationRecord
  belongs_to :user, inverse_of: :viewerships
  belongs_to :record, polymorphic: true
end

class CourseModulesController < ApplicationController
  def show
    ModuleSession.with_viewerships(current_user).each do |s|
      s.viewerships.find_or_initialize_by(user: current_user)
      # s.viewerships.first_or_initialize(user: current_user)
    end
  end
end

When calling the above controller action, a bunch of SQL “Viewership Load” are triggered, see screenshot from rack-mini-profiler:

The N+1 queries disappear when find_or_initialize_by is replaced with first_or_initialize (I don’t know why, but it’s good news). I would have expected a strict loading error to be raised so that I become aware of this problem and can correct it.

first_or_initialize calls first, which is compatible with includes (since it checks for loaded?.

By contrast, find_or_initialize_by calls find_by which always runs a query.

I agree. I think this a bug, I’ll have a go at a fix.

The original strict loading scope just covered things you can cover with includes, preload, etc. That’s probably why this isn’t included.

2 Likes