bullet
bullet copied to clipboard
Incorrectly tells me to eager load intermediate association when eager loading a `has_one` through
Explanation
I'm using Rails 6 and have a has_one through
association. Rails implemented my includes
in my app as an eager_load
, I have reproduced it here by directly calling eager_load
. It gives me the result I want, with a single query. It is what I expected it to be. Bullet, considers it an N+1 query, and wants me to includes
the join table.
This seems like a bug to me.
Possibly related to https://github.com/flyerhzm/bullet/pull/341
Example
# ===== SETUP THE GEMS =====
require 'bundler/inline'
gemfile do
source 'https://rubygems.org'
gem 'activerecord', '=6.1.4.1'
gem 'bullet', '=6.1.5'
gem 'sqlite3', '=1.4.2'
end
require 'active_record'
require 'logger'
require 'bullet'
require 'bullet/active_record61'
ActiveRecord::Base.establish_connection adapter: 'sqlite3', database: ':memory:'
ActiveSupport::LogSubscriber.colorize_logging = false
# ===== DEFINE THE SCHEMA / MODELS =====
ActiveRecord::Schema.define do
self.verbose = false
create_table :lefts do |t|
t.string :name
end
create_table :middles do |t|
t.integer :left_id
t.integer :right_id
end
create_table :rights do |t|
t.string :name
end
end
class Left < ActiveRecord::Base
has_one :middle
has_one :right, through: :middle
end
class Middle < ActiveRecord::Base
belongs_to :left
belongs_to :right
end
class Right < ActiveRecord::Base
end
# ===== CREATE TEST DATA =====
Left.create! name: 'l1', right: Right.new(name: 'r1')
Left.create! name: 'l2', right: Right.new(name: 'r2')
# ===== TURN ON BULLET + LOGGING =====
Bullet.enable = Bullet.raise = true
Bullet.start_request
ActiveRecord::Base.logger = Logger.new $stdout
# ===== THE APP CODE =====
Left.eager_load(:right).all.each do |left|
left.right.name # => "r1", "r2"
end
# ===== BULLET CONSIDERS IT WRONG =====
Bullet.to_enum(:for_each_active_notifier_with_notification).first
# => #<Bullet::Notification::NPlusOneQuery:0x0000000142606808
# @associations=[:middle],
# @base_class="Left",
# @callers=
# ["program.rb:60:in `block in <main>'", "program.rb:59:in `<main>'"],
# @notifier=UniformNotifier::Raise,
# @path=nil>
Bullet.perform_out_of_channel_notifications # ~> Bullet::Notification::UnoptimizedQueryError: user: josh\n \nUSE eager loading detected\n Left => [:middle]\n Add to your query: .includes([:middle])\nCall stack\n program.rb:60:in `block in <main>'\n program.rb:59:in `<main>'\n\n
# >> D, [2021-10-19T20:08:49.500942 #71323] DEBUG -- : SQL (0.1ms) SELECT "lefts"."id" AS t0_r0, "lefts"."name" AS t0_r1, "rights"."id" AS t1_r0, "rights"."name" AS t1_r1 FROM "lefts" LEFT OUTER JOIN "middles" ON "middles"."left_id" = "lefts"."id" LEFT OUTER JOIN "rights" ON "rights"."id" = "middles"."right_id"
# ~> Bullet::Notification::UnoptimizedQueryError
# ~> user: josh
# ~>
# ~> USE eager loading detected
# ~> Left => [:middle]
# ~> Add to your query: .includes([:middle])
# ~> Call stack
# ~> program.rb:60:in `block in <main>'
# ~> program.rb:59:in `<main>'
# ~>
# ~>
# ~> /Users/josh/.gem/ruby/3.0.2/gems/uniform_notifier-1.14.2/lib/uniform_notifier/raise.rb:19:in `_out_of_channel_notify'
# ~> /Users/josh/.gem/ruby/3.0.2/gems/uniform_notifier-1.14.2/lib/uniform_notifier/base.rb:25:in `out_of_channel_notify'
# ~> /Users/josh/.gem/ruby/3.0.2/gems/bullet-6.1.5/lib/bullet/notification/base.rb:50:in `notify_out_of_channel'
# ~> /Users/josh/.gem/ruby/3.0.2/gems/bullet-6.1.5/lib/bullet.rb:237:in `block in perform_out_of_channel_notifications'
# ~> /Users/josh/.gem/ruby/3.0.2/gems/bullet-6.1.5/lib/bullet.rb:299:in `block (2 levels) in for_each_active_notifier_with_notification'
# ~> /Users/josh/.rubies/ruby-3.0.2/lib/ruby/3.0.0/set.rb:344:in `each_key'
# ~> /Users/josh/.rubies/ruby-3.0.2/lib/ruby/3.0.0/set.rb:344:in `each'
# ~> /Users/josh/.gem/ruby/3.0.2/gems/bullet-6.1.5/lib/bullet.rb:297:in `block in for_each_active_notifier_with_notification'
# ~> /Users/josh/.gem/ruby/3.0.2/gems/bullet-6.1.5/lib/bullet.rb:296:in `each'
# ~> /Users/josh/.gem/ruby/3.0.2/gems/bullet-6.1.5/lib/bullet.rb:296:in `for_each_active_notifier_with_notification'
# ~> /Users/josh/.gem/ruby/3.0.2/gems/bullet-6.1.5/lib/bullet.rb:235:in `perform_out_of_channel_notifications'
# ~> program.rb:67:in `<main>'