首页 > 解决方案 > 将 where() 与 join() 和 includes() 一起使用时出现 ActiveRecord 预加载错误

问题描述

我想查找具有特定评论的帖子并打印出所有评论(不仅仅是那个特定评论)。

为了搜索关联模型,我使用了 where() 和 joins() ,如下代码:

posts = Post.joins(:comments).where('comments.value = "value1"')

这会返回正确的帖子,但是当我尝试使用结果中的评论时会生成 n + 1 个查询。所以我尝试使用includes()来预加载:

posts = Post.includes(:comments).joins(:comments).where('comments.value = "value1"')

这也会返回正确的帖子,但是当我尝试调用时post.comments,它在每个帖子中只包含一条评论(值为“value1”的评论),其中每条帖子都应该有多个评论。

对我来说,这似乎是一个 Rails 错误,post.comments应该总是返回所有相关的注释,但在这种情况下它只返回一个。

我的问题是:

  1. 这是 Rails 错误还是我误解了 ActiveRecord 应该如何工作?
  2. 查找所有带有此类评论的帖子并打印所有帖子评论而不生成 + 1 查询的最佳方法是什么?

代码重现错误:

# frozen_string_literal: true

begin
  require "bundler/inline"
rescue LoadError => e
  $stderr.puts "Bundler version 1.10 or later is required. Please update your Bundler"
  raise e
end

gemfile(true) do
  source "https://rubygems.org"

  git_source(:github) { |repo| "https://github.com/#{repo}.git" }

  # Activate the gem you are reporting the issue against.
  gem "activerecord", "5.2.0"
  gem "sqlite3"
end

require "active_record"
require "minitest/autorun"
require "logger"
require "pp"

# Ensure backward compatibility with Minitest 4
Minitest::Test = MiniTest::Unit::TestCase unless defined?(Minitest::Test)

# This connection will do for database-independent bug reports.
ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
ActiveRecord::Base.logger = Logger.new(STDOUT)

ActiveRecord::Schema.define do
  create_table :posts, force: true do |t|
  end

  create_table :comments, force: true do |t|
    t.string  :value
    t.integer :post_id
  end
end

class Post < ActiveRecord::Base
  has_many :comments
end

class Comment < ActiveRecord::Base
  belongs_to :post
end

class BugTest < Minitest::Test
  def test_association_stuff
    post = Post.create!
    post.comments << Comment.create(value: 'value1')
    post.comments << Comment.create(value: 'value2')
    post.comments << Comment.create(value: 'value3')

    post = Post.create!
    post.comments << Comment.create(value: 'value1')
    post.comments << Comment.create(value: 'value2')
    post.comments << Comment.create(value: 'value3')

    # find the post that has comment 'value1'
    posts = Post.joins(:comments).where('comments.value LIKE "%value1%"')

    posts.each { |p| pp p.comments }
    assert_equal 3, posts.first.comments.length

    # to pervent n + 1, use includes as well
    posts = Post.includes(:comments).joins(:comments).where('comments.value LIKE "%value1%"')

    posts.each { |p| pp p.comments }
    assert_equal 3, posts.first.comments.length
  end
end

标签: ruby-on-railsjoinactiverecord

解决方案


推荐阅读