将where()与join()和includes()一起使用时,ActiveRecord预加载错误

时间:2018-07-29 17:55:17

标签: ruby-on-rails join activerecord

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

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

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

这将返回正确的帖子,但是当我尝试在结果中使用注释时,它将生成n + 1个查询。所以我尝试使用include()进行预加载:

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

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

在我看来,post.comments应该总是返回所有关联的注释,但在这种情况下,它仅返回一个。

我的问题是:

  1. 这是Rails的错误还是我误解了ActiveRecord应该如何工作?
  2. 查找带有此类评论的所有帖子并打印帖子的所有评论而不生成n + 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

0 个答案:

没有答案